Skip to content

USBMS hard-hangs Libra 2 on FW 4.38.23697: stale g_mass_storage.ko path heuristic + built-in g_charge_detect wedges ci_hdrc (tested fix included) #8

Description

@Lactorn

Disclosure: this bug was debugged and this report drafted with AI assistance (Claude Code driving the device over SSH). Everything below was verified live on real hardware — my Libra 2 — including the final fix, which I've been using successfully.

Summary

On a Kobo Libra 2 (model 388, i.MX7 / mx6sll-ntx) running FW 4.38.23697 (May 2026, kernel 4.1.15-00868-g58a2758be07 #106), "Start USB storage" hard-hangs the device — unkillable D-state insmod, power-button reset required. Reproduced with every KoboUSBMS build tested (2024-01-12, 2024-10-27, 2025-05-06): it's a firmware behavior change, not a tool-build regression. Nickel USB works fine on the same firmware.

Two script bugs plus one kernel landmine. Patched scripts tested end-to-end on the device (enumerate → mount → transfer → clean session end).

Bug A — start-usbms.sh: stale module-path heuristic

FW 4.38.23697 moved g_mass_storage.ko from /drivers/mx6sll-ntx/ to /drivers/mx6sll-ntx/usb/gadget/. The [ -e "${MODULES_PATH}/g_mass_storage.ko" ] check fails, so the script falls back to insmod g_file_storage.ko — and that insmod is what freezes (see Bug C). Nickel's own /usr/local/Kobo/udev/usb has the same stale check, but survives because its daemon manages the controller state first.

Bug B — end-usbms.sh: same heuristic picks the wrong rmmod

It decides which module to unload from the same .ko-path check, so it runs rmmod g_file_storage while g_mass_storage is loaded; set -e aborts, and the user gets "Could not end the usbms session! The device will shut down in 90 sec." From /usr/local/KoboUSBMS.log:

+ '[' -e /drivers/mx6sll-ntx/g_mass_storage.ko ]     # false → wrong branch
+ rmmod g_file_storage
rmmod: can't unload module 'g_file_storage': No such file or directory

Bug C — why it hangs: built-in g_charge_detect / dirty ci_hdrc endpoint state

This kernel has the NTX charger-detect gadget built in (/sys/module/g_charge_detect present, not in /proc/modules, so never rmmod-able). On cable plug it binds ci_hdrc.0, sniffs the charger via EP0 ([ep0 detected] ricoh619_charger_detect), and normally auto-unbinds. But when endpoint state is left dirty (fingerprint: ci_hdrc ci_hdrc.0: enabling a non-empty endpoint!), the next gadget insmod gets to g_mass_storage ready and then wedges:

g_mass_storage gadget: g_mass_storage ready
ci_hdrc ci_hdrc.0: enabling a non-empty endpoint!
  • insmod stuck in D state at wchan fsg_common_put, module stuck Loading, SIGKILL ineffective.
  • Once wedged, recovery is impossible: the hung insmod holds the device lock, so even echo ci_hdrc.0 > /sys/bus/platform/drivers/ci_hdrc/unbind blocks in D-state too. Only a forced reboot clears it (and the wedged tasks stall normal shutdown as well).

So any controller cleanup has to happen before the insmod — there is no after.

Fix (tested)

start-usbms.sh, in legacy_usb() — find the module at either path, and recreate the UDC immediately before grabbing it:

GMS=""
for cand in "${GADGETS_PATH}/g_mass_storage.ko" "${MODULES_PATH}/g_mass_storage.ko" ; do
    if [ -e "${cand}" ] ; then GMS="${cand}" ; break ; fi
done

# Evict the built-in charge-detect gadget / clear dirty EP state
UDC_DRV="/sys/bus/platform/drivers/ci_hdrc"
if [ -e "${UDC_DRV}/ci_hdrc.0" ] ; then
    echo "ci_hdrc.0" > "${UDC_DRV}/unbind" 2>/dev/null || true
    echo "ci_hdrc.0" > "${UDC_DRV}/bind" 2>/dev/null || true
fi

if [ -n "${GMS}" ] ; then
    insmod "${GMS}" file="${PARTITIONS}" stall=0 removable=1 ${PARAMS}
else
    # ... existing fallback ...
fi

end-usbms.sh, in legacy_usb() — key off /proc/modules, not the path:

if grep -q "^g_mass_storage " "/proc/modules" ; then
    rmmod "g_mass_storage"
else
    rmmod "g_file_storage"
    # ... existing built-in cleanup ...
fi

Verified over SSH with the cable plugged: unbind → bind → insmod → UDC state: configured, current_speed: high-speed; Windows 11 enumerated VID 2237 and mounted KOBOeReader (30 GB FAT32); write/read/delete round-trip OK; session end remounts onboard cleanly. The unbind/rebind did not re-trigger a chargedetect grab (detection is edge-triggered on plug, not on controller probe).

Repro (stock tool)

  1. Libra 2, FW 4.38.23697, any KOReader ≥ v2024.03.
  2. No dropbear running (separate unmount-blocking gotcha).
  3. Plug cable, Menu → Start USB storage.
  4. Device freezes on the USBMS screen; host never enumerates; power-button reset needed. /usr/local/KoboUSBMS.log ends at the insmod line.

Possibly the mechanism behind some of the reports in koreader/koreader#14149. Happy to test patches / provide more logs — the device is SSH-accessible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions