在手头的一台香橙派orangepi neo win掌机上安装了BazziteOS系统,但是休眠唤醒之后,默认的控制器HHD失效了,而且手柄也识别不到,手柄可能几秒就恢复了,也可能十几分钟就恢复不了,然后就进行了一系列的排查。
核心内容:
设备 OrangePi NEO-01,
045e:028eHHD_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项目issue与bazzite官方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 event3 → OSError: [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.0xpad interface remains stable throughout; only1-5:1.1(HID/keyboard) enters the reconnect loop - The reconnect loop on
1-5:1.1appears 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=1by 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 的控制器功能二选一就行。