Blog: Low-Level Disk Access on a 80286 with BASICA (2015-11-15)

I recently dug out an old 80286 PC from the cellar. This machine was the one on which I made my first experiences with computers and programming. It runs some version (I don't know which one, to be honest) of MS-DOS, features 20 MiB of harddisk space and one megabyte of RAM. Note that only 640 KiB of that memory is actually usable, the extra 384 KiB is set up as a ramdisk. Here are some pictures:

Front view of the old 80286. Back view with the existing connection ports. View from the inside. View from the inside.

We've actually had a few computers over the years, but all of them broke at some point and were replaced by the next one for this reason. The 80286 is the only one that still works fine—after almost 30 years. At least for me, it is already quite an interesting piece of computing history. My wife, on the contrary, probably thought me crazy when I arrived with it.

Nevertheless, since this computer contains not only what I consider an interesting old system for tinkering but also some of my very first programs, I decided to try to mirror the harddisk. With a modern computer, I would simply boot a GNU/Linux flash drive and issue a command like:

dd if=/dev/hda of=disk.img

Unfortunately, the old computer is far from sharing any common interface with my modern laptop. Of course, this is something that can be fixed easily. However, it is much more fun to work on that yourself! Thus, I decided to try and find a solution for transferring the 20 MiB of disk content in a creative way. My initial plan was the following: The computer contains an extension card that has a two-way A/D converter, which could be used to output a signal and record it with the microphone input of my laptop. Unfortunately, it seems that the C++ compiler available on the old system is broken due to disk corruption. Furthermore, I was not able to find suitable documentation of the extension card, so that I was not able to make this idea work.

As an alternative, I can still use BASICA present on the computer. The new plan is thus to read the disk sector by sector, writing the data out to the screen, and capturing the output via a camera:

Data transfer via output to the screen and a digital camera.

Of course, a language like BASICA (that is intended for playing around on a high level and also first programming experiences) does not directly allow low-level disk access. It does, however, allow for execution of arbitrary code written in binary or assembly language. Thus, I can make use of the BIOS API for reading disk sectors via such an assembler blob. Each disk sector contains 512 bytes of data, and half of that fits conveniently into a screen full of numbers.

Long story short, after some trial and error I arrived at a surprisingly "simple" BASIC program that reads disk sectors and writes them to the screen. It uses some dirty tricks that would be impossible today in any somewhat "high-level" language, such as manipulating the (real, physical!) memory directly with PEEK and POKE commands, simply putting some executable code there (lines 200 to 500) and then CALLing it on line 800. The data blobs at the end correspond to the binary code that the GNU assembler produces for my assembly code that uses the BIOS interrupt for reading disk sectors.

By changing the head on line 30 and the range of cylinders iterated over on line 705, one can systematically read (and output) the whole disk content. Note also the loop on lines 950 and 955: Even though it just performs 3,000 iterations, this is enough to make the interpreter pause noticably after each written screen. The variables CS1% to CS4% contain ad-hoc checksums that are used to verify correct transfer of each screen (they proved surprisingly useful and accurate, although I have no experience at all with data-transmission protocols; in hindsight it would just have been better to add a check for every line, such that errors can be pin-pointed more precisely when correcting them). In addition to the payload of 256 bytes, each screen also contains metadata about the sector it corresponds to in the first line and these four checksums in the last line.

If you are interested in tinkering yourself, grab yourself some DOS image, run it in QEMU and give it a try!

I decided to do 10 iterations of the cylinder loop for each run, which corresponds to 340 screens of numbers or a data transfer of 87 KB. For my setup, such a run takes around 21 minutes, making the whole data transfer complete in just over 86 hours. You may now agree with my wife that I'm a little crazy, but in the end, it works out nevertheless. Let me show you also a video of how the final recording of my camera looks like for the first four sectors (including the disk's master boot record). Note the speed up during the second half, which is due to enabling the computer's turbo button.

See also my second post describing the method of processing the recorded videos to assemble the disk image on my modern laptop.

Copyright © 2011–2019 by Daniel KraftHomeContactImprint