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)
- Libra 2, FW 4.38.23697, any KOReader ≥ v2024.03.
- No dropbear running (separate unmount-blocking gotcha).
- Plug cable, Menu → Start USB storage.
- 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.
Summary
On a Kobo Libra 2 (model 388, i.MX7 /
mx6sll-ntx) running FW 4.38.23697 (May 2026, kernel4.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 heuristicFW 4.38.23697 moved
g_mass_storage.kofrom/drivers/mx6sll-ntx/to/drivers/mx6sll-ntx/usb/gadget/. The[ -e "${MODULES_PATH}/g_mass_storage.ko" ]check fails, so the script falls back toinsmod g_file_storage.ko— and that insmod is what freezes (see Bug C). Nickel's own/usr/local/Kobo/udev/usbhas the same stale check, but survives because its daemon manages the controller state first.Bug B —
end-usbms.sh: same heuristic picks the wrongrmmodIt decides which module to unload from the same
.ko-path check, so it runsrmmod g_file_storagewhileg_mass_storageis loaded;set -eaborts, and the user gets "Could not end the usbms session! The device will shut down in 90 sec." From/usr/local/KoboUSBMS.log:Bug C — why it hangs: built-in
g_charge_detect/ dirtyci_hdrcendpoint stateThis kernel has the NTX charger-detect gadget built in (
/sys/module/g_charge_detectpresent, not in/proc/modules, so never rmmod-able). On cable plug it bindsci_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 tog_mass_storage readyand then wedges:fsg_common_put, module stuckLoading, SIGKILL ineffective.echo ci_hdrc.0 > /sys/bus/platform/drivers/ci_hdrc/unbindblocks 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, inlegacy_usb()— find the module at either path, and recreate the UDC immediately before grabbing it:end-usbms.sh, inlegacy_usb()— key off/proc/modules, not the path:Verified over SSH with the cable plugged: unbind → bind → insmod → UDC
state: configured,current_speed: high-speed; Windows 11 enumerated VID 2237 and mountedKOBOeReader(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)
/usr/local/KoboUSBMS.logends at theinsmodline.Possibly the mechanism behind some of the reports in koreader/koreader#14149. Happy to test patches / provide more logs — the device is SSH-accessible.