Update: The source is available at github.com/mist64/msdos1

My last post was about the internals of the DOS 1.0 bootsector. This time, let’s look at the next stage of the DOS 1.0 boot process, the hardware abstraction library IBMBIO.COM.

CP/M and DOS History

Let us first look at the historical background: CP/M was an 8 bit operating system that existed for virtually every computer with an 8080/Z80 CPU. It consisted of the three core components: BIOS, BDOS and CCP. BIOS was the machine abstraction layer that allowed CP/M work on different platforms. BDOS was the platform agnostic core library code, and CCP the command line interpreter.

86-DOS by Seattle Computer Products was a clone of CP/M intended for 8086 computers. It shared the architecture of CP/M, having a separate machine abstraction layer (“DOSIO”). When Microsoft bought 86-DOS and ported it to the upcoming IBM PC (model 5150), they kept this architecture, although there was no need to implement custom drivers, since the IBM PC had all its drivers in its “BIOS” firmware. But IBM’s BIOS did not have the same interface as 86-DOS DOSIO, so PC-DOS 1.0 included a very small DOSIO which would just sit on top of BIOS and using its driver library (and work around some bugs).

So DOS 1.0 for the IBM PC consisted of the three parts IBMBIO.COM (machine abstraction), IBMDOS.COM (DOS library) and COMMAND.COM (command line).

Microsoft soon started licensing MS-DOS to other computer manufacturers that wanted to make IBM PC compatibles. Back then, IBM PC compatible meant being able to run DOS applications and not necessarily sharing the whole system design with the IBM PC, so for MS-DOS to run on other 8086-based systems, it was enough to adapt the hardware abstraction layer to these machines, and therefore Microsoft provided the source code of this part of MS-DOS to hardware vendors. In MS-DOS, the system files are called IO.SYS and MSDOS.SYS.

But since MS-DOS only provided a rather narrow API that did not include, for example, access to bitmap graphics, soon many DOS programs accesses hardware directly, forcing clone makers to make their PC compatibles more and more similar to the IBM PC, eventually using the same support chips and a binary compatible firmware interface. The separation of the kernel in two parts was less and less necessary, so that starting with MS-DOS 5.0, Microsoft only provided a single version of IO.SYS, and in MS-DOS 7.0 (Windows 95), IO.SYS and MSDOS.SYS were merged into IO.SYS.

IBMBIO

On DOS 1.0, IBMBIO.COM provides the following library functions to DOS (names taken from 86-DOS DOSIO source):

STATUS check for keypress INP get key from keyboard OUTP send character to screen PRINT send character to printer AUXIN get character from serial AUXOUT send character to serial READ read sector(s) from disk WRITE write sector(s) to disk DSKCHG check for disk change

(READ and WRITE will be directly hooked up by DOS into the INT 0x25/0x26 direct disk I/O API later.)

In addition to this, IBMBIO is the next step in the boot process after the bootloader and responsible for

initializing the serial and printer ports

building a list of floppy drives and its capabilities

setting up exception vectors (division by zero etc.)

call the IBMDOS init code (already in memory)

load and run COMMAND.COM

Let us now look at the library calls it provides:

Serial and Printer

The code to talk to the serial and printer ports is rather straightforward. There is support for a single serial port and a single printer port. IBMBIO sets up the port to 2400 8N1 and has no function to change this setting. I/O will just be passed to the respective BIOS functions, but errors will be evaluated and error messages will be printed in the error case.

Keyboard and Screen

While printing a character just passes the character to BIOS, character input is quite interesting: When reading a character, BIOS returns the ASCII code as well as the raw keyboard scancode. For keys that have no ASCII equivalent like the function or cursor keys, this returns zero as the ASCII code. IBMBIO always returns the ASCII code, but for special keys, it returns two bytes: A zero, indicating that it is a special key, and the BIOS scancode. Therefore in case of a special key, it caches the scancode, returns the zero, and will return the scancode the next time a character is read.

The Control+C/Control+Break handler uses this infrastructure to inject the code “3” into the input stream.

Disk I/O: Virtual Disks

The library code for Disk I/O is the most interesting part, since it can simulate a virtual disk drive, and it works around two issues of the BIOS function.

DOS supports up to four disk drives, A:, B:, C: and D:, but in case there is only a single drive, it will present two drives to DOS. Since all disk access goes through the READ and WRITE functions of IBMBIO, it can compare the requested disk drive with the disk drive last used, and if it’s the other drive, it will print:

Insert diskette for drive A: and strike any key when ready

After pressing a key, the actual I/O access is performed. This way, DOS can be completely agnostic about whether there are two physical drives, or a physical and a virtual drive, and “COPY A:FOO B:BAR” will just work. Note that without this feature, it would be impossible to get data from one disk onto another with standard DOS tools.

Disk I/O: Multiple Tracks

The first IBM PC BIOS issue IBMBIO works around is the fact that the original version of BIOS did not support a read or write of sectors across multiple heads. A track always had 8 sectors, and if your read starting at sector 1 of a track, it is possible to read up to 8 sectors, but starting at sector 8, only one sector can be read – if you want to continue reading sectors from the next track, you have to call BIOS again explicitly. The IBMBIO driver therefore breaks up longer reads if they span tracks.

Disk I/O: 8237 DMA Controller Bug

The second problem is actually a design issue with the Intel 8237 DMA controller in the PC. Although the Intel 8088 CPU was internally 16 bits, had 16 bit registers and could support up to 1 MB of RAM, it was the low cost version that was meant to interface to 8 bit support chips. The 8237 is such a support chip intended for 8 bit systems, and therefore only supports 64 KB of memory. Since this would have meant that data from the disk drive can only be read into the lower 64 KB of the PC, IBM extended the DMA controller by adding an external latch per channel to it: You set up the lower 16 bits of the DMA address in the DMA controller, and the upper 4 bits (20 bits correspond to 1 MB) in an external latch, and the upper four address lines will be provided by the latch when the DMA controller accesses memory.

Unfortunately, the 8237 had no “carry out”, so if you set up a DMA to 0x0FFFF (latch: 0x0, DMA controller: 0xFFFF), the address inside the DMA controller will wrap around to 0x0000, but it will not update the upper four bits of the address in the latch. So DMA that spans a 64 KB boundary will end up at the wrong location.

The idea of a device driver is to abstract away details of a device and work around device bugs, but the BIOS in the first PC failed to work around this quirk in the DMA hardware. Therefore IBMBIO works around it by detecting I/O that spans a 64 KB boundary and performing it in a temporary buffer inside IBMBIO.

What is interesting about these workarounds is that DOS 1.0 was the default operating system for the disk-equipped version of the IBM PC, and IBM shipped it with its first machines, so it should have been possible to include these workarounds in BIOS already. In fact, later versions of BIOS did not have these issues any more, but DOS kept supporting the workarounds for a long time.

Source

Here is the assembly source of IBMBIO 1.0. It can be compiled with NASM and produces a binary which is not 100% identical, because of variations in the instruction encoding of different assemblers. The original assembler wasted a few bytes in the encoding, so NOPs have been added to keep the layout identical. The binary is only 1920 bytes. IBMDOS.COM is 6400 bytes, and I might be looking into that one in the future as well.