记一次BazziteOS系统在win掌机上的问题排查

记一次BazziteOS系统在win掌机上的问题排查

__

在手头的一台香橙派orangepi neo win掌机上安装了BazziteOS系统,但是休眠唤醒之后,默认的控制器HHD失效了,而且手柄也识别不到,手柄可能几秒就恢复了,也可能十几分钟就恢复不了,然后就进行了一系列的排查。

核心内容:

  • 设备 OrangePi NEO-01,045e:028e

  • HHD_HIDE_ALL=1 时生成的规则有语法错误(缺逗号)

  • 1-5:1.1 唤醒后重枚举,触发 devhide 的 RUN+="/bin/chmod 000" 锁死节点

  • logind uaccess ACL mask::--- 导致外部 chmod 无效

  • 请求:在 unhide_all() 时同时调用 setfacl -b 或直接 chmod,或者增加配置项禁用针对特定设备的 devhide

以下是总结原文,目前已提交到GitHub的hhd项目issuebazzite官方issue

[Bug] OrangePi NEO-01: Controller unresponsive after suspend/resume due to devhide permission loop + ACL mask conflict

Device: OrangePi NEO-01
USB Controller: OrangePi USB Controller 045e:028e
System: Bazzite (Fedora Atomic / immutable), kernel 6.17.7-ba29.fc43.x86_64
HHD version: v4.1.5
systemd version: 258 (258.7-1.fc43)


Summary

After suspend/resume, the physical gamepad (xpad, 045e:028e) becomes completely unresponsive. HHD enters a loop of Using cached controller node / OSError: [Errno 19] No such device and never recovers on its own (or only recovers after 10–30+ minutes). Fan curves also fail until HHD is restarted.


Root Cause Analysis

The failure chain involves three compounding issues:

Issue 1: USB interface 1-5:1.1 enters a reconnect loop after resume

On resume from suspend, the device's secondary USB interface (1-5:1.1, bInterfaceClass=03, generic HID) begins disconnecting and reconnecting every ~8 seconds. This is likely firmware-level behavior specific to the NEO-01 after S2idle resume.

kernel: usb 1-5: New USB device found, idVendor=045e, idProduct=028e
kernel: hid-generic 0003:045E:028E.003D: input,hidraw0: USB HID v1.10 Keyboard on usb-0000:c3:00.3-5/input1
kernel: usb 1-5: New USB device found, idVendor=045e, idProduct=028e
kernel: hid-generic 0003:045E:028E.003E: input,hidraw0: USB HID v1.10 Keyboard on usb-0000:c3:00.3-5/input1
... (repeating every ~8 seconds indefinitely)

Each reconnection creates a new HID input node (input27, input29, input31, ...) which gets a new eventX device node. Note: 1-5:1.0 (the xpad joystick interface) remains stable throughout — event3/js0 stays correctly bound to xpad. The reconnect loop is exclusively on 1-5:1.1.

Issue 2: HHD's devhide rule is triggered on every reconnection, chmod 000 on event3

Each time a new input node from 1-5:1.1 appears, HHD calls hide_gamepad() which writes a udev rule to /run/udev/rules.d/95-hhd-devhide-*.rules. This rule contains:

KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="input", MODE="000", GROUP="root", \
  TAG-="uaccess", RUN+="/bin/chmod 000 /dev/input/%k"

Because this KERNEL== match is not scoped to the specific input node (due to a syntax error described below), it matches all event* and js* nodes — including event3 (the working xpad node). As a result, event3 gets chmod 000 repeatedly.

Syntax bug in HHD_HIDE_ALL=1 mode (the default on Bazzite):

The generated rule has a missing comma between ENV{ID_BUS}=="usb" and ATTRS{id/vendor}:

# Generated rule (broken):
SUBSYSTEMS=="input", ENV{ID_BUS}=="usb"ATTRS{id/vendor}=="045e", ATTRS{id/product}=="028e", GOTO="hhd_valid"

The missing comma causes the GOTO="hhd_valid" condition to partially fail, making the subsequent MODE="000" apply more broadly than intended.

Source file: /usr/lib/python3.14/site-packages/hhd/controller/lib/hide.py

if HIDE_ALL:
    root = f"{vid:04x}-{pid:04x}"
    extra = 'ENV{ID_BUS}=="usb"'   # <-- missing trailing comma

The correct line should be:

    extra = 'ENV{ID_BUS}=="usb", '

Issue 3: systemd-logind ACL (uaccess) with mask::--- makes chmod 000 irreversible from userspace

71-microsoft-controllers.rules tags the xpad input node with TAG+="uaccess", which causes systemd-logind to add an ACL entry user:<current_user>:rw- to event3. When HHD then applies TAG-="uaccess" and chmod 000, the resulting ACL state becomes:

# file: dev/input/event3
# owner: root
# group: root
user::---
user:lyymmo:rw-     #effective:---
group::---
mask::---           # <-- masks all permissions to 000, including the rw- ACL entry
other::---

With mask::---, even chmod 660 has no effect because the ACL overrides it. setfacl -b (which removes all ACLs) does temporarily restore the permissions, but logind immediately re-applies the ACL on the next udev event, restoring mask::---.

This creates an unbreakable loop: 1-5:1.1 reconnects → devhide rule fires → chmod 000 + TAG-="uaccess"mask::--- locks the node → HHD cannot open event3OSError: [Errno 19] No such device → loop.


Observed HHD Log (after resume)

ORPI   WARNING  Caching controller to avoid reconnection.
ORPI   ERROR    Received the following error:
                <class 'OSError'>: [Errno 19] No such device
ORPI   ERROR    Assuming controllers disconnected, restarting after 1s.
ORPI   INFO     Launching emulated controller.
ORPI   WARNING  Using cached controller node for Steamdeck Controller.
ORPI   INFO     Starting 'Steam Controller (HHD)'.
ORPI   INFO     Emulated controller launched, have fun!
ORPI   WARNING  Caching controller to avoid reconnection.
ORPI   ERROR    Received the following error:
                <class 'OSError'>: [Errno 19] No such device
... (repeating every ~8 seconds)

Current Workarounds Attempted (all insufficient)

Attempt Result
systemctl restart hhd after resume HHD restarts but finds event3 is chmod 000, loop continues
rm -f /run/udev/rules.d/95-hhd-devhide-*.rules then restart HHD Rule is regenerated immediately, chmod 000 reapplied
chmod 660 /dev/input/event3 Ineffective due to ACL mask::--- override
setfacl -b /dev/input/event3 + chmod 660 Works momentarily, but logind re-applies ACL within seconds
HHD_HIDE_ALL=0 via systemd drop-in Partially helps (rule now scoped to specific input node), but 1-5:1.1 reconnect loop continues to trigger devhide on new nodes, and mask::--- issue persists
sleep hook to rmmod xpad before suspend Caused display/graphics stack failure on resume, abandoned
udev rule to block hid-generic on 1-5:1.1 Ineffective; device re-enumerates and re-binds regardless
modprobe usbhid quirks=0x045e:0x028e:0x0004 No effect on reconnect loop

What does work: The controller eventually recovers on its own after 10–40 minutes (unknown trigger), or if the system is fully rebooted.


System Configuration (Normal/Working State)

# Normal state — both interfaces stable, no reconnect loop:
/sys/bus/usb/drivers/xpad/    → 1-5:1.0  (joystick interface, event3/js0)
/sys/bus/usb/drivers/usbhid/  → 1-5:1.1  (HID keyboard interface)

$ udevadm info /dev/input/event3 | grep -E "DRIVER|ID_INPUT"
E: ID_INPUT=1
E: ID_INPUT_JOYSTICK=1
E: ID_USB_DRIVER=xpad
E: ID_INPUT_JOYSTICK_INTEGRATION=internal

$ ls -la /dev/input/event3
crw-rw----. 1 root input 13, 67  /dev/input/event3   # permissions correct
# Failed state — after resume:
$ ls -la /dev/input/event3
c---------+ 1 root root 13, 67  /dev/input/event3   # chmod 000 + ACL locked

$ getfacl /dev/input/event3
user::---
user:lyymmo:rw-    #effective:---
group::---
mask::---
other::---

Requested Fixes

Fix 1 (Critical): Syntax error in hide.py — missing comma in HIDE_ALL mode

File: hhd/controller/lib/hide.py

# Current (broken):
extra = 'ENV{ID_BUS}=="usb"'

# Fix:
extra = 'ENV{ID_BUS}=="usb", '

Fix 2 (Important): unhide_all() / unhide_gamepad() should restore permissions, not just delete the rule file

Currently unhide_all() removes the rule file and calls reload_children(), but does not restore the chmod 000 that was already applied by the RUN+= action. The device node remains inaccessible even after the rule is removed.

Suggested addition in unhide_all() and unhide_gamepad():

import subprocess
# After removing rule files, restore permissions on all input nodes
for node in os.listdir("/dev/input"):
    path = os.path.join("/dev/input", node)
    if node.startswith("event") or node.startswith("js"):
        try:
            subprocess.run(["setfacl", "-b", path], capture_output=True)
            os.chmod(path, 0o660)
        except Exception:
            pass

Fix 3 (Enhancement): Add a configuration option to disable devhide for specific devices

For devices like the OrangePi NEO-01 where a secondary USB interface causes a reconnect loop that repeatedly triggers devhide, it would be helpful to have a per-device option to skip the devhide mechanism entirely, relying instead on evdev exclusive grab (grab=True) for isolation.

Fix 4 (Enhancement): Do not re-hide on reconnect if the physical controller node (1-5:1.0 / event3) has not changed

The reconnect loop is on 1-5:1.1 (a HID keyboard interface), not on 1-5:1.0 (the xpad joystick interface). HHD could check whether the reconnecting device is the same physical joystick interface before triggering a new devhide rule.


Environment

$ systemctl cat hhd.service | grep -E "Environ|HIDE"
Environment="HHD_HIDE_ALL=0"    # set via drop-in zz-hide-fix.conf
Environment="HHD_HIDE_ALL=1"    # set by /usr/lib/systemd/system/hhd.service.d/override.conf (Bazzite default)
# Note: override.conf loads AFTER our drop-in, so HHD_HIDE_ALL=1 wins unless
# the drop-in filename sorts after "override" alphabetically (e.g. "zz-hide-fix.conf")

$ cat /sys/bus/usb/devices/1-5/power/control
on
$ cat /sys/bus/usb/devices/1-5/power/autosuspend_delay_ms
-1000   # autosuspend disabled via kernel param usbcore.autosuspend=-1

Additional Notes

  • This issue does not occur on cold boot — only after suspend/resume
  • The 1-5:1.0 xpad interface remains stable throughout; only 1-5:1.1 (HID/keyboard) enters the reconnect loop
  • The reconnect loop on 1-5:1.1 appears to be a firmware behavior of the OrangePi NEO-01 hardware after S2idle, not a software issue
  • The fix most likely to help end users immediately is Fix 1 (the missing comma) combined with Fix 2 (restoring permissions in unhide_all())
  • Bazzite sets HHD_HIDE_ALL=1 by default via /usr/lib/systemd/system/hhd.service.d/override.conf; this triggers the broken code path

解决方案:

原本是想停用hhd,然后用powercontrol控制功耗与风扇等,但是powercontrol安装报错了,且在github上发现了同样问题的提交记录,后改用inputplumber来控制手柄,然后将hhd的手柄控制功能关闭,其他功能保持开启,最后停用powercontrol。

还需要写一个休眠唤醒之后,HHD重启的功能,来保证hhd的功耗控制稳定性

创建一个最简单的 resume 服务,只负责重启 HHD:

sudo tee /etc/systemd/system/hhd-resume.service > /dev/null << 'EOF'
[Unit]
Description=Restart HHD after resume to restore fan curve
After=suspend.target hibernate.target hybrid-sleep.target

[Service]
Type=oneshot
ExecStartPre=/bin/sleep 3
ExecStart=/usr/bin/systemctl restart hhd

[Install]
WantedBy=suspend.target hibernate.target hybrid-sleep.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable hhd-resume.service
systemctl is-enabled hhd-resume.service

这个服务唤醒后等 3 秒再重启 HHD,给系统足够时间稳定,然后 HHD 重新加载配置恢复自定义风扇曲线。

之后做一次休眠测试确认风扇曲线恢复正常:

systemctl suspend
# 唤醒后等10秒
journalctl -u hhd-resume --since "2 minutes ago"
journalctl -u hhd --since "2 minutes ago" | grep -E "fan|Fan|Starting|started" | tail -5

最终配置总结

问题根因: HHD 的 devhide 机制在休眠唤醒后把手柄节点权限锁死为 000,加上 1-5:1.1 接口的固件重枚举循环,导致手柄长时间无法识别。

最终方案:

inputplumber 负责手柄、陀螺仪、触摸板、菜单按键,它有专门的 50-orangepi_neo.yaml 配置,休眠唤醒后自动恢复,完全绕开了 HHD 的 devhide 问题。

HHD 保留运行,但控制器模式设为 Disabled,只负责风扇曲线和 TDP 控制。

hhd-resume.service 唤醒后 3 秒重启 HHD,恢复自定义风扇曲线。


保留的文件只有一个:

/etc/systemd/system/hhd-resume.service

其他之前创建的所有文件都已清理干净(之前反复尝试,操作了很多)。


如果后续 HHD 官方修复了 devhide 的 bug,可以重新把控制器模式打开测试,届时 inputplumber 和 HHD 的控制器功能二选一就行。

27H5小游戏数据备份 2026-04-02
Bazzite小黄鸭decky-lsfg-vk注意事项 2026-05-10

评论区