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
2
Makefile
2
Makefile
|
|
@ -116,7 +116,7 @@ KERN_ZSTD = $(addprefix $(BUILD)/zstd/, \
|
||||||
KERN_ZSTD += $(BUILD)/kernel/scff/scff.o
|
KERN_ZSTD += $(BUILD)/kernel/scff/scff.o
|
||||||
|
|
||||||
# GMMOS, what runs after a panic, and its micro drivers. See include/gmmos/gmmos.h.
|
# 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)
|
||||||
|
|
||||||
KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \
|
KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \
|
||||||
proc.o)
|
proc.o)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// One input report, report ID included when the device uses them.
|
||||||
void hid_attached_input(void *h, const uchar *rep, int len);
|
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
|
// I2C-HID: the touchpads (and some keyboards) of laptops. Started once ACPI
|
||||||
// and kernel threads are up; see kernel/drivers/i2c/i2c_hid.c.
|
// and kernel threads are up; see kernel/drivers/i2c/i2c_hid.c.
|
||||||
void i2c_hid_start(void);
|
void i2c_hid_start(void);
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,33 @@ struct gmmos_kbd {
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct gmmos_kbd gmmos_ps2;
|
extern const struct gmmos_kbd gmmos_ps2;
|
||||||
|
extern const struct gmmos_kbd gmmos_i2c;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
// From kernel/core/panic.c: draw the report again, and where its closing
|
// From kernel/core/panic.c: draw the report again, and where its closing
|
||||||
// lines are, which GMMOS writes its status and its keys into.
|
// lines are, which GMMOS writes its status and its keys into.
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
#include <uacpi/utilities.h>
|
#include <uacpi/utilities.h>
|
||||||
#include <uacpi/namespace.h>
|
#include <uacpi/namespace.h>
|
||||||
#include <uacpi/resources.h>
|
#include <uacpi/resources.h>
|
||||||
|
#include "gmmos/gmmos.h"
|
||||||
|
|
||||||
// Each step said before it is taken, in a verbose boot: a machine that stops
|
// 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.
|
// 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
|
static void
|
||||||
i2c_hid_thread(void)
|
i2c_hid_thread(void)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
// 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
|
// 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.
|
// key held at once is dropped, which is what a boot keyboard does too.
|
||||||
static void
|
// Nothing else happens here, which is what lets GMMOS call it after a panic.
|
||||||
report_keyboard(struct hid *h, const uchar *rep, int len)
|
static int
|
||||||
|
to_boot(struct hid *h, const uchar *rep, int len, uchar *boot)
|
||||||
{
|
{
|
||||||
uchar boot[8];
|
|
||||||
int i, n = 0;
|
int i, n = 0;
|
||||||
|
|
||||||
if(h->kbd_id){
|
if(h->kbd_id){
|
||||||
if(len < 1 || rep[0] != h->kbd_id)
|
if(len < 1 || rep[0] != h->kbd_id)
|
||||||
return;
|
return 0;
|
||||||
rep++;
|
rep++;
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
memset(boot, 0, sizeof(boot));
|
memset(boot, 0, 8);
|
||||||
for(i = 0; i < 8; i++)
|
for(i = 0; i < 8; i++)
|
||||||
if(h->mod_bit[i] >= 0 && bits(rep, len, h->mod_bit[i], 1))
|
if(h->mod_bit[i] >= 0 && bits(rep, len, h->mod_bit[i], 1))
|
||||||
boot[0] |= 1 << i;
|
boot[0] |= 1 << i;
|
||||||
|
|
@ -555,7 +555,36 @@ report_keyboard(struct hid *h, const uchar *rep, int len)
|
||||||
if(n < 6)
|
if(n < 6)
|
||||||
boot[2 + n++] = u;
|
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
|
static int
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,30 @@ static addr_t *pcs;
|
||||||
static int npcs;
|
static int npcs;
|
||||||
|
|
||||||
static int sw, sh, cw, ch;
|
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 };
|
enum { MAIN, QRPAGES, LOG, TERM };
|
||||||
static int mode = MAIN;
|
static int mode = MAIN;
|
||||||
|
|
@ -493,6 +516,7 @@ cmd_help(void)
|
||||||
tline("pci PCI devices");
|
tline("pci PCI devices");
|
||||||
tline("mem ADDR [LEN] bytes at a kernel address; both in hex (80, up to 400)");
|
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("uptime how long it ran, and how long since");
|
||||||
|
tline("keyboards which keyboards GMMOS is reading");
|
||||||
tline("clear empty this screen");
|
tline("clear empty this screen");
|
||||||
tline("restart restart the machine");
|
tline("restart restart the machine");
|
||||||
tline("poweroff power it off");
|
tline("poweroff power it off");
|
||||||
|
|
@ -763,6 +787,10 @@ run(void)
|
||||||
else if (streq(w, "pci")) cmd_pci();
|
else if (streq(w, "pci")) cmd_pci();
|
||||||
else if (streq(w, "mem")) cmd_mem(arg);
|
else if (streq(w, "mem")) cmd_mem(arg);
|
||||||
else if (streq(w, "uptime")) cmd_uptime();
|
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, "clear")) tn = 0;
|
||||||
else if (streq(w, "restart") || streq(w, "reboot")) restart();
|
else if (streq(w, "restart") || streq(w, "reboot")) restart();
|
||||||
else if (streq(w, "poweroff")) {
|
else if (streq(w, "poweroff")) {
|
||||||
|
|
@ -829,7 +857,7 @@ draw_status(void)
|
||||||
sb_putc(&sb, ':');
|
sb_putc(&sb, ':');
|
||||||
sb_putc(&sb, (char)('0' + left % 60 / 10));
|
sb_putc(&sb, (char)('0' + left % 60 / 10));
|
||||||
sb_putc(&sb, (char)('0' + left % 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 {
|
} else {
|
||||||
sb_puts(&sb, "Staying on. Nothing here is lost until it restarts.");
|
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);
|
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);
|
gfx_fill_rect(x, y + step * 3, sw - x, ch + 2, PSOGM_BG);
|
||||||
if (kbd)
|
if (nkbd)
|
||||||
textn(x, y + step * 3,
|
textn(x, y + step * 3,
|
||||||
"Space log as QR L read the log T terminal R restart P power off",
|
"Space log as QR L read the log T terminal R restart P power off",
|
||||||
(sw - x) / cw, PSOGM_FG);
|
(sw - x) / cw, PSOGM_FG);
|
||||||
|
|
@ -961,8 +989,9 @@ gmmos_run(char *r, addr_t *p, int n)
|
||||||
rows = (sh - my * 2) / ch;
|
rows = (sh - my * 2) / ch;
|
||||||
|
|
||||||
log_snapshot();
|
log_snapshot();
|
||||||
if (gmmos_ps2.probe())
|
for (int i = 0; i < (int)NELEM(drivers); i++)
|
||||||
kbd = &gmmos_ps2;
|
if (drivers[i]->probe())
|
||||||
|
kbds[nkbd++] = drivers[i];
|
||||||
tline("GMMOS, the Guru Meditation Micro OS. The kernel is dead; this is what");
|
tline("GMMOS, the Guru Meditation Micro OS. The kernel is dead; this is what");
|
||||||
tline("runs after it. help lists the commands.");
|
tline("runs after it. help lists the commands.");
|
||||||
draw_status();
|
draw_status();
|
||||||
|
|
@ -971,10 +1000,10 @@ gmmos_run(char *r, addr_t *p, int n)
|
||||||
int lights = 0, keyed = 0;
|
int lights = 0, keyed = 0;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int k = kbd ? kbd->poll() : 0;
|
int k = poll_keys();
|
||||||
if (k) {
|
if (k) {
|
||||||
if (!keyed && kbd->leds) {
|
if (!keyed) {
|
||||||
kbd->leds(0); // somebody is here: the lights have done their job
|
set_lights(0); // somebody is here: the lights have done their job
|
||||||
lights = 0;
|
lights = 0;
|
||||||
}
|
}
|
||||||
keyed = 1;
|
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
|
// 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.
|
// 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;
|
last_blink = t;
|
||||||
lights = !lights;
|
lights = !lights;
|
||||||
kbd->leds(lights);
|
set_lights(lights);
|
||||||
}
|
}
|
||||||
if (!boot_verbose && !cancelled && t - entered_ms >= OFF_MS) {
|
if (!boot_verbose && !cancelled && t - entered_ms >= OFF_MS) {
|
||||||
acpi_poweroff_now();
|
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