PS4 到手之后游戏买买买,500G的磁盘换成了1TB也快用完了,但是买2TB的话就感觉很麻烦,一来是要全部重新迁移数据,二是换下来的1TB WD Black也没其他地方用,就很难受。

查了一圈后发现,PS4 在某次系统更新后提供了将 USB 存储格式化为扩展存储的功能,也就是说可以用 USB3.0 的移动硬盘来装游戏。可能是有偏见吧我一直觉得移动硬盘是非常不靠谱的东西,不过这个特性倒是一个不错的拓展存储的切入点。

要说扩展存储,如果是普通的 PC 上有这个需求,解决方法非常多,比如我可以建立一个 SMB 协议的共享,Windows 和 macOS 原生兼容,Linux 只需要加载 cifs.ko 即可;当然 NFS 来实现也没有任何问题;如果对加密有需求,或是需要一个裸磁盘,还可以用 iSCSI 来做到。可是 PS4 目前没有破解,以上方法都不可行。

于是就想,PS4 支持 USB 存储的扩展,我能不能将上面提到的一些方式通过 USB 暴露给 PS4 来实现呢?比如这样一个设备:它本身是一个 USB 从机,同时有一个 GbE 接口,可以千兆访问到局域网里的 iSCSI Target,然后通过一些魔法,将 iSCSI Target 拿到的磁盘设备直通到 USB,让 PS4 认为这是一个大容量的 USB 存储。

为了探究这个想法的可行性,我就去某宝买了个 RK3399 的开发板:这货带一个 USB Type-C 的接口和一个千兆以太网接口,同时还有这不错的 CPU 性能,只可惜 Mali GPU 的驱动暂时没法搞到。软件上支持 Android 和 Linux。

板子到手上电后发现预装了 Android 和 Ubuntu 双系统,二话不说果断重灌,Android 我没有需求,而 Ubuntu 不太想用(Arch 大法好),于是找了张 SD 卡往里面放了 ARM64 的 Arch Linux ARM 的根文件系统,使用 Rockchip 的工具修改 parameter 分区内容,指定 root 分区为 SD 卡并重启,等到开机结束后就可以安装 arch-install-scripts 并把 eMMC 内的 Ubuntu 彻底带走了。不过记得要备份一下 Ubuntu 的 /system 目录,里面的 firmware 以后还是要用的(鬼知道为什么是 /system/etc/firmware 而不是 /etc/firmware,rockchip 改过的内核毒性不小)。如果对 U-boot 没有操作需求的话这样就基本够用了,重启后进入 Arch Linux 并进行基本的配置(网络,pacman 源,blabla)。想要使用全功能的 U-boot 的话,可以参考这篇里的一些信息自己编译 rockchip 提供的 u-boot,firefly 给的裁剪严重基本没法用。

然后就要试试如何将 TypeC 接口的 USB 变身成为一个假 U 盘了。本来以为这块要写内核模块的,结果看了看文档发现其实很简单:内核将 USB Gadget 的配置接口通过一个文件系统暴露给了用户,只需要在这个 configfs 里面比划比划,创建点文件,搞点链接就可以完成了。代码和注释放一起吧:

# 首先是要确认一下 configfs 有没有挂载,以及挂载的位置
mount | grep configfs
# 嗯,通常都是在 /sys/kernel/config,于是我们就过去呗
pushd /sys/kernel/config/usb_gadget
# 创建一个 USB Gadget 并添加一个大容量存储的 function
mkdir g.1/functions/mass_storage.0 -p
# 等待内核完成相关工作(脚本时必要)
sleep 1
# 写入需要被直通的设备文件,或者直接是文件也行,不过小心 MMC 被写穿(笑)
# 这里先用 SD 卡试试
echo "/dev/mmcblk0" > g.1/functions/mass_storage.0/lun.0/file
# 标记为不可移动,即不是 U 盘
echo 0 > g.1/functions/mass_storage.0/lun.0/removable

# 创建一些字符串啊和 ID 的信息
mkdir g.1/strings/0x409
mkdir -p g.1/configs/c.1/strings/0x409
echo 0xa4a2 > g.1/idProduct
echo 0x0525 > g.1/idVendor
echo 1234567890 > g.1/strings/0x409/serialnumber
echo ntzyz > g.1/strings/0x409/manufacturer
echo "iSCSI over USB" > g.1/strings/0x409/product

# 创建配置,并将之前设置的 function 链接过来
echo "cnf1" > g.1/configs/c.1/strings/0x409/configuration
ln -s g.1/functions/mass_storage.0 g.1/configs/c.1

# 指定这个 Gadget 使用的控制器,可以通过 find /sys -name gadget 来确认
echo fe800000.dwc3 > g.1/UDC

# 回到之前的CWD
popd

掏出你的 USB 3.0 Type-C 数据线,连接好 RK3399 和你的电脑,然后执行这个脚本,不出意外的话就能看到新硬件了,同时内核大概有这些输出:

[   52.614900] fusb302 4-0022: PD disabled
[   52.617919] cdn-dp fec00000.dp: [drm:cdn_dp_pd_event_work] Not connected. Disabling cdn
[   52.628791] rockchip-dwc3 usb@fe800000: USB peripheral connected
[   52.636063] android_work: did not send uevent (0 0           (null))
[   52.649973] android_work: sent uevent USB_STATE=CONNECTED
[   52.679112] configfs-gadget gadget: super-speed config #1: c
[   52.679840] android_work: sent uevent USB_STATE=CONFIGURED

这就完成了!是不是很简单?

然后就是让 RK3399 能访问到一个远程磁盘了,当然你也可以往 RK3399 上加 SATA 硬盘,不过这样似乎还不如直接用移动硬盘来得方便(笑)。原则上你可以用 NFS/CIFS 之类的网络文件系统,访问到 NAS 的一个分区,然后 DD 出一个比较大的稀疏磁盘镜像,将他格式化,然后直接用这个文件来充当磁盘。可是不知道为什么我这样试的时候,写入一个 1GB 的动画片在刚起步的时候飙到了 300MB/s,没到两秒就降速到了 0,同时看 RK3399 的串口输出看到了内核 panic 的报错((

果然这种情况还是需要用 iSCSI 啊!Windows 上如果想开启 iSCSI 目标服务,可以选择换一个 Server 版的 Windows,内置了 iSCSI 目标服务器的功能,当然买不起/不想换的可以找一些用户层的软件来实现,比如对个人用户免费的 StarWind ,不过这货好像不支持创建稀疏的 img,有点蛋疼。Linux 上可以参考 Arch Wiki 上 Open iSCSI 相关条目的信息。iSCSI 目标服务的配置这里就不废话了,比较简单,用户认证什么的用 CHAP 就行了,不需要太复杂。

RK3399 方面,首先向 iSCSI 服务器发起一个 sendtargets 请求:

iscsiadm -m discovery -t sendtargets -p 10.10.28.75

得到 target 列表后就可以登陆了:

iscsiadm -m node --targetname=iqn.2008-08.com.starwindsoftware:ntzyz-gen8-ps4volume --login

不出意外这时候内核会甩你一脸日志,比如发现 sda 这种,这时候你就成功的创建了与服务器的连接,所有在 sda 上的变更都会被写入到远程磁盘上。只要将这个设备文件作为 Gadget 中 Mass Storage 的 lun.0 对应的 file,就实现了我们的终极目标:USB 接口的远程磁盘!

将 USB 口从电脑上拔下,插到隔壁 PS4 上,进入设置->周边设备->USB 设备就能看到一个名为iSCSI over USB,制造商为 ntzyz 的移动硬盘,将他格式化为扩展存储后就可以使用了。经测试,将 NieR Automatic 移动到该拓展存储并执行没有明显的体验上的区别。