Compare commits

...
Author SHA1 Message Date
Nexoniarz
f6c4b6377f GMMOS: USB keyboards on xHCI
The third micro driver: xhci.c with everything but finding a keyboard and
reading it taken out. It does not take over the live driver's state --
the kernel may have died inside that driver -- so each controller is
halted, reset and enumerated from nothing. Of the live driver it uses
the register mapping and the scratchpad pages the controller was lent
(DCBAA entry 0, read before the reset).

Root ports, and USB 2 hubs up to two deep (power, reset, route string,
transaction translator for a slow device behind a high-speed hub). A
boot-protocol keyboard interface gets Set Configuration, Set Protocol
boot, Set Idle, one interrupt IN endpoint and a Normal TRB, re-queued
after every report. No interrupts, no hot-plug, nothing that is not a
keyboard or a hub. A static 256 KB pool instead of the DMA allocator.

Checked under QEMU with the PS/2 controller switched off (i8042=off): a
USB keyboard on a root port, and one behind a hub, both type into the
GMMOS terminal. QEMU's hub is full speed, so the transaction-translator
path is not exercised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 01:23:14 +02:00
Nexoniarz
c6d90e69fa GMMOS: I2C keyboards, and every keyboard at once
The second micro driver: i2c_hid.c with everything but reading a
keyboard's input reports taken out. No ACPI -- AML cannot run on a dead
kernel -- so the live driver hands over what it found at boot
(i2c_hid_keyboards): the DesignWare controller, the address, the report
size, the AMD GPIO pin if there is one, and the parsed report descriptor.
No power commands, no reset, a read-only transfer.

usb_hid.c's report-protocol rewrite is split out as hid_boot_report(),
which does nothing but rewrite, and kernel/gmmos/hidkeys.c turns boot
reports into keys for this and the USB micro drivers to come.

GMMOS now probes every micro driver and polls all that answer.

Not run: QEMU has no DesignWare I2C, and the laptops at hand have PS/2
keyboards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 01:15:50 +02:00
10 changed files with 1001 additions and 17 deletions

View file

@ -116,7 +116,7 @@ KERN_ZSTD = $(addprefix $(BUILD)/zstd/, \
KERN_ZSTD += $(BUILD)/kernel/scff/scff.o
# GMMOS, what runs after a panic, and its micro drivers. See include/gmmos/gmmos.h.
KERN_GMMOS = $(addprefix $(BUILD)/kernel/gmmos/, gmmos.o ps2.o)
KERN_GMMOS = $(addprefix $(BUILD)/kernel/gmmos/, gmmos.o hidkeys.o ps2.o i2c.o xhci.o)
KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \
proc.o)

View file

@ -12,6 +12,13 @@ void *hid_attach(const uchar *desc, int n, const char *what);
// One input report, report ID included when the device uses them.
void hid_attached_input(void *h, const uchar *rep, int len);
// For GMMOS, after a panic: whether a device is a keyboard, and one of its
// reports rewritten into the 8-byte boot layout (modifiers, reserved, six
// usages) with nothing else done -- no queue, no lock, no events. 0 when the
// report was not a keyboard's.
int hid_is_keyboard(void *h);
int hid_boot_report(void *h, const uchar *rep, int len, uchar *boot);
// I2C-HID: the touchpads (and some keyboards) of laptops. Started once ACPI
// and kernel threads are up; see kernel/drivers/i2c/i2c_hid.c.
void i2c_hid_start(void);

View file

@ -31,6 +31,38 @@ struct gmmos_kbd {
};
extern const struct gmmos_kbd gmmos_ps2;
extern const struct gmmos_kbd gmmos_i2c;
extern const struct gmmos_kbd gmmos_xhci;
// A USB or I2C keyboard report in the boot layout -- modifiers, a reserved
// byte, six usages -- turned into keys. A report is a state, not an event, so
// a key is a usage that was not in the previous one. kernel/gmmos/hidkeys.c.
struct gmmos_hidkeys {
uchar prev[8];
int q[16];
int qh, qt;
};
void gmmos_hid_report(struct gmmos_hidkeys *k, const uchar *boot);
int gmmos_hid_next(struct gmmos_hidkeys *k);
// I2C keyboards, as kernel/acpi/i2c_hid.c found them at boot. GMMOS cannot run
// ACPI to find them again, so the live driver hands over what it learnt: the
// DesignWare controller, the device's address, its input report size, the
// interrupt pin when there is one, and the parsed report descriptor.
struct gmmos_i2c_kbd {
volatile uint32 *regs;
int txdepth, rxdepth;
int addr;
uint32 maxin;
volatile uint32 *pin; // AMD GPIO pin register, or 0: poll blindly
int active_low;
void *hid;
};
int i2c_hid_keyboards(struct gmmos_i2c_kbd *out, int max);
// The xHCI controllers kernel/drivers/bus/xhci/xhci.c brought up: their
// registers, mapped. The micro driver resets each and enumerates again.
int xhci_gmmos_controllers(volatile uchar **caps, int max);
// From kernel/core/panic.c: draw the report again, and where its closing
// lines are, which GMMOS writes its status and its keys into.

View file

@ -50,6 +50,7 @@
#include <uacpi/utilities.h>
#include <uacpi/namespace.h>
#include <uacpi/resources.h>
#include "gmmos/gmmos.h"
// Each step said before it is taken, in a verbose boot: a machine that stops
// here stops with the step it was on as the last line on the screen.
@ -653,6 +654,30 @@ i2c_hid_main(void *arg)
}
}
// For GMMOS. Read without a lock: `ndev` only ever grows, and only before the
// polling loop starts, so a panic sees a list that was finished or not begun.
int
i2c_hid_keyboards(struct gmmos_i2c_kbd *out, int max)
{
int i, n = 0;
for(i = 0; i < ndev && n < max; i++){
struct i2chid *d = &devs[i];
if(!hid_is_keyboard(d->hid))
continue;
out[n].regs = d->c->regs;
out[n].txdepth = d->c->txdepth;
out[n].rxdepth = d->c->rxdepth;
out[n].addr = d->addr;
out[n].maxin = d->maxin;
out[n].pin = d->pin;
out[n].active_low = d->active_low;
out[n].hid = d->hid;
n++;
}
return n;
}
static void
i2c_hid_thread(void)
{

View file

@ -1703,6 +1703,20 @@ xhci_probe(struct pci_dev *d)
return 0;
}
// For GMMOS, after a panic: each controller's registers, as mapped here. Its
// micro driver resets the controller and starts again, so this is all it
// takes -- a BAR above 4 GB has no address of its own without ioremap(),
// which is not a thing to call on a dead kernel.
int
xhci_gmmos_controllers(volatile uchar **caps, int max)
{
int i;
for(i = 0; i < nxhci && i < max; i++)
caps[i] = xhcis[i].cap;
return i;
}
// ---------------------------------------------------------------------------
// Hot-plug
// ---------------------------------------------------------------------------

View file

@ -525,19 +525,19 @@ bits(const uchar *p, int len, int bit, int size)
// A report-protocol keyboard report, rewritten as a boot one: modifiers, a
// reserved byte, six usages. Six is a limit only of the rewrite; the seventh
// key held at once is dropped, which is what a boot keyboard does too.
static void
report_keyboard(struct hid *h, const uchar *rep, int len)
// Nothing else happens here, which is what lets GMMOS call it after a panic.
static int
to_boot(struct hid *h, const uchar *rep, int len, uchar *boot)
{
uchar boot[8];
int i, n = 0;
if(h->kbd_id){
if(len < 1 || rep[0] != h->kbd_id)
return;
return 0;
rep++;
len--;
}
memset(boot, 0, sizeof(boot));
memset(boot, 0, 8);
for(i = 0; i < 8; i++)
if(h->mod_bit[i] >= 0 && bits(rep, len, h->mod_bit[i], 1))
boot[0] |= 1 << i;
@ -555,7 +555,36 @@ report_keyboard(struct hid *h, const uchar *rep, int len)
if(n < 6)
boot[2 + n++] = u;
}
keyboard_report(h, boot, sizeof(boot));
return 1;
}
static void
report_keyboard(struct hid *h, const uchar *rep, int len)
{
uchar boot[8];
if(to_boot(h, rep, len, boot))
keyboard_report(h, boot, sizeof(boot));
}
int
hid_is_keyboard(void *hv)
{
struct hid *h = hv;
return h && (h->rkbd || h->proto == USB_HID_PROTO_KEYBOARD);
}
int
hid_boot_report(void *hv, const uchar *rep, int len, uchar *boot)
{
struct hid *h = hv;
if(h->rkbd)
return to_boot(h, rep, len, boot);
if(h->proto != USB_HID_PROTO_KEYBOARD || len < 8)
return 0;
memmove(boot, rep, 8);
return 1;
}
static int

View file

@ -47,7 +47,32 @@ static addr_t *pcs;
static int npcs;
static int sw, sh, cw, ch;
static const struct gmmos_kbd *kbd;
// Every micro driver, in the order they are tried. All that answer are
// polled: a laptop has its own keyboard and maybe a USB one on the side.
static const struct gmmos_kbd *const drivers[] = {
&gmmos_ps2, &gmmos_i2c, &gmmos_xhci,
};
static const struct gmmos_kbd *kbds[NELEM(drivers)];
static int nkbd;
static int
poll_keys(void)
{
for (int i = 0; i < nkbd; i++) {
int k = kbds[i]->poll();
if (k)
return k;
}
return 0;
}
static void
set_lights(int on)
{
for (int i = 0; i < nkbd; i++)
if (kbds[i]->leds)
kbds[i]->leds(on);
}
enum { MAIN, QRPAGES, LOG, TERM };
static int mode = MAIN;
@ -493,6 +518,7 @@ cmd_help(void)
tline("pci PCI devices");
tline("mem ADDR [LEN] bytes at a kernel address; both in hex (80, up to 400)");
tline("uptime how long it ran, and how long since");
tline("keyboards which keyboards GMMOS is reading");
tline("clear empty this screen");
tline("restart restart the machine");
tline("poweroff power it off");
@ -763,6 +789,10 @@ run(void)
else if (streq(w, "pci")) cmd_pci();
else if (streq(w, "mem")) cmd_mem(arg);
else if (streq(w, "uptime")) cmd_uptime();
else if (streq(w, "keyboards")) {
for (int i = 0; i < nkbd; i++)
tline(kbds[i]->name);
}
else if (streq(w, "clear")) tn = 0;
else if (streq(w, "restart") || streq(w, "reboot")) restart();
else if (streq(w, "poweroff")) {
@ -829,7 +859,7 @@ draw_status(void)
sb_putc(&sb, ':');
sb_putc(&sb, (char)('0' + left % 60 / 10));
sb_putc(&sb, (char)('0' + left % 10));
sb_puts(&sb, kbd ? ". Any key keeps it on." : ".");
sb_puts(&sb, nkbd ? ". Any key keeps it on." : ".");
} else {
sb_puts(&sb, "Staying on. Nothing here is lost until it restarts.");
}
@ -837,7 +867,7 @@ draw_status(void)
textn(x, y + step, sbz(&sb), (sw * 2 / 3 - x) / cw, PSOGM_DIM);
gfx_fill_rect(x, y + step * 3, sw - x, ch + 2, PSOGM_BG);
if (kbd)
if (nkbd)
textn(x, y + step * 3,
"Space log as QR L read the log T terminal R restart P power off",
(sw - x) / cw, PSOGM_FG);
@ -961,8 +991,9 @@ gmmos_run(char *r, addr_t *p, int n)
rows = (sh - my * 2) / ch;
log_snapshot();
if (gmmos_ps2.probe())
kbd = &gmmos_ps2;
for (int i = 0; i < (int)NELEM(drivers); i++)
if (drivers[i]->probe())
kbds[nkbd++] = drivers[i];
tline("GMMOS, the Guru Meditation Micro OS. The kernel is dead; this is what");
tline("runs after it. help lists the commands.");
draw_status();
@ -971,10 +1002,10 @@ gmmos_run(char *r, addr_t *p, int n)
int lights = 0, keyed = 0;
for (;;) {
int k = kbd ? kbd->poll() : 0;
int k = poll_keys();
if (k) {
if (!keyed && kbd->leds) {
kbd->leds(0); // somebody is here: the lights have done their job
if (!keyed) {
set_lights(0); // somebody is here: the lights have done their job
lights = 0;
}
keyed = 1;
@ -1001,10 +1032,10 @@ gmmos_run(char *r, addr_t *p, int n)
}
// The lights until somebody presses a key, as panic_blink did: on a
// machine whose screen is black, they are all there is to say it died.
if (!keyed && kbd && kbd->leds && t - last_blink >= 333) {
if (!keyed && t - last_blink >= 333) {
last_blink = t;
lights = !lights;
kbd->leds(lights);
set_lights(lights);
}
if (!boot_verbose && !cancelled && t - entered_ms >= OFF_MS) {
acpi_poweroff_now();

98
kernel/gmmos/hidkeys.c Normal file
View file

@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//
// Boot-layout keyboard reports to keys, for GMMOS's USB and I2C keyboards.
//
// usb_hid.c's keyboard path with the console, /dev/input, the lock and key
// repeat taken out: what is left is edge detection and a table. US layout, as
// the live USB path is.
#include "core/types.h"
#include "core/defs.h"
#include "dev/kbd.h"
#include "gmmos/gmmos.h"
#define MOD_CTRL 0x11
#define MOD_SHIFT 0x22
static const uchar plain[0x39] = {
0, 0, 0, 0,
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z',
'1','2','3','4','5','6','7','8','9','0',
'\n', 0x1B, '\b', '\t', ' ',
'-','=','[',']','\\','#',';','\'','`',',','.','/',
};
static const uchar shifted[0x39] = {
0, 0, 0, 0,
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'!','@','#','$','%','^','&','*','(',')',
'\n', 0x1B, '\b', '\t', ' ',
'_','+','{','}','|','~',':','"','~','<','>','?',
};
static int
special(uchar u)
{
switch (u) {
case 0x4A: return KBD_HOME;
case 0x4B: return KBD_PGUP;
case 0x4C: return KBD_DEL;
case 0x4D: return KBD_END;
case 0x4E: return KBD_PGDN;
case 0x4F: return KBD_RT;
case 0x50: return KBD_LF;
case 0x51: return KBD_DN;
case 0x52: return KBD_UP;
case 0x56: return '-'; // keypad minus and plus: the QR size keys
case 0x57: return '+';
case 0x58: return '\n'; // keypad Enter
}
return 0;
}
static int
was_down(const uchar *rep, uchar u)
{
for (int i = 2; i < 8; i++)
if (rep[i] == u)
return 1;
return 0;
}
void
gmmos_hid_report(struct gmmos_hidkeys *k, const uchar *boot)
{
uchar mods = boot[0];
for (int i = 2; i < 8; i++) {
uchar u = boot[i];
int c;
if (u < 4 || was_down(k->prev, u))
continue;
if (u < 0x39)
c = (mods & MOD_SHIFT) ? shifted[u] : plain[u];
else
c = special(u);
if (c == 0)
continue;
if ((mods & MOD_CTRL) && c < KBD_SPECIAL) {
if (c >= 'a' && c <= 'z') c = c - 'a' + 1;
else if (c >= 'A' && c <= 'Z') c = c - 'A' + 1;
else continue;
}
if (k->qt - k->qh < (int)NELEM(k->q))
k->q[k->qt++ % NELEM(k->q)] = c;
}
memmove(k->prev, boot, 8);
}
int
gmmos_hid_next(struct gmmos_hidkeys *k)
{
if (k->qh == k->qt)
return 0;
return k->q[k->qh++ % NELEM(k->q)];
}

155
kernel/gmmos/i2c.c Normal file
View file

@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//
// GMMOS's I2C keyboards: kernel/acpi/i2c_hid.c with everything but reading a
// keyboard's input reports taken out.
//
// No ACPI -- the firmware's AML cannot run on a dead kernel, so the devices
// are the ones the live driver found at boot (i2c_hid_keyboards). No power
// commands, no reset, no report descriptor: the device is already on and was
// already described. No interrupt: the pin is read when the device has one,
// otherwise the input register is read every few milliseconds regardless.
// And the DesignWare transfer is read-only, since an input report is a plain
// read with no register write before it.
#include "core/types.h"
#include "core/defs.h"
#include "dev/hid.h"
#include "gmmos/gmmos.h"
#define IC_TAR 0x04
#define IC_DATA_CMD 0x10
#define IC_RAW_INTR_STAT 0x34
#define IC_CLR_INTR 0x40
#define IC_CLR_TX_ABRT 0x54
#define IC_CLR_STOP_DET 0x60
#define IC_ENABLE 0x6C
#define IC_TXFLR 0x74
#define IC_RXFLR 0x78
#define IC_ENABLE_STATUS 0x9C
#define CMD_READ (1 << 8)
#define CMD_STOP (1 << 9)
#define INTR_TX_ABRT (1 << 6)
#define INTR_STOP_DET (1 << 9)
#define AMD_PIN_STS (1u << 16)
#define NKBD 4
static struct gmmos_i2c_kbd kb[NKBD];
static int nkb, turn;
static uchar buf[4096];
static struct gmmos_hidkeys keys;
static uint32
rd(struct gmmos_i2c_kbd *d, int reg)
{
return d->regs[reg / 4];
}
static void
wr(struct gmmos_i2c_kbd *d, int reg, uint32 v)
{
d->regs[reg / 4] = v;
}
static int
enable(struct gmmos_i2c_kbd *d, int on)
{
for (int i = 0; i < 1000; i++) {
wr(d, IC_ENABLE, on);
if ((rd(d, IC_ENABLE_STATUS) & 1) == (uint32)on)
return 0;
microdelay(25);
}
return -1;
}
// n bytes from the device. Disabling the controller first also abandons
// whatever transfer the live driver had going when the kernel died.
static int
read_bytes(struct gmmos_i2c_kbd *d, uchar *r, int n)
{
int sent = 0, got = 0, spin;
if (enable(d, 0) < 0)
return -1;
wr(d, IC_TAR, d->addr);
rd(d, IC_CLR_INTR);
if (enable(d, 1) < 0)
return -1;
for (spin = 0; got < n; spin++) {
if (rd(d, IC_RAW_INTR_STAT) & INTR_TX_ABRT)
goto abort;
while (got < n && rd(d, IC_RXFLR) > 0)
r[got++] = rd(d, IC_DATA_CMD);
while (sent < n && (int)rd(d, IC_TXFLR) < d->txdepth &&
sent - got < d->rxdepth) {
wr(d, IC_DATA_CMD, CMD_READ | (sent == n - 1 ? CMD_STOP : 0));
sent++;
}
if (spin > 20000)
goto abort;
microdelay(10);
}
for (spin = 0; !(rd(d, IC_RAW_INTR_STAT) & INTR_STOP_DET); spin++) {
if (spin > 2000)
goto abort;
microdelay(10);
}
rd(d, IC_CLR_STOP_DET);
enable(d, 0);
return 0;
abort:
rd(d, IC_CLR_TX_ABRT);
rd(d, IC_CLR_INTR);
enable(d, 0);
return -1;
}
static int
probe(void)
{
nkb = i2c_hid_keyboards(kb, NKBD);
for (int i = 0; i < nkb; i++)
if (kb[i].maxin > sizeof(buf))
kb[i].maxin = sizeof(buf);
return nkb > 0;
}
static int
poll(void)
{
int k = gmmos_hid_next(&keys);
if (k)
return k;
// GMMOS polls every millisecond; a blind read is a whole report's worth of
// bus time, so a device with no pin is read every fourth time.
for (int i = 0; i < nkb; i++) {
struct gmmos_i2c_kbd *d = &kb[i];
if (d->pin) {
int high = (*d->pin & AMD_PIN_STS) != 0;
if (high == d->active_low)
continue;
} else if (turn++ % 4) {
continue;
}
if (read_bytes(d, buf, d->maxin) < 0)
continue;
int len = buf[0] | (buf[1] << 8);
uchar boot[8];
if (len > 2 && len <= (int)d->maxin &&
hid_boot_report(d->hid, buf + 2, len - 2, boot))
gmmos_hid_report(&keys, boot);
}
return gmmos_hid_next(&keys);
}
const struct gmmos_kbd gmmos_i2c = {
.name = "I2C",
.probe = probe,
.poll = poll,
};

593
kernel/gmmos/xhci.c Normal file
View file

@ -0,0 +1,593 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//
// GMMOS's USB keyboards on xHCI: kernel/drivers/bus/xhci/xhci.c with
// everything but finding a keyboard and reading it taken out.
//
// It does not take over the live driver's state. The kernel may have died
// inside that driver, mid-command, with its rings half-written; so each
// controller is reset and enumerated from nothing, and of the live driver only
// two things are used: where the registers are mapped, and the scratchpad
// pages the controller was already lent (DCBAA entry 0 survives in memory).
//
// What is left, in order:
//
// - halt, reset, a command ring, an event ring, run
// - every connected root port: reset, Enable Slot, Address Device, the
// device descriptor, the configuration
// - a USB 2 hub, up to two deep: power its ports, reset each connected one,
// and enumerate what is behind it
// - a boot-protocol keyboard interface: Set Configuration, Set Protocol
// (boot), Set Idle, Configure Endpoint for its interrupt IN endpoint, and
// a Normal TRB queued on it
//
// and then the event ring is polled and every completed report re-queued. No
// interrupts, no hot-plug, no mice, no storage, no USB 3 hubs (a keyboard is
// never behind one's SuperSpeed half). Memory is a static pool: the DMA
// allocator belongs to the dead kernel.
#include "core/types.h"
#include "core/defs.h"
#include "arch/memlayout.h"
#include "dev/usb.h"
#include "gmmos/gmmos.h"
#include "../drivers/bus/xhci/xhci.h"
#define NHC 4
#define NKBD 4
#define NTRB 64 // per ring, link TRB included
#define MAXSLOT 32
// --- memory ----------------------------------------------------------------
static uchar pool[256 * 1024] __attribute__((aligned(4096)));
static uint64 used;
static void *
palloc(uint64 size, uint64 align)
{
used = (used + align - 1) & ~(align - 1);
if (used + size > sizeof(pool))
return 0;
void *p = pool + used;
used += size;
memset(p, 0, size);
return p;
}
static addr_t
phys(volatile void *p)
{
return V2P((addr_t)p);
}
// --- one controller --------------------------------------------------------
struct ring {
volatile struct trb *trb;
int enq, cycle;
};
struct hc {
volatile uchar *cap, *op, *rt;
volatile uint32 *db;
int ctxsize, maxslots, maxports;
uint64 *dcbaa;
struct ring cmd;
volatile struct trb *ev;
int evdeq, evcycle;
struct ring ep0[MAXSLOT + 1];
};
struct kbd {
struct hc *hc;
int slot, dci;
struct ring ring;
uchar *buf;
int len;
};
static struct hc hcs[NHC];
static struct kbd kbds[NKBD];
static int nkbds;
static struct gmmos_hidkeys keys;
static inline uint32 rd32(volatile uchar *p, int o) { return *(volatile uint32 *)(p + o); }
static inline void wr32(volatile uchar *p, int o, uint32 v) { *(volatile uint32 *)(p + o) = v; }
static inline void wr64(volatile uchar *p, int o, uint64 v)
{
*(volatile uint32 *)(p + o) = (uint32)v;
*(volatile uint32 *)(p + o + 4) = (uint32)(v >> 32);
}
static int
ring_init(struct ring *r)
{
r->trb = palloc(NTRB * 16, 4096);
if (r->trb == 0)
return -1;
r->enq = 0;
r->cycle = 1;
r->trb[NTRB - 1].param = phys(r->trb);
r->trb[NTRB - 1].control = TRB_TYPE(TRB_LINK) | TRB_TC;
return 0;
}
static void
push(struct ring *r, uint64 param, uint32 status, uint32 control)
{
volatile struct trb *t = &r->trb[r->enq];
t->param = param;
t->status = status;
__sync_synchronize();
t->control = (control & ~TRB_C) | (r->cycle ? TRB_C : 0);
__sync_synchronize();
if (++r->enq == NTRB - 1) {
r->trb[NTRB - 1].control = TRB_TYPE(TRB_LINK) | TRB_TC | (r->cycle ? TRB_C : 0);
r->enq = 0;
r->cycle ^= 1;
}
}
static int
pop(struct hc *h, struct trb *out)
{
volatile struct trb *e = &h->ev[h->evdeq];
uint32 c = e->control;
if (((c & TRB_C) ? 1 : 0) != h->evcycle)
return 0;
__sync_synchronize();
out->param = e->param;
out->status = e->status;
out->control = c;
if (++h->evdeq == NTRB) {
h->evdeq = 0;
h->evcycle ^= 1;
}
wr64(h->rt, XHCI_IR0 + IR_ERDP, (phys(h->ev) + h->evdeq * 16) | ERDP_EHB);
return 1;
}
static void report(struct hc *h, struct trb *ev);
// The next event of this type, within ms -- for a transfer event, the one
// for EP0 of this slot. Keyboard reports that arrive meanwhile, a key pressed
// while a second keyboard is enumerated, are handled rather than lost.
static int
wait_event(struct hc *h, int type, int slot, struct trb *out, int ms)
{
for (int i = 0; i < ms * 100; i++) {
while (pop(h, out)) {
int t = TRB_GET_TYPE(out->control);
if (t == TRB_XFER_EVENT && (type != t || TRB_GET_SLOT(out->control) != slot ||
TRB_GET_EPID(out->control) != DCI_EP0)) {
report(h, out);
continue;
}
if (t == type)
return 0;
}
microdelay(10);
}
return -1;
}
static int
command(struct hc *h, uint64 param, uint32 control, struct trb *ev)
{
struct trb e;
push(&h->cmd, param, 0, control);
h->db[0] = 0;
if (wait_event(h, TRB_CMD_COMPLETE, 0, &e, 500) < 0)
return -1;
if (ev)
*ev = e;
return TRB_CC(e.status);
}
static uint32 *
ctx(struct hc *h, void *base, int i)
{
return (uint32 *)((uchar *)base + i * h->ctxsize);
}
// --- devices ---------------------------------------------------------------
struct dev {
int slot, port, speed, route, tier;
int tt_slot, tt_port;
void *devctx;
};
static uchar dbuf[512] __attribute__((aligned(64)));
static int
control(struct hc *h, int slot, int type, int req, int value, int index,
void *buf, int len)
{
struct usb_setup s = { type, req, value, index, len };
struct ring *r = &h->ep0[slot];
int in = (type & 0x80) != 0;
struct trb ev;
push(r, *(uint64 *)&s, 8, TRB_TYPE(TRB_SETUP) | TRB_IDT |
((uint32)(len ? (in ? TRT_IN : TRT_OUT) : TRT_NONE) << 16));
if (len)
push(r, phys(buf), len, TRB_TYPE(TRB_DATA) | (in ? DIR_IN : 0));
push(r, 0, 0, TRB_TYPE(TRB_STATUS) | TRB_IOC | ((len == 0 || !in) ? DIR_IN : 0));
h->db[slot] = DCI_EP0;
if (wait_event(h, TRB_XFER_EVENT, slot, &ev, 500) < 0)
return -1;
int cc = TRB_CC(ev.status);
return cc == CC_SUCCESS || cc == CC_SHORT_PACKET ? 0 : -1;
}
static int
mps0(int speed)
{
return speed == XSPEED_HIGH ? 64 : speed == XSPEED_SUPER ? 512 : 8;
}
// Enable Slot and Address Device: the device answers on its own address with
// EP0 working at the default packet size.
static int
address(struct hc *h, struct dev *d)
{
struct trb ev;
if (command(h, 0, TRB_TYPE(TRB_ENABLE_SLOT), &ev) != CC_SUCCESS)
return -1;
d->slot = TRB_GET_SLOT(ev.control);
if (d->slot <= 0 || d->slot > MAXSLOT || d->slot > h->maxslots)
return -1;
d->devctx = palloc(h->ctxsize * 32, 64);
void *in = palloc(h->ctxsize * 33, 64);
if (!d->devctx || !in || ring_init(&h->ep0[d->slot]) < 0)
return -1;
h->dcbaa[d->slot] = phys(d->devctx);
uint32 *c = ctx(h, in, 0), *sc = ctx(h, in, 1), *ep0 = ctx(h, in, 2);
c[1] = 0x3;
sc[0] = (d->route & 0xFFFFF) | ((uint32)d->speed << 20) | (1u << 27);
sc[1] = (uint32)d->port << 16;
sc[2] = d->tt_slot ? ((uint32)d->tt_slot | ((uint32)d->tt_port << 8)) : 0;
ep0[1] = (EPTYPE_CONTROL << 3) | (3 << 1) | ((uint32)mps0(d->speed) << 16);
*(uint64 *)&ep0[2] = phys(h->ep0[d->slot].trb) | 1;
ep0[4] = 8;
if (command(h, phys(in), TRB_TYPE(TRB_ADDRESS_DEV) | TRB_SLOT(d->slot), 0) != CC_SUCCESS)
return -1;
// A full-speed device's real EP0 packet size is in byte 7 of its device
// descriptor, and the first eight bytes can be read at any size.
if (control(h, d->slot, 0x80, 6, 0x0100, 0, dbuf, 8) < 0)
return -1;
if (d->speed == XSPEED_FULL && dbuf[7] && dbuf[7] != 8) {
memset(in, 0, h->ctxsize * 3);
c[1] = 0x2;
ep0[1] = (EPTYPE_CONTROL << 3) | (3 << 1) | ((uint32)dbuf[7] << 16);
*(uint64 *)&ep0[2] = phys(h->ep0[d->slot].trb) | h->ep0[d->slot].cycle;
ep0[4] = 8;
command(h, phys(in), TRB_TYPE(TRB_EVAL_CTX) | TRB_SLOT(d->slot), 0);
}
return 0;
}
// Configure Endpoint with only the slot context: how a slot is told it is a
// hub, and how many ports it has. Before anything behind it is addressed.
static int
make_hub(struct hc *h, struct dev *d, int nports, int mtt)
{
void *in = palloc(h->ctxsize * 3, 64);
if (!in)
return -1;
uint32 *c = ctx(h, in, 0), *sc = ctx(h, in, 1);
c[1] = 1;
sc[0] = (d->route & 0xFFFFF) | ((uint32)d->speed << 20) | (1u << 27) |
(1u << 26) | (mtt ? (1u << 25) : 0);
sc[1] = ((uint32)d->port << 16) | ((uint32)nports << 24);
sc[2] = d->tt_slot ? ((uint32)d->tt_slot | ((uint32)d->tt_port << 8)) : 0;
return command(h, phys(in), TRB_TYPE(TRB_CONFIG_EP) | TRB_SLOT(d->slot), 0)
== CC_SUCCESS ? 0 : -1;
}
static int
interval(int speed, int bInterval)
{
int i;
if (speed == XSPEED_HIGH || speed == XSPEED_SUPER)
i = bInterval - 1;
else {
int f = bInterval ? bInterval : 1;
for (i = 0; (1 << (i + 1)) <= f; i++)
;
i += 3;
}
return i < 0 ? 0 : i > 15 ? 15 : i;
}
static void
arm(struct kbd *k)
{
push(&k->ring, phys(k->buf), k->len, TRB_TYPE(TRB_NORMAL) | TRB_IOC | TRB_ISP);
k->hc->db[k->slot] = k->dci;
}
static int
keyboard(struct hc *h, struct dev *d, int config, int iface,
const struct usb_endpoint_desc *ep)
{
if (nkbds == NKBD)
return -1;
struct kbd *k = &kbds[nkbds];
if (control(h, d->slot, 0x00, 9, config, 0, 0, 0) < 0) // SET_CONFIGURATION
return -1;
control(h, d->slot, 0x21, 0x0B, 0, iface, 0, 0); // SET_PROTOCOL boot
control(h, d->slot, 0x21, 0x0A, 0, iface, 0, 0); // SET_IDLE: changes only
int mps = ep->wMaxPacketSize & 0x7FF;
k->hc = h;
k->slot = d->slot;
k->dci = (ep->bEndpointAddress & 0xF) * 2 + 1;
k->len = mps < 64 ? mps : 64;
k->buf = palloc(64, 64);
void *in = palloc(h->ctxsize * 33, 64);
if (!k->buf || !in || ring_init(&k->ring) < 0)
return -1;
uint32 *c = ctx(h, in, 0), *sc = ctx(h, in, 1), *ec = ctx(h, in, k->dci + 1);
uint32 *cur = ctx(h, d->devctx, 0);
c[1] = 1 | (1u << k->dci);
sc[0] = (cur[0] & ~(0x1Fu << 27)) | ((uint32)k->dci << 27);
sc[1] = cur[1];
sc[2] = cur[2];
ec[0] = (uint32)interval(d->speed, ep->bInterval) << 16;
ec[1] = (EPTYPE_INT_IN << 3) | (3 << 1) | ((uint32)mps << 16);
*(uint64 *)&ec[2] = phys(k->ring.trb) | 1;
ec[4] = (uint32)mps | ((uint32)mps << 16);
if (command(h, phys(in), TRB_TYPE(TRB_CONFIG_EP) | TRB_SLOT(d->slot), 0) != CC_SUCCESS)
return -1;
nkbds++;
arm(k);
return 0;
}
static void enumerate(struct hc *h, struct dev *d);
// A USB 2 hub: power every port, then for each that has something on it,
// reset it and enumerate what comes up.
static void
hub(struct hc *h, struct dev *d, int config, int mtt)
{
if (d->tier >= 2)
return;
if (control(h, d->slot, 0x00, 9, config, 0, 0, 0) < 0)
return;
if (control(h, d->slot, 0xA0, 6, 0x2900, 0, dbuf, 9) < 0)
return;
int nports = dbuf[2], pgood = dbuf[5] * 2;
if (nports > 15)
nports = 15;
if (make_hub(h, d, nports, mtt) < 0)
return;
for (int p = 1; p <= nports; p++)
control(h, d->slot, 0x23, 3, 8, p, 0, 0); // PORT_POWER
microdelay((pgood < 100 ? 100 : pgood) * 1000);
for (int p = 1; p <= nports; p++) {
uchar st[4];
if (control(h, d->slot, 0xA3, 0, 0, p, st, 4) < 0 || !(st[0] & 1))
continue;
control(h, d->slot, 0x23, 3, 4, p, 0, 0); // PORT_RESET
int ok = 0;
for (int i = 0; i < 50 && !ok; i++) {
microdelay(10000);
if (control(h, d->slot, 0xA3, 0, 0, p, st, 4) == 0 && (st[2] & 0x10))
ok = 1;
}
control(h, d->slot, 0x23, 1, 20, p, 0, 0); // C_PORT_RESET
control(h, d->slot, 0x23, 1, 16, p, 0, 0); // C_PORT_CONNECTION
if (!ok || !(st[0] & 2))
continue;
microdelay(10000);
struct dev c;
memset(&c, 0, sizeof(c));
c.port = d->port;
c.tier = d->tier + 1;
c.route = d->route | (p << (4 * d->tier));
c.speed = (st[1] & 0x02) ? XSPEED_LOW : (st[1] & 0x04) ? XSPEED_HIGH : XSPEED_FULL;
// A slow device behind a high-speed hub goes through that hub's
// transaction translator; behind a full-speed hub, through whichever one
// the hub itself is behind.
if (c.speed != XSPEED_HIGH && d->speed == XSPEED_HIGH) {
c.tt_slot = d->slot;
c.tt_port = p;
} else {
c.tt_slot = d->tt_slot;
c.tt_port = d->tt_port;
}
enumerate(h, &c);
}
}
// Address a device, and keep it only if it is a hub or a keyboard.
static void
enumerate(struct hc *h, struct dev *d)
{
if (address(h, d) < 0)
return;
int devclass = dbuf[4], devproto = dbuf[6];
if (control(h, d->slot, 0x80, 6, 0x0200, 0, dbuf, 9) < 0)
return;
int total = dbuf[2] | (dbuf[3] << 8);
if (total > (int)sizeof(dbuf))
total = sizeof(dbuf);
if (control(h, d->slot, 0x80, 6, 0x0200, 0, dbuf, total) < 0)
return;
int config = dbuf[5];
if (devclass == 9) {
if (d->speed != XSPEED_SUPER)
hub(h, d, config, devproto == 2);
return;
}
// The first boot keyboard interface, and its interrupt IN endpoint.
int iface = -1;
for (int o = 0; o + 2 <= total && dbuf[o] >= 2; o += dbuf[o]) {
if (dbuf[o + 1] == 4 && o + 9 <= total) {
const struct usb_interface_desc *id = (const void *)&dbuf[o];
iface = (id->bInterfaceClass == 3 && id->bInterfaceSubClass == 1 &&
id->bInterfaceProtocol == 1) ? id->bInterfaceNumber : -1;
} else if (dbuf[o + 1] == 5 && o + 7 <= total && iface >= 0) {
const struct usb_endpoint_desc *ep = (const void *)&dbuf[o];
if ((ep->bEndpointAddress & 0x80) && USB_EP_XFERTYPE(ep->bmAttributes) == 3) {
struct usb_endpoint_desc copy = *ep;
keyboard(h, d, config, iface, &copy);
return;
}
}
}
}
static int
start(struct hc *h, volatile uchar *cap)
{
h->cap = cap;
h->op = cap + (rd32(cap, XHCI_CAPLENGTH) & 0xFF);
h->rt = cap + (rd32(cap, XHCI_RTSOFF) & ~0x1Fu);
h->db = (volatile uint32 *)(cap + (rd32(cap, XHCI_DBOFF) & ~0x3u));
uint32 hcs1 = rd32(cap, XHCI_HCSPARAMS1), hcs2 = rd32(cap, XHCI_HCSPARAMS2);
h->maxslots = HCS1_MAXSLOTS(hcs1) < MAXSLOT ? HCS1_MAXSLOTS(hcs1) : MAXSLOT;
h->maxports = HCS1_MAXPORTS(hcs1);
h->ctxsize = (rd32(cap, XHCI_HCCPARAMS1) & HCC1_CSZ) ? 64 : 32;
// The scratchpad array the live driver lent the controller, from the DCBAA
// it gave it. Read before the reset, which forgets where that was.
uint64 scratch = 0;
uint64 old = rd32(h->op, XHCI_DCBAAP) | ((uint64)rd32(h->op, XHCI_DCBAAP + 4) << 32);
if (old)
scratch = *(uint64 *)P2V(old & ~0x3Full);
int spb = (HCS2_SPB_HI(hcs2) << 5) | HCS2_SPB_LO(hcs2);
if (spb && !scratch)
return -1;
wr32(h->op, XHCI_USBCMD, rd32(h->op, XHCI_USBCMD) & ~(CMD_RS | CMD_INTE));
for (int i = 0; i < 100 && !(rd32(h->op, XHCI_USBSTS) & STS_HCH); i++)
microdelay(1000);
wr32(h->op, XHCI_USBCMD, CMD_HCRST);
int i;
for (i = 0; i < 1000; i++) {
microdelay(1000);
if (!(rd32(h->op, XHCI_USBCMD) & CMD_HCRST) && !(rd32(h->op, XHCI_USBSTS) & STS_CNR))
break;
}
if (i == 1000)
return -1;
h->dcbaa = palloc((MAXSLOT + 1) * 8, 4096);
uint64 *erst = palloc(64, 64);
h->ev = palloc(NTRB * 16, 4096);
if (!h->dcbaa || !erst || !h->ev || ring_init(&h->cmd) < 0)
return -1;
h->dcbaa[0] = scratch;
h->evdeq = 0;
h->evcycle = 1;
erst[0] = phys(h->ev);
erst[1] = NTRB;
wr32(h->op, XHCI_CONFIG, h->maxslots);
wr64(h->op, XHCI_DCBAAP, phys(h->dcbaa));
wr64(h->op, XHCI_CRCR, phys(h->cmd.trb) | CRCR_RCS);
wr32(h->rt, XHCI_IR0 + IR_IMAN, IMAN_IP); // pending cleared, IE off
wr32(h->rt, XHCI_IR0 + IR_ERSTSZ, 1);
wr64(h->rt, XHCI_IR0 + IR_ERDP, phys(h->ev));
wr64(h->rt, XHCI_IR0 + IR_ERSTBA, phys(erst));
wr32(h->op, XHCI_USBCMD, CMD_RS);
for (i = 0; i < 100 && (rd32(h->op, XHCI_USBSTS) & STS_HCH); i++)
microdelay(1000);
if (rd32(h->op, XHCI_USBSTS) & STS_HCH)
return -1;
// USB 3 ports train on their own after a reset, USB 2 ports need one; a
// device that has not reconnected yet gets a moment.
microdelay(300000);
for (int p = 1; p <= h->maxports; p++) {
uint32 sc = rd32(h->op, XHCI_PORTSC(p));
if (!(sc & PORT_CCS))
continue;
if (!(sc & PORT_PED)) {
wr32(h->op, XHCI_PORTSC(p), (sc & ~PORT_RW1C) | PORT_PR);
for (i = 0; i < 200; i++) {
microdelay(1000);
sc = rd32(h->op, XHCI_PORTSC(p));
if (sc & PORT_PRC)
break;
}
wr32(h->op, XHCI_PORTSC(p), (sc & ~PORT_RW1C) | PORT_PRC | PORT_CSC);
sc = rd32(h->op, XHCI_PORTSC(p));
}
if (!(sc & PORT_PED))
continue;
struct dev d;
memset(&d, 0, sizeof(d));
d.port = p;
d.speed = PORT_SPEED(sc);
enumerate(h, &d);
}
return 0;
}
static void
report(struct hc *h, struct trb *ev)
{
int slot = TRB_GET_SLOT(ev->control), dci = TRB_GET_EPID(ev->control);
for (int i = 0; i < nkbds; i++) {
struct kbd *k = &kbds[i];
if (k->hc != h || k->slot != slot || k->dci != dci)
continue;
int cc = TRB_CC(ev->status);
if ((cc == CC_SUCCESS || cc == CC_SHORT_PACKET) &&
k->len - (int)(ev->status & 0xFFFFFF) >= 8)
gmmos_hid_report(&keys, k->buf);
arm(k);
return;
}
}
static int
probe(void)
{
volatile uchar *caps[NHC];
int n = xhci_gmmos_controllers(caps, NHC);
for (int i = 0; i < n; i++)
start(&hcs[i], caps[i]);
return nkbds > 0;
}
static int
poll(void)
{
struct trb ev;
int k = gmmos_hid_next(&keys);
if (k)
return k;
for (int i = 0; i < NHC; i++) {
if (!hcs[i].ev)
continue;
while (pop(&hcs[i], &ev))
if (TRB_GET_TYPE(ev.control) == TRB_XFER_EVENT)
report(&hcs[i], &ev);
}
return gmmos_hid_next(&keys);
}
const struct gmmos_kbd gmmos_xhci = {
.name = "USB (xHCI)",
.probe = probe,
.poll = poll,
};