From First Light to First Key: Ceylon's Keyboard Path Comes Alive
Ceylon reached first light this morning. Tonight it went further: a real USB keyboard, discovered, configured, and decoded into semantic key events flowing through Ceylon's bounded input architecture.
Earlier today, Ceylon reached first light.
For the first time, the operating system booted in QEMU, brought up its display stack, passed a frame through Iris and Loom, presented it through the virtual graphics hardware, and put an actual Ceylon startup screen on a 1024×768 display.
Tonight, we pushed considerably farther.
Ceylon can now discover a USB keyboard, configure it, receive real hardware-style keyboard reports from it, decode those reports into semantic key events, and publish those events into the operating system's bounded input architecture.
That sounds like a lot of words for "the keyboard works."
It is.
Because we're not trying to make a keyboard work by taking shortcuts.
We're trying to build the machinery that a real operating system can depend on.
The long way around to the letter A
The keyboard in the current development machine isn't a physical USB keyboard yet. Ceylon is still being built inside QEMU, where we can inspect, reproduce, break, and validate the architecture before taking it to bare metal.
The path currently looks something like this:
VNC key event → QEMU input subsystem → emulated USB keyboard → xHCI USB controller → Ceylon USB/xHCI driver → HID interrupt endpoint → 8-byte keyboard report → Ceylon input decoder → semantic key event
Every one of those arrows represents something we had to make real.
There is no BIOS keyboard call hiding underneath it. No PS/2 fallback. No fake input queue injected directly into the GUI.
Ceylon is talking to a modern xHCI USB controller.
That matters because the same plumbing we're building for the keyboard is the beginning of the USB infrastructure that will eventually support mice, MIDI devices, storage, and other modern peripherals.
First, we had to make the controller real
The first challenge wasn't actually the keyboard.
It was convincing the USB controller to do anything at all.
Ceylon had to acquire authority over the controller's memory-mapped registers, a bounded DMA area, and its interrupt.
Then came the usual xHCI machinery: controller reset, capability discovery, Device Context Base Address Array, command ring, event ring, Event Ring Segment Table, interrupt configuration, controller RUN state.
The first meaningful test was intentionally boring: submit an xHCI No-Op Command and wait for the controller to complete it.
Boring is good.
A No-Op gave us a way to prove the entire controller command path without involving USB device enumeration yet.
Eventually QEMU fetched the command from Ceylon's DMA memory and returned a successful Command Completion Event.
Then we hit an interrupt problem.
Eleven was not twenty-two
This one is worth writing down because it is exactly the sort of thing that can consume an afternoon in systems programming.
The xHCI PCI device reported: Interrupt Line: 11, Interrupt Pin: A.
Naturally, we initially treated that as interrupt 11.
That was wrong.
On the Q35 machine model, the PCI Interrupt Line value is a legacy PIC-era representation. The actual routed IOAPIC interrupt for this particular xHCI INTx-A path is GSI 22.
Once we followed QEMU's lower-level IOAPIC trace, the real path became obvious: xHCI INTx-A → Q35 PCI routing → IOAPIC GSI 22 → Ceylon interrupt vector → USB driver.
After correcting that, the interrupt storm we'd been seeing disappeared.
The controller began doing exactly what it was supposed to do: raise one meaningful interrupt for the completion, let Ceylon service it, and return to an idle state.
That also reinforced something we're trying to be disciplined about throughout Ceylon: measure the hardware behavior; don't infer it from a convenient number.
Then the keyboard appeared
Once the controller itself was trustworthy, USB enumeration could begin.
Ceylon scanned the xHCI root ports and found the keyboard attached to port 5.
From there it performed the normal USB sequence: Port detection → Port reset → Enable Slot → Address Device → Endpoint 0 control transfers → Device Descriptor → Configuration Descriptor.
The device identified itself as: Vendor ID: 0627, Product ID: 0001, USB version: 2.00, Configuration: 1.
More importantly, Ceylon found exactly the interface we were looking for: Class: HID, Subclass: Boot, Protocol: Keyboard, Interface: 0, Endpoint: 0x81, Packet size: 8 bytes.
At that point we had independently proven, from inside Ceylon, that the device was a USB HID Boot Keyboard.
We weren't relying on the fact that the QEMU command line happened to say usb-kbd.
One tiny packet
A USB Boot Keyboard report is wonderfully small.
Eight bytes.
Once Ceylon configured the interrupt-IN endpoint, we armed a transfer and injected an actual key event through QEMU's VNC input path.
The first key was deliberately unexciting: A.
The report that arrived in Ceylon was: 00:00:04:00:00:00:00:00.
Then we released the key: 00:00:00:00:00:00:00:00.
That little 04 is the USB HID usage for the A key.
It came through the real path: VNC → QEMU input → USB keyboard → xHCI → DMA → Transfer Event → Ceylon.
Not simulated guest memory.
Not a fabricated report.
Not a test hook bypassing USB.
A real emulated keyboard transaction through the same architecture we expect to carry onto physical hardware.
That felt like a pretty good moment.
Turning USB into Ceylon input
Raw USB reports should not be the interface applications see.
An application shouldn't care whether a key arrived from USB, Bluetooth, a future accessibility device, or something else entirely.
So the next layer feeds those reports into Ceylon's existing input core.
The A-key report now becomes a semantic event: key = 0x1e, state = pressed, modifiers = 0, and then: key = 0x1e, state = released, modifiers = 0.
The input transport is deliberately small and bounded.
It currently uses a 32-event shared ring with: one producer, one consumer, fixed-size entries, sequence numbers, generation tracking, overflow accounting, stale-generation rejection, explicit publication ordering.
There is no dynamically growing queue and no USB memory exposed to the GUI.
The USB driver owns USB. The GUI gets semantic input.
That separation is important.
Where we stopped tonight
We're not quite ready to draw the login box yet.
The provider side is now doing its job: real HID report → semantic decoder → semantic input event → bounded shared transport → notification.
The remaining issue is at the final delivery boundary.
The input provider is publishing events and issuing its notification, but NuGUI has not yet demonstrated that it is receiving that notification and consuming the shared event ring.
So tonight's result is not: "Keyboard input is finished."
It's more precise than that: the hardware-facing keyboard stack works. Real HID reports are being converted into Ceylon semantic input. The remaining work is connecting that semantic transport cleanly into NuGUI's locked pre-login input consumer.
That is a much smaller problem than where we started this morning.
And first light is still there
Through all of this, the display path remains intact.
Ceylon still boots into its 1024×768 startup display through the graphics architecture we established earlier today.
The known first-light frame checksum remains: 0xa98302d3.
That matters too.
Adding USB shouldn't mysteriously damage graphics.
Adding input shouldn't require the GUI to own the USB controller.
One subsystem coming alive should not mean destabilizing everything around it.
That's part of what we're trying to preserve from the Amiga philosophy while building something meant for modern hardware: specialized components, clear responsibilities, asynchronous work, and a machine that stays responsive because the architecture isn't constantly tripping over itself.
What's next
The next milestone is wonderfully ordinary.
Get this: A key → USB → Ceylon input → NuGUI.
Then put a real locked login surface on the screen.
After that comes focus, editable fields, credential handling, authentication, and finally the transition from the pre-login environment into an authenticated Ceylon desktop.
There is still a lot of operating system between a boot logo and something you can sit down and use.
But tonight, for the first time, Ceylon saw someone press a key.
And it knew which key it was.
Originally published on The Weekend Report: https://theweekendersproject.com/report/from-first-light-to-first-key-ceylons-keyboard-path-comes-alive