Information wants to be free...

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:

XT-IDE loaded on Commodore PC 50-II

Take note of the 0s near the top, which represents status codes, in this case OK, from sector reads.

Topic: Scripts and Code, by Kjetil @ 08/01-2022, Article Link