Today I made some progress on PS/2 driver, because I wanted to make a proper keyboard and mouse driver!
I referenced this article from osdev.org for the device specification.
Before we delve into it, a few critical things need to be addressed. The PS/2 controller has two I/O ports: 0x60 and 0x64. The 0x60(Data) port contains the data received from the PS/2 controller. We can also write data to it if needed. The 0x64(Command) port sends commands and receives the controller's status. When we read the port 0x64, we get something called Status Register, which, as the name suggests, indicates the status of the PS/2 controller. When we write to port 0x64, however, we are writing to the command register, which we will delve into later.
Please check out the article I referenced up there, which explains this whole lot better than me. (Thank you, osdev!)
We'll be starting from the initialization. According to the article, these are the steps for initializing ps/2 controller :
Check out the implementation on this github link.
Now that we have initialized the PS/2 controller and also enabled the devices, what we need to do is just send commands directly to the second PS/2 device(which is the PS/2 mouse) and make it transmit the data. I referenced this article for all the specifications of commands.
To enable the mouse:
Since the code is not that long, here's the implementation:
void enable_mouse(void) {
ps2::wait_for_input_buffer_status(PS2_EMPTY); // set the settings to default
io_write_byte(PS2_COMMAND_PORT , 0xD4);
ps2::wait_for_input_buffer_status(PS2_EMPTY);
io_write_byte(PS2_DATA_PORT , 0xF6);
ps2::wait_for_output_buffer_status(PS2_FULL);
io_read_byte(PS2_DATA_PORT);
ps2::wait_for_input_buffer_status(PS2_EMPTY); // enable data packet streaming
io_write_byte(PS2_COMMAND_PORT , 0xD4);
ps2::wait_for_input_buffer_status(PS2_EMPTY);
io_write_byte(PS2_DATA_PORT , 0xF4);
debug::out::printf("Enabling data packet streaming\n");
// clear out the output buffer
ps2::wait_for_output_buffer_status(PS2_FULL);
byte buffer = io_read_byte(PS2_DATA_PORT);
}
Now, here's the very important thing. As you know, the IRQ number of the PS/2 mouse is 12, which is equivalent to interrupt number 44. Here's the quirky thing: you must unmask IRQ number 2 to make the IRQ12 receive the interrupt. I don't know why, but it's just designed that way.
I was puzzled as to why the mouse interrupt didn't occur, and I happened to stumble upon the solution coincidentally! How weird is that?
void ps2::initialize_interrupt(void) {
interrupt::general::register_interrupt(0x20+1 , ps2_interrupt_handler_irq1 , INTERRUPT_HANDLER_HARDWARE|INTERRUPT_HANDLER_LEVEL_KERNEL);
// somehow you have to unmask the interrupt number 2 to make mouse work... how weird!
interrupt::general::set_interrupt_mask(0x20+2 , false);
interrupt::general::register_interrupt(0x20+12 , ps2_interrupt_handler_irq12 , INTERRUPT_HANDLER_HARDWARE|INTERRUPT_HANDLER_LEVEL_KERNEL);
}
One more problem we need to address is the interrupt system. As of now, we don't have a proper interrupt system for numerical interrupts. Individual interrupt handlers must all have architecture-dependent interrupt wrapper that quickly gets redundant in the code. To solve this problem, we need a similar solution as to how we take care of the "hardware-specific" interrupts---providing the pre-declared interrupt wrapper functions. Thus, my next goal is to establish an interrupt system that can simplify the interrupt handler by implementing pre-declared interrupt wrapper functions(just like the "hardware-specific" interrupts.)