From cbb0204cd5f26375290d72bd4027e42832786f22 Mon Sep 17 00:00:00 2001
From: =?utf8?q?Duje=20Mihanovi=C4=87?= <duje.mihanovic@skole.hr>
Date: Sun, 20 Mar 2022 12:36:25 +0100
Subject: [PATCH] Add PS/2 driver

Very incomplete PS/2 driver which can currently reset the controller
(partially), accept keystrokes and supports the shift key.

TODO: Find out why testing a **PS/2 device** hangs
---
 Makefile                             |   6 +-
 include/arch/x86/bitflags.h          |   9 ++
 include/arch/x86/input/ps2.h         |  92 +++++++++++++++
 kernel/arch/x86/irq/sample_handler.c |  30 ++++-
 kernel/drivers/input/ps2.c           | 164 +++++++++++++++++++++++++++
 kernel/kernel.c                      |  15 ++-
 6 files changed, 312 insertions(+), 4 deletions(-)
 create mode 100644 include/arch/x86/bitflags.h
 create mode 100644 include/arch/x86/input/ps2.h
 create mode 100644 kernel/drivers/input/ps2.c

diff --git a/Makefile b/Makefile
index aee8cc1..7d10f97 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ export GIT_REV = $(shell git describe --long HEAD)
 
 CFLAGS = -std=gnu89 -g -Iinclude/arch/x86 -ffreestanding -DGIT_COMMIT=\"$(GIT_REV)\"
 
-KERNEL_OBJ = kernel/entry.o kernel/arch/x86/tty/tty.o kernel/drivers/irq/i8259a.o kernel/arch/x86/irq/idt.o kernel/arch/x86/irq/sample_handler.o kernel/kernel.o
+KERNEL_OBJ = kernel/entry.o kernel/arch/x86/tty/tty.o kernel/drivers/irq/i8259a.o kernel/arch/x86/irq/idt.o kernel/arch/x86/irq/sample_handler.o kernel/drivers/input/ps2.o kernel/kernel.o
 BOOTLOADER_OBJ = boot/x86/mbr boot/x86/vbr-fat32 boot/x86/stage3/LOADER.BIN
 
 default: kernel/kernel.elf bootloader
@@ -42,6 +42,10 @@ kernel/entry.o: kernel/entry.s
 kernel/arch/x86/irq/sample_handler.o: kernel/arch/x86/irq/sample_handler.c
 	$(CC) $(CFLAGS) -mgeneral-regs-only -c $< -o $@
 
+kernel/drivers/input/ps2.o: kernel/drivers/input/ps2.c
+
+kernel/kernel.o: kernel/kernel.c
+
 kernel/kernel.elf: kernel/kernel.bin
 	$(CC) -ffreestanding -nostdlib -o $@ -T kernel/linker.ld ${KERNEL_OBJ} -Wl,--oformat=elf32-i386
 	i686-elf-objcopy --only-keep-debug kernel/kernel.elf kernel/kernel.dbg
diff --git a/include/arch/x86/bitflags.h b/include/arch/x86/bitflags.h
new file mode 100644
index 0000000..377f730
--- /dev/null
+++ b/include/arch/x86/bitflags.h
@@ -0,0 +1,9 @@
+/* Common bit flag ops */
+#ifndef BITFLAGS_H
+#define BITFLAGS_H
+
+#define SET(flags, flag) flags |= (1 << flag)
+#define CLEAR(flags, flag) flags &= ~(1 << flag)
+#define IS_SET(flags, flag) (flags & (1 << flag) == (1 << flag))
+
+#endif
diff --git a/include/arch/x86/input/ps2.h b/include/arch/x86/input/ps2.h
new file mode 100644
index 0000000..8b90a73
--- /dev/null
+++ b/include/arch/x86/input/ps2.h
@@ -0,0 +1,92 @@
+#ifndef X86_INPUT_PS2_H
+#define X86_INPUT_PS2_H
+
+/* port constants */
+#define PS2_DATA_PORT 0x60
+#define PS2_CMD_STS_PORT 0x64
+
+/* command constants */
+#define PS2_CMD_PORT_2_DISABLE 0xA7
+#define PS2_CMD_PORT_1_DISABLE 0xAD
+#define PS2_CMD_PORT_2_ENABLE 0xA8
+#define PS2_CMD_PORT_1_ENABLE 0xAE
+#define PS2_CMD_READ_CCB 0x20
+#define PS2_CMD_WRITE_CCB 0x60
+#define PS2_CMD_CONTROLLER_TEST 0xAA
+#define PS2_CMD_PORT_1_TEST 0xAB
+#define PS2_CMD_PORT_2_TEST 0xA9
+#define PS2_CMD_PORT_2_WRITE 0xD4
+
+#define PS2_DEV_RESET 0xFF
+
+#define PS2_CONTROLLER_GOOD 0x55
+#define PS2_CONTROLLER_BAD 0xFC
+
+enum PS2_CCB {
+	PORT_1_IRQ = 0,
+	PORT_2_IRQ,
+	SYSTEM_FLAG,
+	ZERO,
+	PORT_1_CLK,
+	PORT_2_CLK,
+	PORT_1_TRANSL,
+	ZERO2
+};
+
+enum PS2_STATUS {
+	OUT_STATUS = 0,
+	IN_STATUS,
+	STS_SYSTEM_FLAG,
+	CMD_DATA,
+	UNKNOWN,
+	UNKNOWN2,
+	TIMEOUT_ERROR,
+	PARITY_ERROR
+};
+
+enum PS2_SPECIAL {
+	SELF_TEST_GOOD = 0xAA,
+	SELF_TEST_BAD = 0xFC,
+	SELF_TEST_BAD_2 = 0xFD,
+	ACK = 0xFA,
+	RESEND = 0xFE
+};
+
+static const char scancodes[] = {
+	/* letters */
+	[0x15] = 'q',
+	[0x1d] = 'w',
+	[0x24] = 'e',
+	[0x2d] = 'r',
+	[0x2c] = 't',
+	[0x35] = 'y',
+	[0x3c] = 'u',
+	[0x43] = 'i',
+	[0x44] = 'o',
+	[0x4d] = 'p',
+	[0x1c] = 'a',
+	[0x1b] = 's',
+	[0x23] = 'd',
+	[0x2b] = 'f',
+	[0x34] = 'g',
+	[0x33] = 'h',
+	[0x3b] = 'j',
+	[0x42] = 'k',
+	[0x4b] = 'l',
+	[0x1a] = 'z',
+	[0x22] = 'x',
+	[0x21] = 'c',
+	[0x2a] = 'v',
+	[0x32] = 'b',
+	[0x31] = 'n',
+	[0x3a] = 'm',
+
+	/* special */
+	[0x29] = ' '
+};
+
+extern int ps2_initialize();
+extern void ps2_input_wait();
+extern void ps2_output_wait();
+
+#endif
diff --git a/kernel/arch/x86/irq/sample_handler.c b/kernel/arch/x86/irq/sample_handler.c
index f4f4a4b..72a86c6 100644
--- a/kernel/arch/x86/irq/sample_handler.c
+++ b/kernel/arch/x86/irq/sample_handler.c
@@ -2,6 +2,10 @@
 #include <irq/i8259a.h>
 #include <io.h>
 #include <stdint.h>
+#include <input/ps2.h>
+
+int was_released = 0;
+int is_caps = 0;
 
 typedef uint32_t uword_t;
 
@@ -17,8 +21,30 @@ __attribute__((interrupt))
 void keyb_handler(struct interrupt_frame *frame)
 {
 	pic_send_eoi(1);
-	kprint("Got a keyboard interrupt!\n", 0);
-	inb(0x60);
+	uint8_t scancode = inb(PS2_DATA_PORT);
+	if (was_released) {
+		was_released = 0;
+		return;
+	}
+
+	if (scancode == 0xf0) {
+		was_released = 1;
+		uint8_t scancode = inb(PS2_DATA_PORT);
+		if (scancode == 0x12) {
+			is_caps = 0;
+		}
+		return;
+	} else {
+		if (scancode == 0x12) {
+			is_caps = 1;
+			return;
+		}
+		if (!is_caps) {
+			kprintc(scancodes[scancode], 0);
+		} else {
+			kprintc(scancodes[scancode] - ('a'-'A'), 0);
+		}
+	}
 }
 
 
diff --git a/kernel/drivers/input/ps2.c b/kernel/drivers/input/ps2.c
new file mode 100644
index 0000000..0507024
--- /dev/null
+++ b/kernel/drivers/input/ps2.c
@@ -0,0 +1,164 @@
+#include <tty.h>
+#include <io.h>
+#include <input/ps2.h>
+#include <bitflags.h>
+
+int ps2_initialize()
+{
+	uint8_t ccb, is_2channel, port_1_test, port_2_test;
+	
+	kprint("ps2: Begin initializing PS/2 controller\n", 0);
+	outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_2_DISABLE);
+	ps2_input_wait();
+	outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_1_DISABLE);
+	ps2_input_wait();
+	inb(PS2_DATA_PORT);
+
+	outb(PS2_CMD_STS_PORT, PS2_CMD_READ_CCB);
+	ps2_output_wait();
+	ccb = inb(PS2_DATA_PORT);
+
+	CLEAR(ccb, 0);
+	CLEAR(ccb, 1);
+	CLEAR(ccb, 6);
+	
+	ps2_input_wait();
+	outb(PS2_CMD_STS_PORT, PS2_CMD_WRITE_CCB);
+	ps2_input_wait();
+	outb(PS2_DATA_PORT, ccb);
+
+	ps2_input_wait();
+	outb(PS2_CMD_STS_PORT, PS2_CMD_CONTROLLER_TEST);
+	ps2_output_wait();
+	if (inb(PS2_DATA_PORT) != PS2_CONTROLLER_GOOD) {
+		kprint("ps2: Controller self test failed, exiting!\n", 0);
+		return -1;
+	};
+	
+	ps2_input_wait();
+	outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_2_ENABLE);
+	ps2_input_wait();
+	outb(PS2_CMD_STS_PORT, PS2_CMD_READ_CCB);
+	ps2_output_wait();
+	ccb = inb(PS2_DATA_PORT);
+
+	if (ccb & PORT_2_CLK != PORT_2_CLK) {
+		is_2channel = 0;
+		kprint("ps2: Controller is single-channel\n", 0);
+	} else {
+		is_2channel = 1;
+		kprint("ps2: Controller is dual-channel\n", 0);
+		ps2_input_wait();
+		outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_2_DISABLE);
+	}
+
+	ps2_input_wait();
+	outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_1_TEST);
+	ps2_output_wait();
+	
+	if (inb(PS2_DATA_PORT) != 0) {
+		port_1_test = 0;
+		kprint("ps2: Port 1 test failed!\n", 0);
+		if (!is_2channel) {
+			kprint("ps2: No functional port, exiting!\n", 0);
+			return -1;
+		}
+	} else port_1_test = 1;
+
+	ps2_input_wait();
+	if (is_2channel) {
+		outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_2_TEST);
+		ps2_output_wait();
+
+		if (inb(PS2_DATA_PORT) != 0) {
+			port_2_test = 0;
+			kprint("ps2: Port 2 test failed!", 0);
+			if (!port_1_test) {
+				kprint("ps2: No functional port, exiting!\n", 0);
+				return -1;
+			}
+		} else port_2_test = 1;
+	}
+
+	if (port_1_test) { ps2_input_wait(); outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_1_ENABLE); }
+	if (port_2_test) { ps2_input_wait(); outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_2_ENABLE); }
+
+	int dev_1_test, dev_2_test;
+
+/*
+	if (port_1_test) {
+		uint8_t resp;
+		
+		do {
+			ps2_input_wait();
+			outb(PS2_DATA_PORT, PS2_DEV_RESET);
+			ps2_output_wait();
+			resp = inb(PS2_DATA_PORT);
+		} while (resp == RESEND);
+
+		if (resp == SELF_TEST_BAD || resp == SELF_TEST_BAD_2) {
+			dev_1_test = 0;
+			kprint("ps2: Port 1 device self test failed!\n", 0);
+			if (!port_2_test) {
+				kprint("ps2: No functioning devices, exiting!\n", 0);
+				return -1;
+			}
+		} else { dev_1_test = 1; kprint("ps2: Port 1 device test successful\n", 0); }
+	}
+
+	if (port_2_test) {
+		int resp = RESEND;
+
+		while (resp == RESEND) {
+			ps2_input_wait();
+			outb(PS2_CMD_STS_PORT, PS2_CMD_PORT_2_WRITE);
+			ps2_input_wait();
+			outb(PS2_DATA_PORT, PS2_DEV_RESET);
+			ps2_output_wait();
+			resp = inb(PS2_DATA_PORT);
+		}
+
+		if (resp == SELF_TEST_BAD || resp == SELF_TEST_BAD_2) {
+			dev_2_test = 0;
+			kprint("ps2: Port 2 device self test failed!\n", 0);
+			if (!dev_1_test) {
+				kprint("ps2: No functioning devices, exiting!\n", 0);
+				return -1;
+			}
+		} else { dev_2_test = 1; kprint("ps2: Port 2 test successful\n", 0); }
+	}
+*/
+
+	ps2_input_wait();
+	outb(PS2_CMD_STS_PORT, PS2_CMD_READ_CCB);
+	ps2_output_wait();
+	ccb = inb(PS2_DATA_PORT);
+
+	SET(ccb, PORT_1_IRQ);
+
+	ps2_input_wait();
+	outb(PS2_CMD_STS_PORT, PS2_CMD_WRITE_CCB);
+	ps2_input_wait();
+	outb(PS2_DATA_PORT, ccb);
+
+	if (dev_2_test && dev_1_test) return 1;
+	else return 0;
+}
+
+void ps2_output_wait()
+{
+	uint8_t status;
+	status = inb(PS2_CMD_STS_PORT);
+
+	while (!IS_SET(status, OUT_STATUS)) status = inb(PS2_CMD_STS_PORT);
+	return;
+}
+
+void ps2_input_wait()
+{
+	uint8_t status;
+	status = inb(PS2_CMD_STS_PORT);
+
+	while (IS_SET(status, IN_STATUS)) status = inb(PS2_CMD_STS_PORT);
+	return;
+}
diff --git a/kernel/kernel.c b/kernel/kernel.c
index 4c6efd8..3f6c256 100644
--- a/kernel/kernel.c
+++ b/kernel/kernel.c
@@ -1,8 +1,9 @@
+#include <stdint.h>
 #include <tty.h>
 #include <io.h>
 #include <irq/idt.h>
 #include <irq/i8259a.h>
-#include <stdint.h>
+#include <input/ps2.h>
 
 extern void double_fault(struct abort_frame *frame);
 extern void keyb_handler(struct interrupt_frame *frame);
@@ -11,10 +12,22 @@ struct idtr idtr __attribute__((aligned(0x10)));
 
 void kmain(void)
 {
+	int ps2_success;
 	screen_clear();
 	kprint("Welcome to Nameless OS!\nRunning revision: ", 0);
 	kprint(GIT_COMMIT, 0);
 	kprint("\n", 0);
+	ps2_success = ps2_initialize();
+	switch (ps2_success) {
+		case -1:
+			kprint("No PS/2 devices found or working, we will have no input\n", 0);
+			break;
+		case 0:
+			kprint("Found one working PS/2 device\n", 0);
+			break;
+		case 1:
+			kprint("Found two working PS/2 devices\n", 0);
+	}
 	kprint("Preparing IDT...\n", 0);
 	idt_set_descriptor(idt, 0x8, 0x8, (uint32_t) double_fault, IDT_TRAP_GATE, 0x0);
 	idt_set_descriptor(idt, 0x21, 0x8, (uint32_t) keyb_handler, IDT_INTERRUPT_GATE, 0x0);
-- 
2.39.5