Skip to content

Fix host USB HID boot, mode, and recovery bugs - #3

Closed
mjc wants to merge 4 commits into
mainfrom
mjc/fix-usb-hid-boot-subclass
Closed

Fix host USB HID boot, mode, and recovery bugs#3
mjc wants to merge 4 commits into
mainfrom
mjc/fix-usb-hid-boot-subclass

Conversation

@mjc

@mjc mjc commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

This PR fixes host-visible USB HID failures where NanoKVM keyboard and mouse input can fail in BIOS/UEFI, boot menus, GRUB, or after USB gadget recovery.

The core fixes are:

  • advertise the keyboard as boot HID keyboard
  • advertise the relative mouse as boot HID mouse
  • keep the absolute/touch HID interface non-boot
  • generate HID report descriptors with portable POSIX shell writes
  • write USB string descriptors without shell-added newlines
  • keep normal mode and HID-only mode on the same gadget setup path
  • recover stale /dev/hidg* handles after the gadget is rebuilt

This PR is intentionally limited to HID and USB descriptor/recovery bugs. It does not change virtual media behavior or UI.

Problem

The existing normal and HID-only init scripts build USB gadgets through separate, duplicated paths. That allowed HID descriptor behavior to drift between modes and made firmware/host enumeration sensitive to boot flags and previous host state.

The main failure modes addressed here:

Failure Fix
Firmware or early boot environments do not reliably accept keyboard/mouse input Always expose boot keyboard and boot relative mouse interfaces; keep absolute/touch non-boot
Linux can enumerate the interfaces but fail to create usable HID input devices Write exact string descriptors and exact HID report descriptor bytes
HID-only mode can differ from normal HID setup Share descriptor, string, wake, UDC, stop/restart, and PHY-reset helpers
HID writes can stop after gadget reset/rebuild Close/reopen HID handles around resets and reopen stale /dev/hidg* handles on write

What Changed

  • Added S03usb-common for shared USB gadget setup.
  • Refactored S03usbdev and S03usbhid to use the shared helper.
  • Added portable HID report descriptor generation using octal escapes and single configfs writes.
  • Preserved wake-on-write behavior: enabled by default when supported, disabled only by /boot/usb.notwakeup.
  • Selected exactly one UDC when binding or rebinding the gadget.
  • Made start rebuild an existing gadget tree after stop.
  • Made HID reset run the init script via sh with a timeout.
  • Fixed HID mode/status detection for normal vs HID-only mode.
  • Fixed support-layer HID detection to check concrete hid.GS0, hid.GS1, and hid.GS2 config links.
  • Added Go handling for deleted or stale /dev/hidg* handles after gadget rebuilds.
  • Added shell and Go tests for the HID gadget setup and recovery behavior.

Related Issues

Issue Relationship
sipeed#544 Fixes the HID boot subclass/protocol behavior discussed in the issue thread.
sipeed#50 Fixes script-side HID descriptors where keyboard/mouse interfaces did not advertise boot subclass/protocol.

Validation

Automated checks:

make test-usb-scripts
go test ./service/hid
go test -race ./service/hid
git diff --check main..HEAD

Additional shell compatibility checks were run against the USB init script harness with:

sh tests/usb-init-scripts-test.sh
dash tests/usb-init-scripts-test.sh
busybox sh tests/usb-init-scripts-test.sh
bash --posix tests/usb-init-scripts-test.sh

The fake configfs harness verifies:

  • normal mode exposes HID plus configured non-HID functions
  • HID-only mode exposes only HID functions
  • keyboard uses boot subclass/protocol
  • relative mouse uses boot subclass/protocol
  • absolute/touch HID remains non-boot
  • /boot/BIOS no longer changes the intended HID layout
  • /boot/disable_hid removes HID functions in normal mode
  • USB string descriptors are exact strings
  • HID report descriptors are exact byte sequences
  • wake-on-write remains controlled by /boot/usb.notwakeup
  • UDC bind, unbind, restart, single-controller selection, stop/start rebuild, and restart_phy flows work through the shared helper

Live validation confirmed that cold firmware setup accepts NanoKVM keyboard input with this branch, and that normal Linux enumeration binds all three HID interfaces without usbhid/hid-generic errors.

Copilot AI review requested due to automatic review settings June 5, 2026 18:13
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Lost in the diff? Review this PR in Change Stack to follow the change map from intent to exact ranges.

Review Change Stack

📝 Walkthrough

Walkthrough

Refactors USB gadget init into a shared helper script, updates S03usbdev and S03usbhid to use it, adds an integration test suite that simulates configfs/sysfs, and adjusts Go/C++ HID handling and tests.

Changes

USB Gadget Initialization Refactoring and Testing

Layer / File(s) Summary
Shared USB gadget helper functions
kvmapp/system/init.d/S03usb-common
Introduces configurable environment variables (USB_BOOT_DIR, USB_GADGET_ROOT, USB_GADGET_NAME, USB_UDC_CLASS, USB_OTG_ROLE) and reusable helper functions for test-mode gadget preparation, string/config descriptor writing, HID function creation, and UDC bind/unbind/restart operations.
S03usbdev script refactoring
kvmapp/system/init.d/S03usbdev
Refactors start_usb_dev to source common helpers, use environment-driven paths, delegate HID and mass-storage setup to shared functions (including LUN backing handling), and use bind_first_udc/run_usb_action for lifecycle.
S03usbhid script refactoring
kvmapp/system/init.d/S03usbhid
Replaces inline gadget construction with sourcing S03usb-common, calls prepare_usb_gadget + add_hid_functions + bind_first_udc, and delegates action handling to run_usb_action.
Shell-based USB init script integration tests
tests/usb-init-scripts-test.sh
Adds comprehensive test suite that emulates configfs/sysfs, provides assertion helpers, and verifies HID/mass-storage descriptors, BIOS-flag/disable behaviors, RNDIS/ISO cases, UDC selection, and lifecycle/PHY restart flows.
Go HID status: context timeout and variable ModeFlag
server/service/hid/status.go
Changes ModeFlag from const to var, imports context, and refactors ResetUSBPHY to use exec.CommandContext with a 10-second timeout.
Go tests for HID mode detection
server/service/hid/status_test.go
Adds tests validating getHidMode() for known bcdDevice values, unknown value rejection, and missing-file handling; includes a helper to temporarily override ModeFlag.
C++ HID state detection refinement
support/sg2002/kvm_system/main/lib/system_state/system_state.cpp
Adds constants for three HID gadget link paths and path_exists() helper, then updates HID state detection to check those specific links rather than a wildcard.
Makefile test target wiring
Makefile
Adds test-usb-scripts to .PHONY, updates help to advertise it, and creates a target that runs tests/usb-init-scripts-test.sh.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through configfs, links, and strings with glee,

helpers stitched the gadget where hard-coding used to be.
Tests in a temp tree danced, ensuring bindings sound,
Go and C++ now listen when HID modes are found.
A tiny rabbit cheers: integrated, testable, and free!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix host USB HID boot, mode, and recovery bugs' directly and clearly summarizes the main changes in the PR, which address host USB HID failures in BIOS/UEFI/boot contexts with fixes to boot subclass advertising, descriptor writing, gadget setup, and recovery paths.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mjc/fix-usb-hid-boot-subclass

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch from 88594bc to eeb55d5 Compare June 5, 2026 19:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eeb55d5dc1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kvmapp/system/init.d/S03usb-common Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74a1f917c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kvmapp/system/init.d/S03usb-common Outdated
@mjc mjc changed the title Fix USB HID boot interfaces and descriptor strings Fix host USB HID boot, mode, and recovery bugs Jun 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@kvmapp/system/init.d/S03usb-common`:
- Around line 82-95: The UDC binding code in bind_first_udc and restart_udc
writes every basename from ${USB_UDC_CLASS} into UDC which can produce a
multi-line value; change both locations to select exactly one UDC name (e.g.,
pipe the find/basename output through head -n1) and write only that single name
to the UDC target (use "${USB_GADGET_ROOT}/${USB_GADGET_NAME}/UDC" in
restart_udc and UDC file in bind_first_udc); additionally add a simple check
that the selected name is non-empty (and optionally log/exit when none found or
when more than one entry exists) so binding never receives a multi-line value.

In `@tests/usb-init-scripts-test.sh`:
- Around line 201-237: Add a regression test that exercises the production
stop→start lifecycle: create a new test function (e.g.,
test_stop_then_start_rebinds_udc) that uses new_env and run_start to initialize
the gadget tree, then invoke sh "${NORMAL_SCRIPT}" stop and then sh
"${NORMAL_SCRIPT}" start (with USB_TEST_MODE, USB_COMMON, USB_BOOT_DIR,
USB_GADGET_ROOT, USB_UDC_CLASS, and USB_OTG_ROLE set as in other tests) and
assert the gadget was reconfigured (check "${base}/gadget/g0/UDC" equals the
expected UDC string like "4340000.usb" and "${base}/proc/cviusb/otg_role" is set
appropriately) so the suite covers start after an already-initialized gadget.
- Around line 173-199: The test test_hid_only_descriptors_and_no_wake only
checks the serial string; add assertions to verify the full English string set
(manufacturer, product, and configuration) are written in HID-only mode by
asserting "${g}/strings/0x409/manufacturer" and "${g}/strings/0x409/product" and
"${g}/configs/c.1/strings/0x409/configuration" equal the same expected values
used by the normal-mode test (i.e., copy the assert_eq lines for
manufacturer/product/configuration from the normal-mode test into
test_hid_only_descriptors_and_no_wake so HID-only uses identical strings).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fa6b6393-7d6c-49e0-8319-6adc2e1da9f8

📥 Commits

Reviewing files that changed from the base of the PR and between 3de4a18 and 20d82c3.

📒 Files selected for processing (8)
  • Makefile
  • kvmapp/system/init.d/S03usb-common
  • kvmapp/system/init.d/S03usbdev
  • kvmapp/system/init.d/S03usbhid
  • server/service/hid/status.go
  • server/service/hid/status_test.go
  • support/sg2002/kvm_system/main/lib/system_state/system_state.cpp
  • tests/usb-init-scripts-test.sh

Comment thread kvmapp/system/init.d/S03usb-common
Comment thread tests/usb-init-scripts-test.sh
Comment thread tests/usb-init-scripts-test.sh
@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch from 74bebf4 to 4dcaa87 Compare June 5, 2026 21:35
@mjc
mjc requested a review from Copilot June 5, 2026 21:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@kvmapp/system/init.d/S03usb-common`:
- Around line 272-278: The restart_usb_phy function calls start_usb_dev which
may not be defined by every script sourcing S03usb-common; add a guard to check
for the existence of start_usb_dev before calling it (e.g., use a conditional
like command -v or type to test start_usb_dev) and log or return an error if
it's missing, or alternatively document in the file header that any script
sourcing S03usb-common must define start_usb_dev; update restart_usb_phy to call
start_usb_dev only when present to avoid silent failures.

In `@kvmapp/system/init.d/S03usbdev`:
- Around line 43-54: The code redundantly reads "${USB_BOOT_DIR}/usb.disk0"
twice; reuse the previously read variable disk instead of catting the file again
— in the S03usbdev script, inside the if/else that references
USB_MASS_STORAGE_FUNC and lun.0/file, replace the second cat
"${USB_BOOT_DIR}/usb.disk0" usage with the $disk variable so the else branch
writes the already-read content to
"functions/${USB_MASS_STORAGE_FUNC}/lun.0/file".

In `@tests/usb-init-scripts-test.sh`:
- Around line 270-281: The test test_restart_phy_rebuilds_gadget relies on an
implicit equality between USB_PHY_DEVICE and USB_DEFAULT_UDC; make this explicit
by setting USB_PHY_DEVICE to the expected USB_DEFAULT_UDC value when invoking
run_script_action (or define a dedicated test constant) so restart_phy writes
the same device string the assertions expect; update the run_script_action call
or the test setup to export or pass USB_PHY_DEVICE="4340000.usb" (or use a named
constant) before calling restart_phy so the checks against dwc2/unbind,
dwc2/bind and g/UDC are deterministic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6aa367b7-148e-40c7-9d2e-4222951e65fe

📥 Commits

Reviewing files that changed from the base of the PR and between 20d82c3 and 74bebf4.

📒 Files selected for processing (4)
  • kvmapp/system/init.d/S03usb-common
  • kvmapp/system/init.d/S03usbdev
  • kvmapp/system/init.d/S03usbhid
  • tests/usb-init-scripts-test.sh

Comment thread kvmapp/system/init.d/S03usb-common
Comment thread kvmapp/system/init.d/S03usbdev
Comment thread tests/usb-init-scripts-test.sh
@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch from 4dcaa87 to 2e1bfa9 Compare June 5, 2026 21:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment thread server/service/hid/status.go
@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch from 2e1bfa9 to 964eb9b Compare June 5, 2026 21:42
@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch from f903df6 to 5b0ac1c Compare June 5, 2026 22:03
@mjc

mjc commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

Closing this fork-local PR; reopening the same branch against upstream sipeed/NanoKVM.

@mjc mjc closed this Jun 5, 2026
mjc pushed a commit that referenced this pull request Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants