From bfb2bce1a7b4cb623e2ace5b6b2fa6718bd6e2bc Mon Sep 17 00:00:00 2001 From: Ian Moffett Date: Sun, 12 May 2024 19:41:05 -0400 Subject: kernel/amd64: isa: Add i8042 driver Signed-off-by: Ian Moffett --- sys/arch/amd64/isa/i8042.c | 272 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 sys/arch/amd64/isa/i8042.c (limited to 'sys/arch/amd64') diff --git a/sys/arch/amd64/isa/i8042.c b/sys/arch/amd64/isa/i8042.c new file mode 100644 index 0000000..9b9e254 --- /dev/null +++ b/sys/arch/amd64/isa/i8042.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2023-2024 Ian Marco Moffett and the Osmora Team. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +__MODULE_NAME("i8042"); +__KERNEL_META("$Hyra$: i8042.c, Ian Marco Moffett, " + "i8042 PS/2 driver"); + +static struct spinlock data_lock; +static bool shift_key = false; +static bool capslock = false; + +static int dev_send(bool aux, uint8_t data); + +static char keytab[] = { + '\0', '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', '\n', '\0', 'a', 's', 'd', 'f', 'g', 'h', + 'j', 'k', 'l', ';', '\'', '`', '\0', '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0', ' ' +}; + +static char keytab_shift[] = { + '\0', '\0', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', + '_', '+', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', + 'O', 'P', '{', '}', '\n', '\0', 'A', 'S', 'D', 'F', 'G', 'H', + 'J', 'K', 'L', ':', '\"', '~', '\0', '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', '\0', '\0', '\0', ' ' +}; + +static char keytab_caps[] = { + '\0', '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '-','=', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', + 'O', 'P', '[', ']', '\n', '\0', 'A', 'S', 'D', 'F', 'G', 'H', + 'J', 'K', 'L', ';', '\'', '`', '\0', '\\', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', ',', '.', '/', '\0', '\0', '\0', ' ' +}; + +static void +kbd_set_leds(uint8_t mask) +{ + dev_send(false, 0xED); + dev_send(false, mask); +} + +/* + * Convert scancode to character + * + * @sc: Scancode + * @chr: Character output + * + * Returns 0 when a char is given back. + */ +static int +scancode_to_chr(uint8_t sc, char *chr) +{ + bool release = __TEST(sc, __BIT(7)); + + switch (sc) { + /* Capslock */ + case 0x3A: + capslock = !capslock; + + if (!capslock) { + kbd_set_leds(0); + } else { + kbd_set_leds(I8042_LED_CAPS); + } + return -EAGAIN; + /* Shift */ + case 0x36: + case 0xAA: + case 0x2A: + case 0xB6: + if (!release) { + shift_key = true; + } else { + shift_key = false; + } + return -EAGAIN; + } + + if (release) { + return -EAGAIN; + } + + if (capslock) { + *chr = keytab_caps[sc]; + return 0; + } + + if (shift_key) { + *chr = keytab_shift[sc]; + return 0; + } + + *chr = keytab[sc]; + return 0; +} + +/* + * Write to an i8042 register. + * + * @port: I/O port + * @val: Value to write + */ +static void +i8042_write(uint16_t port, uint8_t val) +{ + while (__TEST(I8042_STATUS, I8042_IBUF_FULL)); + outb(port, val); +} + +/* + * Read the i8042 config register + */ +static uint8_t +i8042_read_conf(void) +{ + i8042_write(I8042_COMMAND, I8042_GET_CONFB); + return inb(I8042_DATA); +} + +/* + * Write the i8042 config register + */ +static void +i8042_write_conf(uint8_t value) +{ + i8042_write(I8042_COMMAND, I8042_SET_CONFB); + i8042_write(I8042_DATA, value); +} + +/* + * Flush the data register until it is empty. + */ +static void +i8042_drain(void) +{ + spinlock_acquire(&data_lock); + while (__TEST(inb(I8042_STATUS), I8042_OBUF_FULL)) { + inb(I8042_DATA); + } + + spinlock_release(&data_lock); +} + +/* + * Send a data to a device + * + * @aux: If true, send to aux device (mouse) + * @data: Data to send. + */ +static int +dev_send(bool aux, uint8_t data) +{ + if (aux) { + i8042_write(I8042_COMMAND, I8042_PORT1_SEND); + } + + for (;;) { + if (!__TEST(inb(I8042_STATUS), I8042_IBUF_FULL)) { + break; + } + } + + i8042_write(I8042_DATA, data); + inb(0x80); /* Waste a cycle */ + return inb(I8042_DATA); +} + +__attr(interrupt) +static void +kb_isr(void *sf) +{ + uint8_t data; + char c; + + spinlock_acquire(&data_lock); + while (__TEST(inb(I8042_STATUS), I8042_OBUF_FULL)) { + data = inb(I8042_DATA); + if (scancode_to_chr(data, &c) == 0) { + tty_putc(&g_root_tty, c); + } + } + tty_flush(&g_root_tty); + spinlock_release(&data_lock); + lapic_send_eoi(); +} + +static int +i8042_init(void) +{ + uint8_t conf; + + /* + * Disable the ports and drain the output buffer + * to avoid interferance with the initialization + * process. + */ + i8042_write(I8042_COMMAND, I8042_DISABLE_PORT0); + i8042_write(I8042_COMMAND, I8042_DISABLE_PORT1); + i8042_drain(); + + /* Disable aux data streaming */ + dev_send(true, I8042_AUX_DISABLE); + i8042_drain(); + + /* Setup kbd interrupts */ + idt_set_desc(SYSVEC_PCKBD, IDT_INT_GATE_FLAGS, (uintptr_t)kb_isr, 0); + ioapic_set_vec(1, SYSVEC_PCKBD); + ioapic_irq_unmask(1); + + /* Setup config bits */ + conf = i8042_read_conf(); + conf |= I8042_PORT0_INTR; + conf &= ~I8042_PORT1_INTR; + i8042_write_conf(conf); + + /* Enable the keyboard */ + i8042_write(I8042_COMMAND, I8042_ENABLE_PORT0); + + /* + * It seems one I/O bus cycle isn't enough to wait for data + * to accumulate on some machines... Give it around 50ms then + * drain the output buffer. + */ + hpet_msleep(50); + i8042_drain(); + return 0; +} + +DRIVER_EXPORT(i8042_init); -- cgit v1.2.3