Hailo-8L 的奇妙电源之旅
Hailo-8L 的奇妙电源之旅
最近给家里部署监控,用 Frigate 做 NVR. 新时代里,object detection 已经有了更合适的硬件,不比以往只能用 GPU,遂购入一块 Hailo-8L——一块 M.2 B+M key 的 inference chip,标称 13 TOPS,支持 YOLO 很轻松。
然而我的 Frigate 不是 bare metal,它在 Hyper-V 的 VM 里。GPU(捡垃圾来的 T400)和这块 Hailo 都是通过 DDA passthrough 进 VM 的。NVIDIA T400 passthrough 一切太平,Hailo 就有点麻烦了。
一
最先注意到的是 Frigate 起不来,detector 初始化 failed,log 反映 Hailo error 36:
$ docker compose logs frigate 2>&1 | grep -Ei 'hailo|detector|error'
frigate.detectors.plugins.hailo8l ERROR : Failed to initialize Hailo device
...
hailort [HailoRT] [error] CHECK failed - Failed to open device. status=HAILO_DRIVER_OPERATION_FAILED(36)driver、firmware 都是按照官方指南成功安装的。使用 hailortcli identify 这块卡,看看是不是卡本身坏的:
$ sudo hailortcli fw-control identify
Executing on device: 2f7f:00:00.0
[HailoRT] [error] CHECK_SUCCESS failed with status=HAILO_DRIVER_OPERATION_FAILED(36)
[HailoRT] [error] Failed to open device device_id=2f7f:00:00.0
Device disconnected while opening device读不出来。
总结现象:
hailortcli fw-control identify提示HAILO_DRIVER_OPERATION_FAILED(36),Device disconnected while opening device- Frigate 侧 detector 初始化失败,同样是 error 36
- 但设备本身在系统里可以通过 lspci 看到
下文的 2f7f:00:00.0 就是这块卡在 VM 里的 PCI 地址,请按自己的实际情况替换。
二
passthrough 不比 bare metal,如果是 bare metal 估计直接就找卖家了,但我这是 passthrough,我们还是先不要怀疑硬件坏了,这样也就两个可能:
- passthrough 坏了
- 驱动坏了
先看 lspci 的详细信息,interrupt 有没有分配上:
$ lspci -nn | grep -i hailo
2f7f:00:00.0 Co-processor [0b40]: Hailo Technologies Ltd. Hailo-8 AI Processor [1e60:2864]
$ grep -i hailo /proc/interrupts
... hailo设备在、interrupt 在,说明 guest 里的 Linux 认到了卡,PCIe link 也是通的,基本健康。硬件损坏这一点可以先排除。
$ dmesg | grep -i hailo
hailo: probing driver hailo_pci
hailo 2f7f:00:00.0: Firmware loaded successfully
hailo 2f7f:00:00.0: Waking up board change state from 3 to PCI_D0
hailo 2f7f:00:00.0: Device disconnected while opening device看一眼 dmesg,hailo driver 打印的 while opening device 这个措辞很微妙:设备不是一开始就没有,而是在 open 的那一刻才没的,而且前面还紧跟着一条 waking up:
change state from 3 的这个 3 就是 D3hot. 网上没搜到什么有用的信息,好在 Hailo 的 PCIe driver hailort-drivers 是开源的,直接把这句报错 grep 进源码:
$ grep -rn "Device disconnected while opening device" linux/pcie/
linux/pcie/src/fops.c:130: hailo_err(board, "Device disconnected while opening device\n");顺着 fops.c 的 open 路径读下去:用户 open 设备的时候,driver 先把它从当前 state 唤醒回 D0,紧接着检查设备还在不在,不在就打出这句报错:
previous_power_state = board->pdev->current_state;
if (PCI_D0 != previous_power_state) {
hailo_info(board, "Waking up board change state from %d to PCI_D0\n", previous_power_state);
err = pci_set_power_state(board->pdev, PCI_D0);
...
}
if (!hailo_pcie_is_device_connected(&board->pcie_resources)) {
hailo_err(board, "Device disconnected while opening device\n");
err = -ENXIO;
goto l_revert_power_state;
}报错的上一步是 pci_set_power_state 到 D0, 既然 open 的时候要唤醒,说明设备原来就待在某个更低的 power state——那是谁把它放进去的?再看 probe:pcie.c 里 firmware 加载完之后,driver 就让它进了 D3hot:
hailo_disable_interrupts(board);
if (power_mode_enabled()) {
// Setting the device to low power state, until the user opens the device
hailo_info(board, "Power change state to PCI_D3hot\n");
err = pci_set_power_state(board->pdev, PCI_D3hot);
...
}两头一对,流程就串起来了:
driver probe 时加载 firmware,随后进入 low power mode,把卡放到 D3hot;用户 open 设备时,driver 把它唤醒回 D0,紧接着检查设备是否还在——就是这一步报了错。
driver 为了省电,probe 完就让卡进入 D3hot,等用户 open 时再唤醒,而报错正是在叫醒之后出现的。如果卡 idle 时真的待在 D3hot,那不去 open 它、直接读它的 power state,就应该能看到 D3。PCI 设备当前的 power state 写在 PMCSR (Power Management Control/Status Register) 里,这个 register 位于 PCI Power Management capability 往后偏移 4 字节处,用 setpci 读它的 16-bit 值:
$ sudo setpci -s 2f7f:00:00.0 CAP_PM+4.w
0003-s 2f7f:00:00.0选中这块卡CAP_PM定位到 PCI Power Management capability+4.w读偏移 4 处的 16-bit (word) register,也就是 PMCSR
读出来的低两位(PMCSR 的 PowerState 字段)就是设备当前的 power state。PCIe 规范定义了这么几个 device power state:
| PowerState | 状态 | 说明 |
|---|---|---|
0 | D0 | fully on,正常工作状态 |
1 | D1 | 轻度低功耗(可选,很多设备不实现) |
2 | D2 | 更深一档低功耗(可选) |
3 | D3hot | 软件可控的低功耗,context 由 driver 保存,靠总线供电,需软件唤醒回 D0 |
(另外还有个 D3cold,是设备主电源被切断的状态,这里不涉及。)
这里读出来 0003,低两位为 3——卡在 idle 时确实躺在 D3hot 里,源码和实际状态对上了。
三
那 D3hot -> D0 transition 为什么会失败?hailo_pcie_is_device_connected 就是读一下设备的 Vendor ID,正常应该读到 0x1e60。实际读到的是 0xffff——这是 PCIe 上“设备没了”的症状——说明 D3hot 回到 D0 之后,设备的 config space / MMIO 没有被正确 restore。整条链是这样的:
probe 加载 firmware 后把设备切换到 D3hot。用户 open 时,driver 将它唤醒回 D0,但 MMIO 没有被正确恢复;随后 driver 读取 Vendor ID,读到 0xffff 而不是 0x1e60,于是报出 Device disconnected while opening device。
这里得严谨一点:我这套环境是 Hyper-V DDA passthrough,问题很可能出在 virtual PCI bus 上——DDA 把 D3hot 的 power state 切换转交给 Hyper-V 处理,而它在设备醒来之后没能把 MMIO 正确 restore 回去。旁证是同一台机器上 passthrough 的 NVIDIA T400 一切正常,说明这条 virtual bus 对 D3hot 的处理至少对某些设备是不完整的。
不过我没有条件把这块 Hailo-8L 插到 bare metal Linux 上做对照实验,所以不能说裸机就一定没这个问题——也许某些主板/BIOS 的 PCIe root complex 在 D3hot restore 上同样有问题,只是 DDA 这层把问题放大或者提前暴露了出来。更稳妥的说法是:Hailo driver 的 D3hot power management 和(至少)Hyper-V DDA 之间存在一处 interoperability bug。不管根因在哪一层,只要设备别进 D3hot,问题就能绕过去。
修
既然根源是 D3hot,思路就很简单了:禁止 sleep. Hailo driver 正好有个 module param no_power_mode:
module_param_named(no_power_mode,
g_is_power_mode_enabled,
invbool,
S_IRUGO);注意这个 invbool,逻辑是反着来的:
no_power_mode=N -> g_is_power_mode_enabled = true
no_power_mode=Y -> g_is_power_mode_enabled = false所以 no_power_mode=Y 才是关掉 automatic power management。把它写进 modprobe 配置里,再 rebuild initramfs(不然 passthrough 早期加载的 driver 读不到这个 option):
$ echo 'options hailo_pci no_power_mode=Y' | \
sudo tee /etc/modprobe.d/hailo_pci.conf
options hailo_pci no_power_mode=Y
$ sudo update-initramfs -u重启,先确认 option 确实起效:
$ cat /sys/module/hailo_pci/parameters/no_power_mode
Y再读一遍 PMCSR 看 power state:
$ sudo setpci -s 2f7f:00:00.0 CAP_PM+4.w
0000低两位是 00,卡留在了 D0 而不是 D3hot [1]。这时候 hailortcli 也正常了:
$ sudo hailortcli fw-control identify
Executing on device: 2f7f:00:00.0
Identifying board
Control Protocol Version: 2
Firmware Version: 4.24.0 (release,app,extended context switch buffer)
Logger Version: 0
Device Architecture: HAILO8L
Serial Number: HLDDLBB261602218
Part Number: HM21LB1C2LAE
Product Name: HAILO-8L AI ACC M.2 B+M KEY MODULE EXT TMP最后拉起 Frigate,顺便确认 MSI interrupt 和 detector 都正常初始化:
$ cd /opt/frigate && docker compose up -d
$ grep -i hailo /proc/interrupts
$ docker compose logs frigate 2>&1 | grep -Ei 'hailo|detector|error'日志里 Hailo detector 正常 init,不再是之前那个 error 36。吼了!
有时会读到
8000,低两位仍是00(D0),bit 15 是 PME status(write-one-to-clear),不影响。想清掉可以sudo setpci -s 2f7f:00:00.0 CAP_PM+4.w=8000:8000。 ↩︎
