[SOLVED] IRQ1 on PS2 controller commands response

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
Aerath
Posts: 10
Joined: Thu Jun 16, 2016 6:03 am
Libera.chat IRC: Aerath

[SOLVED] IRQ1 on PS2 controller commands response

Post by Aerath »

Hello, I have some problems writing my PS/2 mouse driver. Whenever the controller responds to a command (read byte 0 here) with both port 1 and interrupts for port 1 enabled in controller's configuration, it fire IRQ1.

From the wiki:
If you send a command to the PS/2 controller that involves a response, the PS/2 controller will put a "response byte" into the buffer and won't generate any IRQ
When I initialize the controller, I disable both ports, flush buffers, and disable interrupts in controller's configuration byte. No IRQ fired, as expected.
When I initialize the first PS/2 device, I unmask IRQ1 and link it to my ps2kb_handler, read controller's configuration, test the device, and enable interrupts, clock and translation for the first port in controller's configuration. Still no IRQ, everything is okay. But then, I enable first port and read the controller's configuration byte. As soon as I outb'ed 0x20, IRQ1 is fired, calling my handler. That's was not expected, but no problems for now (I have a counter for bytes to be ignored, ie manually read, not added to keyboard event buffer). So my PS/2 keyboard driver is kind of working.

But when I try to set up the PS/2 mouse, same problem occurs : I unmask IRQ12, link it to ps2mouse_handler, and try to read controller's configuration. Again, as soon as the command is sent to controller, IRQ1 is fired, probably because the response is ready. And so the configuration byte ends up being handled as a keystroke. After that the kernel hangs infinitely polling in ps2_read, waiting for a response that was intercepted.

Code: Select all

// 8042.c

 uint8_t ps2_read(void)
{
	do ctrlr_status = inb(STAT_PORT);
	while(!(ctrlr_status & STAT_OUTFULL));
	uint8_t data = inb(PS2_DATA_PORT);
	ctrlr_status = inb(STAT_PORT);
	return data;
}

void ps2_write(uint8_t port, uint8_t data)
{
	if(port==2)
		outb(CMD_PORT, PS2_CTRLR_WRITE_P2IN);
	do ctrlr_status = inb(STAT_PORT);
	while(ctrlr_status & STAT_INFULL);
	outb(PS2_DATA_PORT, data);
}

void ps2_init(void)
{
	outb(CMD_PORT, PS2_CTRLR_P1_DS);
	outb(CMD_PORT, PS2_CTRLR_P2_DS);

	// Flush out buffers
	ctrlr_status = inb(STAT_PORT);
	while(ctrlr_status & STAT_OUTFULL)
	{
		inb(PS2_DATA_PORT);
		ctrlr_status = inb(STAT_PORT);
	}

	outb(CMD_PORT, PS2_CTRLR_READ_RAM(0));
	uint8_t config = ps2_read();
	if(~config & CONFIG_PORT2_CLK_DS)
		panic("Single channel PS/2 controller not supported");

	outb(CMD_PORT, PS2_CTRLR_TEST_CTRLR);
	if(ps2_read() != SELF_TEST_SUCCESS)
		panic("PS/2 controller self test failed");

	config &= ~(CONFIG_PORT1_INT|CONFIG_PORT2_INT|CONFIG_PORT1_TRANSL);
	outb(CMD_PORT, PS2_CTRLR_WRITE_RAM(0));
	ps2_write(1, config);
}

void ps2dev_init(ps2dev_t* dev, uint8_t port)
{
	memcpy(dev->iodev.name, "PS2", 3);
	dev->iodev.name[3] = '0'+port;
	dev->iodev.name[4] = 0;
	dev->iodev.active = false;
	dev->iodev.data = dev;
	dev->iodev.read = NULL;
	dev->iodev.write = NULL;
	dev->iodev.prev = NULL;
	dev->iodev.next = NULL;

	outb(CMD_PORT, PS2_CTRLR_READ_RAM(0));    // IRQ1 fired here, when port==2
	uint8_t config = ps2_read();    // Kernel hangs here when port==2, waiting for data in the do-while loop
	
	outb(CMD_PORT, port == 1 ? PS2_CTRLR_P1_TEST : PS2_CTRLR_P2_TEST);
	if(ps2_read())
	{
		klog("PS/2 port ");
		klog(dev->iodev.name+3);
		panic(" test failed");
	}

	config &= ~ (port == 1 ? CONFIG_PORT1_CLK_DS : CONFIG_PORT2_CLK_DS);
	config |= port == 1 ? CONFIG_PORT1_INT | CONFIG_PORT1_TRANSL : CONFIG_PORT2_INT;
	outb(CMD_PORT, PS2_CTRLR_WRITE_RAM(0));
	ps2_write(1, config);

	outb(CMD_PORT, port == 1 ? PS2_CTRLR_P1_EN : PS2_CTRLR_P2_EN);
	dev->expected_bytes = 1;
	outb(CMD_PORT, PS2_CTRLR_READ_RAM(0));    // First IRQ1 fired between this line and the next one, when port==1
	config = ps2_read();
}

// ps2kb.c


iodev_t* ps2kb_init(void)
{
	rbuf.data_end = rbuf.data_start = rbuf.buf_start = buffer;
	rbuf.buf_end = buffer+sizeof(buffer);

	install_irq_handler(1, ps2kb_handler);
	unmask_irq(1);

	ps2dev_init(&ps2kb, 1);
	ps2kb.iodev.read = io_ps2kb_read;

	if(ps2_cmd(&ps2kb, PS2_CMD_SCAN_DS) != PS2_ACK)
		panic("PS2 scan disable failed");
	ps2kb.expected_bytes++;
	if(ps2_cmd(&ps2kb, PS2_CMD_RESET_TEST) != PS2_ACK || ps2_read() != PS2_TEST_SUCCESS)
		panic("PS2 self-test failed");

	ps2kb.expected_bytes++;
	if(ps2_cmd(&ps2kb, PS2_CMD_IDENTIFY) == PS2_ACK)
	{
		uint8_t prev = ps2kb.expected_bytes ++;
		if((type[0] = ps2_read()) == 0xAB)
		{
			while(ps2kb.expected_bytes > prev);
			type[1] = ps2_read();
		}
		else panic("Device on PS/2 port 1 is expected to be a keyboard, but doesn't seems to be one");
	}

	if(ps2_cmd(&ps2kb, PS2_CMD_ECHO) != PS2_ECHO_RES)
		panic("PS/2 ECHO failed on port 1");
	if(ps2_cmd(&ps2kb, PS2_CMD_SCAN_EN) != PS2_ACK)
		panic("PS/2 scan enable failed port1");

	return &ps2kb.iodev;
}
If I try to initialize the mouse before the keyboard, IRQ12 is fired on controller's response if mouse and mouse interrupts are enabled (in controller's configuration) and I ends up with the same problem (but reversed) : with unwanted IRQ12 while setting up keyboard (and IRQ12 while setting up mouse, but handled by the same workaround).

Thanks for reading me and have a nice day !
PS: I have checked with GDB that the correct bytes are sent and received, no problem with defines.
PPS: This happens on Qemu
Last edited by Aerath on Sat Feb 25, 2017 8:02 am, edited 2 times in total.
Octocontrabass
Member
Member
Posts: 5587
Joined: Mon Mar 25, 2013 7:01 pm

Re: PS2 controller commands fire IRQ1 on response byte recep

Post by Octocontrabass »

A quick glance at this disassembly of an IBM AT keyboard controller firmware suggests that the keyboard controller should raise IRQ1 for every response byte.

Unfortunately, you can't tell from that if every keyboard controller will raise IRQ1 for response bytes. That's part of the reason why the wiki recommends only using commands that have response bytes while IRQs and both ports are disabled.
Aerath
Posts: 10
Joined: Thu Jun 16, 2016 6:03 am
Libera.chat IRC: Aerath

Re: PS2 controller commands fire IRQ1 on response byte recep

Post by Aerath »

OK, I changed my initialization function, it works now, thanks to you :)
Maybe the WARNING in the wiki article should be mitigated ?
Octocontrabass
Member
Member
Posts: 5587
Joined: Mon Mar 25, 2013 7:01 pm

Re: PS2 controller commands fire IRQ1 on response byte recep

Post by Octocontrabass »

I have updated the warning in the wiki to reflect actual hardware (and Qemu) behavior.

Is there any particular reason you're reading the PS/2 controller configuration byte after you've enabled the PS/2 ports? You should only need to read it when you're initializing the PS/2 controller, before you're ready to enable either port.
Aerath
Posts: 10
Joined: Thu Jun 16, 2016 6:03 am
Libera.chat IRC: Aerath

Re: PS2 controller commands fire IRQ1 on response byte recep

Post by Aerath »

Because of enabling keyboard before configuring mouse.
User avatar
dchapiesky
Member
Member
Posts: 204
Joined: Sun Dec 25, 2016 1:54 am
Libera.chat IRC: dchapiesky

Re: PS2 controller commands fire IRQ1 on response byte recep

Post by dchapiesky »

could you post revised code and change to [SOLVED]? Will help others in future. Cheers
Plagiarize. Plagiarize. Let not one line escape thine eyes...
Aerath
Posts: 10
Joined: Thu Jun 16, 2016 6:03 am
Libera.chat IRC: Aerath

Re: [SOLVED] IRQ1 on PS2 controller commands response

Post by Aerath »

New working code :

Code: Select all

// ****************************************************************************
// 8042.h
// ****************************************************************************

#ifndef __DEVICES_8042_H__
#define __DEVICES_8042_H__

#define PS2_DATA_PORT	0x60

/*
 * \brief Commands for PS/2 devices, to be written to DATA port
 * For commands with a parameter byte (set typematic, set indicators),
 * controller responds with ACK after both command byte and parameter byte
 */
#define PS2_CMD_SET_INDICATORS	0xED
#define PS2_CMD_ECHO			0xEE
#define PS2_CMD_IDENTIFY			0xF2
#define PS2_CMD_SET_TYPEMATIC		0xF3
#define PS2_CMD_SCAN_EN			0xF4
#define PS2_CMD_SCAN_DS			0xF5	/** Also RESET_PARAMS **/
#define PS2_CMD_RESET_PARAMS		0xF6	/** Doesn't disable scanning **/
#define PS2_CMD_RESEND			0xFE
#define PS2_CMD_RESET_TEST		0xFF

#define PS2_TEST_SUCCESS	0xAA
#define PS2_ECHO_RES		0xEE
#define PS2_ACK			0xFA
#define PS2_TEST_FAIL1	0xFC
#define PS2_TEST_FAIL2	0xFD
#define PS2_RESEND		0xFE

// ****************************************************************************
// 8042.c
// ****************************************************************************

#define CMD_PORT	0x64
#define STAT_PORT	0x64

#define PS2_CTRLR_READ_RAM(n)	(0x20|(n&0x1F))
#define PS2_CTRLR_WRITE_RAM(n)	(0x60|(n&0x1F))
#define PS2_CTRLR_P2_DS			0xA7
#define PS2_CTRLR_P2_EN			0xA8
#define PS2_CTRLR_P2_TEST			0xA9
#define PS2_CTRLR_TEST_CTRLR		0xAA
#define PS2_CTRLR_P1_TEST			0xAB
#define PS2_CTRLR_RAM_DUMP		0xAC
#define PS2_CTRLR_P1_DS			0xAD
#define PS2_CTRLR_P1_EN			0xAE
#define PS2_CTRLR_READ_IN			0xC0
#define PS2_CTRLR_READ_OUT		0xD0
#define PS2_CTRLR_WRITE_OUT		0xD1
#define PS2_CTRLR_WRITE_P1OUT		0xD2
#define PS2_CTRLR_WRITE_P2OUT		0xD3
#define PS2_CTRLR_WRITE_P2IN		0xD4

#define SELF_TEST_SUCCESS 0x55

#define STAT_OUTFULL			(1<<0)
#define STAT_INFULL			(1<<1)
#define STAT_SYSTEM_FLAG		(1<<2)
#define STAT_FOR_CONTROLER	(1<<3)
#define STAT_SPECIFIC		(1<<4)
#define STAT_MOUSE_DATA		(1<<5)
#define STAT_TIMEOUT_ERR		(1<<6)
#define STAT_PARITY_ERR		(1<<7)

#define CONFIG_PORT1_INT		(1<<0)
#define CONFIG_PORT2_INT		(1<<1)
#define CONFIG_SYSTEM_FLAG	(1<<2)
#define CONFIG_ZERO1			(1<<3)
#define CONFIG_PORT1_CLK_DS	(1<<4)
#define CONFIG_PORT2_CLK_DS	(1<<5)
#define CONFIG_PORT1_TRANSL	(1<<6)
#define CONFIG_ZERO2			(1<<7)

uint8_t ps2_read(void)
{
	do ctrlr_status = inb(STAT_PORT);
	while(!(ctrlr_status & STAT_OUTFULL));
	uint8_t data = inb(PS2_DATA_PORT);
	ctrlr_status = inb(STAT_PORT);
	return data;
}

uint8_t ps2_read_when(ps2dev_t* dev, uint8_t target)
{
	while(dev->expected_bytes > target);
	return inb(PS2_DATA_PORT);
}

void ps2_write(uint8_t port, uint8_t data)
{
	if(port==2)
		outb(CMD_PORT, PS2_CTRLR_WRITE_P2IN);
	do ctrlr_status = inb(STAT_PORT);
	while(ctrlr_status & STAT_INFULL);
	outb(PS2_DATA_PORT, data);
}

void ps2_init(void)
{
	outb(CMD_PORT, PS2_CTRLR_P1_DS);
	outb(CMD_PORT, PS2_CTRLR_P2_DS);

	// Flush out buffers
	ctrlr_status = inb(STAT_PORT);
	while(ctrlr_status & STAT_OUTFULL)
	{
		inb(PS2_DATA_PORT);
		ctrlr_status = inb(STAT_PORT);
	}

	outb(CMD_PORT, PS2_CTRLR_READ_RAM(0));
	uint8_t config = ps2_read();
	if(~config & CONFIG_PORT2_CLK_DS)
		panic("Single channel PS/2 controller not supported");

	outb(CMD_PORT, PS2_CTRLR_TEST_CTRLR);
	if(ps2_read() != SELF_TEST_SUCCESS)
		panic("PS/2 controller self test failed");

	outb(CMD_PORT, PS2_CTRLR_P1_TEST);
	if(ps2_read())
		panic("PS/2 port 1 test failed");

	outb(CMD_PORT, PS2_CTRLR_P2_TEST);
	if(ps2_read())
		panic("PS/2 port 2 test failed");

	config |= CONFIG_PORT1_INT|CONFIG_PORT2_INT|CONFIG_PORT1_TRANSL;
	config &= ~ (CONFIG_PORT1_CLK_DS | CONFIG_PORT2_CLK_DS);
	outb(CMD_PORT, PS2_CTRLR_WRITE_RAM(0));
	ps2_write(1, config);
}

void ps2dev_init(ps2dev_t* dev, uint8_t port)
{
	dev->type[0] = dev->type[1] = 0xFF;
	memcpy(dev->iodev.name, "PS2", 3);
	dev->iodev.name[3] = '0'+port;
	dev->iodev.name[4] = 0;
	dev->iodev.active = false;
	dev->iodev.data = dev;
	dev->iodev.read = NULL;
	dev->iodev.write = NULL;
	dev->iodev.prev = NULL;
	dev->iodev.next = NULL;

	outb(CMD_PORT, port == 1 ? PS2_CTRLR_P1_EN : PS2_CTRLR_P2_EN);
	if(ps2_cmd(dev, PS2_CMD_SCAN_DS) != PS2_ACK)
	{
		klog("PS/2 scan disable failed on port ");
		panic(dev->iodev.name+3);
	}
	
	dev->expected_bytes = 1;
	if(ps2_cmd(dev, PS2_CMD_RESET_TEST) != PS2_ACK
		|| ps2_read_when(dev, 0) != PS2_TEST_SUCCESS)
	{
		klog("PS/2 reset-test failed on port ");
		panic(dev->iodev.name+3);
	}

	dev->expected_bytes = port == 1 ? 2 : 1;
	if(ps2_cmd(dev, PS2_CMD_IDENTIFY) == PS2_ACK)
	{
		dev->type[0] = ps2_read_when(dev, port == 1 ? 1 : 0);
		if(port == 1)
		{
			if(dev->type[0] != 0xAB)
				panic("Device on PS/2 port 1 is expected to be a keyboard, but doesn't seems to be");
			dev->type[1] = ps2_read_when(dev, 0);
		}
	}

	if(port == 1 && ps2_cmd(dev, PS2_CMD_ECHO) != PS2_ECHO_RES)
		panic("PS/2 ECHO failed on port 1");
	if(ps2_cmd(dev, PS2_CMD_SCAN_EN) != PS2_ACK)
	{
		klog("PS/2 scan enable failed on port ");
		panic(dev->iodev.name+3);
	}
}

// ****************************************************************************
// ps2kb.c
// ****************************************************************************

iodev_t* ps2kb_init(void)
{
	rbuf.data_end = rbuf.data_start = rbuf.buf_start = buffer;
	rbuf.buf_end = buffer+sizeof(buffer);

	install_irq_handler(1, ps2kb_handler);
	unmask_irq(1);

	ps2dev_init(&ps2kb, 1);
	ps2kb.iodev.read = io_ps2kb_read;

	return &ps2kb.iodev;
}
Post Reply