summaryrefslogtreecommitdiff
path: root/stage3/ps2.c
diff options
context:
space:
mode:
Diffstat (limited to 'stage3/ps2.c')
-rw-r--r--stage3/ps2.c205
1 files changed, 205 insertions, 0 deletions
diff --git a/stage3/ps2.c b/stage3/ps2.c
new file mode 100644
index 0000000..61f3582
--- /dev/null
+++ b/stage3/ps2.c
@@ -0,0 +1,205 @@
+#include "ps2.h"
+#include "def.h"
+#include "io.h"
+#include "font.h"
+#include "halt.h"
+
+// this should probably be refactored some time - i dont have the energy rn
+
+typedef enum {
+ PS2_CMD_READ_INTERNAL = 0x20,
+ PS2_CMD_WRITE_INTERNAL = 0x60,
+ PS2_CMD_DISABLE_PORT2 = 0xA7,
+ PS2_CMD_ENABLE_PORT2 = 0xA8,
+ PS2_CMD_TEST_PORT2 = 0xA9,
+ PS2_CMD_TEST_CTRL = 0xAA,
+ PS2_CMD_TEST_PORT1 = 0xAB,
+ PS2_CMD_DIAGNOSTIC_DUMP = 0xAC,
+ PS2_CMD_DISABLE_PORT1 = 0xAD,
+ PS2_CMD_ENABLE_PORT1 = 0xAE,
+ PS2_CMD_READ_CTRL_IN = 0xC0,
+ PS2_CMD_COPY_TO_STATUS_LOW = 0xC1,
+ PS2_CMD_COPY_TO_STATUS_HIGH = 0xC2,
+ PS2_CMD_READ_CTRL_OUT = 0xD0,
+ PS2_CMD_WRITE_CTRL_OUT = 0xD1,
+ PS2_CMD_WRITE_PORT1_OUT = 0xD2,
+ PS2_CMD_WRITE_PORT2_OUT = 0xD3,
+ PS2_CMD_WRITE_PORT2_IN = 0xD4,
+ PS2_CMD_PULSE_OUT = 0xF0,
+} ps2_cmd;
+
+typedef enum {
+ PS2_STATUS_OUT = 1 >> 0,
+ PS2_STATUS_IN = 1 >> 1,
+ PS2_STATUS_POST = 1 >> 2,
+ PS2_STATUS_RECEIVER = 1 >> 3,
+ PS2_STATUS_ERR_TIMEOUT = 1 >> 6,
+ PS2_STATUS_ERR_PARITY = 1 >> 7,
+} Ps2Status;
+
+typedef struct {
+ bool output : 1;
+ bool input : 1;
+ bool post : 1;
+ bool receiver : 1;
+ bool unknown_1 : 1;
+ bool unknown_2 : 1;
+ bool err_timeout : 1;
+ bool err_parity : 1;
+} __attribute__((packed)) ps2_status;
+
+typedef struct {
+ bool int_port1 : 1;
+ bool int_port2 : 1;
+ bool post : 1;
+ bool zero_1 : 1;
+ bool clk_port1 : 1;
+ bool clk_port2 : 1;
+ bool translation : 1;
+ bool zero_2 : 1;
+} __attribute__((packed)) ps2_ctrl_cfg;
+
+typedef enum {
+ PS2_CFG_INT_PORT1 = 1 >> 0,
+ PS2_CFG_INT_PORT2 = 1 >> 1,
+ PS2_CFG_POST = 1 >> 2,
+ // PS2_CFG_ZERO1 = 1 >> 3,
+ PS2_CFG_CLK_PORT1 = 1 >> 4,
+ PS2_CFG_CLK_PORT2 = 1 >> 5,
+ PS2_CFG_TRANS_PORT1 = 1 >> 6,
+ // PS2_CFG_ZERO2 = 1 >> 7,
+} Ps2CtrlCfg;
+
+typedef enum {
+ PS2_CTRL_SYSRESET = 1 >> 0,
+ PS2_CTRL_A20 = 1 >> 1,
+ PS2_CTRL_CLK_PORT2 = 1 >> 2,
+ PS2_CTRL_DATA_PORT2 = 1 >> 3,
+ PS2_CTRL_OUT_PORT1 = 1 >> 4,
+ PS2_CTRL_OUT_PORT2 = 1 >> 5,
+ PS2_CTRL_CLK_PORT1 = 1 >> 6,
+ PS2_CTRL_DATA_PORT1 = 1 >> 7,
+} Ps2CtrlOut;
+
+typedef enum {
+ PS2_TEST_CTRL_PASS = 0x55,
+ PS2_TEST_CTRL_FAIL = 0xFC,
+} Ps2TestCtrl;
+
+typedef enum {
+ PS2_TEST_PORT_PASS = 0x00,
+ PS2_TEST_PORT_CLK_STUCK_LOW = 0x01,
+ PS2_TEST_PORT_CLK_STUCK_HIGH = 0x02,
+ PS2_TEST_PORT_DATA_STUCK_LOW = 0x03,
+ PS2_TEST_PORT_DATA_STUCK_HIGH = 0x04,
+} Ps2TestPort;
+
+void ps2_poll_status(u8 mask, bool set)
+{
+ // TODO: wait instead of busy polling
+ // TODO: timeout
+ while (set == !(inb(IO_PS2_CTRL) & mask))
+ ;
+}
+
+u8 ps2_read_data()
+{
+ ps2_poll_status(PS2_STATUS_OUT, true);
+ return inb(IO_PS2_DATA);
+}
+
+void ps2_write_data(u8 val)
+{
+ ps2_poll_status(PS2_STATUS_IN, false);
+ outb(IO_PS2_DATA, val);
+}
+
+void ps2_write_ctrl(u8 val)
+{
+ ps2_poll_status(PS2_STATUS_IN, false);
+ outb(IO_PS2_CTRL, val);
+}
+
+u8 ps2_read_mem(u8 byte)
+{
+ ps2_write_ctrl(PS2_CMD_READ_INTERNAL + byte);
+ return ps2_read_data();
+}
+
+void ps2_write_mem(u8 byte, u8 val)
+{
+ ps2_write_ctrl(PS2_CMD_WRITE_INTERNAL + byte);
+ ps2_write_data(val);
+}
+
+void ps2_test_port(ps2_cmd cmd, str desc)
+{
+ ps2_write_ctrl(cmd);
+ print(S("PS/2 ")); print(desc); print(S(" test: "));
+ u8 b = ps2_read_data();
+ switch (b) {
+ case PS2_TEST_PORT_PASS: print(S("pass\n")); break;
+ case PS2_TEST_PORT_CLK_STUCK_LOW: panic(S("clock stuck low\n")); break;
+ case PS2_TEST_PORT_CLK_STUCK_HIGH: panic(S("clock stuck high\n")); break;
+ case PS2_TEST_PORT_DATA_STUCK_LOW: panic(S("data stuck low\n")); break;
+ case PS2_TEST_PORT_DATA_STUCK_HIGH: panic(S("data stuck high\n")); break;
+ default: print_hex(b); panic(S(" (unknown response)\n")); break;
+ }
+}
+
+void ps2_init()
+{
+ // TODO: USB bullshit
+ // TODO: determine if exists
+
+ // disable devices
+ ps2_write_ctrl(PS2_CMD_DISABLE_PORT1);
+ ps2_write_ctrl(PS2_CMD_DISABLE_PORT2);
+
+ // flush output buffer
+ inb(IO_PS2_DATA);
+
+ // disable IRQs and translation
+ u8 config = ps2_read_mem(0);
+ config &= ~(PS2_CFG_INT_PORT1 | PS2_CFG_INT_PORT2 | PS2_CFG_TRANS_PORT1);
+ ps2_write_mem(0, config);
+
+ // perform self-test
+ ps2_write_ctrl(PS2_CMD_TEST_CTRL);
+ print(S("PS/2 controller test: "));
+ switch (ps2_read_data()) {
+ case PS2_TEST_CTRL_PASS: print(S("pass\n")); break;
+ case PS2_TEST_CTRL_FAIL: panic(S("fail\n")); break;
+ default: panic(S("unknown response\n")); break;
+ }
+ ps2_write_mem(0, config); // restore config byte
+
+ // check if dual
+ bool dual = !!(config & PS2_CFG_CLK_PORT2);
+ if (dual) {
+ ps2_write_ctrl(PS2_CMD_ENABLE_PORT2);
+ config = ps2_read_mem(0);
+ dual = !(config & PS2_CFG_CLK_PORT2);
+
+ if (dual)
+ ps2_write_ctrl(PS2_CMD_DISABLE_PORT2);
+ }
+ print(S("PS/2 second port ")); print(dual ? S("enabled\n") : S("disabled\n"));
+
+ // test devices
+ ps2_test_port(PS2_CMD_TEST_PORT1, S("port 1"));
+ if (dual)
+ ps2_test_port(PS2_CMD_TEST_PORT2, S("port 2"));
+
+ // enable devices
+ ps2_write_ctrl(PS2_CMD_ENABLE_PORT1);
+ if (dual)
+ ps2_write_ctrl(PS2_CMD_ENABLE_PORT2);
+
+ // enable IRQs
+ config |= PS2_CFG_INT_PORT1;
+ ps2_write_mem(0, config);
+
+ ps2_write_data(0xFF);
+ ps2_read_data();
+}