IFDEF DEBUG TEXT SEGMENT WORD PUBLIC 'TEXT' TEXT ENDS LOAD SEGMENT PARA PUBLIC 'LOAD' LOAD ENDS TEXT SEGMENT WORD PUBLIC 'TEXT' ASSUME CS:TEXT, DS:TEXT LOAD SEGMENT PARA PUBLIC 'LOAD' org 0 loadBuffer dw 32767 dup (?) LOAD ENDS ELSE TEXT SEGMENT WORD PUBLIC 'TEXT' ASSUME CS:TEXT, DS:TEXT ENDIF IFDEF DEBUG mov dl, BOOTDRIVE jmp Entry BASE equ 7c00h ELSE BASE equ 0 ENDIF org BASE Entry: jmp real_start ; bp is initialized to 7c00h oem equ [bp+3] bytesPerSector equ [bp+0bh] sectPerCluster equ [bp+0dh] resSectors equ [bp+0eh] nFats equ [bp+10h] nRootDir equ [bp+11h] nSectors equ [bp+13h] MID equ [bp+15h] sectPerFat equ [bp+16h] sectPerTrack equ [bp+18h] nHeads equ [bp+1ah] nHidden equ [bp+1ch] nSectorHuge equ [bp+20h] drive equ [bp+24h] extBoot equ [bp+26h] volid equ [bp+27h] vollabel equ [bp+2bh] filesys equ [bp+36h] ;IFDEF DEBUG db 'FreeDOS ' dw 512 ; bytes/sector db 1 ; sectors/allocation unit dw 1 ; # reserved sectors db 2 ; # of fats dw 224 ; # of root directories dw 2880 ; # sectors total in image db 0f0h ; media descrip: fd=2side9sec, etc... dw 9 ; # sectors in a fat dw 18 ; # sectors/track dw 2 ; # heads dd 0 ; # hidden sectors dd 0 ; # sectors if > 65536 db 00h ; drive number db 00h db 29h ; extended boot signature dd 0 db 'DOS-C BOOT ' db 'FAT12 ' ;LOADSEG equ seg LOAD ;ELSE LOADSEG equ 2000h ;ENDIF FATBUF equ 4000h ; offset of temporary buffer for FAT ; chain RETRYCOUNT equ 5 ; number of retries on disk errors ; Some extra variables that are created on the stack frame fat_start equ [bp-4] ; first FAT sector root_dir_start equ [bp-8] ; first root directory sector data_start equ [bp-12] ; first data sector ; To save space, functions that are just called once are ; implemented as macros instead. Four bytes are saved by ; avoiding the call / ret instructions. ; FINDFILE: Searches for the file in the root directory. ; ; Returns: ; If file not found: CF set ; If file found: CF clear ; AX = first cluster of file FINDFILE MACRO ; First, read the whole root directory ; into the temporary buffer. mov ax, word ptr root_dir_start mov dx, word ptr root_dir_start+2 mov di, nRootDir xor bx, bx mov es, tempbuf call readDisk jc ffDone xor di, di next_entry: mov cx, 11 mov si, offset filename+7c00h push di repe cmpsb pop di mov ax, es:[di][1ah] ; get cluster number from directory entry clc je ffDone add di, 20h ; go to next directory entry cmp byte ptr es:[di], 0 ; if the first byte of the name is 0, jnz next_entry ; there is no more files in the directory stc ffDone: ENDM ; GETDRIVEPARMS: Calculate start of some disk areas. GETDRIVEPARMS MACRO mov si, word ptr nHidden mov di, word ptr nHidden+2 add si, word ptr resSectors adc di, 0 ; DI:SI = first FAT sector mov word ptr fat_start, si mov word ptr fat_start+2, di mov al, nFats xor ah, ah mul word ptr sectPerFat ; DX:AX = total number of FAT sectors add si, ax adc di, dx ; DI:SI = first root directory sector mov word ptr root_dir_start, si mov word ptr root_dir_start+2, di ; Calculate how many sectors the root directory occupies. mov bx, bytesPerSector mov cl, 5 ; divide BX by 32 shr bx, cl ; BX = directory entries per sector mov ax, nRootDir xor dx, dx div bx mov nRootDir, ax ; AX = sectors per root directory add si, ax adc di, 0 ; DI:SI = first data sector mov data_start, si mov data_start+2, di ENDM ; GETFATCHAIN: ; ; Reads the FAT chain and stores it in a temporary buffer in the first ; 64 kb. The FAT chain is stored an array of 16-bit cluster numbers, ; ending with 0. ; ; The file must fit in conventional memory, so it can't be larger than ; 640 kb. The sector size must be at least 512 bytes, so the FAT chain ; can't be larger than around 3 kb. ; ; Call with: AX = first cluster in chain ; ; Returns: CF clear on success, set on error GETFATCHAIN MACRO push ax ; store first cluster number ; Load the complete FAT into memory. The FAT can't be larger ; than 128 kb, so it should fit in the temporary buffer. mov es, tempbuf xor bx, bx mov di, sectPerFat mov ax, word ptr fat_start mov dx, word ptr fat_start+2 call readDisk pop ax ; restore first cluster number jc boot_error ; Set ES:DI to the temporary storage for the FAT chain. mov di, FATBUF next_clust: stosw ; store cluster number mov si, ax ; SI = cluster number cmp byte ptr extBoot, 29h jne fat_12 cmp byte ptr filesys[4], '6' ; check for FAT-16 system je fat_16 ; This is a FAT-12 disk. fat_12: add si, si ; multiply cluster number by 3... add si, ax shr si, 1 ; ...and divide by 2 lodsw ; If the cluster number was even, the cluster value is now in ; bits 0-11 of AX. If the cluster number was odd, the cluster ; value is in bits 4-15, and must be shifted right 4 bits. If ; the number was odd, CF was set in the last shift instruction. jnc fat_even mov cl, 4 shr ax, cl ; shift the cluster number fat_even: and ah, 0fh ; mask off the highest 4 bits cmp ax, 0fffh ; check for EOF jmp short next_test ; This is a FAT-16 disk. The maximal size of a 16-bit FAT ; is 128 kb, so it may not fit within a single 64 kb segment. fat_16: mov dx, tempbuf add si, si ; multiply cluster number by two jnc first_half ; if overflow... add dh, 10h ; ...add 64 kb to segment value first_half: mov ds, dx ; DS:SI = pointer to next cluster lodsw ; AX = next cluster cmp ax, 0fff8h ; >= FFF8 = 16-bit EOF next_test: jb next_clust ; continue if not EOF finished: ; Mark end of FAT chain with 0, so we have a single ; EOF marker for both FAT-12 and FAT-16 systems. xor ax, ax stosw fatError: ENDM ; loadFile: Loads the file into memory, one cluster at a time. loadFile MACRO mov es, tempbuf ; set ES:BX to load address xor bx, bx mov si, FATBUF ; set DS:SI to the FAT chain push cs pop ds next_cluster: lodsw ; AX = next cluster to read or ax, ax ; if EOF... je boot_success ; ...boot was successful dec ax ; cluster numbers start with 2 dec ax mov di, word ptr sectPerCluster and di, 0ffh ; DI = sectors per cluster mul di add ax, data_start adc dx, data_start+2 ; DX:AX = first sector to read call readDisk jnc next_cluster ENDM org BASE+3eh tempbuf equ [bp+3eh] load_seg dw LOADSEG real_start: cli cld mov ax, cs mov ss, ax ; initialize stack mov bp, 7c00h lea sp, [bp-20h] sti mov es, ax mov ds, ax mov drive, dl ; BIOS passes drive number in DL GETDRIVEPARMS FINDFILE ; locate file in root directory jc boot_error ; fail if not found GETFATCHAIN ; read FAT chain LOADFILE ; load file (jumps to boot_sucess if successful) boot_error: mov cx, ERRMSGLEN mov si, offset errmsg+7c00h next_char: lodsb ; print error message mov ah, 0eh xor bh, bh int 10h loop next_char xor ah, ah int 16h ; wait for keystroke int 19h ; invoke bootstrap loader boot_success: mov dl, drive db 0eah ; far jump to LOADSEG:0000 dw 0 dw LOADSEG ; readDisk: Reads a number of sectors into memory. ; ; Call with: DX:AX = 32-bit DOS sector number ; DI = number of sectors to read ; ES:BX = destination buffer ; ES must be 64k aligned (1000h, 2000h etc). ; ; Returns: CF set on error ; ES:BX points one byte after the last byte read. readDisk proc push si read_next: push dx push ax ; ; translate sector number to BIOS parameters ; ; ; abs = sector offset in track ; + head * sectPerTrack offset in cylinder ; + track * sectPerTrack * nHeads offset in platter ; ; t1 = abs / sectPerTrack (ax has t1) ; sector = abs mod sectPerTrack (cx has sector) ; div word ptr sectPerTrack mov cx, dx ; ; t1 = head + track * nHeads ; ; track = t1 / nHeads (ax has track) ; head = t1 mod nHeads (dl has head) ; xor dx, dx div word ptr nHeads ; the following manipulations are necessary in order to ; properly place parameters into registers. ; ch = cylinder number low 8 bits ; cl = 7-6: cylinder high two bits ; 5-0: sector mov dh, dl ; save head into dh for bios ror ah, 1 ; move track high bits into ror ah, 1 ; bits 7-6 (assumes top = 0) xchg al, ah ; swap for later mov dl, byte ptr sectPerTrack sub dl, cl inc cl ; sector offset from 1 or cx, ax ; merge cylinder into sector mov al, dl ; al has # of sectors left ; Calculate how many sectors can be transfered in this read ; due to dma boundary conditions. push dx mov si, di ; temp register save ; this computes remaining bytes because of modulo 65536 ; nature of dma boundary condition mov ax, bx ; get offset pointer neg ax ; and convert to bytes jz ax_min_1 ; started at seg:0, skip ahead xor dx, dx ; convert to sectors div word ptr bytesPerSector cmp ax, di ; check remainder vs. asked jb ax_min_1 ; less, skip ahead mov si, ax ; transfer only what we can ax_min_1: pop dx ; Check that request sectors do not exceed track boundary mov si, sectPerTrack inc si mov ax, cx ; get the sector/cyl byte and ax, 03fh ; and mask out sector sub si, ax ; si has how many we can read mov ax, di cmp si, di ; see if asked <= available jge ax_min_2 mov ax, si ; get what can be xfered ax_min_2: mov si, RETRYCOUNT mov ah, 2 mov dl, drive retry: push ax int 13h pop ax jnc read_ok push ax xor ax, ax ; reset the drive int 13h pop ax dec si jnz retry stc pop ax pop dx pop si ret read_next_jmp: jmp short read_next read_ok: xor ah, ah mov si, ax ; AX = SI = number of sectors read mul word ptr bytesPerSector ; AX = number of bytes read add bx, ax ; add number of bytes read to BX jnc no_incr_es ; if overflow... mov ax, es add ah, 10h ; ...add 1000h to ES mov es, ax no_incr_es: pop ax pop dx ; DX:AX = last sector number add ax, si adc dx, 0 ; DX:AX = next sector to read sub di, si ; if there is anything left to read, jg read_next_jmp ; continue clc pop si ret readDisk endp errmsg db "Boot error" ERRMSGLEN equ $ - errmsg filename db "IPL SYS" org BASE+01feh sign dw 0aa55h TEXT ENDS end