Sold under various names — mine is an MK10 — these cheap keyboards work fine on Windows but hard-freeze on Linux the first time you press Volume Up / Volume Down. The whole keyboard dies, not just the media keys, and the only way back is to physically unplug and replug it.
Tested on Zorin OS (GNOME, PipeWire) with kernel 7.1.5, but nothing here is distro-specific.
- Press a media key (volume, play/pause, …) → keyboard stops responding entirely.
- Normal keys are dead too. Caps Lock LED stops toggling.
journalctl -kshows absolutely nothing at the moment of the freeze — no-71, no-110, no reset, no disconnect. The device just goes silent.- Unplug + replug is the only recovery. A software USB reset is not enough; the controller needs to be power-cycled.
- Works perfectly on Windows.
The keyboard exposes two HID interfaces:
| Interface | Purpose | Status on Linux |
|---|---|---|
1.0 |
Normal keys | ✅ valid descriptor, works |
1.1 |
Consumer control (media keys) | ❌ rejected with -EINVAL |
At boot, every enumeration logs this:
hid-generic 0003:1C4F:0202.0009: unknown main item tag 0x0 (x10, "54 callbacks suppressed")
hid-generic 0003:1C4F:0202.0009: unbalanced collection at end of report description
hid-generic 0003:1C4F:0202.0009: probe with driver hid-generic failed with error -22
The firmware lies about its report descriptor. Interface 1's HID descriptor declares
wDescriptorLength = 79, but the device only ever returns the first 15 bytes:
05 0c Usage Page (Consumer)
09 01 Usage (Consumer Control)
a1 01 Collection (Application)
85 01 Report ID (1)
19 00 Usage Minimum (0)
2a 80 03 Usage Maximum (0x0380)
15 00 Logical Minimum (0)
<-- data ends here, 64 bytes short
usbhid hands the full declared length to the parser regardless of how much actually arrived,
so the remaining 64 bytes are read as zero padding — that's the run of unknown main item tag 0x0.
The Collection (Application) is never closed, so the parse fails with -EINVAL.
The freeze follows from that failure. With no HID driver bound to interface 1, hid_hw_start()
is never called, so usbhid never submits an interrupt URB to endpoint 0x82. The endpoint is
never polled. Press a media key and the device queues a report that nobody ever drains; its
buffer fills, the MCU wedges, and it takes the other interface down with it.
That's why the whole keyboard dies, why the kernel logs nothing (the host never had an error — the device simply stopped talking), and why only a power cycle recovers it.
Interface 1 also declares bInterfaceSubClass 1 (Boot Interface) / bInterfaceProtocol 1
(Keyboard). Windows falls back to the boot protocol and keeps draining 0x82 without ever
caring that the report descriptor is malformed. Linux insists on parsing the report descriptor
first and bails out — so the endpoint goes unpolled.
Confirm the short read (unbind first, or lsusb reports ** UNAVAILABLE ** because usbhid holds
the interface):
echo "3-3:1.1" | sudo tee /sys/bus/usb/drivers/usbhid/unbind # adjust to your bus-port
sudo lsusb -d 1c4f:0202 -v | grep -A25 "bInterfaceNumber *1"You should see:
wDescriptorLength 79
Warning: incomplete report descriptor
Report Descriptor: (length is 15)
79 declared, 15 returned is the bug in one line.
A ~90-line HID driver that substitutes the complete descriptor the device meant to send.
The first 15 bytes are byte-for-byte what the device actually returns; the rest is the natural
completion — a single 16-bit array field (Usage Maximum is 0x0380, so it cannot be narrower)
plus the closing End Collection.
See hid-mk10.c in this gist.
The driver deliberately matches on both the declared size and the 15-byte prefix, so a device with fixed firmware — or a differently broken revision — is left untouched.
sudo mkdir -p /usr/src/hid-mk10-1.0
sudo cp hid-mk10.c Makefile dkms.conf /usr/src/hid-mk10-1.0/
sudo dkms add -m hid-mk10 -v 1.0
sudo dkms build -m hid-mk10 -v 1.0
sudo dkms install -m hid-mk10 -v 1.0Then replug the keyboard (or modprobe hid-mk10). DKMS rebuilds it automatically on kernel
updates, and MODULE_DEVICE_TABLE means it auto-loads by modalias at boot.
Building against a clang/LLVM-built kernel (XanMod and similar) needs LLVM=1, which the
included Makefile passes. Check with:
grep CONFIG_CC_IS_CLANG /boot/config-$(uname -r)If your kernel was built with GCC, drop LLVM=1 from the Makefile.
⚠️ Do not write adkms.confwhoseMAKEline can expand to an emptyM=. An emptyM=turns the invocation into an in-tree build inside your kernel headers directory and clobbers its generated config files (include/generated/autoconf.h,rustc_cfg). If that happens,sudo apt install --reinstall linux-headers-$(uname -r)puts it back. Thedkms.confhere routes everything through theMakefile'sKDIRto avoid the trap.
hid-mk10 0003:1C4F:0202.000D: replacing truncated consumer descriptor (79 bytes claimed, 15 returned)
input: Usb KeyBoard as .../3-3:1.1/0003:1C4F:0202.000D/input/input20
Interface 1 now gets a real input node — it never had one before. Confirm the media keycodes are present:
sudo libinput list-devices | grep -A5 "Usb KeyBoard"
# or check that something (your compositor) holds it open, which is what makes usbhid poll 0x82:
sudo lsof /dev/input/eventNThen press Volume Up. The volume changes and the keyboard keeps working.
Diagnosed and fixed with Claude Code.
Licensed GPL-2.0-or-later, same as the kernel.