10

There is a not-unheard-of technique in DOS software to detect certain keypresses (like Ctrl+Break) by intercepting the keyboard interrupt (IRQ 1) and reading the scancode directly from the keyboard controller. If the scancode matches, it is routed to the appropriate handler; if it does not match, the interrupt is forwarded to the original service routine, where the same scancode is read from the controller again and processed as usual. Here’s for example DJGPP libc doing something like this.

This technique uncovers a particularly irritating bug in QEMU in that the scancode byte read from the keyboard controller is immediately replaced with the next queued byte, so the original service routine has no chance to read the scancode again, making some programs unusable.

If I read their code correctly, other emulators, which seemingly work fine, tend to have a single-byte buffer and a timer that checks if the buffered byte has been read, and if so, updates the buffer with the next queued byte. (Which still leaves a window for a race condition, although one rather unlikely to be hit – and fortunately enough, one less likely to be hit as the CPU becomes faster.) But I don’t know if this is just a hack deemed ‘good enough’ for emulation, or if it accurately reflects the behaviour of physical hardware.

How does real hardware operate in this case? When does a new scancode byte arrive at the keyboard controller port?

1
  • 5
    Don't know about the IBM PC, but only being able to read things once is pretty normal for hardware queues. That's how a normal hardware queue works.
    – user253751
    Feb 22 at 17:24
12

About 0.6 to 1 ms after the last one is read.

The 8042 keyboard controller does not queue scancodes; it just stops receiving bits from the keyboard until a byte is read from port 0x60. When a scancode byte is read out, the controller gets ready to receive another byte from the keyboard. But since serial communication is not instantaneous, actually receiving a scancode byte takes some time, which means the last received byte is going to linger on the port for a while. And it turns out someone has already measured how long it is. Michal Necasek of OS/2 Museum writes:

IBM gives the CLK inactive period as 30-50 μs, and CLK active period as 30-50 μs as well. The time to transfer one bit is then 60-100 μs. That’s a bit rate of about to 10-16.67 kHz, which at 11 bits per byte translates to about 900 to 1,500 bytes per second. In other words, it takes 660 to 1,100 μs to transfer one byte (scan code) from the keyboard.

[…] In reality the time is likely longer because the keyboard controller is not infinitely fast and the keyboard is probably not communicating at the maximum allowed speed.

To put things into perspective, the original IBM 5150’s CPU frequency of 4.77 MHz translates to about 0.21 µs per cycle. 660 µs is plenty time to forward the interrupt to another handler (which takes at most a couple dozen instructions anyway). Which means that even at the original PC’s speed, it’s unlikely this technique would lead to lost scancodes. CPUs have only gotten faster since then, while keyboard controllers… well, didn’t. And the latency can only get higher when scancode translation comes into play: the blog goes on to cite an example of a key release code that takes three bytes on the wire being translated into two bytes when read out from the controller.

Of course emulators don’t receive scancodes from a direct serial link to the keyboard; the host environment sends them keyboard events, which most emulators translate into an internal queue of scancodes. But all in all, it seems that delaying the availability of already-queued scancode bytes replicates observable controller behaviour quite accurately.

(Thanks to @john_e for pointing me to that blog post.)

1
  • The timing is only true for single byte scan codes and such produced by the keyboard. timing is quite different on multi byte scan codes and translated ones.
    – Raffzahn
    Feb 22 at 21:36
9

[Caveat, this is from memory, and about a real PC-AT, with real, 16 bit BIOS and code]

TL;DR:

When does an IBM-compatible PC keyboard controller dequeue scancodes?

(I assume this is about a PC-AT, not a PC or PC-XT)

Never, as there is no queue. At least none in hardware.

The 8042 has simply 4 registers Data In/Out, Status and Command. All synchronisation has to be done 'manually'. Reading the data register clears the Output Buffer Full flag, interrupting the 8042 CPU, enabling it to enter the next byte (byte, not scan code) - unless that function is disabled, which is what to be done to avoid overrun and enable multiple reads.

Details:

keyboard interrupt (IRQ 1)

While the interrupt line is #1 the interrupt in question is 09h

Here’s for example DJGPP libc doing something like this.

I'm not really sure if that's a good code, as any KBD handler should disable the keyboard before reading a keystroke - at least that's what the BIOS does, and all handlers I've seen - to avoid overrun.

This technique uncovers a particularly irritating bug in QEMU in that the scancode read from the keyboard controller is immediately replaced with the next queued scancode,

Is it really a bug or simply working like the original 8042 did when the keyboard doesn't get disabled before reading?

other emulators, [...] tend to have a single-scancode buffer and a timer [...]

Sounds like a bad hack.

Which still leaves a window for a race condition,

The very same race condition an original AT, as slow as it was, had with the keyboard controller.

I don’t know if this is just a hack deemed [...] or if it accurately reflects the behaviour of physical hardware.

Maybe good enough on rather fast machines.

How does real hardware operate in this case?

The 8042AH does only feature an interface of 4 registers.

  • Data Buffer In - Data from main CPU, i.e. holds last item written with A0=0 (Port 60h)
  • Data Buffer Out - Data prepared to be read by main CPU, i.e. what gets delivered when reading with A0=0 (Port 60h)
  • Data Buffer Status - Various Satus Flags to be read by main CPU with A0=1 (Port 64h)
    • Bit 0 -> Output Buffer Full - can be as well outputted on a dedicated pin
    • Bit 1 -> Input Buffer Full - can be as well outputted on a dedicated pin
    • All others: Application specific
  • Instruction Register - Command from main CPU, written with A0=1 (Port 64h)

All handling of these ports within the 8042 is done by software. There is

  • Implied hardware support for setting OBF when the output buffer gets written.
  • Implied hardware support for clearing IBF when the input buffer is read.
  • An interrupt can be generated when data is written to the input buffer.
  • A dedicated jump instructions to test IBF/OBF.
  • A special instruction for setting certain status bits.

When does a new scancode arrive at the keyboard controller port?

As soon as the prior gets read out.

During normal operation the controller waits for a key press, translates it according to which keycode table is active (XT or AT) and puts it into the Output Buffer register when empty. otherwise it waits for OBF to go away to place it. In case of multi byte codes (like all break or function codes or alike), it puts repeats this step for as many bytes there are.

That's why the original AT BIOS (*1) does send a disable keyboard command before doing anything else:

; Disable Keyboard Input
    CLI
    SUB  CX,CX     ; Wait 'Time'
LP1:
    IN   AL,064h   ; Read Status Port
    TEST AL,IBF    ; Input Buffer Full (02h) ?
    LOOPNZ LP1     ; Wait for Empty

    MOV AL,0ADh    ; Disable Opcode
    OUT 064h,AL    ; Send it
    STI

(The AT BIOS uses a subroutine to do so. See file KYBD on page 5-129 of the March 1986 PC-AT Technical Reference Manual)

Doing so leaves enough time to read and process whatever the controller has to offer. Most important here, the output buffer (what's delivered when reading port 060h) will keep it's value indefinite, so it can be read as often as needed.

Again, this is the workings of the original 8042 as used with the IBM PC-AT and later PS/2. Might be different with newer chipsets.


*1 - The XT BIOS didn't do so. I guess it simply became a necessity with the faster PC-AT.

7
  • 3
    The screen grabbers in Windows 1.x and 2.x (CGA.GRB etc.) use a similar technique: Hook INT 9, read port 60h to see if PrintScreen was pressed, and if not chain to the previous handler. Michal Necasek describes the Turbo Pascal 6.0 runtime doing the same thing: os2museum.com/wp/how-fast-is-a-ps-2-keyboard . So it's a hack, but widely used in software 'in the wild' and something emulators would have to deal with.
    – john_e
    Feb 22 at 12:18
  • 1
    @john_e Err ... Not sure how this fits. Intercepting INT 9h might not be the best way (as there is INT 15h 4Fh), but AFAI read the question, that's not the heck meant here, but what emulators seam to do.
    – Raffzahn
    Feb 22 at 12:30
  • 1
    My point was to mention some other DOS software that does what the questioner described: hook the IRQ1 handler (INT9), read from port 60h, and then chain to the original handler on the assumption that it will also read from port 60h and get the same keycode back. I didn't mean to suggest that it was a technique used in emulators, just that it's something emulators would have to work around.
    – john_e
    Feb 22 at 12:44
  • @john_e Still, AFAICT, this is not an issue of emulation but bad coding. Something that might fail on a genuine IBM-AT the very same way as on an emulation.
    – Raffzahn
    Feb 22 at 12:48
  • 3
    This might have been a better answer if you spent more time researching instead of writing from faulty memory, and less time questioning the premises. I asked ‘what happens if I do this’, and your answer boils down to ‘don’t do that, it’s wrong’ and the false claim that the next byte appears ‘[a]s soon as the prior gets read out’. In fact, there is a non-negligible delay. Not to mention you’re being hypocritical here: when your attitude is ‘documentation doesn’t matter, all observable behaviour is fair game’, then so is relying on keyboard serial link latency. Feb 23 at 9:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.