I'm fairly new to OS development and I recently started a hobby project of creating a simple-as-possible text-only operating system. It's written in C with some help from assembly and uses GRUB for booting, and I've been testing it in VirtualBox and also occasionally putting it on a flash drive for testing on an ancient (~2009) laptop. So far I've implemented some basic text output functions, and I think my GDT and IDT implementations are okay given the lack of crashing lately. Currently I'm trying to get an interrupt-driven keyboard driver working.
I think I've got the PICs set up correctly, and it seems I've had luck in giving commands to the PS/2 controller and keyboard and capturing responses via an interrupt handler. For example, here's the debug output when giving the keyboard an identify command:
Initializing kernel...
Setting PS/2 controller status: 0x05
Sending keyboard command: 0xF2
Keyboard interrupt: 0xFA
Keyboard interrupt: 0xAB
Keyboard interrupt: 0x83
The data returned seems to be correct, and this proves that my interrupt handler is able to work multiple times in succession without crashing or anything, so I'm not too worried about my IDT or ISR implementation. Now here's the output when I send the 0xF4 command to the keyboard to start scanning for key presses:
Initializing kernel...
Setting PS/2 controller status: 0x05
Sending keyboard command: 0xF4
Keyboard interrupt: 0xFA
The interrupt with the "acknowledge" status code 0xFA seems promising, but afterwards nothing happens when I press keys. For both examples, I got the same results when running both in VirtualBox and on the laptop I've been using.
Here's some relevant code from the keyboard driver:
#define KEYBD_DATA 0x60
#define KEYBD_CMD 0x64
// wrapper for interrupt service routine written in assembly
extern void keyboard_interrupt();
// called from assembly ISR
void keyboard_handler() {
u8 data = read_port(KEYBD_DATA);
print("Keyboard interrupt: 0x");
printx(data);
putc('\n');
pic_eoi();
}
// functions to print command before sending it to the port
void keyboard_command(u8 cmd) {
print("Sending keyboard command: 0x");
printx(cmd);
putc('\n');
write_port(KEYBD_DATA, cmd);
}
void controller_command(u8 cmd) {
print("Sending controller command: 0x");
printx(cmd);
putc('\n');
write_port(KEYBD_CMD, cmd);
}
void setup_keyboard() {
// flush keyboard output
while(read_port(KEYBD_CMD) & 1)
read_port(KEYBD_DATA);
// set interrupt descriptor table entry (default code segment and access flags)
set_idt_entry(0x21, &keyboard_interrupt);
// activate device
write_port(KEYBD_CMD, 0xAE);
wait();
// get status
write_port(KEYBD_CMD, 0x20);
wait();
u8 status = (read_port(KEYBD_DATA) | 1) & 0x05;
print("Setting PS/2 controller status: 0x");
printx(status);
putc('\n');
wait();
// set status
write_port(KEYBD_CMD, 0x60);
wait();
write_port(KEYBD_DATA, status);
wait();
// enable keyboard scanning
keyboard_command(0xf4);
}
Not that I think it's the root of the problem, but here's the assembly part of the interrupt handler just in case (in GNU assembly):
.extern keyboard_handler
.global keyboard_interrupt
keyboard_interrupt:
cli
pusha
cld
call keyboard_handler
popa
sti
iret
Here's the code that sets up the PICs beforehand:
#define MASTER_CMD 0x20
#define MASTER_DATA 0x21
#define SLAVE_CMD 0xA0
#define SLAVE_DATA 0xA1
#define PIC_EOI 0x20
// hopefully this gives a long enough delay
void wait() {
for (u8 i = 0; i < 255; i++);
}
// alert the PICs that the interrupt handling is done
// (later I'll check whether the slave PIC needs to be sent the EOI, but for now it doesn't seem to hurt to give it anyway)
void pic_eoi() {
write_port(MASTER_CMD, PIC_EOI);
write_port(SLAVE_CMD, PIC_EOI);
wait();
}
void setup_pic() {
write_port(MASTER_CMD, 0x11);
write_port(SLAVE_CMD, 0x11);
wait();
write_port(MASTER_DATA, 0x20);
write_port(SLAVE_DATA, 0x28);
wait();
write_port(MASTER_DATA, 0x4);
write_port(SLAVE_DATA, 0x2);
wait();
write_port(MASTER_DATA, 0x1);
write_port(SLAVE_DATA, 0x1);
wait();
write_port(MASTER_DATA, 0x0);
write_port(SLAVE_DATA, 0x0);
wait();
}
Here's the order of initializations in the main part of the kernel:
// initialize global descriptor table and interrupt descriptor table
setup_gdt();
setup_idt();
// setup hardware interrupts
setup_pic();
setup_keyboard();
activate_idt(); // assembly routine with lidt and sti
I also know that the keyboard is in fact doing its thing and putting scan codes on port 0x60, and I've been able to get a polling method of getting keypresses working, but it's messy and it would make it much harder to handle things like key repetition and keeping track of the shift key. Let me know if more code is needed. Hopefully there's just something obvious I'm either forgetting or doing wrong :)
pic_eoi
what happens if you removewrite_port(SLAVE_CMD, PIC_EOI); wait();
? No need to delay after sending EOI either. You shouldn't be sending an EOI to the slave if the interrupt occurred on the master. I am aware of your comment, just don't do it. When sending to the PS/2 controller you should be properly waiting on the input buffer status bit becoming 0. When reading data you should be waiting for the output status bit to be set to 1. Do you have a github project or somewhere we can see the entire project? – Bleedfor (u8 i = 0; i < 255; i++);
may be optimized entirely away and give no delay. – Bleed