Page 1 of 1
82C42 Keyboard Controller
Posted: Mon Mar 05, 2007 2:28 am
by XCHG
The Bit #1 of the onboard keyboard controller at port 0x64 is supposed to tell you whether data is available on port 0x60 or not, right? So when this bit is set it means that data is available at port 0x60. I wrote the below procedure to disable the keyboard controller but the procedure gets stuck in the [.Wait1] loop. Could somebody tell me what I am doing wrong?
Code: Select all
__KeyboardDisable:
PUSH EAX
MOV AL , 0x20
OUT 0x64 , AL
.Wait1:
IN AL , 0x64
TEST AL , 0x02
JE .Wait1
IN AL , 0x60
OR AL , 0x10
MOV AH , AL
MOV AL , 0x60
OUT 0x64 , AL
.Wait2:
IN AL , 0x64
TEST AL , 0x01
JNZ .Wait2
MOV AL , AH
OUT 0x60 , AL
POP EAX
RET
Posted: Mon Mar 05, 2007 11:25 am
by salil_bhagurkar
This might be the problem:
In your [.Wait1] loop after test you have written 'je' (Thats jump if equal to) and that makes no sense... You should use 'jnz'.
Posted: Mon Mar 05, 2007 2:05 pm
by XCHG
The BIT #1 should be one to indicate a byte available at port 0x60. So when TEST/ANDing the byte with 0x02, the result should NOT be equal to zero if some byte is available. If the byte IS equal to zero as in JZ, then the loop should be repeated, right? And since JZ and JE just check the zero flag, they both do the same thing. Anyway, thanks for your reply.
Posted: Tue Mar 06, 2007 1:34 am
by XCHG
Do I just have to wait for a certain amount of milliseconds for the keyboard microcontroller to make the byte available at port 0x60 or is there any other way I can find out that a byte is available at port 0x60 without using IRQs?
Re: 82C42 Keyboard Controller
Posted: Tue Mar 06, 2007 9:15 am
by Brendan
Hi,
XCHG wrote:Could somebody tell me what I am doing wrong?
The keyboard controller documentation *seems* backwards. Data from the CPU goes into the keyboard controller's input buffer, and data ready to go out of the keyboard controller is put in the keyboard controller's output buffer.
Basically, you're waiting until the keyboard controller's input buffer is full (i.e. waiting until the keyboard controller has recieved input from the CPU), when you should be waiting until the keyboard controller's output buffer is full (i.e. waiting until the keyboard controller is ready to output a byte to the CPU).
All you need to do use use Bit #0 of the status register instead of Bit #1 (and Bit #1 instead of Bit #0). For example:
Code: Select all
__KeyboardDisable:
PUSH EAX
;Send "get keyboard controller command byte"
MOV AL , 0x20
OUT 0x64 , AL
;Read the keyboard controller's command byte (hopefully)
.Wait1:
IN AL , 0x64
TEST AL , 0x01 ;Is keyboard controller ready to output data to the CPU?
JE .Wait1 ; no, wait
IN AL , 0x60 ;al = keyboard controller's command byte
;Set the "disable first PS/2 port's clock" flag
OR AL , 0x10 ;Set "disable first PS/2 port's clock" flag
MOV AH , AL ;ah = keyboard controller's command byte with first PS/2 port's clock disabled
;Send "set keyboard controller command byte"
MOV AL , 0x60
OUT 0x64 , AL
;Set the new keyboard controller command byte
.Wait2:
IN AL , 0x64
TEST AL , 0x02 ;Is keyboard controller ready to receive input data from the CPU?
JNZ .Wait2 ; no, wait
MOV AL , AH ;al = keyboard controller's command byte with first PS/2 port's clock disabled
OUT 0x60 , AL
POP EAX
RET
BTW, unless the keyboard is disabled the keyboard controller could receive data from the keyboard at any time. This means the user can press a key immediately before you send the "get keyboard controller command byte" and what you read from the data port will be the scan code for the key they pressed and not the keyboard controller command byte that you're expecting - it's an unfixable race condition.
Instead you should do this:
Code: Select all
__KeyboardDisable:
PUSH EAX
;Send "disable keyboard feature" to the keyboard controller
MOV AL , 0xAD
OUT 0x64 , AL
POP EAX
RET
This does exactly the same as your code (disables the clock line for the first PS/2 ports serial interface), except there is no unfixable race condition (and it's shorter/faster).
Also, I probably should mention that the first time I saw this post I thought I'd be able to find the problem fairly quickly/easily. Then I saw the code (one slab of assembly with no comments), got lazy and decided someone else would be able spend 15 minutes digging through the keyboard controller docuementation to decipher it (and then find the problem fairly quickly/easily). If the original code had comments I would've replied a few days ago instead of being lazy (not that I'm always lazy, it's just that sometimes my mind is on other things).
Cheers,
Brendan
Posted: Tue Mar 06, 2007 11:48 am
by ~
It's because when the keyboard has no more data to send, it will get stuck waiting for more data and since you have disabled the keyboard it will never will leave that loop. With this approach you would have to make a timeout routine for each byte, so if the last byte delays too much (that would always happen when there is no data) it will have to end due to a timeout. But it will have you trouble because if you use the mouse while it is waiting you will eventually lose keyboard/mouse data bytes plus you will slow down the execution because you will have to wait around 25 or 30 miliseconds. Whether you don't wait enough or you wait too much, you will get a terrible bug that could even cause the whole kernel to lock up and it would execute at the pace of mouse/keyboard, as a strange bug.
Don't do it this way. It's better if you code an Interrupt Handler for the keyboard, which would ONLY read one byte at one time, stores it in a temporary byte array, and then would acknowledge PIC and would return with IRET, and along a table containing all scan codes you want to recognize. You will need a byte array so when it reaches certain size or when it matches a comparisson with the static scan code table, it does something you want.
Belive me that this is the only easy way of gathering bytes even from keyboard and mouse simultaneously without unnecessary delays and without having to ever disable keyboard or mouse.
Try this way and you will see that your code will run smoothly, and you won't need to play around with status bytes or keyboard/mouse/controller commands. It seems to me that it's a very bad way of having designed the keyboard not being able just to poll all the bytes at once by reading the data port as one would do when reading the hard disk. This is why we must take one byte at a time and let the PIC and interrupts to provide us the best time to keep gathering and then just emulate a keybard queue with a byte array.
Posted: Wed Mar 07, 2007 12:44 am
by XCHG
It is confusing because Randall Hyde in his Art of Assembly book mentions that, quote from Chapter 20 section 20.2, “Communication to the microcontroller in the keyboard unit is via the bytes at I/O addresses 60h and 64h. Bits zero and one in the status byte at port 64h provide the necessary handshaking control for these ports. Before writing any data to these ports, bit zero of port 64h must be zero; data is available for reading from port 60h when bit one of port 64h contains a one.”
The VT82C42 Keyboard Controller datasheet doesn’t provide much of programming the controller but instead focusing more on the hardware design. So let me see if I got this straight. Bit 0 of the status byte of the keyboard microcontroller is set only if data is available to be read from port 0x60 and Bit 1 is set if there should be no more bytes sent to port 0x64, is that close on being on the money?
Brendan, thank you so much for your reply. I really appreciate it. And about writing comments, oh god I write comments and documentations more than I write the code. The reason I didn’t’ provide any comments for the above code was that I thought the problem was just the [.Wait1] loop as I mentioned in the original post and didn’t think I had gotten the idea wrong.
~, the design that you just mentioned looks great. I will have a go at it. For now, I have created a procedure called “__RegisterKeyboardHandler” which accepts one DWORD parameter which should be an offset of a normal procedure which itself accepts one parameter. IRQ 1 handler procedure reads keys that are pressed and passes them as an argument to the procedure which is registered by the “__RegisterKeyboardHandler” procedure. The lease significant byte of the DWORD parameter that is passed to the registered procedure will be the pressed key and the second byte will be equal to 0xE0 if an extended key has been pressed. For now it is working fine but I surely have to create an array later.