[Caveat, this is from memory, and about a real PC-AT, with real, 16 bit BIOS and code]
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.
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
SUB CX,CX ; Wait 'Time'
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
(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.