Markus Fritsche bdb0e2d99f Fix BIOS-mode 10x repeat: change-detection + real SET_IDLE / SET_PROTOCOL
Symptom: in boot protocol (BIOS / UEFI) one keypress emitted ~10
characters. _kbd_write_ep1 was rebuilding and re-arming EP1 on every
USB SOF poll; report-mode HID drivers diff consecutive reports and
collapse duplicates, but boot-mode hosts treat each IN as a fresh
press.

Changes:
- Add 9-byte lastEP1_* shadow in bank-0 RAM, plus hidProtocol /
  hidIdleRate / hidIdleCounter state.
- _kbd_write_ep1: compare freshly-built bytes against the shadow with
  CMPRS A, M. On match, only resend when the SET_IDLE counter has
  expired; otherwise NAK silently. On mismatch, copy to shadow and
  send.
- _usb_htd_hid_set_idle: actually capture wValueHi (HID 1.11 §7.2.4)
  instead of just ACKing — store as hidIdleRate, reload counter.
- New _usb_htd_hid_set_protocol: capture wValueLo (HID 1.11 §7.2.5)
  into hidProtocol; invalidate shadow so the new wire format ships
  immediately. Wire dispatch table (0x210b) to it instead of the
  default STALL handler.
- _usb_sof: tick hidIdleCounter when rate is non-zero so the resend
  fires at idle-rate cadence.
- _kbd_write_ep1 sets UE1R_C=8 in boot protocol, 9 in report. The
  9th byte (consumer/extraState0) is kept in the EP1 buffer slack
  but truncated on the wire under boot protocol.

Net effect: a held key in BIOS mode now produces one keydown plus one
keyup, matching report-mode behaviour.
2026-04-13 15:08:30 +00:00
2021-12-19 16:28:26 +01:00
2022-01-16 19:47:50 +01:00
2021-12-19 16:21:56 +01:00
2022-01-16 21:58:00 +01:00

ku1255cfw

Custom open firmware for the Lenovo KU-1255 compact USB keyboard

Overview

The Lenovo ThinkPad Compact USB Keyboard with TrackPoint (KU-1255) uses a Sonix SN8F2288FG 8-bit MCU with 12K words (24KB) flash and 512 bytes RAM. The TrackPoint is connected via bit-banged I2C (P2.4 SCL, P2.5 SDA, address 0x2A, Synaptics proprietary protocol).

This firmware is a from-scratch rewrite based on the original by ranma, adding standalone middle-button scroll and other missing features so the keyboard works fully without an external converter.

Features added over base firmware

Middle-button scroll

3-state state machine (IDLE / UNDECIDED / SCROLLING) with 150ms timeout:

  • Short press (<150ms): sends a normal middle click (deferred on release)
  • Hold + TrackPoint movement: converts XY deltas to scroll wheel events
  • FN + middle button: passes middle button through directly (no scroll logic)

CapsLock LED feedback

Host LED output reports (SET_REPORT) are parsed and CapsLock state (bit 1) drives the power LED on P5.3/PWM0 (active-low). The flasher magic byte sequence detection is preserved.

FN+F7 through FN+F12

Key Normal With FN held
F7 F7 LGUI+P (display settings)
F8 F8 F8 (passthrough)
F9 F9 LGUI+I (settings)
F10 F10 LGUI (search)
F11 F11 LCTRL+LALT+TAB (task switch)
F12 F12 F12 (passthrough)

USB compliance fixes

  • HID GET_REPORT: returns proper per-interface empty reports (8B keyboard / 5B mouse) instead of stale buffer contents. Some OSes query this on resume from suspend.
  • SET/CLEAR FEATURE: tracks DEVICE_REMOTE_WAKEUP state instead of just ACKing.

Flash space

Firmware Words used Free Utilization
OEM 10,226 / 10,239 13 99.9%
This 10,238 / 10,239 1 100.0%

Key debouncing is not implemented (and not needed) — the 8ms scan cycle naturally debounces scissor switches (<1ms bounce time). The OEM firmware also does not debounce.

Flashing

Requires vpelletier/dissn8 tools.

  1. Build: asn8 main.s -o ku1255cfw.bin
  2. Enter bootloader: hold Return while plugging in the keyboard
  3. Flash: flashsn8 ku1255cfw.bin

Simulator testing

test_scroll.py runs 23 automated tests against the dissn8 simulator via the ku1255_sim.py harness. Tests cover all scroll state transitions, deferred click timing, FN modifier interaction, and edge cases (rapid clicks, timeout behavior).

Note: The simulator requires a fix for PnUR register read handlers (see below).

Simulator fixes (for vpelletier/dissn8)

Running this firmware in the dissn8 simulator exposed three bugs:

  1. PnUR read handlers crash_volatile_dict maps P0UR-P5UR read handlers to None. B0BSET/B0BCLR (read-modify-write) on these registers causes TypeError: 'NoneType' object is not callable. Fixed by adding readPullUp() returning the latch value. Branch: pnur_readpullup (PR pending upstream).

  2. Hardcoded HID descriptor sizesku1255_sim.py assumed 0x51/0xD3 byte HID report descriptors (OEM sizes). This firmware uses 91/61 bytes. Fixed by parsing sizes dynamically from the config descriptor.

  3. Wrong HID descriptor recipient — HID report descriptor requests must use interface recipient (0x81), not device (0x80), per USB spec. The OEM firmware happened to accept both.

Dev setup

PCB photo

S
Description
Custom firmware for Lenovo KU-1255 keyboard (SN8F2288) — middle-button scroll, CapsLock LED, FN+F7-F12
Readme GPL-3.0 1.1 MiB
Languages
Assembly 82.9%
Python 17.1%