diff options
| author | Ada Christine <adachristine18@gmail.com> | 2023-02-14 00:02:55 +0000 |
|---|---|---|
| committer | Ada Christine <adachristine18@gmail.com> | 2023-02-14 00:02:55 +0000 |
| commit | ec04c40823f1a4265be8a6f81cbf5776dc2b2495 (patch) | |
| tree | f4a5f5e2301ec3ac15773fadf658315689a74ceb /kc/core/i8042.c | |
| parent | 6cdf57afc0a1da4abec33527cc70626d704ba565 (diff) | |
ps2 driver
Diffstat (limited to 'kc/core/i8042.c')
| -rw-r--r-- | kc/core/i8042.c | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/kc/core/i8042.c b/kc/core/i8042.c new file mode 100644 index 0000000..0ba12dc --- /dev/null +++ b/kc/core/i8042.c @@ -0,0 +1,183 @@ +#include "input.h" +#include "port.h" +#include "i8042.h" +#include "pic8259.h" +#include <lib/kstdio.h> + +static void i8042_init(void); +static void i8042_enable(void) {} +static void i8042_disable(void) {} + +static int i8042_append_callback(input_callback func) {(void)func; return 0;} +static void i8042_delete_callback(input_callback func) {(void)func;} + +const struct input_source i8042_input_source = +{ + "i8042", + i8042_init, + i8042_enable, + i8042_disable, + + i8042_append_callback, + i8042_delete_callback, +}; + +#define PS2_STATUS 0x64 +#define PS2_COMMAND 0x64 +#define PS2_DATA 0x60 +#define PS2_IRQ 1 + +static void i8042_disable_ports() +{ + outb(PS2_COMMAND, 0xad); + outb(PS2_COMMAND, 0xa7); +} + +static uint8_t i8042_command(int c) +{ + outb(PS2_COMMAND, c); + return (uint8_t)c; +} + +static uint8_t i8042_status() +{ + return inb(PS2_STATUS); +} + +static bool i8042_read(uint8_t *b) +{ + if (i8042_status() & 1) + { + (b != NULL) ? *b = inb(PS2_DATA) : inb(PS2_DATA); + return true; + } + + return false; +} + +static void i8042_flush() +{ + while(i8042_status() & 1) + { + i8042_read(NULL); + } +} + +static int i8042_callback(uint8_t irq) +{ + (void)irq; + i8042_flush(); + return 0; +} + + +#define I8042_MAX_TRIES 4096 + +static bool i8042_write(uint8_t b) +{ + for (int i = 0; i < I8042_MAX_TRIES; i++) + { + if (i8042_status() & 2) + { + continue; + } + + outb(PS2_DATA, b); + return true; + } + + return false; +} + +static void i8042_set_config(void) +{ + bool channel_a_enable = false; + bool channel_b_enable = false; + + bool maybe_dual_channel = false; + bool is_dual_channel = false; + i8042_command(0x20); + uint8_t config_byte; + i8042_read(&config_byte); + kprintf("i8042: current config: %#0.2x\n", config_byte); + config_byte &= 0xfc; + if (config_byte & (0x1 << 5)) + { + maybe_dual_channel = true; + } + config_byte |= 0x01; + kprintf("i8042: new config: %#0.2x\n", config_byte); + i8042_command(0x60); + i8042_write(config_byte); + i8042_command(0xaa); + uint8_t bist_result; + if (i8042_read(&bist_result) && bist_result != 0x55) + { + kprintf("i8042: warning: error self-testing device. aborting.\n"); + return; + } + + if (maybe_dual_channel) + { + i8042_command(0xa8); + i8042_command(0x20); + uint8_t config_byte; + if (i8042_read(&config_byte) && config_byte & (0x1 << 5)) + { + kprintf("i8042: single-channel controller detected\n"); + } + else + { + kprintf("i8042: dual-channel controller detected\n"); + is_dual_channel = true; + } + } + + outb(PS2_COMMAND, 0xab); + if (inb(PS2_DATA) != 0x00) + { + kprintf("i8042: failed testing port a\n"); + } + else + { + channel_a_enable = true; + } + if (is_dual_channel) + { + outb(PS2_COMMAND, 0xa9); + if (inb(PS2_DATA) != 0x00) + { + kprintf("i8042: failed testing port b\n"); + } + else + { + channel_b_enable = true; + } + i8042_disable_ports(); + } + + if (channel_a_enable) + { + outb(PS2_COMMAND, 0xae); + i8042_write(0xff); + if(inb(PS2_DATA) == 0xfa && inb(PS2_DATA) == 0xaa) + { + kprintf("i8042: detected device on channel, type %#0.2x\n", inb(PS2_DATA)); + } + } + + i8042_flush(); + (void)channel_b_enable; +} + +static void i8042_init(void) +{ + i8042_disable_ports(); + i8042_flush(); + i8042_set_config(); + + pic8259_irq_install(PS2_IRQ, i8042_callback); + pic8259_irq_unmask(PS2_IRQ); +} + + |
