Learn Something Old Every Day, Part XV: KEYB Is Half of Keyboard BIOS

Recently I had an opportunity to reacquaint myself with the DOS KEYB utility. KEYB is interesting in that it is designed primarily for international users, but one can also run KEYB US to load KEYB with standard US layout.

It is obvious that the KEYB utility was written by IBM. Unlike most of DOS, KEYB not only uses documented BIOS interfaces, but makes quite a few assumptions about how the keyboard BIOS works internally, in ways that were never documented.

This starts when KEYB loads and needs to detect whether the system BIOS supports extended keyboards or not—that is, whether the BIOS supports INT 16h functions 10h and 11h. To do that, KEYB calls INT 16h function 92h. Which is not, in fact, a BIOS function at all.

Rather, KEYB knows exactly how the IBM INT 16h implementation works. KEYB expects that when encountering an unsupported (too high) function, BIOS INT 16h keeps decrementing register AH until it decides that it’s too low, and then returns with that modified AH. On systems with extended keyboard support (newer PC/AT systems and most later machines), AH will be decremented to 80h. On old systems, AH will be decremented to somewhere around 8Eh. On machines with 122-key support, AH will be decremented even further, below 80h. In any case, KEYB executes INT 16h/92h and if the resulting AH value is 80h or less, it assumes that extended keyboard BIOS should be enabled.

Because KEYB “knows” exactly how the keyboard BIOS works, it gets away with only overriding half of it. While KEYB installs an INT 9h handler (corresponding to IRQ 1, or the keyboard hardware interrupt), it leaves INT 16h (the user callable BIOS keyboard service) untouched. In fact, the INT 9h handler in KEYB clearly started its life as a copy of the INT 9h handler in the IBM PC/AT BIOS, which only IBM could legally do.

The way the keyboard BIOS works is roughly as follows.

When the user presses a key, the INT 9h interrupt service runs, reads the scan code from the keyboard, and decides what to do with it.

The INT 9h routine is fairly complex and treats different keys quite differently. Modifier keys (Shift, Alt, Ctrl) have their up/down state tracked in flags stored in the BDA (BIOS Data Area). Releases of non-modifier keys are generally ignored. Key presses generate user visible events… but not always, and quite a few key combinations are simply ignored.

For key combinations that do generate events (most keys without modifiers, most combinations with Shift, and many with Ctrl and Alt), INT 9h places a two-byte “packet” in the keyboard queue in the BDA. This packet in general contains the scan code and ASCII code of the key.

Applications (mostly) interact with the INT 16h service. There are two primary functions; INT 16h function 0h/10h reads the next available keystroke and possibly waits until there is one, whereas INT 16h function 1h/11h checks whether there is a keystroke available in the queue and if there is, returns it but does not remove it from the queue.

Thus INT 16h offers both a “blocking” service that waits until input is available, as well as a “non-blocking” service which check if input is available but does not wait.

Now, the interesting part is that INT 16h mostly just returns what INT 9h placed in the keyboard queue… but only mostly.

IBM designed the 101/102-key extended keyboard BIOS to be highly compatible with the standard BIOS for 83/84-key keyboards. That caused some interesting problems. For example, pressing the 5 key on the num pad when Num Lock is off produces a keystroke with the extended BIOS, but it is ignored by the standard BIOS.

That poses a dilemma for the INT 9h interrupt handler. It can’t know whether an application will call INT 16h functions 0h/1h (ignoring the keystroke) or functions 10h/11h (expecting to see the keystroke). Therefore INT 9h places a special F0h code in the low byte of the packet in the keyboard queue.

If the extended INT 16h service sees the special F0h code, it will change it to zero. If the standard INT 16h service sees the code, it will remove the packet from the keyboard queue but ignore it.

This is where backward compatibility only can go so far: The standard BIOS service is not supposed to “see” this keystroke, but must discard it from the queue. If it didn’t, it would get stuck and never see any other input unless an extended INT 16h function removed the keystroke from the queue… but that will not generally happen with software that uses the standard INT 16h functions.

The standard INT 16h functions also translate several specific keystrokes, for example the num pad / and Enter keys. These return different events through standard vs extended INT 16h functions.

In effect, this is a private undocumented interface between the INT 9h hardware interrupt handler and the INT 16h keyboard service. Since KEYB only replaces the INT 9h part, the INT 16h implementation in the system BIOS must behave exactly like IBM’s for KEYB to work correctly.

In the vast majority of clone BIOSes, it does—because clone BIOS vendors found out soon enough if KEYB didn’t work right, and adjusting the BIOS to behave like IBM’s is not particularly technically difficult.

This entry was posted in BIOS, DOS, IBM, Keyboard. Bookmark the permalink.

3 Responses to Learn Something Old Every Day, Part XV: KEYB Is Half of Keyboard BIOS

  1. MiaM says:

    Interesting!

    Wait, so there are differences between motherboards re supporting 101/102/104/105 key keyboards v.s 83/84 key keyboards?

    Relevant side track: The fact that it uses code that was part of the AT bios, is that the reason for KEYB as one program with different parameters for different layouts not being a thing before a certain DOS version? (I’m 95% sure that there were separate binaries called KEYBxx for each layout in earlier DOS versions).

  2. Michal Necasek says:

    Depends on what you mean by “motherboard”. The hardware is the same, the BIOS is not. Remember that the 101/102 keyboard for example added F11 and F12 keys. The old BIOS simply does not know what to do with those. Old BIOS also can’t distinguish between the gray arrow and positioning keys and the num pad keys, and there are other differences.

    Yes, there used to be different KEYBxx executables. That may have been just because it was simpler to do. For DOS 3.3, IBM added comprehensive NLS with codepages, fonts, keyboards, printers, and all that stuff, and they probably concluded that a single KEYB.COM + KEYBOARD.SYS takes up a lot less space than a dozen KEYBxx utilities.

    I believe prior to DOS 3.3, IBM shipped KEYBxx utilities for major languages reasonably supported by CP437, and there were just five (FR/GR/IT/SP/UK). DOS 3.3 supported about 20 keyboard layouts out of the box (using different codepages), and 20 separate KEYB utilities probably weren’t something anyone was looking forward to.

  3. JQW says:

    Amstrad supplied a custom version of KEYBUK on the MS-DOS 3.2 floppy that shipped with the Amstrad PC1512. These machines had some odd hardware quirks, such as having a mouse/joystick port on the back of the keyboard, with the mouse buttons and joystick fire buttons to specific keys which could be re-mapped via the NVR. It was also possible to remap the behaviour of some other keys via NVR, such as the enter key on the numeric keypad.

    Suffice to say booting up a non-Amstrad PC with this version of DOS was problematic, as some keys didn’t work as expected.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.