PS2 Interrupts Not Firing

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
tristanseifert
Posts: 10
Joined: Mon Oct 14, 2013 3:53 pm

PS2 Interrupts Not Firing

Post by tristanseifert »

Greetings everyone,

Yesterday I started work on a little PS2 driver so my kernel could finally read keyboard input and (eventually) use the mouse, but the driver seems to have some issues cooperating. Receive interrupts do not want to fire on QEMU, but I know my interrupts are set up correctly as they can be triggered on BOCHS as well as through a software interrupt instruction. The keyboard is detected correctly (it responds with 0xAB83 to an IDENTIFY command) so I believe my IO routines to talk to the controller work. BOCHS also doesn't seem to output the proper scancodes for the keys, usually being either completely off or off by one or two.

I'm really not quite sure why my interrupts aren't getting called. Here is the code for the driver:

Code: Select all

#include <types.h>
#include "io.h"
#include "ps2.h"
#include "device/pic.h"
#include "sys/system.h"

#define PS2_DATA 0x60
#define PS2_CMD 0x64

// assembly IRQ handlers
extern void ps2_irq_ch1(void);
extern void ps2_irq_ch2(void);

uint16_t ps2_ch1_buffer_off;
uint8_t *ps2_ch1_buffer;
uint16_t ps2_ch2_buffer_off;
uint8_t *ps2_ch2_buffer;

static uint16_t ps2_device1_type;
static uint16_t ps2_device2_type;
static uint8_t ps2_config_byte;
static bool ps2_keyboard_ok;

/*
 * Waits for the PS2 controller to have data available
 */
static void ps2_wait_io() {
	// Wait for bit 0 (output buffer full) to become 1
	while(!(io_inb(PS2_CMD) & 0x01));
}
/*
 * Waits for the PS2 controller to be ready to accept more data
 */
static void ps2_wait_write_rdy() {
	// Wait for bit 1 (input buffer full) to become 0
	while(io_inb(PS2_CMD) & 0x02);
}

/*
 * Initialises the driver and associated PS2 hardware
 */
int ps2_init() {
	// Disable both channel's devices
	io_outb(PS2_CMD, 0xAD);
	ps2_wait_write_rdy();
	io_outb(PS2_CMD, 0xA7);

	// Clear receive buffer
	io_inb(PS2_DATA);

	// Read configuration byte
	io_outb(PS2_CMD, 0x20);
	ps2_wait_io();
	ps2_config_byte = io_inb(PS2_DATA);

	// Clear bits 0, 1, and 6 (no IRQ, no translation)
	ps2_config_byte &= 0xBC;

	// Write back the byte
	io_outb(PS2_CMD, 0x60);
	ps2_wait_write_rdy();
	io_outb(PS2_DATA, ps2_config_byte);

	// Perform controller self test
	io_outb(PS2_CMD, 0xAA);

	ps2_wait_io();

	uint32_t counter = 0x00;
	while(io_inb(PS2_DATA) != 0x55) {
		counter++;
		if(counter > PS2_INIT_TIMEOUT) return -1;
	}

	// Initialise second channel if existant
	if(ps2_config_byte & (1 << 5)) {
		io_outb(PS2_CMD, 0xA8);		
	}

	// Test if ports work
	io_outb(PS2_CMD, 0xAB);
	ps2_wait_io();
	if(io_inb(PS2_DATA) != 0x00) {
		return -2; // error out if port is broken
	}

	// Enable IRQs
	ps2_config_byte |= 0x03;

	io_outb(PS2_CMD, 0x60);
	ps2_wait_write_rdy();
	io_outb(PS2_DATA, ps2_config_byte);

	// Enable devices
	io_outb(PS2_CMD, 0xAE);
	ps2_wait_write_rdy();
	io_outb(PS2_CMD, 0xA8);
	ps2_wait_write_rdy();

	// Reset device 1 and discard ACK
	if(ps2_write(0xFF, false, true, false)) return -3;
	ps2_wait_io();
	uint8_t temp = io_inb(PS2_DATA);

	if(temp == 0xFA) {
		terminal_write_string("Keyboard test OK\n");
		ps2_keyboard_ok = true;

		// We will probably get 0xAA too
		ps2_wait_io();
		io_inb(PS2_DATA);
	} else if(temp == 0xFC) {
		ps2_keyboard_ok = false;
		terminal_write_string("Keyboard failure\n");
		return -16;
	}

	// Identify device
	ps2_device1_type = ps2_id_device(false);
	ps2_device2_type = ps2_id_device(true);

	// If we have a keyboard, initialise it
	if((ps2_device1_type & 0xFF00) == 0xAB00) {
		// Enable scanning
		if(ps2_write(0xF4, false, true, true)) return -4;

		// Set keycode set to set 1
		ps2_write(0xF0, false, true, false);
		ps2_write(0x01, false, true, true);
	}

	// Set up IRQ handlers and PIC
	sys_pic_irq_clear_mask(1);
	sys_pic_irq_clear_mask(12);

	// Set up IRQ handlers
	sys_set_idt_gate(IRQ_1, (uint32_t) ps2_irq_ch1, 0x08, 0x8E);
	sys_set_idt_gate(IRQ_12, (uint32_t) ps2_irq_ch2, 0x08, 0x8E);

	terminal_write_string("PS2 Cfg: 0x");
	terminal_write_byte(ps2_config_byte);

	terminal_write_string("\nPS2 Device 1 Type: 0x");
	terminal_write_word(ps2_device1_type);
	terminal_write_string("\nPS2 Device 2 Type: 0x");
	terminal_write_word(ps2_device2_type);

	return 0;
}

/*
 * Identifies the device on the given port.
 */
uint16_t ps2_id_device(bool secondPort) {
	uint8_t temp;

	ps2_write(0xF5, secondPort, true, true); // disable scanning
	ps2_write(0xF2, secondPort, true, true); // Identify command

	ps2_wait_io();

	// Now, read identification data
	uint8_t identify_1, identify_2;
	identify_1 = io_inb(PS2_DATA);

	// The device *may* not send a second byte, so use timeout
	uint16_t timeout = 0;
	while(!(io_inb(PS2_CMD) & 0x01)) {
		timeout++;
		// We only got one byte, so return just that
		if(timeout > PS2_READ_TIMEOUT) return identify_1;
	}

	identify_2 = io_inb(PS2_DATA);

	return identify_1 << 8 | identify_2;
}

/*
 * IRQ handler for both PS2 devices
 */
void ps2_irq_handler(bool secondPort) {
	uint8_t readValue = io_inb(PS2_DATA);

	// This macro halts the machine and prints this text: I know it works.
	PANIC("PS2 Int");

	ps2_ch1_buffer_off = readValue;

	sys_pic_irq_eoi(secondPort ? 0x0C : 0x01); // send int ack
}

/*
 * Write a byte to the PS2 device.
 * If shouldWait is set, the function will wait for the input buffer bit to
 * indicate it's empty before writing.
 */
int ps2_write(uint8_t value, bool secondPort, bool shouldWait, bool waitACK) {
	uint16_t timeout = 0;

	// Wait for controller to be ready to accept data or timeout
	if(shouldWait) {
		while(io_inb(PS2_CMD) & 0x02) {
			timeout++;

			if(timeout > PS2_READ_TIMEOUT) return -1;
		}
	}

	// Write data out
	if(!secondPort) {
		io_outb(PS2_DATA, value);
	} else {
		io_outb(PS2_CMD, 0xD4); // write port 2

		// Wait for controller to accept data
		timeout = 0;
		while(io_inb(PS2_CMD) & 0x02) {
			timeout++;

			if(timeout > PS2_READ_TIMEOUT) return -1;
		}

		io_outb(PS2_DATA, value);
	}

	// If desired, wait for ACK (0xFA) from device
	if(waitACK) {
		ps2_wait_io();

		uint8_t temp;
		while((temp = io_inb(PS2_DATA)) != 0xFA) {
			ps2_wait_io();
		}
	}

	return 0;
}

/*
 * Test function to return the last byte read from the controller
 */
uint16_t ps2_read(bool secondPort) {
	uint8_t value;

	return ps2_ch1_buffer_off;
}
EDIT: I have verified that my PIC configuration is correct, as my timer interrupt works, and both IRQ0 and IRQ1 are unmasked.

Thanks for any help in resolving this.
mrstobbe
Member
Member
Posts: 62
Joined: Fri Nov 08, 2013 7:40 pm

Re: PS2 Interrupts Not Firing

Post by mrstobbe »

Well, I don't know about the qemu thing, but your scan codes begin off is likely due to you expecting one of the three scan code tables out there, but bochs is giving you something that you don't expect (for example, you expect scan code table 1, but bochs is giving you 2). Didn't read the code (sorry) but just a hunch.
Post Reply