Experiments with the 8042 keyboard controller

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.
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Experiments with the 8042 keyboard controller

Post by sunnysideup »

I'm doing some experiments programming the 8042 PS2 controller on a 32 bit kernel.

**I emulate on qemu**

Here are my routines:

Code: Select all

read_port:
	mov edx, [esp + 4]
	in al, dx	
	ret

write_port:
	mov   edx, [esp + 4]    
	mov   al, [esp + 4 + 4]  
	out   dx, al  
	ret
I just want to understand how the 8042 is programmed.
I understand the following:

Writing to port 0x64 is sending commands to the controller. (always)
Reading from port 0x64 is reading the status register of the controller. (always)
Reading/writing to port 0x60 has different functionalities.

Here's my first question: Where do these data buffers come into the picture?
That is, when one reads the status register, bit#0 and bit#1 tell us the status of the input and output buffers respectively. What are these? Do I "fill" this buffer whenever I write to 0x60/0x64? Am I reading from the input (output? from the perspective of the controller) buffer whenever I read from 0x60? I'm guessing that reading from 0x64 shouldn't affect the buffers because the status register is what's returned in that case (always?).

I was doing some experiments in my c kernel.

Code: Select all

uint32_t read_port (uint32_t portnumber);
void write_port (uint32_t portnumber,uint32_t data);

//printhex is function that prints the hex value of a 32 bit integer
kmain(){
      write_port(0x64,0x20); //This command outputs the byte 0 of internal RAM : the CCB
      printhex(read_port(0x64)); 
      printhex(read_port(0x60));

      write_port(0x64,0x23); //This command outputs the byte 3 of internal RAM
      printhex(read_port(0x64)); 
      printhex(read_port(0x60)); //I should be getting the value of byte 3 now?
}
In this case, I get the following values: 1Dh,9Ch,1Dh,61h
I'm guessing that the status register has the value 1D.
Byte number 0 of internal RAM has the value 0x9C.
Byte number 3 of internal RAM has the value 0x61.

Is this right? But when I do this:

Code: Select all

kmain(){
      write_port(0x64,0x23); //This command outputs the byte 3 of internal RAM
      printhex(read_port(0x64)); 
      printhex(read_port(0x60)); //I should be getting the value of byte 3 now?
}
I get the values 1Dh, 9Ch!! I expected that I would get the value as before, but I get a different value here? Why is this???
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: Experiments with the 8042 keyboard controller

Post by Octocontrabass »

sunnysideup wrote:**I emulate on qemu**
The keyboard controller is a surprisingly complex device. You may notice some functions don't work as described in qemu, in other emulators, or on real hardware that's more recent than the mid-90s or so. It's especially problematic when the BIOS is emulating a keyboard controller through SMM in order to provide USB support.

Windows and Linux work around these issues by not using most of the keyboard controller's functions, and telling the BIOS to not emulate a keyboard controller.
sunnysideup wrote:That is, when one reads the status register, bit#0 and bit#1 tell us the status of the input and output buffers respectively. What are these?
These are buffers used to hold byes being transmitted between the CPU and the keyboard controller. The input buffer holds the byte you've written to either port (so you must make sure it's empty before writing). The output buffer holds the byte that is ready to be read from port 0x60 (so you must make sure it's full before reading).

Note that if you receive an IRQ from the keyboard controller, you don't need to check if the output buffer is full before reading port 0x60, since the IRQ is triggered by the buffer being full. Accessing the keyboard controller is usually pretty slow, in case you're wondering why you'd want to skip checking the status register.
sunnysideup wrote:Byte number 0 of internal RAM has the value 0x9C.
Byte number 3 of internal RAM has the value 0x61.

Is this right?
No. You need to disable the keyboard port and flush the output buffer before trying to use keyboard controller commands that return response bytes, because there's no way to tell if the output buffer contains the response to your command or a byte from the keyboard. Incidentally, the value 0x9C doesn't make a whole lot of sense if it's byte 0 of internal RAM, but it does if it's a set 1 scan code.

(You may or may not also need to disable the mouse port. In theory, the keyboard controller raises IRQ12 for bytes received on the mouse port and IRQ1 for everything else, but I'm not sure if this particular detail works correctly everywhere.)

Also, byte 3 of the keyboard controller's internal RAM has no useful function, so it's entirely possible that command 0x23 won't do what you expect.
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Experiments with the 8042 keyboard controller

Post by sunnysideup »

So in modern hardware, where there are USB keyboards and mice, the BIOS comes in (via SMM) everytime I do in/out to port 0x60/0x64?
No. You need to disable the keyboard port and flush the output buffer before trying to use keyboard controller commands that return response bytes, because there's no way to tell if the output buffer contains the response to your command or a byte from the keyboard. Incidentally, the value 0x9C doesn't make a whole lot of sense if it's byte 0 of internal RAM, but it does if it's a set 1 scan code.
What does disabling the keyboard port mean? I'm guessing it disables the "connection" between the keyboard and the PS2 controller.
How would you do this is a code snippet??

Also, IR1 is fired everytime the output buffer is full? So even if I send a command word to 0x64 and get a response byte, which would fill the output buffer, I would get an IRQ which would cause my interrupt handler to be called? (assuming that I don't mask anything and interrupts are enabled)
Last edited by sunnysideup on Thu Mar 12, 2020 1:52 am, edited 1 time in total.
User avatar
bzt
Member
Member
Posts: 1584
Joined: Thu Oct 13, 2016 4:55 pm
Contact:

Re: Experiments with the 8042 keyboard controller

Post by bzt »

Hi,
sunnysideup wrote:In this case, I get the following values: 1Dh,9Ch,1Dh,61h
...
I get the values 1Dh, 9Ch!! I expected that I would get the value as before, but I get a different value here? Why is this???
To me these bytes look the same in both case (1Dh, 9Ch).

For more detailed answer, read 8042 on the wiki. The short answer is, think of the PS2 controller as a state machine. Repeatedly reading the same data register should return different bytes because the controller's internal state is changing. For example, you can set it so that port 0x60 returns controller responses, or could be as well return device responses, and you can also switch between two devices (keyboard and mouse). Your program and the controller's internal state must be in sync so that your program knows what values it's reading.

Furthermore, you can't read the data port any time you want; you must poll the status port to see if you can actually get a meaningful value. Here's how I read data from the 8042:

Code: Select all

uint8_t ps2_read()
{
    int cnt = 1024;
    while(!(inb(PS2_CTRL) & 1) && --cnt) cpu_relax;
    return cnt ? inb(PS2_DATA) : 0xFF;
}
Cheers,
bzt
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Experiments with the 8042 keyboard controller

Post by sunnysideup »

I understand that I can't read the data port anytime I wanted :D .
But I did read from port 0x64 (status register) which has the value 1D = 00011101. This states that the output buffer is full and that the input buffer is empty... Am I right? At least in this case XD ... I was just experimenting here.
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: Experiments with the 8042 keyboard controller

Post by Octocontrabass »

sunnysideup wrote:So in modern hardware, where there are USB keyboards and mice, the BIOS comes in (via SMM) everytime I do in/out to port 0x60/0x64?
Yes.
sunnysideup wrote:What does disabling the keyboard port mean?
It means the keyboard port clock line is disabled, so the attached keyboard (or whatever) can't send you any data.
sunnysideup wrote:Also, IR1 is fired everytime the output buffer is full?
IRQ1 is raised every time the output buffer is filled by the device plugged into the keyboard port, and IRQ12 is raised every time the output buffer is filled by the device plugged into the mouse port.

If the output buffer is filled for any other reason, a "real" 8042 keyboard controller (from the mid-90s or earlier) will raise IRQ1. But, as far as I know, Windows and Linux always poll the keyboard controller instead of using IRQs when they're expecting data in response to keyboard commands, so newer keyboard controllers may not raise IRQ1.
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Experiments with the 8042 keyboard controller

Post by sunnysideup »

Octocontrabass wrote:It means the keyboard port clock line is disabled, so the attached keyboard (or whatever) can't send you any data.
I'm just confused about this 'keyboard port'. I'm guessing that this is a connection between the PS2 controller and the keyboard, and has no 'port number' as such.

The port numbers 0x60 and 0x64 correspond to the PS2 controller, right? And not the keyboard encoder in the keyboard.

And I'm guessing sending 0xAD to port 0x64 would disable the keyboard and is what you mean
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: Experiments with the 8042 keyboard controller

Post by Octocontrabass »

Correct.
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Experiments with the 8042 keyboard controller

Post by sunnysideup »

Alright! Makes sense..
I'm guessing I can flush the output buffer by doing a dummy read from port 0x60.

I also read that "polling" the bit 0 of the status register flushes the output buffer.. how does this work?
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: Experiments with the 8042 keyboard controller

Post by Octocontrabass »

As far as I know, it doesn't work that way. Where did you read that?
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Experiments with the 8042 keyboard controller

Post by sunnysideup »

Step 4: Flush The Output Buffer
Sometimes (e.g. due to interrupt controlled initialisation causing a lost IRQ) data can get stuck in the PS/2 controller's output buffer. To guard against this, now that the devices are disabled (and can't send more data to the output buffer) it can be a good idea to flush the controller's output buffer. There's 2 ways to do this - poll bit 0 of the Status Register (while reading from IO Port 0x60 if/when bit 0 becomes set), or read from IO Port 0x60 without testing bit 0. Either way should work (as you're discarding the data and don't care what it was)
Quoted from https://wiki.osdev.org/%228042%22_PS/2_Controller
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: Experiments with the 8042 keyboard controller

Post by Octocontrabass »

poll bit 0 of the Status Register (while reading from IO Port 0x60 if/when bit 0 becomes set),
Reading from port 0x60 flushes the buffer. Polling lets you skip the read unless the buffer needs to be flushed.
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Experiments with the 8042 keyboard controller

Post by sunnysideup »

Alright... now assume that I have the IR1 interrupt handler set up. All interrupts are unmasked, i.e, the IMRs have value 0.

This is my isr (in C). This is called by assembly stub (which is basically just a call keyboard_handler(); followed by an iret)

Code: Select all

 void keyboard_handler(){
        uint8_t scan_code = read_port(0x60);
        printf("\nThe scan code is:");
        printhex(scan_code);
 }
This works in qemu, where everytime I press a key, I get the make and break codes. (Eg. The scan code is: 0x1C when I press enter)

However, this does not work in real hardware (I think IRQs aren't generated or something).... (I'm assuming that you've to initialize the keyboard controller some way).

I don't have any keyboard initializing code in my kernel.

What do I have to do to get the keyboard to work? (It does work in qemu as I expect).
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: Experiments with the 8042 keyboard controller

Post by Octocontrabass »

You probably just need to flush the output buffer, but the full configuration steps are:

1. Have your USB drivers disable the BIOS SMM emulation
2. Check the ACPI tables to ensure a keyboard controller really exists
3. Disable the keyboard and mouse ports
4. Flush the output buffer
5. Probe the controller for PS/2 compatibility (whether or not it has a mouse port)
6. Initialize the command byte
7. Enable the keyboard and mouse ports

On most (but not all) hardware, you can skip everything except flushing the output buffer to immediately begin using the keyboard.
sunnysideup
Member
Member
Posts: 106
Joined: Sat Feb 08, 2020 11:11 am
Libera.chat IRC: sunnysideup

Re: Experiments with the 8042 keyboard controller

Post by sunnysideup »

Thanks! Just flushing the buffer works!
Also, what does it mean to initialize the command byte?
Post Reply