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
|
||||
|
||||
# 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/, \
|
||||
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.
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,33 @@ struct gmmos_kbd {
|
|||
};
|
||||
|
||||
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
|
||||
// lines are, which GMMOS writes its status and its keys into.
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
#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.
|
||||
|
|
@ -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
|
||||
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
|
||||
// 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.
|
||||
static void
|
||||
report_keyboard(struct hid *h, const uchar *rep, int len)
|
||||
// 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)
|
||||
{
|
||||
uchar boot[8];
|
||||
int i, n = 0;
|
||||
|
||||
if(h->kbd_id){
|
||||
if(len < 1 || rep[0] != h->kbd_id)
|
||||
return;
|
||||
return 0;
|
||||
rep++;
|
||||
len--;
|
||||
}
|
||||
memset(boot, 0, sizeof(boot));
|
||||
memset(boot, 0, 8);
|
||||
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,9 +555,38 @@ report_keyboard(struct hid *h, const uchar *rep, int len)
|
|||
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;
|
||||
}
|
||||
|
||||
static int
|
||||
field(const uchar *p, int len, const struct hfield *f)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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