WSL2的USB支持(USBIP方式)

警告

由于Windows vista之后,所有驱动必须签名才可以加载,因此本方案要求禁用Secure Boot!

如果你无法接受关闭Secure Boot,请等待微软官方提出解决方案

总所周知WSL2发布了有一段时间了,但WSL2仍然不支持USB,这意味着你没有办法在WSL2里面使用主机上的USB设备,例如arduino等设备,这显得很不方便

不过最近我找到了一个通过USBIP来使得WSL2“支持”USB的方法,当然也踩了很多坑。

首先声明,本文基本上根据的是https://github.com/rpasek/usbip-wsl2-instructions 上的方案,其中略微有些改动。

安装USBIP

这个嘛,没什么好说的,基本上去 https://github.com/cezanne/usbip-win/releases 这里下载最新的预编译好的文件,Assets里面除了那俩 Source Code之外全都下载就完事了。

下载好之后首先需要安装签名的证书,usbip_test.pfx就是这个文件,直接双击进行导入,导入密码是usbip. 需要将这个证书导入两次,导入到本地计算机的“受信任的根证书颁发机构”和“受信任的发布者”这两个地方。

注意

一定要导入到的是“本地计算机”!!!!!!!!!!!

然后就是启用测试签名了,开一个具有管理员权限的CMD或者PowerShell都行,运行
bcdedit.exe /set TESTSIGNING ON 就好了。如果提示“该值受安全引导策略保护,无法进行修改或删除”,请翻到本文开头,仔细阅读红色提示框内的内容。

然后重启。。。如果你还开了BitLocker,那你一定要在重启前准备好恢复密钥,不然。。。

如果不知道恢复密钥,可以去http://account.microsoft.com/devices/recoverykey 这里查看。前提是你登录了微软账号。

编译内核模块

是的,由于微软默认提供的内核是不带USB模块的,因此需要自己编译一下内核的USB模块。

首先需要拉下WSL用的内核源码

git clone https://github.com/microsoft/WSL2-Linux-Kernel.git

这个仓库大概是有1.4GB左右,总之我挂代理拉了两次都失败了,最后只能先加一个 --depth=1参数,先拉一部分源码下来,然后再执行一次 git pull --unshallow把剩下的代码拉下来。

结果fetch完进去一看,似乎tag不是很全,少了4.19.104的tag,于是只能再git pull --tags把所有的tag拉下来。

然后就是先看看当前用的是什么版本的内核,uname -r看了下是4.19.104-microsoft-standard的版本,于是就先checkout到这个tag上。

其实这个时候本来想,都重新编译内核了,不如干脆改成5.x算了,不过看了下5.x的没有microsoft的标签,担心不兼容就没测试。

然后就可以修改下配置文件进行编译了,一开始我是把 /proc/config.gz 复制过来,然后在这个基础上修改再编译的,结果编译了四五次,生成的内核都没办法启动,内核模块也没法使用。最后发现似乎再Microsoft目录下已经有一份配置文件了。于是就改用这个配置文件了。

make KCONFIG_CONFIG=Microsoft/config-wsl CC=clang LD=ld.lld menuconfig

这里先用KCONFIG_CONFIG指定使用这个配置文件,然后我还顺便改用clang去编译了,至于为什么,因为我之前全部都是用 gcc-10 编译的,全都没办法启动。当然当时用的是 /proc/config.gz的配置文件,暂时没办法测试到底是编译器还是配置文件的问题。

这里主要需要修改的配置文件是 Device Drivers里面的,在里面找到USB support,先按空格启用,然后再按回车进入里面。空格勾选Support for Host-side USB。然后将子选项里面自己需要用的东西进行勾选。

这里我选了大概有

  • xHCI HCD support
  • EHCI HCD support
  • OHCI HCD support
  • USB Mass Storage support
  • USB/IP support
  • USB/IP support => VHCI hcd

剩下的可以根据自己需要进行勾选。

然后我还改了一个地方,是内核的版本号,是最上层的配置中,General setup里面的Local version。我给他原来的后面加了一个-usb的后缀。其实这个地方我本来是不打算修改的,但是当我没有修改的时候,编译出来的内核可以启动,但是内核模块没法加载,会提示找不到一个符号,然而那个符号在 System.map 里面是可以找到的,十分奇怪。总之改了个版本号之后就莫名其妙的可以了。这部分大概过几天有时间的时候再重新进行测试。

然后就是编译了

注意

记得保存!!!!!!!!

make LOCALVERSION= KCONFIG_CONFIG=Microsoft/config-wsl CC=clang LD=ld.lld -j4 all

这里还顺便加了个 LOCALVERSION,不加的话编译出来的版本号后面会多一个“+”号,觉得无所谓的可以不加。至于-j参数,自己根据性能改改。总之我8G的情况下只能-j4左右了,多了会直接炸内存。。。

差不多我Surface pro 7编译了十五分钟左右。

有坑!!!

编译到最后链接内核的时候,遇到了重复定义的问题,是一个叫做 __force_order的变量的重复定义,在arch/x86/boot/compressed/kaslr_64.carch/x86/boot/compressed/pgtable_64.c这两个文件里面都有,然而我翻了一下 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 这里,kaslr_64.c里面是没有这个变量的,于是我就干脆也把这个文件里面的删掉了。暂时没发现有什么异常。。。

安装和配置内核

编译完成之后首先安装下内核模块

make modules_install

就好了,模块会被放到 /lib/modules/里面,只需要检查下里面有我们需要的模块就好了。

然后是内核的部分,默认的情况下,WSL自带的内核没有启用模块兼容的,总之我的测试结果是,即使使用了和WSL一样的版本的源码编译出来的,也是没办法加载我们的模块的,于是还得顺便把内核也得换一个。

没错,WSL2的内核是可以自己换的,找到 arch/x86/boot/bzImage,这就是编译好的内核,把他复制的Windows上的任意一个目录,这里我就直接放我用户目录里面创了个 kernel了,也就是路径是C:\Users\<xxx>\kernel\bzImage这里。

然后接着在用户目录里面创建一个文件,文件名是.wslconfig,内容是

[wsl2]
kernel=c:\\Users\\<xxx>\\kernel\\bzImage

具体路径自己改下哦。

然后先关闭当前的WSL实例

wsl --shutdown

接着再开启WSL之后,用uname -r看看内核是不是正确的(废话,不正确就不会启动了

这个适合需要测试下刚刚的内核模块能不能用,按照我自己的配置来说的话

modprobe hid-generic
modprobe hid
modprobe usbhid
modprobe usbcore
modprobe usb-common
modprobe usb-storage
modprobe usbip-core
modprobe vhci-hcd

大概就是这个几个模块,没有报错就行了,顺便还可以dmesg看看输出。这个时候用lsusb就应该可以看到默认的Linux Foundation root hub设备了。

提示

上面的一堆modprobe可以放到一个sh脚本里面,然后加入到比如 zshrc之类的地方启动的时候运行。

USBIP 客户端的安装

还是刚刚内核源码里面,进入到 tools/usb/usbip 目录里面,这里面是USBIP的源码,还得编译安装这个。

./autogen.sh
./configure.sh
make -j

基本上就是这三个步骤。

不过我在编译的时候其实是报错了的,首先是一个

usbip_device_driver.c:108:2: error: ‘strncpy’ specified bound 256 equals destination size [-Werror=stringop-truncation]

这么一个错误,懒得处理了,直接

sed 's/-Werror//g' -i Makefile
sed 's/-Werror//g' -i src/Makefile
sed 's/-Werror//g' -i libsrc/Makefile

-Werror去掉算了

然后还遇到一个链接时期的错误,基本上是一个 udev_context的重复定义的问题,大概看了下代码,个人认为libsrc/usbip_host_common.c文件里面的第38行的定义可以改成 static作用域的,也就是在struct udev *udev_context;前面加一个 static就好。

改完编译完成,然后make install就好。

Windows上的设置

在Windows上主要是USBIP的设置。首先找到之前下载的一堆usbip的文件。运行一下

usbip.exe list -l

查看下当前的设备,基本上输出就是

- busid 1-63 (045e:09c0)
  Microsoft Corp. : unknown product (045e:09c0)
- busid 1-18 (045e:0306)
  Microsoft Corp. : unknown product (045e:0306)
- busid 1-68 (046d:c084)
  Logitech, Inc. : unknown product (046d:c084)
- busid 1-86 (8087:0026)
  Intel Corp. : unknown product (8087:0026)

这样,记录下需要转发的设备,比如那个鼠标,是1-68,然后运行一下

usbip.exe bind -b 1-68

然后启动服务端

usbipd.exe -d

顺手启用下debug输出。默认会在IPv4和IPv6上都监听,而且是监听的0.0.0.0,但其实我觉得没必要,127.0.0.1就好了。

注意

如果你看到了类似

usbip: error: get_device_path: traverse_intfdevs failed, returned: 0

usbip: error: open_stub_dev: invalid devno: 194

usbip: error: export_device: cannot open devno: 194

usbip: error: failed to export device: 1-194, err:-1

的错误,请翻到本文开头仔细阅读红色框内的文字!

WSL内的设置

WSL内的话是启动的客户端

usbip attach -r <ip> -b 1-68

这里换成自己的IP,至于怎么看IP,Windows上用 ipconfig看看,找vEthernet (WSL)的网卡上的IP就是了。-b后面就是在上一步bind的ID,是可以绑定多个的。

这里就是另一个巨坑了,也不知道为什么,如果Windows上监听的是127.0.0.1的话,这里是不会出现问题的。然而如果监听的是0.0.0.0的话,WSL访问主机的包就会被防火墙拦截下来。。。

而且由于vEthernet (WSL)是放在公共网络里面的,于是你在防火墙设置里面看到公共网络里面是没有活动连接的,当时我就只关了私有网络的防火墙(因为当前的网络配置的确是私有的),结果测试了很久也不行,最后干脆把公共的也关了,才发现可以。。。

其实这个是可以在防火墙里面加个规则放行下的,不过这个不在本文的讨论当中。

然后这个时候重新用lsusb看一下,就可以看到刚刚转发的那个USB设备了。

大概就是这样。当然智能卡的话,还得顺便先启动下 pcscd这个进程。

总结

全是坑,浪费我一个下午+一个晚上,建议大家不要轻易尝试。

关于 “WSL2的USB支持(USBIP方式)” 的 4 个意见

  1. 编译出错了,查了一下错误也不知道是什么问题
    cryptsetup: ERROR: Couldn’t resolve device rootfs
    cryptsetup: WARNING: Couldn’t determine root device

  2. 这种方式弄完之后是不是 在wsl2里连接了usb,那在windows下就没办法用了?就像vmware一样是一个系统独占的?

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注