base equ 0x7C00 org base ; boot sector identification bytes: jmp short init ; EB3C nop ; 90 ; OEM vendor field vendor .space 8, 0x20 ; bios parameter block ss .space 2 ; word bytes per sector sc .space 1 ; byte sectors per cluster rsc .space 2 ; word reserved sector count fn .space 1 ; byte number of fat tables rde .space 2 ; word root disk entries ts .space 2 ; word total sectors md .space 1 ; byte media descriptor byte fs .space 2 ; word sectors per fat spt .space 2 ; word sectors per track nos .space 2 ; word number of sides/heads hs .space 4 ; dword partition offset lts .space 4 ; dword large total sectors ; extended bios parameter block data .space base+03Eh-$ init xor ax, ax ; ax as quick zero value ; initialize all segment registers ; the docs say we cant rely on them to have any sensible values ; we only work with the first 64kB per default ; so we set all of them to zero mov ds, ax mov es, ax mov ss, ax mov sp, ax jmp main ; print AX in base BX ; destroys DX putnum xor dx, dx div bx test cx, cx loopnz .pad test ax, ax je .nib .pad push dx call putnum pop dx ; print lower DL as digit .nib add dl, 0x30 cmp dl, 0x3a jl putc add dl, 7 ; print DL as char putc mov ah, 0x0e mov al, dl int 0x10 ret ; print str at SI ; NUL-terminated puts lodsb test al, al jz .l01 mov dl, al call putc jmp puts .l01 ret ; print a newline newlin mov dl, 0x0A call putc mov dl, 0x0D jmp putc ; print register set ; order AX CX DX BX BP SI DI IP ; trashes flags debug push di push si push bp push bx push dx push cx push ax mov si, sp call dump pop ax pop cx pop dx pop bx pop bp pop si pop di ret ; dump the 8 words at SI dump lea di, [si+16] mov bx, 0x10 .loop lodsw mov cx, 4 call putnum mov dl, 020h ; space char call putc cmp si, di jc .loop jmp newlin ; Advantages over using int13h directly: ; - Translation from linear sector number into CHS data ; - Reads across different tracks. ; Older BIOSes don't support that. ; - Nearby sector reads/writes are coalesced into one int 13h call ; This improves performance on real hardware. ; Only floppies are supported, and only variants with two sides ; This includes the standard 5 1/4 360kB and 3 1/2 1.44MB formats ; When a sector is requested, int13h is not called yet, the parameters ; for int 13h are recorded. When the next sector is requested, either ; it it suitable for coalescing, then the recorded parameters are ; adjusted. When the sector is not suitable for coalescing, the ; previous sector is read/written and the parameters for the new ; sector are put into the record. If the program is done with listing ; the wanted sectors, another function can be called to force doing ; the recorded io request ; Select a sector for a disk operation ; IN ax linear sector number ; cx number of sectors to read/write ; dl drive number sector push ax push cx ; split up sector number into CHS data ; divisor is sectors per track field in bios parameter block ; quotient al as track number ; remainder ah as sector number (starting with 0) div byte [spt] ; phys sector number cl (starting with 1) mov cl, ah inc cl ; get head number dh from lowest bit of track number ; hard-coded assumption: two-sided floppy mov dh, al and dh, 1 ; cylinder number ch is track number, except the head number shifted out mov ch, al shr ch, 1 ; Unlucky if different head (dh) cmp byte dh, [disk_dh] jne .miss ; Unlucky if different track (ch) or not subsequent sector (cl + number of op sectors) mov ax, cx sub al, [disk_al] cmp ax, [disk_cx] jne .miss ; We are lucky! We can extend the current request by increasing the number of op sectors ; Do nothing else inc byte [disk_al] jmp .end ; The sector we just selected does not fit into the recorded io request ; So we submit that request first and then record a new io request .miss: push cx push dx call diskio pop dx pop cx ; Record new io request mov word [disk_cx], cx mov byte [disk_dh], dh mov byte [disk_al], 1 ; only one sector yet .end pop cx pop ax inc ax loop sector ret ; Do the io request we recorded previously now diskio mov ax, [disk_ax] mov cx, [disk_cx] mov dx, [disk_dx] mov bx, [disk_bx] ; no-op if amount of sectors is zero cmp al, 0 je .ret ; TODO: retry is needed for real floppies call debug int 0x13 jc .ret ; Advance read/write pointer in memory mov ah, 0 mov cl, 9 shl ax, cl add word [disk_bx], ax ; Reset op sector length, work done mov byte [disk_al], 0 .ret ret ; Load a file from disk to 0x7C00 ; calculate size of the directory in sectors load: mov ax, [rde] mov cl, 4 ; sector=512b, entry=32b -> shift 4 bits shr ax, cl mov cx, ax push cx ; remember number of dir entries for later ; calculate start of second fat table mov ax, [fs] ; read fat table length push ax add cx, ax ; make cx size of FAT + dir inc ax ; add one for the boot sector ; Read second FAT and directory into buffer mov byte [disk_ah], 2 ; set read OP mov word [disk_bx], fatbuf ; write into our own buffer call sector call diskio ; get address of root directory in frame pop bx mov cl, 9 shl bx, cl add bx, fatbuf pop dx mov word [disk_bx], 0x7C00 ; search through root directory .loop: mov si, bx mov ax, [bx+0x1A] mov di, fname mov cx, 11 repe cmpsb je .cloop add bx, 0x20 or byte [bx], 0 jz .end jmp .loop ; calculate sector offset in cluster area .cloop push ax sub ax, 2 mov ch, 0 mov cl, [sc] push cx mul cx ; calculate sector offset from root directory mov dx, [rde] mov cl, 4 ; sector=512b, entry=32b -> shift 4 bits shr dx, cl add ax, dx ; add length of fat tables add ax, [fs] add ax, [fs] ; plus 1 for the boot sector inc ax ; load cluster pop cx call sector ; follow fat chain pop ax mov si, ax shr si, 1 add si, ax mov dx, [fatbuf+si] ; shift value if needed test ax, 1 jz .noshr mov cl, 4 shr dx, cl .noshr and dx, 0x0FFF mov ax, dx cmp ax, 0xFF0 jc .cloop .fend: call diskio .end: ret main: mov byte [disk_dl], dl call load db 0xEA ; jmp far 0:0x7C00 dw 0x7C00, 0 ; register buffer for disk io disk_al db 0 ; number of sectors to read disk_ah db 2 ; 2=read op disk_cl db 1 ; sector number disk_ch db 1 ; cylinder number disk_dl db 0 ; drive number disk_dh db 0 ; head number disk_bx dw fatbuf ; data buffer addr, advanced by 'diskio' disk_ax equ disk_al disk_cx equ disk_cl disk_dx equ disk_dl fname db "@RDOS ", "COM" .space base+01FEh-$ dw 0xAA55 ; buffer for fat table and root directory ; we load it together to save an int 13h read ; at the cost of having to calculate offset of rootdir fatbuf equ $