首先 probe 失败的问题
需要先添加 iommu capable 函数, 表明 IOMMU_CAP_CACHE_COHERENCY
能力为 true, 否则 probe 时会失败.
1 | static struct iommu_ops xt_iommu_ops = { |
打开 option allow_unsafe_interrupts=1
编译为动态模块时, insmod 会读取 /etc/modprobe.d/iommu_unsafe_interrupts.conf
文件, 将 allow_unsafe_interrupts 设置为 1.
1 | options vfio_iommu_type1 allow_unsafe_interrupts=1 |
如果不是动态模块, 包含在 kernel Image 中, 则需要强制打开
1 | static bool allow_unsafe_interrupts = 1; |
其次 xt_iommu 在 base 版本上使用的一些接口在升级版本上发生了变化:
如 iommu_device_set_ops
iommu_device_set_fwnode
bus_set_iommu
不见了, 取而代之的是总结成了一个接口 iommu_device_register
, 该接口支持的参数也有变化.
其次某些函数的参数个数意义变化:xt_iommu_map
xt_iommu_unmap
, size 变成了 pgsize 和 pgcount.
需要相应的适配修改.
kernel 的相关 config 需要打开:
1 | CONFIG_IOMMU_IOVA=y |
移植 iommu 后, 直通给 guest 的网卡 e1000e 报错, log 如下:
经过分析后, 得出结论, 网卡通过 dma 发包出现异常, 最后由 ndo_tx_timeout 回调了 e1000e 的 e1000_tx_timeout
函数将网卡重启.
由监控的 watchdog
1 | WARN_ONCE(1, "NETDEV WATCHDOG: %s (%s): transmit queue %u timed out %u ms\n", |
打印出了下面的堆栈:
1 | [ 42.096669] NETDEV WATCHDOG: eth0 (e1000e): transmit queue 0 timed out 9960 ms |
首先状态上来说, 中断是正常的, 但是 dma remapping 不正常.
在 riscv 基础版本上 (未开启 AIA), 只支持 intx 线中断模式e1000_intr
中断处理函数可以正常触发.
再由不同版本对比后, 发现 qemu (host) / qemu (guest) 以及 kernel (guest) 为不变量, 变量仅有 kernel (host) 从 5.10 版本升级到了 6.4 版本.
在 kernel 变更版本后, 相对应的 vfio 框架发生了一些变化, 在确认 5.10 -> 6.4 iommu / vfio 相关的 config 都相同时, 无明显的其他异常 log.
无明显的排查点, 再往下追只能根据代码行为去正向跟踪.
qemu xmmuv1 模拟行为
正常的 log, 未开启虚拟化时, host 中对 e1000e 网卡的处理就经由了 dma, 而在将 kernel 版本升级到 6.4 后, 却没触发对应的 xmmuv1_translate.
说明 base 版本上开了 e1000e 网卡的 iommu 支持, 而升级版本后关闭了网卡的 iommu 支持.
未开启虚拟化时, 还没有涉及到 vfio 的特性, 所以应重点排查 iommu 相关的 feature.
1 | #0 0x000055555591fa12 in xmmuv1_translate (mr=<optimized out>, addr=4294963200, flag=<optimized out>, iommu_idx=<optimized out>) at ../hw/riscv/xmmuv1.c:186 |
- 怀疑点 CONFIG_IOMMU_DMA, 这个开关在 base 和升级版本上都开了.
升级版本的开机日志:
dmesg | grep -E “DMAR|IOMMU”
1 | [ 1.156855] Failed to set up IOMMU for device Fixed MDIO bus.0; retaining platform DMA ops |
base 版本中的开机日志中并没有上述异常.
先看下这处异常:
1 | struct iommu_domain *iommu_get_domain_for_dev(struct device *dev) |
该 domain 应该来自于 iommu driver 设置的 default_domain
跟踪堆栈, 发现 xt_iommu driver 分配 default_domain 时失败了.
1 | #0 xt_iommu_domain_alloc (type=<optimized out>) at ../drivers/iommu/xuantie-iommu.c:365 |
在 __iommu_domain_alloc
函数中, 这个地方退出了, 而 base 版本没有这个逻辑.
1 | if (iommu_is_dma_domain(domain) && iommu_get_dma_cookie(domain)) { |
将这段注释掉后, 继而需要将 pci_bus_type 注册 iommu_ops, 但其他的 bus 类型 (platform_bus_type) 不能注册, 会导致异常, base 版本上的 xtiommu 只支持 pci_bus.
但升级版本已经没有 bus_set_iommu 相关的函数了, 只能改代码进行定制.
在改完后, 网卡在进行 dma 操作时, 已经使用 xtiommu, 但仍出现了异常.
还是表现在地址翻译时, 触发的地址 iova 不正常.
正常的 log:
xmmuv1_translate addr 4294963200
xmmuv1_translate addr 4294963201
xmmuv1_translate addr 4294963202
xmmuv1_translate addr 4294963203
…
而异常的 log:
xmmuv1_translate addr 4294963200
xmmuv1_translate addr 4294963204
xmmuv1_translate addr 4294963208
xmmuv1_translate addr 4294963212
退出
堆栈:
1 | #0 0x000055555591f75b in xmmuv1_translate (mr=0x555557025af0, addr=4294963201, flag=IOMMU_RO, iommu_idx=0) at ../hw/riscv/xmmuv1.c:104 |
在 flatview_read_continue 处追踪, 发现步长来自于翻译结果, 进一步跟踪发现
xt iommu 在第一级地址转换处出问题了 riscv_one_stage
, 地址翻译出错了.
qemu 中的 xt iommu 是未改动的, 且在 base 版本和升级版本都是同一份, 所以问题应该出现在 map 的地方.
跟踪 kernel 中 xt_iommu_map 的过程
1 | #0 xt_iommu_map (domain=0xff6000008023a6b0, iova=4294963200, paddr=4397146112, pgsize=4096, pgcount=1, iommu_prot=7, gfp=3520, mapped=0xff20000000d13218) at ../drivers/iommu/xuantie-iommu.c:70 |
追踪 map 的过程, 发现 xt iommu 驱动代码中做 mmu 映射时, 启用的 sv39 mode, 第一级的 pgd_shift 错误了使用了系统中定义的PGDIR_SHIFT
这个值是跟着系统的页表模式走的, 当前系统使用了 sv57 mode 5 级页表, 所以这个值是 48, 而 sv39 mode 这个值应该是 30.
1 | for (i = 0; i < loop; ++i) { |
将 PGDIR_SHIFT 修改为 PGDIR39_SHIFT (30) 后
再进行测试, 发现 host 中启用 xt iommu 进行 dma 的寻址终于正常了.
host 中正常后, 再进行 guest 直通网卡测试
发现 guest kvm-mode 下直通的 e1000e 网卡也正常工作了