XT-IDE Loaded from Fake VBR
Here is a trick I have used to get my Commodore PC 50-II to boot DOS off a unsupported Compact Flash (CF) card. The BIOS on this machine is dated between 1987-1989 and only has a set of hard-coded hard disk types with specific C/H/S values. A common way to get around such limitations is to use the excellent XT-IDE BIOS, which is usually put in a separate option ROM. Instead of using a EPROM I wanted to find a way to load it using software instead.
My software trick uses a fake volume boot record (VBR) to load XT-IDE into RAM. This approach is similar to optromloader which loads ROMs from floppies.
Since the BIOS on this machine has support for hard disks, it will always be able to load the master boot record (MBR) located at the first sector 0/0/1, regardless of C/H/S settings. The VBR (for DOS) is usually stored at the first sector of the second head 0/1/1. Most of the hard disk types available in this BIOS has the "Sector per Track" set to 17, and this is what I will use. The CF card I use has real C/H/S values 1003/16/32. This means that when the MBR initially loads the VBR through the original BIOS int 13h routines it will actually get sector 18 from the CF card, and this is where to store the fake VBR that loads XT-IDE. When the boot sequence is re-initiated after this the MBR will get reloaded, but this time it will use the int 13h routines provided by XT-IDE and get the real DOS VBR from sector 33.
Here is a diagram that shows the layout of the start of the CF card:
Content Sector (Byte Offset) +--------+ 1 (0) | MBR | +--------+ 2 (512) | | | XT-IDE | | ROM | | | +--------+ 18 (8704) | Fake | | VBR | +--------+ 19 (9216) | | | Unused | | Space | | | +--------+ 33 (16384) | Real | | VBR | +--------+ 34 (16896) | | | |
Here is the assembly source code for the fake VBR that loads the XT-IDE ROM:
org 0x7C00 bits 16 cpu 8086 section .text start: ; Setup stack area for boot loader code. cli xor ax, ax mov ss, ax mov sp, 0x7C00 push ax pop ds sti ; Initial values for BIOS disk operations. mov bx, 0x9E00 ; Destination segment. mov es, bx xor bx, bx ; Destination offset. mov cx, 0x0002 ; Sector 2, Cylinder 0. mov dx, 0x0080 ; First harddisk, Head 0. read_again: ; Call BIOS to reset disk system. xor ax, ax int 0x13 ; Push registers to stack before doing BIOS video service. push bx push cx push dx ; Display last BIOS disk operation status. mov dl, cl ; Column = Sector number. sub dl, 2 mov dh, 1 ; Row 1 mov bx, 0x000F mov ah, 0x02 ; Set cursor position. int 0x10 mov cx, 1 mov ah, 0x09 mov al, [int13_status] add al, 0x30 ; Convert to ASCII numerical or higher. and al, 0x7F ; Make sure it is a valid ASCII value. int 0x10 ; Pop registers from stack after doing BIOS video service. pop dx pop cx pop bx ; Call BIOS to read 1 sector. mov ax, 0x0201 int 0x13 mov [int13_status], ah jb read_again ; Increment destination memory and sector number. add bh, 2 inc cl cmp cl, 18 ; Stop at 16 sectors (8KB). jne read_again ; Call BIOS to get memory size. int 0x12 sub ax, 8 ; Subtract 8K blocks... mov [0x413], ax ; ...and store new memory size in BDA at 0x40:0x0013 call 0x9E00:0003 ; Call the option ROM. int 0x19 ; Call BIOS to initiate boot process (again). int13_status: db 0
To put this at the correct location on the CF card a utility coded in C is provided:
#include <stdlib.h> #include <stdio.h> int main(int argc, char *argv[]) { FILE *fh_vbr = NULL; FILE *fh_optrom = NULL; FILE *fh_disk = NULL; int c, n, vbr_sector; if (argc != 5) { printf("Usage: %s <vbr> <optrom> <disk> <vbr-sector>\n", argv[0]); return 1; } fh_vbr = fopen(argv[1], "rb"); if (NULL == fh_vbr) { printf("Error: Unable to open '%s'\n", argv[1]); goto end; } fh_optrom = fopen(argv[2], "rb"); if (NULL == fh_optrom) { printf("Error: Unable to open '%s'\n", argv[2]); goto end; } fh_disk = fopen(argv[3], "r+b"); if (NULL == fh_disk) { printf("Error: Unable to open '%s'\n", argv[3]); goto end; } vbr_sector = atoi(argv[4]); if (0 == vbr_sector) { printf("Error: Invalid VBR sector: %s\n", argv[4]); goto end; } /* Write option ROM, after MBR on sector 2 (1-indexed) onwards. */ fseek(fh_disk, 512, SEEK_SET); while ((c = fgetc(fh_optrom)) != EOF) { fputc(c, fh_disk); } /* Write fake VBR on selected sector (1-indexed). */ n = 0; fseek(fh_disk, 512 * (vbr_sector - 1), SEEK_SET); while ((c = fgetc(fh_vbr)) != EOF) { fputc(c, fh_disk); n++; } /* Pad rest of the fake VBR area with zeroes... */ for (; n < 0x1FE; n++) { fputc('\x00', fh_disk); } /* ...and finish with the correct signature. */ fputc('\x55', fh_disk); fputc('\xAA', fh_disk); end: if (NULL != fh_vbr) fclose(fh_vbr); if (NULL != fh_optrom) fclose(fh_optrom); if (NULL != fh_disk) fclose(fh_disk); return 0; }
Both can be assembled/compiled with this Makefile:
all: vbr.bin modify-disk vbr.bin: vbr.asm nasm vbr.asm -fbin -o vbr.bin modify-disk: modify-disk.c gcc -o modify-disk -Wall -Wextra modify-disk.c .PHONY: clean clean: rm -f vbr.bin modify-disk
These tools are meant to be used with a XT-IDE ROM of 8KB in size. I should also mention that I did not get this machine to boot with the 386 version, this always resulted in "Boot sector not found" errors. Instead I have used the "ide_at.bin" version.
Once assembled and compiled the tools are typically used as such, where /dev/sdX is the CF card:
./modify-disk vbr.bin ide_at.bin /dev/sdX 18
A "screenshot" of what it should look like when booting:
Take note of the 0s near the top, which represents status codes, in this case OK, from sector reads.