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:
Nexoniarz 2026-10-25 01:15:50 +02:00
commit c6d90e69fa
8 changed files with 387 additions and 17 deletions

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

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