Skip to content

Fix host-visible USB virtual media behavior#4

Open
mjc wants to merge 44 commits into
mjc/fix-usb-hid-boot-subclassfrom
mjc/fix-usb-virtual-media-host
Open

Fix host-visible USB virtual media behavior#4
mjc wants to merge 44 commits into
mjc/fix-usb-hid-boot-subclassfrom
mjc/fix-usb-virtual-media-host

Conversation

@mjc

@mjc mjc commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Context

This PR fixes host-visible USB virtual media behavior, stacked on top of the HID/descriptor fix branch.

The key bug is that NanoKVM used one overloaded usb.disk0 state for both selected virtual media and the NanoKVM data partition fallback. When no image was selected, the gadget could expose /dev/mmcblk0p3 as fake media. That makes the host see media that is not actually mounted by the user, and it can confuse firmware, OS installers, and eject/remount flows.

What changed

  • Added server/service/usb as the backend helper for host-visible USB gadget operations:
    • UDC detach/attach/rebind
    • HID close/reopen locking around gadget mutations
    • first-UDC discovery
    • optional forced_eject use
    • safe optional configfs/sysfs writes
    • mass-storage, data-disk, and RNDIS function/link/flag toggles
  • Changed virtual media semantics:
    • mass_storage.disk0 is the selected virtual image device
    • no mounted image means lun.0/file is empty, not /dev/mmcblk0p3
    • /dev/mmcblk0p3 is treated as legacy "no image" when reporting mounted image state
    • ro, cdrom, inquiry string, and backing file changes happen while the UDC is detached
    • ISO/CD-ROM mode is explicit; normal image mode remains USB mass storage
    • forced_eject is used only when the kernel exposes it
  • Restored the NanoKVM data partition as a separate host-visible function:
    • mass_storage.disk1 is the data-disk export
    • the data disk is backed by /dev/mmcblk0p3 only when that separate disk toggle is enabled
    • disabling virtual image media no longer disables the data disk, and disabling the data disk no longer tears down virtual image media
  • Extended the shared init-script helper for boot-time media/data-disk setup.
  • Fixed SG2002 status reporting to check concrete configfs links instead of a glob passed to access().
  • Added tests for gadget helper behavior, media/data-disk independence, optional forced_eject, legacy /dev/mmcblk0p3 state, HID mode reporting, and boot-script media composition.
  • Added make test for the host USB script and server tests.

Scope table

Area Fixed in this PR Not changed in this PR
No-media state Empty selected media leaves mass_storage.disk0/lun.0/file empty. UI wording/layout is unchanged here.
/dev/mmcblk0p3 fallback Stops using the NanoKVM data partition as fake selected image media. Keeps /dev/mmcblk0p3 as the explicit data-disk export when that separate function is enabled.
Disk vs CD-ROM mode CD-ROM mode is explicit for ISO/read-only CD behavior; normal image mounts stay mass-storage disk behavior. Multiple simultaneous selected image slots are not added.
LUN mutation ordering Detaches/rebinds the gadget around backing file, ro, and cdrom changes. The broader UI/API workflow is unchanged.
Eject behavior Uses forced_eject when the kernel exposes it, otherwise clears the LUN backing file. Kernels without forced_eject continue to use the clear-file path.
Data disk export Separates selected virtual image media from the NanoKVM data partition export. The data-disk feature is not removed.
Runtime USB mutations Moves host-visible media/network/disk mutations through the USB gadget helper. Descriptor customization and HID runtime mode switching are unchanged.
Tests Adds Go and fake-configfs coverage for host-visible media behavior and exposes it through make test. Web build wiring is unchanged here.

Related upstream PRs fixed here

PR Relationship
sipeed#741 Same core no-media bug. This PR implements that behavior and also separates image media from the data-disk export, detaches/rebinds around LUN changes, treats legacy /dev/mmcblk0p3 as no mounted image, and adds tests.

Related reports fixed here

Issue Relationship
sipeed#633 Directly related. Reports mounted ISOs appearing as the size/content of /dev/mmcblk0p3; this PR stops using that partition as fake no-image media.
sipeed#516 Directly related. This PR uses forced_eject when available and otherwise clears lun.0/file.
sipeed#438 Directly related. The image device and data-disk export are separate toggles/functions instead of one overloaded "Virtual Disk" state.
sipeed#547 Directly related. Disabled selected image media no longer leaves the wrong mass-storage backing exposed.

Validation

Validated locally with the Nix dev shell from the companion mjc/setup-nix-build-env branch:

nix develop /home/mjc/projects/NanoKVM-nix-build-env -c make -C /home/mjc/projects/NanoKVM test

Also validated from the stacked cleanup branch with:

nix develop /home/mjc/projects/NanoKVM-nix-build-env -c make -C /home/mjc/projects/NanoKVM check

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

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ffa85dd1-de78-482f-897b-35275d33b275

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mjc/fix-usb-virtual-media-host

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.

Pull request overview

This PR refactors host-visible USB gadget handling to eliminate the “fake media” /dev/mmcblk0p3 fallback, separating selected virtual media (disk0) from the optional host-visible data disk export (disk1), and centralizing runtime gadget mutations behind a new Go USB helper.

Changes:

  • Introduces server/service/usb gadget helper to detach/attach UDC around safe LUN/function mutations and to toggle RNDIS / virtual media / data disk independently.
  • Updates boot-time init scripts and server APIs to use usb.media0 for selected virtual media enablement, while exporting the NanoKVM data partition only via mass_storage.disk1 when enabled.
  • Adds/updates regression and unit tests, plus a make test target to run both script and Go server test suites.

Reviewed changes

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

Show a summary per file
File Description
tests/usb-init-scripts-test.sh Updates regression expectations for media/data-disk split and new boot flags.
support/sg2002/kvm_system/main/lib/system_state/system_state.cpp Improves SG2002 USB status detection by checking concrete configfs links and adds safer UDC state open handling.
server/service/vm/virtual-device.go Switches virtual device toggles from shell scripts to the new USB gadget helper; adds “media” toggle.
server/service/usb/gadget.go New centralized USB gadget helper for UDC detach/attach, function/link toggles, and LUN image state.
server/service/usb/gadget_test.go Adds fake-configfs unit tests covering helper behavior, independence, and error paths.
server/service/storage/image.go Routes mount/unmount and status queries through the USB gadget helper (no-media now clears LUN file).
server/service/hid/status.go Reuses USB helper paths and PHY restart logic; removes duplicated reset sequence.
server/service/hid/status_test.go Adds tests for HID mode detection using overridable usb.ModeFlag.
Makefile Adds make test and make test-server targets; preserves make test-usb-scripts.
kvmapp/system/init.d/S03usbdev Updates boot-time gadget composition for usb.media0 (disk0) and usb.disk0 data disk (disk1).
kvmapp/system/init.d/S03usb-common Refactors common helpers (string/config string/wakeup) without behavior change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 225 to +229
FILE *fp;
int file_size;
uint8_t RW_Data[10];
fp = fopen("/sys/class/udc/4340000.usb/state", "r");
fp = fopen(USB_UDC_STATE, "r");
if(fp == NULL) {

@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: 877aa7d6aa

ℹ️ 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/S03usbdev Outdated
Comment on lines +78 to +85
if [ -e "${USB_BOOT_DIR}/usb.disk0" ]
then
mkdir functions/mass_storage.disk1
prepare_fake_lun mass_storage.disk1
ln -s functions/mass_storage.disk1 configs/c.1/
echo 1 > functions/mass_storage.disk1/lun.0/removable
printf '%s' "NanoKVM USB Data Disk0520" > functions/mass_storage.disk1/lun.0/inquiry_string
echo /dev/mmcblk0p3 > functions/mass_storage.disk1/lun.0/file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve legacy media flags before exporting the data disk

On upgraded devices that already have /boot/usb.disk0 populated with an image path from the previous boot-time media behavior, this new branch treats that legacy media flag as the data-disk toggle and unconditionally exposes /dev/mmcblk0p3, while the media branch above only reads the new /boot/usb.media0 name. In that upgrade scenario the selected image is dropped and the host sees the NanoKVM data partition instead; migrate or distinguish non-empty legacy usb.disk0 before using it as the data-disk flag.

Useful? React with 👍 / 👎.

Comment thread server/service/usb/gadget.go Outdated
func SetLUNImage(h HIDController, image string, cdrom bool) error {
image = NormalizeImage(image)
return WithDetachedUDC(h, func() error {
if err := EnsureFile(MassStorageFlag); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Create the data partition for media-only users

When a fresh device uses only the virtual-media flow, SetLUNImage persists /boot/usb.media0, but the boot filesystem script still creates /dev/mmcblk0p3 only when /boot/usb.disk0 exists (kvmapp/system/init.d/S01fs:21). In that scenario the /data partition used by the image list/download paths is never created or mounted unless the separate data-disk export is also enabled, so media-only users can lose the storage area needed for ISOs/images after reboot.

Useful? React with 👍 / 👎.

Comment thread server/service/usb/gadget.go Outdated
Comment on lines +201 to +205
if err := EnsureFile(MassStorageFlag); err != nil {
return err
}
if err := ensureMassStorageLink(); err != nil {
return err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor hid-only mode before creating media functions

When the active gadget is in HID-only mode, S03usbhid composes only HID functions, but image mounting now calls SetLUNImage and these lines unconditionally create/link mass_storage.disk0. Since the image menu is still reachable in HID-only mode, a user can expose virtual storage despite selecting HID-only; previously this path would fail without an existing LUN. Check the current HID mode before ensuring media functions.

Useful? React with 👍 / 👎.

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 11 out of 11 changed files in this pull request and generated 3 comments.

Comment thread server/service/usb/gadget.go Outdated
Comment on lines +142 to +149
errs := []error{
RemoveIfExists(MassStorageLink),
RemoveIfExists(MassStorageFlag),
}
if Exists(LUNFile) {
errs = append([]error{DetachLUN()}, errs...)
}
return errors.Join(errs...)
Comment thread tests/usb-init-scripts-test.sh Outdated
@@ -86,7 +86,6 @@ test_normal_hid_descriptors(){
assert_hex "${g}/strings/0x409/manufacturer" "736970656564"
assert_hex "${g}/strings/0x409/product" "4e616e6f4b564d"
assert_hex "${g}/strings/0x409/serialnumber" "30313233343536373839414243444546"
Comment thread tests/usb-init-scripts-test.sh Outdated

assert_eq "$(cat "${g}/bcdUSB")" "0x0110" "hid-only bcdUSB"
assert_eq "$(cat "${g}/bcdDevice")" "0x0623" "hid-only bcdDevice"
assert_eq "$(cat "${g}/configs/c.1/bmAttributes")" "0xA0" "hid-only bmAttributes"
@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch from 88594bc to eeb55d5 Compare June 5, 2026 19:38
@mjc
mjc force-pushed the mjc/fix-usb-virtual-media-host branch from 877aa7d to 288422b Compare June 5, 2026 19:39

@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: 288422bc36

ℹ️ 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 server/service/usb/gadget.go Outdated
Comment on lines +182 to +186
if err := ensureRNDISFunction(); err != nil {
return err
}
if !Exists(RNDISLink) {
return os.Symlink(RNDISFunction, RNDISLink)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor the NCM boot flag when enabling USB networking

On devices configured with /boot/usb.ncm, the init script explicitly creates ncm.usb0 instead of rndis.usb0 (S03usbdev checks usb.ncm before usb.rndis0). This runtime path ignores that flag and always creates/links the RNDIS function, so toggling the Network switch exposes a different USB network gadget until the next reboot/restart. Check the NCM flag and create the same function the boot script would use.

Useful? React with 👍 / 👎.

@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch 4 times, most recently from f903df6 to 5b0ac1c Compare June 5, 2026 22:03
@mjc
mjc force-pushed the mjc/fix-usb-virtual-media-host branch from 288422b to f16e3c2 Compare June 5, 2026 23:14

@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: f16e3c275b

ℹ️ 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 server/service/usb/gadget.go Outdated
Comment on lines +201 to +203
if err := EnsureFile(MassStorageFlag); err != nil {
return err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist the selected media path before reattaching

When a user mounts an image through the API, this only creates /boot/usb.media0 and updates the live LUN; it never writes the normalized image into the boot flag. The updated init script restores virtual media after reboot by reading /boot/usb.media0, so API-mounted images either disappear on reboot when the file is empty or an older manually written path gets remounted instead. Write the selected image path to MassStorageFlag (and clear it on unmount) before reattaching the gadget.

Useful? React with 👍 / 👎.

Comment thread server/service/usb/gadget.go Outdated
detachErr := DetachUDC()
mutateErr := mutate()
attachErr := AttachUDC()
openErr := h.OpenNoLockWithRetry(hidReopenTimeout, hidReopenDelay)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't fail gadget toggles when HID is disabled

On devices booted in normal mode with /boot/disable_hid, S03usbdev intentionally skips creating the HID functions while the virtual device switches remain reachable because bcdDevice still reports normal mode. After toggling network/media/disk, reopening /dev/hidg* now becomes part of the returned error, so the API reports operation failed even though the gadget mutation already happened; missing HID devices in this configured mode should not make non-HID gadget updates fail.

Useful? React with 👍 / 👎.

Comment thread server/service/usb/gadget.go Outdated
return err
}

if err := DetachLUN(); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the current image when a replacement fails

When a mount request names a backing file that no longer exists (for example a stale image list or a concurrent delete), DetachLUN ejects the currently mounted image before the later write to LUNFile is the first operation that can fail for that missing path. The handler then reports mount image failed, but the host has already lost the previous media while the UI still believes it is mounted; validate the requested backing file before detaching, or otherwise avoid clearing the current LUN until the replacement can succeed.

Useful? React with 👍 / 👎.

@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: d0c0efeb84

ℹ️ 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 server/service/usb/gadget.go Outdated
Comment on lines +34 to +35
DataDiskFunction = GadgetPath + "/functions/mass_storage.disk0"
DataDiskLink = ConfigPath + "/mass_storage.disk0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use a distinct data-disk function

Because the data-disk paths are aliases for mass_storage.disk0, the data-disk toggle manipulates the same configfs function and LUN as virtual media instead of the separate mass_storage.disk1 export described by this change. When a user enables the data disk while an ISO/image is mounted, or mounts an image while the data disk is exposed, one backing file necessarily replaces the other and the opposite boot flag is removed, so the two host-visible devices cannot coexist.

Useful? React with 👍 / 👎.

@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch 2 times, most recently from a65a15b to 3d3c5e0 Compare June 7, 2026 19:04
mjc pushed a commit that referenced this pull request Jun 8, 2026
@mjc
mjc force-pushed the mjc/fix-usb-hid-boot-subclass branch from 3d3c5e0 to 04e0aa5 Compare June 11, 2026 18:48
mjc added 29 commits June 11, 2026 13:38
@mjc
mjc force-pushed the mjc/fix-usb-virtual-media-host branch from f36ee45 to 295cffb Compare June 11, 2026 19:43
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