diff options
Diffstat (limited to 'sys/arch/amd64/isa/i8042.c')
-rw-r--r-- | sys/arch/amd64/isa/i8042.c | 193 |
1 files changed, 87 insertions, 106 deletions
diff --git a/sys/arch/amd64/isa/i8042.c b/sys/arch/amd64/isa/i8042.c index beacc87..eb8960c 100644 --- a/sys/arch/amd64/isa/i8042.c +++ b/sys/arch/amd64/isa/i8042.c @@ -39,6 +39,7 @@ #include <dev/acpi/acpi.h> #include <dev/timer.h> #include <dev/cons/cons.h> +#include <dev/dmi/dmi.h> #include <machine/cpu.h> #include <machine/pio.h> #include <machine/isa/i8042var.h> @@ -68,6 +69,7 @@ static struct proc polltd; static struct timer tmr; static bool is_init = false; +static void i8042_ibuf_wait(void); static int dev_send(bool aux, uint8_t data); static int i8042_kb_getc(uint8_t sc, char *chr); static void i8042_drain(void); @@ -103,41 +105,30 @@ kbd_set_leds(uint8_t mask) dev_send(false, mask); } -/* - * Poll the i8042 status register - * - * @bits: Status bits. - * @pollset: True to poll if set - */ -static int -i8042_statpoll(uint8_t bits, bool pollset) +static void +i8042_obuf_wait(void) { - size_t usec_start, usec; - size_t elapsed_msec; - uint8_t val; - bool tmp; + uint8_t status; - usec_start = tmr.get_time_usec(); for (;;) { - val = inb(I8042_STATUS); - tmp = (pollset) ? ISSET(val, bits) : !ISSET(val, bits); - usec = tmr.get_time_usec(); - elapsed_msec = (usec - usec_start) / 1000; - - IO_NOP(); - - /* If tmp is set, the register updated in time */ - if (tmp) { - break; + status = inb(I8042_STATUS); + if (ISSET(status, I8042_OBUFF)) { + return; } + } +} - /* Exit with an error if we timeout */ - if (elapsed_msec > I8042_DELAY) { - return -ETIME; +static void +i8042_ibuf_wait(void) +{ + uint8_t status; + + for (;;) { + status = inb(I8042_STATUS); + if (!ISSET(status, I8042_IBUFF)) { + return; } } - - return val; } /* @@ -162,34 +153,47 @@ i8042_drain(void) static void i8042_write(uint16_t port, uint8_t val) { - i8042_statpoll(I8042_IBUFF, false); + i8042_ibuf_wait(); outb(port, val); } /* - * Read the i8042 config register + * Read from an i8042 register. + * + * @port: I/O port + */ +static uint8_t +i8042_read(uint16_t port) +{ + i8042_obuf_wait(); + return inb(port); +} + +/* + * Read the i8042 controller configuration + * byte. */ static uint8_t i8042_read_conf(void) { - i8042_drain(); + uint8_t conf; + i8042_write(I8042_CMD, I8042_GET_CONFB); - i8042_statpoll(I8042_OBUFF, true); - return inb(I8042_DATA); + i8042_obuf_wait(); + conf = i8042_read(I8042_DATA); + return conf; } /* - * Write the i8042 config register + * Write a new value to the i8042 controller + * configuration byte. */ static void -i8042_write_conf(uint8_t value) +i8042_write_conf(uint8_t conf) { - i8042_drain(); - i8042_statpoll(I8042_IBUFF, false); i8042_write(I8042_CMD, I8042_SET_CONFB); - i8042_statpoll(I8042_IBUFF, false); - i8042_write(I8042_DATA, value); - i8042_drain(); + i8042_ibuf_wait(); + i8042_write(I8042_DATA, conf); } /* @@ -205,14 +209,13 @@ dev_send(bool aux, uint8_t data) i8042_write(I8042_CMD, I8042_PORT1_SEND); } - i8042_statpoll(I8042_IBUFF, false); i8042_write(I8042_DATA, data); - i8042_statpoll(I8042_OBUFF, true); + i8042_obuf_wait(); return inb(I8042_DATA); } -void -i8042_kb_event(void) +static int +i8042_kb_event(void *sp) { struct cpu_info *ci; struct cons_input input; @@ -232,50 +235,31 @@ i8042_kb_event(void) input.chr = c; cons_ibuf_push(&g_root_scr, input); done: - ci->irq_mask &= CPU_IRQ(1); + ci->irq_mask &= ~CPU_IRQ(1); spinlock_release(&isr_lock); - lapic_eoi(); + return 1; /* handled */ } static void i8042_en_intr(void) { + struct intr_hand ih; uint8_t conf; - int vec; - - pr_trace("ENTER -> i8042_en_intr\n"); - i8042_write(I8042_CMD, I8042_DISABLE_PORT0); - pr_trace("port 0 disabled\n"); - vec = intr_alloc_vector("i8042-kb", IPL_BIO); - idt_set_desc(vec, IDT_INT_GATE, ISR(i8042_kb_isr), IST_HW_IRQ); - ioapic_set_vec(KB_IRQ, vec); - ioapic_irq_unmask(KB_IRQ); - pr_trace("irq 1 -> vec[%x]\n", vec); + ih.func = i8042_kb_event; + ih.priority = IPL_BIO; + ih.irq = KB_IRQ; + intr_register("i8042-kb", &ih); - /* Setup config bits */ + /* + * Enable the clock of PS/2 port 0 and tell + * the controller that we are accepting + * interrupts. + */ conf = i8042_read_conf(); + conf &= ~I8042_PORT0_CLK; conf |= I8042_PORT0_INTR; - conf &= ~I8042_PORT1_INTR; i8042_write_conf(conf); - pr_trace("conf written\n"); - - i8042_write(I8042_CMD, I8042_ENABLE_PORT0); - pr_trace("port 0 enabled\n"); -} - -static void -esckey_reboot(void) -{ - syslock(); - kprintf(OMIT_TIMESTAMP "** Machine going down for a reboot\f"); - - for (size_t i = 0; i < 3; ++i) { - kprintf(OMIT_TIMESTAMP ".\f"); - tmr.msleep(1000); - } - - cpu_reboot(0); } /* @@ -292,10 +276,6 @@ i8042_kb_getc(uint8_t sc, char *chr) bool release = ISSET(sc, BIT(7)); switch (sc) { - /* Left alt [press] */ - case 0x38: - esckey_reboot(); - break; /* Caps lock [press] */ case 0x3A: /* @@ -351,43 +331,30 @@ i8042_kb_getc(uint8_t sc, char *chr) return 0; } -static void -i8042_sync_loop(void) -{ - /* Wake up the bus */ - outb(I8042_DATA, 0x00); - i8042_drain(); - - for (;;) { - i8042_sync(); - md_pause(); - } -} - /* * Grabs a key from the keyboard, used typically * for syncing the machine however can be used - * to bypass IRQs in case of buggy EC. + * to bypass IRQs to prevent lost bytes. */ void i8042_sync(void) { static struct spinlock lock; struct cons_input input; - uint8_t data; + uint8_t data, status; char c; if (spinlock_try_acquire(&lock)) { return; } - if (ISSET(quirks, I8042_HOSTILE) && is_init) { - if (i8042_statpoll(I8042_OBUFF, true) < 0) { - /* No data ready */ + if (is_init) { + status = inb(I8042_STATUS); + if (!ISSET(status, I8042_OBUFF)) { goto done; } - data = inb(I8042_DATA); + data = inb(I8042_DATA); if (i8042_kb_getc(data, &c) == 0) { input.scancode = data; input.chr = c; @@ -404,9 +371,20 @@ i8042_quirk(int mask) quirks |= mask; } +static void +i8042_sync_loop(void) +{ + for (;;) { + i8042_obuf_wait(); + i8042_sync(); + } +} + static int i8042_init(void) { + const char *prodver = NULL; + /* Try to request a general purpose timer */ if (req_timer(TIMER_GP, &tmr) != TMRR_SUCCESS) { pr_error("failed to fetch general purpose timer\n"); @@ -425,6 +403,9 @@ i8042_init(void) return -ENODEV; } + i8042_write(I8042_CMD, I8042_DISABLE_PORT0); + i8042_write(I8042_CMD, I8042_DISABLE_PORT1); + /* * On some thinkpads, e.g., the T420s, the EC implementing * the i8042 logic likes to play cop and throw NMIs at us @@ -432,9 +413,12 @@ i8042_init(void) * etc... As of now, treat the i8042 like a fucking bomb * if this bit is set. */ - if (strcmp(acpi_oemid(), "LENOVO") == 0) { + if ((prodver = dmi_prodver()) == NULL) { + prodver = "None"; + } + if (strcmp(prodver, "ThinkPad T420s") == 0) { quirks |= I8042_HOSTILE; - pr_trace("lenovo device, assuming hostile\n"); + pr_trace("ThinkPad T420s detected, assuming hostile\n"); pr_trace("disabling irq 1, polling as fallback\n"); spawn(&polltd, i8042_sync_loop, NULL, 0, NULL); } @@ -445,11 +429,8 @@ i8042_init(void) i8042_en_intr(); } - if (dev_send(false, 0xFF) == 0xFC) { - pr_error("kbd self test failure\n"); - return -EIO; - } - + i8042_write(I8042_CMD, I8042_ENABLE_PORT0); + i8042_drain(); is_init = true; return 0; } |