diff --git a/Makefile b/Makefile index b9cad50..0ba6fde 100644 --- a/Makefile +++ b/Makefile @@ -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 hidkeys.o ps2.o i2c.o xhci.o) +KERN_GMMOS = $(addprefix $(BUILD)/kernel/gmmos/, gmmos.o ps2.o) KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \ proc.o) diff --git a/include/dev/hid.h b/include/dev/hid.h index e6c21f4..a390ddb 100644 --- a/include/dev/hid.h +++ b/include/dev/hid.h @@ -12,13 +12,6 @@ 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); diff --git a/include/gmmos/gmmos.h b/include/gmmos/gmmos.h index 5fd2343..0e8b31d 100644 --- a/include/gmmos/gmmos.h +++ b/include/gmmos/gmmos.h @@ -31,38 +31,6 @@ 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. diff --git a/kernel/acpi/i2c_hid.c b/kernel/acpi/i2c_hid.c index e9a6c88..b4309a8 100644 --- a/kernel/acpi/i2c_hid.c +++ b/kernel/acpi/i2c_hid.c @@ -50,7 +50,6 @@ #include #include #include -#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. @@ -654,30 +653,6 @@ 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) { diff --git a/kernel/drivers/bus/xhci/xhci.c b/kernel/drivers/bus/xhci/xhci.c index 25642a6..a80e740 100644 --- a/kernel/drivers/bus/xhci/xhci.c +++ b/kernel/drivers/bus/xhci/xhci.c @@ -1703,20 +1703,6 @@ 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 // --------------------------------------------------------------------------- diff --git a/kernel/drivers/usb/hid/usb_hid.c b/kernel/drivers/usb/hid/usb_hid.c index af185bb..94cea98 100644 --- a/kernel/drivers/usb/hid/usb_hid.c +++ b/kernel/drivers/usb/hid/usb_hid.c @@ -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. -// 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) +static void +report_keyboard(struct hid *h, const uchar *rep, int len) { + uchar boot[8]; int i, n = 0; if(h->kbd_id){ if(len < 1 || rep[0] != h->kbd_id) - return 0; + return; rep++; len--; } - memset(boot, 0, 8); + memset(boot, 0, sizeof(boot)); 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,36 +555,7 @@ to_boot(struct hid *h, const uchar *rep, int len, uchar *boot) if(n < 6) boot[2 + n++] = u; } - 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; + keyboard_report(h, boot, sizeof(boot)); } static int diff --git a/kernel/gmmos/gmmos.c b/kernel/gmmos/gmmos.c index 1e66370..5bad2ca 100644 --- a/kernel/gmmos/gmmos.c +++ b/kernel/gmmos/gmmos.c @@ -47,32 +47,7 @@ static addr_t *pcs; static int npcs; static int sw, sh, cw, ch; -// 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); -} +static const struct gmmos_kbd *kbd; enum { MAIN, QRPAGES, LOG, TERM }; static int mode = MAIN; @@ -518,7 +493,6 @@ 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"); @@ -789,10 +763,6 @@ 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")) { @@ -859,7 +829,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, nkbd ? ". Any key keeps it on." : "."); + sb_puts(&sb, kbd ? ". Any key keeps it on." : "."); } else { sb_puts(&sb, "Staying on. Nothing here is lost until it restarts."); } @@ -867,7 +837,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 (nkbd) + if (kbd) 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); @@ -991,9 +961,8 @@ gmmos_run(char *r, addr_t *p, int n) rows = (sh - my * 2) / ch; log_snapshot(); - for (int i = 0; i < (int)NELEM(drivers); i++) - if (drivers[i]->probe()) - kbds[nkbd++] = drivers[i]; + if (gmmos_ps2.probe()) + kbd = &gmmos_ps2; tline("GMMOS, the Guru Meditation Micro OS. The kernel is dead; this is what"); tline("runs after it. help lists the commands."); draw_status(); @@ -1002,10 +971,10 @@ gmmos_run(char *r, addr_t *p, int n) int lights = 0, keyed = 0; for (;;) { - int k = poll_keys(); + int k = kbd ? kbd->poll() : 0; if (k) { - if (!keyed) { - set_lights(0); // somebody is here: the lights have done their job + if (!keyed && kbd->leds) { + kbd->leds(0); // somebody is here: the lights have done their job lights = 0; } keyed = 1; @@ -1032,10 +1001,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 && t - last_blink >= 333) { + if (!keyed && kbd && kbd->leds && t - last_blink >= 333) { last_blink = t; lights = !lights; - set_lights(lights); + kbd->leds(lights); } if (!boot_verbose && !cancelled && t - entered_ms >= OFF_MS) { acpi_poweroff_now(); diff --git a/kernel/gmmos/hidkeys.c b/kernel/gmmos/hidkeys.c deleted file mode 100644 index e924f9e..0000000 --- a/kernel/gmmos/hidkeys.c +++ /dev/null @@ -1,98 +0,0 @@ -// 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)]; -} diff --git a/kernel/gmmos/i2c.c b/kernel/gmmos/i2c.c deleted file mode 100644 index 82f11e4..0000000 --- a/kernel/gmmos/i2c.c +++ /dev/null @@ -1,155 +0,0 @@ -// 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, -}; diff --git a/kernel/gmmos/xhci.c b/kernel/gmmos/xhci.c deleted file mode 100644 index 2900b90..0000000 --- a/kernel/gmmos/xhci.c +++ /dev/null @@ -1,593 +0,0 @@ -// 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, ©); - 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, -};