Compare commits

..
10 changed files with 17 additions and 1001 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 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)

View file

@ -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);

View file

@ -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.

View file

@ -50,7 +50,6 @@
#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.
@ -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)
{

View file

@ -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
// ---------------------------------------------------------------------------

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.
// 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

View file

@ -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();

View file

@ -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)];
}

View file

@ -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,
};

View file

@ -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, &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,
};