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
This commit is contained in:
parent
cb9b06d46f
commit
c6d90e69fa
8 changed files with 387 additions and 17 deletions
|
|
@ -47,7 +47,30 @@ 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 };
|
||||
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 +516,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 +787,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 +857,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 +865,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 +989,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 +1000,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 +1030,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
98
kernel/gmmos/hidkeys.c
Normal 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
155
kernel/gmmos/i2c.c
Normal 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,
|
||||
};
|
||||
Loading…
Reference in a new issue