; this is a tentative reconstruction of the boot sector from my SOL ; CP/M boot disk. this second-level boot program is read into memory ; at $9C00 to $9DFF by the PROM on-board the disk controller. ; ; this boot program reads in these sectors to the following addresses: ; trk 0 sec 5 $9E00 to $9FFF ; trk 0 sec 6 $A000 to $A1FF ; trk 0 sec 7 $A200 to $A3FF ; trk 0 sec 8 $A400 to $A5FF ; trk 0 sec 9 $A600 to $A7FF ; trk 1 sec 0 $A800 to $A9FF ; trk 1 sec 1 $AA00 to $ABFF ; trk 1 sec 2 $AC00 to $ADFF ; trk 1 sec 3 $AE00 to $AFFF ; trk 1 sec 4 $B000 to $B1FF ; trk 1 sec 5 $B200 to $B3FF ; trk 1 sec 6 $B400 to $B5FF ; trk 1 sec 7 $B600 to $B7FF ; trk 1 sec 8 $B800 to $B9FF ; trk 1 sec 9 $BA00 to $BBFF ; ; assuming all programs are read in successfully, it then ; relocates the code if it needs to be, then jumps to some ; magic entry address. ; disk controller memory mapped registers: DISKDATA EQU 0EB40H ; unconfirmed but strong guess ; CP/M services: BDOS EQU 05H ; CP/M bdos entry point CONOUT EQU 02H ; console output of E RESETDISK EQU 0DH ; default: drive=A, DMA is $0080, R/W access SELECTDISK EQU 0EH ; select default disk for subsequent ops OPENFILE EQU 0FH ; open disk file: A=$FF on error CLOSEFILE EQU 10H ; close file (de): A=$FF on error SRCHFIRST EQU 11H ; find file that matches FCB: A=$FF on error SRCHNEXT EQU 12H ; next file that matches FCB: A=$FF on error DELETEFILE EQU 13H ; delete file FCB: A=$FF on error READSEQ EQU 14H ; read sequential next 128B: A=0 means OK STACKTOP EQU 9C00H ORG 9C00H ; the next four bytes, $59,$59,$59,$01 were jammed here by the boot ; ROM on the disk controller and aren't what is on the disk itself. ; The disk I have actually contains the following bytes: ; $9C, $AF, $21, $F7 ; 00: 9C: specifies which page to use for the boot loader. ; 01: AF: XRA A ; 02: 21 F7 B9: LXI H,$B9F7 ; 05: 36 44: MVI M,$44 ; I don't know if this is actually how they are used. I did ; a DU B9F7 and it contains $43. hmm. MOV E,C ; $59 \ MOV E,C ; $59 >-- put here by bootrom. why? MOV E,C ; $59 / LXI B,36B9H ; even the LD BC opcode, 01, came from bootrom MOV B,H JMP DORELOC ; on entry, HL is the same as the address of "entry" (since we came ; here via a "jp (hl)" instruction). de is $EB40. ENTRY: DI ; why didn't boot rom do this? LXI SP,STACKTOP ; just below the start of this code XCHG ; HL <-- $EB40, the disk read data address SHLD DDVEC ; save address of read data port MOV A,H STA DDPAGE ; top 8b of DDVEC contents ; read track 0 sectors 5-9 into $9E00 to $A7FF. ; (actually, this code assumes we're already on track 0.) LXI H,9E00H ; just after this 512B boot loader LXI B,0505H ; B is counter, C is sector pointer RDTRK0: PUSH H PUSH B CALL RDSECTOR ; read 512B sector C to memory addr HL JNZ FAIL POP B POP H INR H ; bump up ... INR H ; ... 512 bytes INR C ; point to next sector DCR B JNZ RDTRK0 ; step the read head to track 1 PUSH H ; $A800 LHLD DDVEC ; $EB40 DCR H MVI L,21H ; $EA21, prepare step in drive 1 (A) MOV A,M MVI L,31H ; $EA31, actually step in drive 1 (A) MOV A,M MVI L,21H ; $EA21, prepare step in drive 1 (A) MOV A,M MVI D,02H CALL SECD ; wait for 2 sectors to go by ; read track 1, sectors 0-9 into $A800 to $BBFF. POP H ; $A800 LXI B,0A00H ; B is counter, C is sector pointer RDTRK1: PUSH H PUSH B CALL RDSECTOR ; read 512B sector C to memory addr HL JNZ FAIL POP B POP H INR H ; bump up ... INR H ; ...512 bytes INR C ; point to next sector DCR B JNZ RDTRK1 MVI A,01H ; make sure flag is set for unknown reason DORELOC: ORA A CALL RELOCATE LDA DDPAGE ; $EB SUI 03H ; $E8 STA 0B9F5H ; base address of disk controller board NOP JMP 0B300H ; on to bios cold boot ; read sector C of current track into memory HL. RDSECTOR: PUSH H ; save address we want to save sector to W84SEC: CALL NXTSEC MVI L,35H ; $EB35 MOV A,M ; read C-status, turn on drive motors ANI 0FH ; isolate sector counter field CMP C JNZ W84SEC ; we haven't found the sector we want yet LHLD DDVEC MVI L,10H ; $EB10 W84RE: MOV A,M ; read A-status, disk 1 ANI 04H ; test RE (read enable) bit JZ W84RE ; no phase-lock yet ; kill a few microseconds MVI A,09H ; [ 7] DLAY: DCR A ; [ 4] JNZ DLAY ; [10] MVI B,8CH ; 140 tries LHLD DDVEC MVI L,10H ; $EB10 W84BD: MOV A,M ; read A-status, disk 1 RRC ; get BD (body) bit into carry flag JC READIT ; jump if we've seen sector sync byte(s) DCR B JNZ W84BD ; try again ; we come here when the read has failed, eg bad sector checksum. ; it might make a hint of sense if we instead jumped to $E800 ; instead of $EB00. FAIL: LDA DDPAGE ; $EB MOV H,A MVI L,00H ; $EB00 PCHL ; jump to $EB00. nonsensical. ; read in the 512 bytes of the sector READIT: LHLD DDVEC ; $EB40 XCHG ; DE=$EB40 POP H ; HL=address to store sector to MVI B,00H ; init sector checksum MVI L,00H ; forced to be page aligned ; read in first half of 512B sector PAGE1: LDAX D ; get next data byte from disk MOV M,A ; save it XRA B ; \ RLC ; >-- checksum MOV B,A ; / INR L JNZ PAGE1 ; read in second half of 512B sector INR H ; save to next page PAGE2: LDAX D MOV M,A XRA B ; \ RLC ; >-- checksum MOV B,A ; / INR L JNZ PAGE2 LDAX D ; get stored sector checksum XRA B ; compare to computed checksum JNZ FAIL ; death! RET ; wait for next sector NXTSEC: MVI D,01H ; wait for D'th sector SECD: LHLD DDVEC MVI L,11H ; $EB11 MOV A,M ; read status-A, reset sector flag LHLD DDVEC MVI L,10H ; $EB10 W84SF: MOV A,M ORA A JP W84SF ; poll until SF (sector flag) is seen DCR D LHLD DDVEC MVI L,11H ; $EB11 MOV A,M ; read status-A, reset sector flag JNZ SECD ; loop until D=00 RET ; This routine makes the driver independent of the controller base address. ; The BIOS contains a table of address that reference the disk controller ; absolutely. By scanning the table and adding an offset to all indicated ; bytes, the hardcoded address references are fixed up. ; ; The code as written assumes a base address of $EB00 for the disk control ; registers. If it isn't $EB00, the relocation processes occurs. The ; table consists of a list of (lsb,msb) words. Each entry supplies a ; byte in the program that needs a relative offset added to it to fix ; the code. The table is terminated by an entry of $0000. ; ; The boot address of the board is actually E800, but EB00 just happens ; to be the value pointed to by the DE pair on entry to the boot program. RELOCATE: LDA DDPAGE ; $EB SUI 0EBH ; build-time assumption is EBxxH RZ ; code is OK as-is MOV C,A ; C=relative page offset from $EBxx LXI H,0A811H ; relocation table embedded in code RELOC: MOV E,M INX H MOV D,M MOV A,M ; MOV A,D would have been faster ORA E RZ ; return if de=0000H LDAX D ; pick up absolute byte ADD C ; add relative page offset STAX D ; write it back with new address INX H JMP RELOC DDVEC: DW DISKDATA ; (DS 2) pointer to disk controller address DB 0,0,0,0,0,0,0,0,0,0,0 ; 11 bytes unused DDPAGE: DB 0EBH ; (DS 1) ms 8b of ddvec contents ; I'm not sure why the program doesn't just access ; like this: "ld a,(ddvec+1)", but it doesn't ; JMP 0A05CH ; 9D00 : C3 5C A0 " \ " JMP LA05C JMP 0A058H ; 9D03 : C3 58 A0 " X " JMP LA058 ; MOV A,A ; 9D06: 7F INR B ; 9D07: 04 ; interestingly, 4+1 + 49+1 + 73 = 128 AUTOSTR: ; unused by this code DB 'AUTO' DB 0 ; string terminator COPYRIGHTSTR: ; unused by this code DB ' COPYRIGHT (C) 1979, DIGITAL RESEARCH ' DB 0 ; string terminator DS 73 ; unused DW AUTOSTR ; unused by this code DW 0 ; send the contents of A out on the console BC may be affected (unused) OUTA: MOV E,A MVI C,CONOUT JMP BDOS ; like outA, except BC is preserved (unused) PRINTA: PUSH B CALL OUTA POP B RET ; print carriage return and line feed characters (unused) OUTCRLF: MVI A,0DH ; carriage return CALL PRINTA MVI A,0AH ; line feed JMP PRINTA ; print a space character on output (unused) PRTPC: MVI A,' ' ; space JMP PRINTA ; print CR/LF and then a string pointed at by BC (unused) PRTNSTR: PUSH B CALL OUTCRLF POP H ; send the null-terminated string at (hl) to the console output (unused) PRSTRZ: MOV A,M ORA A ; test for null char RZ ; done INX H PUSH H CALL OUTA ; print next char POP H JMP PRSTRZ ; unused routine MVI C,RESETDISK JMP BDOS ; unused routine MOV E,A MVI C,SELECTDISK JMP BDOS ; perform bdos call and save return code DOBDOS: CALL BDOS STA 0A4EEH INR A RET ; DOOPEN: MVI C,OPENFILE JMP DOBDOS ; unused routine XRA A STA 0A4EDH LXI D,0A4CDH JMP DOOPEN ; MVI C,CLOSEFILE JMP DOBDOS ; DOSRCH: MVI C,SRCHFIRST JMP DOBDOS ; unused routine MVI C,SRCHNEXT JMP DOBDOS ; unused routine LXI D,0A4CDH JMP DOSRCH ; unused routine MVI C,DELETEFILE JMP BDOS ; BDSTAT: CALL BDOS ORA A ; set flags on return code RET ; unused routine MVI C,READSEQ ; read next 128B of file JMP BDSTAT ; return with zflag=1 if OK, else EOF ; DB 11H ; also "ld de,$xxCD" DB 0CDH