Bootloader junky by mrdata-- (Shawn Poulson) What is a Boot Loader? A boot loader is generally referred to the part of an operating system that starts itself up from the boot phase. A conceptual model of a bootloader in example is the boot part of Windows95 that displays "Starting Windows 95..." then goes about setting up the interrupt 21h handler, system timer, any buffers it needs, then parses out the config.sys, etc. By the time the operating system gets to the autoexec.bat, it's already a running operating system and is almost fully booted up. What's Good About It? A boot loader is an interesting topic because not many people have exploited the boot loader stage of software development in the demoscene. For us, it's great to work with a fresh new system that we KNOW doesn't have any strange drivers, memory setups or special processor modes (with the exception of the possibility of Disk Manager, which doesn't make much of a difference). Protected mode is only 3 commands away, and flat real mode is never a problem either. You can optimize the CPU/architecture operation to however best suits your code. What's Bad About It? There's also a down side. You see, there's two sides to every schwartz. One backstep in taking this approach is that you have no operating system, no file system, and no memory management. All of which you will have to code yourself, should you need them. I sure think an operating system is a necessity, so if you've always wanted to do better than Dos or any other flakey operating system, this is your chance to take on the challenge. How the Boot Process Works The whole bootup process of any x86 system is pretty simple. When you flip the power switch, all the hardware powers up and initializes. The first noticable init is the video card when it flips and displays the video card init lines. As soon as the video card is done, BIOS immediately takes over the processor and begins its usual scanning for hardware and connections. Some BIOS do more than others but the essential idea is that it finds the floppy and hard drives on the system and tries to boot from one of those depending on your setup. For this article, let's just assume our conceptual dream PC (a dual p6/240mmx of course) is set to boot from its floppy drive 0 first if there is a disk in it. Also assume that we are using a 3.5" HD drive with matching disk. How the bootloader starts up: - With a boot disk in the first floppy drive, the BIOS reads the first sector of the disk, sector 1 (the boot sector), to 0000:7c00. - The BIOS sets CS:IP to 0000:7c00h and *BAM* your code executes. (the computer doesn't really make a *BAM* sound. If it does, then you need to fix something.) Anatomy of a Floppy Drive To code bootloader code you MUST know how the floppy hardware works with software. Fortunately, BIOS provides an interface to sector-based floppy disk access on interrupt 13h. Disk Type Heads Tracks Sectors per track --------- ----- ------ ----------------- 3.5" 1.44M 2 80 18 3.5" 720k 2 80 9 5.25" 1.2M 2 80 15 5.25" 360k 2 40 9 All values are passed to BIOS floppy functions starting from zero (i.e. the Heads value will be 0 or 1), except the sector value which starts from one. The correct order of sector access is to process sectors in a track on head 0, then process the sectors of the same track on head 1, then fall back to head 0 and increment the track number. You can have your code read/write sectors in any ordering you want, but there's a standard method for a reason. :) Floppy disk timings are crucial to the operating of the floppy drive. Unfortunately the original PC was developed with dumb hardware controllers. The PC has to spoonfeed every device with every little bit of info it needs. The same goes with the floppy drive system. Such timings of the floppy drive include the seek time and the motor spin-up time. Upon issuing a track seek command to the floppy controller directly, the head will move but when it stops there is a bit of vibration in the head that will cause read/write errors on immediate following commands. The hardware needs up to 10ms to account for this movement. Additionally, sending a motor spin-up command to the floppy hardware directly will require 500ms to come up to speed. Fortunately the BIOS floppy services handle the seek timing, but does not issue any sort of motor spin-up delay. This will prompt you to make sure that you write floppy routines that can handle issuing 2 or 3 retries, if necessary. The BIOS will return an error as many times as you try until it succeeds. Dos handles this condition by returning an error if the operation did not succeed after the 3rd time. If you want to write your own hardware- level floppy routines to bypass the BIOS ones, I'd say check out the "Undocumented PC" book. BIOS Controls BIOS calls to int 13h are all that's needed to read/write sectors on the disk. Below is a short description of the important calls: -- Interrupt 13h ------------------------------------------------------------- Function: 02h Read sector Entry: ah = 02h al = number of sectors to read in succession cl = sector ch = track dl = drive (0 = 'A drive', 1 = 'B drive') dh = head es:bx = destination buffer Return: cf = set on error Note: - BIOS is not smart enough to wait for the motor to spin up, so there may be a few initial errors upon this condition. Retry 2 or 3 times if needed. A physical error would be apparent after 3 tries. Function: 03h Write sector Entry: ah = 03h al = number of sectors to write in succession cl = sector ch = track dl = drive (0 = 'A drive', 1 = 'B drive') dh = head es:bx = source buffer Return: cf = set on error Note: - BIOS is not smart enough to wait for the motor to spin up, so there may be a few initial errors upon this condition. Retry 2 or 3 times if needed. A physical error would be apparent after 3 tries. ---------------------- 8< -------- CUT HERE -------- 8< ---------------------- Coding the Boot Sector Before you worry about the whole picture, think about how the code in the bootsector would work. Essentially you can put any code you want in it, but you will want some intiailization or continuation of code in it. For my boot code I setup a system stack (which upon bootup is located in 0000:00ff which isn't good if you push a lot of values :), load any additional sectors off the disk that I'll need (perhaps a mini-filesystem of your own type, or secondary boot code), and perhaps code to switch to something along the lines of flat real mode or protected mode. Beings that you are assured that there are no memory managers or special conditions, changing cpu and memory modes should be a snap if you know what you're doing. If you don't know what you're doing then I don't know why you're reading this. Finally, you would jump to the secondary boot code, which should be already loaded. What I did to create the source and assembled code was to use TASM (or MASM if you were taught by the wrong person) and setup a basic single-segment layout like that of a COM file. The difference is that COM files use a relative origin of 100h, whereas the boot sector is at absolute offset 7c00h. Therefore an 'Org 7c00h' in a requirement, or else memory references and branches will be wrong. To assemble and link, follow this example: tasm -ml -m2 boot.asm tlink -3 -t boot.obj, boot.boo where 'boot.boo' is the actual assembled code without headers. You cannot use a .com extension because that requires an origin of 100h in the code. Additionally tlink does not like using extensions smaller than 3 letters, i.e. boot.b will not work. Putting the Boot Sector on Disk Once you have your code that you want to play with, you gotta get it on a disk and boot it up. Problem is, you have to write another program to write the program to the boot sector (and any other sectors that you want to write). I just wrote a quickie program in Borland C++ 3.1 under Dos to do this task. Actually two programs. One creates a disk image that includes the boot sector code, a secondary boot program, a mini-filesystem, and the files in the filesystem. The second simply writes it to the disk. If you do plan to get into more complicated sector layouts on the disk, you'll soon see that it's a thorn in the ass to manage the sector/track/head values. What I did was to use linear sector numbers and have a function convert the linear to sector/ track/head. Problem solved. For all simplistic purposes, you can start with a simple program to quickly put the boot.boo file onto sector 1, track 0, head 0 and it'll run What I Did I might as well give you a layout of my design to further the academic institution that is known as the demoscene. The sector layout I came up with is as such: linear sector range description ------------ -------------------------- 0 boot sector 1 to x secondary boot code x to x+5 5 sector filesystem x+5 to wherever the files that are in the filesystem graphic layout: ++-- . . . --+---+--- . . . + 01 x x+5 wherever When the computer starts up and executes the boot sector portion, I immediately install a 4k stack in a different part of memory. I then load in the sectors for the secondary boot code and the filesystem. I then install code to initialize flat real mode. I then clear the general variables and jump to 07e0:0000, which is where I put my secondary boot code. The Secondary Boot Code When the boot code does the jump to the secondary code, which I put starting at 07e0:0000. I decided to make many of my routines based on my own real mode interrupt service handler, which I aptly installed at int 20h. The filesystem routines I coded were put in the int 20h handler along with malloc/free functions I wrote for flat real mode. I had already coded a library for watcom that did a trillion functions for text mode, so I converted all of that to work under flat real mode. I did the same for my PCX image decoder. Each section of code is compiled in its own object file, but to ensure proper linking and easy handling I made everything have its own segment name but in the same segment group 'CGROUP'. All in all, I have quite a bit to swim in; however, I never really got my file functions working right :/ Memory Allocation At first coding malloc/free sounds terribly difficult, but once you figure out the structure it's really simple. I think I'll conclude this section by just saying it's a linked list. (Thanks to Dave Cooper for the similar hint back in '94. It only took me 2 years to finally figure out what he meant.) The Filesystem I did not make any provision for file create and writing because I have no provision for a cluster map, nor do I have the immediate need for creating files on the floppy disk. My filesystem is a pretty simple static size array of 64 entries. Each entry only describes a 32-character filename, the size in bytes, and the starting linear sector on the disk. The filesystem routines work hand in hand with malloc/free because when you open a file, it has to malloc space for a file descriptor. Conclusion I think entire concept of coding an operating system from scratch very fascinating. I encourage others to code in this area more so that the demo scene can expand to areas of originality never before thought of. I know there are many coders out there that could really benefit from organizing their own system and coding any way they want without software compatibility problems. Although bootloaders have been done back in the ages of the Atari 8-bit series, and its successor (in architecture anyway) the Amiga, but this is an excellent unexplored platform for coders to show their experience in a new way. Shawn Poulson aka mrdata-- on #coders (irc.another.net) mrdata@interstat.net http://mrdata.interstat.net Other sources: ; x2ftp Archive ftp://x2ftp.oulu.fi/pub/msdos/programming ; Hornet Archive http://www.hornet.org/pub/demos/code ; My coding links page http://mrdata.interstat.net/coding.html