PURPOSE

Being able to boot a 16-bit real mode PC without any working drives using the serial port.

The MAX_NUM_DRIVES define in config.h controls the number of floppy drives.

serial_server loads file images as floppy drives.

dosa360.img appears as A:\>
dosb360.img appears as B:\> (if defined)
dosc360.img appears as C:\> (if defined)
dosc360.img appears as D:\> (if defined)


WORKFLOW

Having the computer that will boot into some sort of user input when there is no floppy disk is required.

The Model 5150 and IBM PCjr would default to Cassette BASIC.

The Zenith Z-151 would enter a machine monitor (MFM-150) similar to debug.com.

The user would then have to manually type the bytes of a small stub load (about 128 bytes) to get the process started.

The stub would load a larger boot loader over the serial port, hook DOS interrupt 0x13, then would proceed to load from a disk image provided by a server from the serial port.


ARCHITECTURE

The file server was written in C for linux and has been tested with a 486sx/33MHz and an ARM chromeboook running Debian with a USB serial port.

The program has only been tested with patient zero, a Zenith Z-151 with an 8088 running at 4.77 MHz, with a malfunctioning floppy drive controller.

ecm: Testing is done using a recent dosemu2 revision (as of 2026 October), on an amd64 server, connected to one end of a socat vptty, with the server connected to the other end.


Makefile
  Builds all of the necessary projects files

config.h
  Centralized configuration of serial port parameters

gen_config.py
  Script to generate a NASM config.inc from config.h

micro_stub.asm
  The hand-typed program needed to start the boot process.

serial_port_boot_sector.asm
  The stage 2 boot loader, streamed over the serial port, which relocates the top of memory, hooks int 0x13, loads the disk image from the server, then jumps to 0000h:7C00h to start the boot process.

serial_server.c
  The file server which responds to sector requests over the serial port and sends data.


MANUAL ENTRY

The raw bytes of the stub can be obtained a number of ways. Note that the default configuration, as depicted here, is configured to use COM1 (base port 03F8h), 57600 Baud, and 8N1 as line parameters.

For example,

$ hexdump -e '"%04.4_ax  " 16/1 "%02X " "\n"' bootstub.bin
0000  FA BA FB 03 B0 80 EE B2 F8 B0 02 EE 42 B0 00 EE
0010  42 42 B0 03 EE FC E8 08 00 C0 00 FE 00 00 7C 93
0020  C0 5E B9 08 00 BA FD 03 EC A8 20 74 FB 2E AC B2
0030  F8 EE E2 F1 8E C1 BF 00 7C 06 57 0E E8 11 00 E8
0040  18 00 E8 15 00 88 C5 E8 14 00 AA E2 FA E8 0A 00
0050  B4 01 E8 0B 00 3C C0 75 F7 CB E8 01 00 91 B4 00
0060  BA FD 03 EC A8 01 74 FB B2 F8 EC 80 EC 01 74 0F
0070  77 05 3C DB 74 EA C3 3C DC B0 C0 74 02 B0 DB C3

The bytes then have to be manually entered.

For a debug.com style entry, this can work:

e 1000:0000  FA BA FB 03 B0 80 EE B2 F8 B0 02 EE 42 B0 00 EE
e 1000:0010  42 42 B0 03 EE FC E8 08 00 C0 00 FE 00 00 7C 93
e 1000:0020  C0 5E B9 08 00 BA FD 03 EC A8 20 74 FB 2E AC B2
e 1000:0030  F8 EE E2 F1 8E C1 BF 00 7C 06 57 0E E8 11 00 E8
e 1000:0040  18 00 E8 15 00 88 C5 E8 14 00 AA E2 FA E8 0A 00
e 1000:0050  B4 01 E8 0B 00 3C C0 75 F7 CB E8 01 00 91 B4 00
e 1000:0060  BA FD 03 EC A8 01 74 FB B2 F8 EC 80 EC 01 74 0F
e 1000:0070  77 05 3C DB 74 EA C3 3C DC B0 C0 74 02 B0 DB C3
g =1000:0000


For BASIC entry, this will get the program into memory:

10 REM --- Serial Bootloader Stub (1000:0000) ---
20 DEF SEG = &H1000
30 FOR I = 0 TO &H7F
40 READ A$: POKE I, VAL("&H" + A$)
50 NEXT I
60 CALL &H0000
70 DATA FA,BA,FB,03,B0,80,EE,B2,F8,B0,02,EE,42,B0,00,EE
80 DATA 42,42,B0,03,EE,FC,E8,08,00,C0,00,FE,00,00,7C,93
90 DATA C0,5E,B9,08,00,BA,FD,03,EC,A8,20,74,FB,2E,AC,B2
100 DATA F8,EE,E2,F1,8E,C1,BF,00,7C,06,57,0E,E8,11,00,E8
110 DATA 18,00,E8,15,00,88,C5,E8,14,00,AA,E2,FA,E8,0A,00
120 DATA B4,01,E8,0B,00,3C,C0,75,F7,CB,E8,01,00,91,B4,00
130 DATA BA,FD,03,EC,A8,01,74,FB,B2,F8,EC,80,EC,01,74,0F
140 DATA 77,05,3C,DB,74,EA,C3,3C,DC,B0,C0,74,02,B0,DB,C3

To run the program:

DEF SEG = &H1000
CALL &H0000

GWBASIC seems to run the following:

GOTO 10
DEF SEG = &H1000
SERBOOT = &H0000
CALL SERBOOT


PROCEDURE

0. Set up the serial port, eg using socat (may need to rerun this to clear stale data)
1. Start the server with ./serial_server -s /PATH/TO/SERIAL-PORT-FILE
2. Manually enter the stub then run it.
3. The server should send the transient image (maximum 32 KiB) which runs and boots drive A: if everything works.

(Note that in this fork by ecm, the server should be started before running the entered stub.)

To swap disks:

1. Hit Ctrl+C to kill the server
2. Copy a disk image file over dosa360.img, or dosb360.img, or...
3. Restart the server with the same ./serial_server ... command as initially.

(Note that the amount of configured disks is only detected during boot load of the resident extension, so the server should not be restarted with fewer disk images than it had during boot load.)


dosemu2 2026 October example, in three terminals, with ldebug.com placed as kernel.sys or rxdos.com in C:\ directory:

socat pty,link=/tmp/vptty-dos-2,rawer pty,link=/tmp/vptty-linux-2,rawer

./serial_server -s /tmp/vptty-linux-2 -d 1

dosemu -I "serial { com 1 device /tmp/vptty-dos-2 }" \
  :FDCMDLINE="boot protocol freedos segment 1000 minpara 2 bootstub.bin; if (rc == 0) then q"


To create empty disk images:

dd if=/dev/zero of=disk.img bs=1024 count=360

To format the newly created image:

/usr/sbin/mkfs.vfat -F 12 -I disk.img

(May be at /sbin/mkfs.vfat instead.)

To copy files to the images:

mcopy -i disk.img myfile.txt ::

To generate an image using the lDOS bootimg script:

nasm ~/proj/ldosboot/boot.asm -I ~/proj/lmacros/ -o boot12.bin
nasm ~/proj/bootimg/bootimg.asm -I ~/proj/lmacros/ \
  -D_BOOTPATCHFILE="'boot12.bin'" \
  -D_SPI=720 -D_SPF=2 -D_SPC=2 -D_NUMROOT=0x70 \
  -D_MEDIAID=0FDh -D_CHS_SECTORS=9 \
  -D_PAYLOADFILE=drive_c/ldos.com,drive_c/command.com,drive_c/quit.com,bin/ldebug.com \
  -o dosa360.img


LICENSE

MIT license, see the LICENSE file
