# 1 "first-dos.S" # 1 "" # 1 "" # 1 "first-dos.S" ; first.S - primary boot loader for DOS ; ; Copyright (C) 1996-1998 Gero Kuhlmann ; Modifications for booting FreeDOS by Ken Yap ; ; 2003.04.28 Robb Main ; Tweaks & bugfixes to allow use with much larger disk images, remove ; '2 head assumption', use 32-bit GDT, and other 'stuff'. ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 2 of the License, or ; any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program; if not, write to the Free Software ; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # 41 "first-dos.S" # 55 "first-dos.S" # 1 "first-dos.h" 1 # 20 "first-dos.h" # 64 "first-dos.h" # 93 "first-dos.h" # 111 "first-dos.h" # 124 "first-dos.h" # 169 "first-dos.h" # 179 "first-dos.h" # 60 "first-dos.S" 2 # 1 "version-dos.h" 1 # 61 "first-dos.S" 2 text _start: mov dx,ds mov ax,cs ; set DS mov ds,ax mov [oldES],es mov [oldDS],dx ; save old register values in case mov [oldBP],bp ; we have to return to the boot rom mov [oldSI],si mov [oldDI],di mov bp,sp mov ax,[bp+4] mov [header+0],ax ; load the address of the boot header mov ax,[bp+6] mov [header+2],ax mov ax,[bp+8] mov [bootp+0],ax ; load the address of the bootp block mov ax,[bp+10] mov [bootp+2],ax ; Tell the user who we are and that we started running mov si,sigmsg call prnstr ; Check if the boot image header is correct. les bx,[header] mov si,bmerr ; prepare for correct error message es mov ax,[bx+0+0] cmp ax,[bmagic+0] ; compare boot rom magic numbers jne doerr1 es mov ax,[bx+0+2] cmp ax,[bmagic+2] jne doerr1 mov si,vmerr ; prepare for correct error message es mov al,[bx+4] mov cl,4 shr al,cl and al,0x0F cmp al,4 ; check vendor ID size jne doerr1 xor di,di dovmag: mov al,[di+vmagic] ; check vendor ID or al,al jz getrd ; vendor ID ok, continue es cmp al,[bx+di+16] jne doerr1 inc di jmp short dovmag doerr1: call prnstr ; in case of error return to the mov si,[oldSI] ; boot rom with registers set mov di,[oldDI] ; correctly mov bp,[oldBP] mov es,[oldES] mov ds,[oldDS] retf ; Next get the address of the ramdisk and its size. getrd: mov si,recerr mov al,17 call fndldr ; find load record for ramdisk mov ax,es or ax,di jz doerr1 es mov al,[di+3] ; get load record flags test al,0x01 + 0x02 ; check that it has a jnz doerr1 ; correct flag es mov ax,[di+4+0] ; get base adress of ramdisk es mov bx,[di+4+2] mov [rdaddr+0],ax mov [rdaddr+2],bx es mov ax,[di+12+0] ; get ramdisk size es mov bx,[di+12+2] add ax,0x03ff ; round to nearest kb adc bx,byte 0 mov cl,10 shr ax,cl ; convert into kb mov cl,6 shl bx,cl or ax,bx mov [rdsize],ax ; Get the disk geometry out of the vendor information block in the ; load record es mov bl,[di+0] and bl,0x0f xor bh,bh ; compute pointer to shl bx,1 ; vendor block shl bx,1 es mov ax,[di+bx+0] ; get number of sectors mov [secnumlo],ax es mov ax,[di+bx+0+2] ; get number of sectors mov [secnumhi],ax es mov ax,[di+bx+4] ; get head count mov [heads],al es mov ax,[di+bx+6] ; get sectors per track mov [secptk],al es mov ax,[di+bx+8] ; get number of cylinders mov [cylnum],ax es mov al,[di+bx+11] ; get no-hard-disk flag mov [nohd],al es mov al,[di+bx+10] ; get ram disk drive id mov [drvid],al ; Set the address of the BIOS disk status byte mov bx,0x0041 cmp al,0x80 jb setsts mov bx,0x0074 setsts: mov [statof],bx ; Get system configuration from BIOS push ax int 0x11 mov [syscnf],ax pop ax ; Get the number of floppy or hard disk drives in the system and set ; the DOS disk parameter block cmp al,byte 0x80 jae getnm2 mov ah,0x08 xor dl,dl int 0x13 ; get the number of floppy disk jc getnm1 ; drives from the BIOS or dl,dl jnz gotnum inc dl ; indicate at least one drive jmp short gotnum getnm1: mov dx,[syscnf] ; if int 13h didnt work try it with test dl,0x01 ; the mainboard dip switch config jz getnm3 mov cl,6 shr dl,cl and dl,0x03 ; determine number of floppy disk inc dl ; drives jmp short gotnum getnm2: mov ah,0x08 mov dl,0x80 int 0x13 ; get the number of hard disk jc getnm3 ; drives from the BIOS inc dl jmp short gotnum ; The next line was mov dl,1 in netboot-0.8.1. This was probably an error. getnm3: mov dl,1 ; we have at least one drive gotnum: mov [drvnum],dl ; save number of disk drives call setdpb ; set disk parameter block ; Now get the boot sector of the ramdisk and check that its correct. If ; we are simulating a hard disk the boot sector contains the partition ; table, which we have to analyze. Then load the partitions boot sector. mov ax,0x7000 mov es,ax ; pointer to temporary buffer xor bx,bx xor dx,dx ; first sector xor ax,ax xor ch,ch ; indicate read mov cl,1 ; one sector call rwsect ; read boot sector mov si,rderr jc doerr2 cmp byte [drvid],0x80 ; if the ram disk is simulates a jb chkbot ; floppy, there is no partition table mov si,dskerr ; prepare for correct error message es cmp byte [0x01BE],0x80 jne doerr2 es cmp byte [0x01C2],0x04 je partok es cmp byte [0x01C2],0x01 jne doerr2 partok: es mov dx,[0x01C6+2] ; get number of first sector es mov ax,[0x01C6+0] xor ch,ch ; indicate read mov cl,1 ; one sector call rwsect ; read boot sector mov si,rderr jc doerr2 jmp short dobotp chkbot: mov si,dskerr ; prepare for correct error message mov al,[drvid] es cmp byte [36],al ; check boot disk number jne doerr2 es cmp word [11],512 ; check sector size jne doerr2 es cmp word [26],byte 16 ; check number of heads jg doerr2 es mov ax,[24] ; check number of sectors per track cmp al,[secptk] je dobotp doerr2: call prnstr ; in case of error return to the push ax mov ah,0 ; wait for a keypress, so user int 0x16 ; can read the error message pop ax mov si,[oldSI] ; boot rom with registers set mov di,[oldDI] ; correctly mov bp,[oldBP] mov es,[oldES] mov ds,[oldDS] retf ; Save the BOOTP record for later retrieval by a DOS program. dobotp: cld xor dx,dx les di,[bootp] mov ax,es or ax,di jz dobot9 es mov al,[di+0] ; op code must indicate reply cmp al,2 jne dobot9 ; it isnt add di,236 mov bx,di mov si,pmagic ; compare vendor ID dobot1: mov di,bx mov cx,4 repe cmpsb jz dobot2 ; vendor ID is valid add si,cx cmp byte [si],0 ; check next vendor ID jne dobot1 dobot9: jmp short nobot2 ; vendor ID not found doerr6: jmp short doerr2 dobot2: sub si,byte 4 sub si,pmagic mov ax,si push ds mov bx,ds mov es,bx mov di,btpnew lds si,[bootp] mov bx,si mov dx,300 or ax,ax ; if not RFC vendor ID the bootp jnz dobot7 ; record has fixed length xor cx,cx add si,236 + 4 dobot3: lodsb cmp al,0 ; handle NOP tag jnz dobot4 inc cx cmp cx,byte 16 ; more than 16 NOP tags is VERY unusual jae dobot7 ; so the bootp record maybe broken jmp short dobot3 ; loop to next tag nobot2: jmp short nobotp dobot4: cmp al,255 ; handle END tag jnz dobot6 mov dx,si sub dx,bx ; compute length of bootp record cmp dx,300 jae dobot7 mov dx,300 ; use minimum size jmp short dobot7 dobot6: lodsb ; handle all other tags mov cl,al xor ch,ch add si,cx ; jump to next tag xor cx,cx ; reset NOP counter jmp short dobot3 ; proceed with next tag dobot7: mov si,btperr mov ax,btpnew ; bootp record cannot be larger add ax,dx ; than the current segment jc doerr6 mov cx,dx mov si,bx ; restore source pointer rep movsb ; save the bootp record pop ds nobotp: mov [btplen],dx ; set length of bootp record ; Everything is right, so we can now move the resident section to the ; end of the conventional memory, thus overwriting the bootrom data ; area. Therefore there is no chance of returning to the bootrom from ; now on. ; Note that the resident section doesnt start at offset 0, so we have ; to set the segment address to somewhere lower. cli mov ax,0x7000 ; set new stack mov ss,ax mov sp,0xFFF0 cld int 0x12 ; get memory size in kB mov cl,6 shl ax,cl ; compute last usable segment mov bx,btpnew add bx,[btplen] mov dx,bx mov cl,4 shr bx,cl ; compute size of code segment in inc bx ; paragraphs sub ax,bx ; compute new code segment mov [resseg],ax mov es,ax ; set source and destination ptrs mov si,start_resident mov di,si mov cx,dx sub cx,si ; compute size of resident area rep movsb ; move it # 466 "first-dos.S" ; Setup all interrupt vectors mov bx,[resseg] push ds mov ds,bx xor ax,ax mov es,ax es mov ax,[0x13 * 4+0] mov [old13h+0],ax es mov ax,[0x13 * 4+2] mov [old13h+2],ax es mov ax,[0x2F * 4+0] mov [old2Fh+0],ax es mov ax,[0x2F * 4+2] mov [old2Fh+2],ax es mov ax,[0xF1 * 4+0] mov [oldF1h+0],ax es mov ax,[0xF1 * 4+2] mov [oldF1h+2],ax es mov ax,[0xF8 * 4+0] mov [oldF8h+0],ax es mov ax,[0xF8 * 4+2] mov [oldF8h+2],ax pop ds es mov [0x13 * 4+2],bx ; interrupt vector 13h es mov word [0x13 * 4+0],int13 es mov [0x2F * 4+2],bx ; interrupt vector 2Fh es mov word [0x2F * 4+0],int2F es mov [0xF8 * 4+2],bx ; interrupt vector F8h es mov word [0xF8 * 4+0],intF8 mov di,0xF1 * 4 mov si,if1sig ; interrupt vector F1h mov cx,4 ; contains the string "NetB" rep ; to provide as an installation movsb ; check sti ; Output some debugging messages by simply calling the interrupt vectors ; which we just created. # 553 "first-dos.S" ; The boot sector had to be placed into a temporary memory area to ; avoid overwriting any bootrom structures. However, a call back ; to the bootrom is no longer possible, so we can move the bootblock ; where it belongs. push ds mov ax,0x7000 mov ds,ax xor ax,ax mov es,ax xor si,si mov di,0x7C00 mov cx,512 rep movsb ; move it pop ds ; Finally call the boot sector mov dl,[drvid] ; boot block may expect this jmp 0:0x7C00 ;==================================================================== ; ; Setup DOS drive parameter block (DPB) for floppy disk drive. The ; DPB is required to activate the floppy disk drive after the ramdisk ; has been turned off. ; Input: none ; Output: none ; Registers changed: AX, BX, CX, DX setdpb: push es push si push di mov dl,[drvid] cmp dl,0x80 ; can only restore floppy drives jae setdp8 ; First get the drive parameters from the BIOS mov [dpb_phys],dl ; set physical drive ID mov ah,0x08 ; get drive parameters from BIOS int 0x13 jc setdp8 xor ah,ah mov al,ch ; set max number of cylinders inc ax mov [dpb_cyls],ax mov si,dpb_bpb_cur mov al,cl ; set max number of sectors per track mov [si+13],ax mov al,dh inc ax ; set max number of heads mov [si+15],ax ; Determine DOS disk parameters by drive type cmp bl,5 jb setdp1 ; check for invalid drive type mov bl,4 setdp1: xor bh,bh dec bx push bx shl bx,1 shl bx,1 ; compute address into drive para- add bx,drvtab ; meter table xor ah,ah mov al,[bx+0] ; get # of entries in root dir mov [si+6],al mov al,[bx+1] ; get # of sectors per FAT mov [si+11],ax mov al,[bx+2] ; get # of sectors per cluster mov [si+2],al mov al,[bx+3] ; get media ID mov [si+10],al pop bx mov al,[bx+typtab] ; get drive type mov [dpb_type],al ; Determine number of bytes per sector es mov cl,[di+3] ; get shift value from BIOS media mov ax,128 ; parameter table shl ax,cl ; shift base value mov [si+0],ax jmp short setdp4 setdp8: jmp short setdp9 ; needed for short jumps ; Determine total number of sectors setdp4: mov ax,[si+13] mul word [dpb_cyls] or dx,dx ; should not overflow jnz setdp8 cmp word [si+15],byte 2 jb setdp3 shl ax,1 jc setdp8 setdp3: mov [si+8],ax ; Determine if the drive can detect disk changes mov ah,0x15 mov dl,[drvid] int 0x13 ; get DASD type from BIOS mov bl,ah mov ax,0x0020 + 0x0040 jc setdp2 cmp bl,0x02 ; check if drive detects disk changes jne setdp2 or ax,0x0002 setdp2: mov [dpb_flags],ax ; set flags ; Thats it inc byte [dpb_valid] ; increment valid flag setdp9: pop di pop si pop es ret ;==================================================================== ; ; Find a load record in the boot header. The ID number of the load ; record is in AL, and ES:DI points to requested load record, or is ; the NULL pointer if load record not found. ; ; Changed registers: AX, DI, ES fndldr: push cx mov ch,al les di,[header] ; examine boot image header es mov al,[di+4] ; get length of image header call getlen add di,ax ; get the pointer to first load record fndl1: es cmp ch,[di+1] ; is it the desired one ? je fndl3 es mov al,[di+3] ; no, so check if its the last record test al,0x04 jnz fndl2 es mov al,[di+0] ; no, get the address of the next one call getlen add di,ax jmp short fndl1 fndl2: xor ax,ax ; couldnt find the desired record mov es,ax mov di,ax fndl3: pop cx ret ;==================================================================== ; ; Compute the length of a load record address from a length byte ; in AL. Return the offset in AX. ; ; Changed registers: AX getlen: push cx mov ah,al mov cl,4 shr ah,cl and ax,0x0f0f ; compute the total length in add al,ah ; bytes from the length of the xor ah,ah ; record and that of the vendor shl ax,1 ; information. shl ax,1 pop cx ret ;==================================================================== ; Print a string in DS:SI onto the console ; ; Changed registers: AL prnstr: push si cld prns1: lodsb ; loop over all characters of or al,al ; string jz prns2 push bx mov ah,0x0E ; print it mov bl,0x07 xor bh,bh int 0x10 pop bx jmp short prns1 prns2: pop si ret # 814 "first-dos.S" ;==================================================================== ; ; String and constants definitions ; Startup signature sigmsg: db 0x0D, 0x0A db 'DOS Net Boot Image Loader ' db 'Version 0.8.1' db ' ' db "mknbi-dos-1.4.4" db 0x0D, 0x0A db 'GPLed by G. Kuhlmann' db 0x0D, 0x0A crlf: db 0x0D, 0x0A db 0 ; Magic numbers for boot record and bootp entry bmagic: dd 0x1B031336 ; boot image magic number vmagic: db "mknbi-dos-1.4.4" ; vendor magic ID db 0 ; end of vendor magic ID pmagic: db 0x63, 0x82, 0x53, 0x63 ; bootp magic ID for RFC 1048 db 0x43, 0x4D, 0x55, 0x00 ; bootp magic ID for CMU db 0x53, 0x54, 0x41, 0x4E ; bootp magic ID for Stanford db 0 ; Specifications for different types of disk drives. The order is: ; # dir entries, # sects per FAT, # sects per cluster, media ID drvtab: db 112, 2, 2, 0xfd ; 360kB disk db 224, 7, 1, 0xf9 ; 1.2MB disk db 112, 3, 2, 0xf9 ; 720kB disk db 224, 9, 1, 0xf0 ; 1.44 MB disk typtab: db 0 ; type values for drive parameter block db 1 db 2 db 7 ; Error messages recerr: db 'Error in load record data' db 0x0D, 0x0A db 0 bmerr: db 'Invalid boot header magic number' db 0x0D, 0x0A db 0 vmerr: db 'Invalid vendor magic ID' db 0x0D, 0x0A db 0 rderr: db 'Error while accessing ramdisk' db 0x0D, 0x0A db 0 dskerr: db 'Wrong ramdisk image' db 0x0D, 0x0A db 0 btperr: db 'BOOTP record invalid' db 0x0D, 0x0A db 0 ; Debug messages # 904 "first-dos.S" # 914 "first-dos.S" ;==================================================================== ; ; Variable definitions header: dd 0 ; pointer to boot header from boot rom bootp: dd 0 ; pointer to bootp block from boot rom resseg: dw 0 ; segment of resident section oldDS: dw 0 ; old DS from boot rom oldES: dw 0 ; old ES from boot rom oldBP: dw 0 ; old BP from boot rom oldSI: dw 0 ; old SI from boot rom oldDI: dw 0 ; old DI from boot rom ;==================================================================== ; ; Start of resident section. This will be placed at the end of the ; low 640kB RAM area. ; ;==================================================================== ; align 16, db 0 ; has to be paragraph aligned start_resident: ; indicate start of resident section ;==================================================================== ; ; New interrupt 2Fh routine. This routine gets called by IO.SYS ; in order to determine the maximum amount of memory usable to ; DOS. This only works with DOS versions 5.0 and higher. The DOS ; function which gets installed into the interrupt 2Fh vector ; does not call the old vector (i.e. it does not daisy-chain), ; and therefore we can redirect 2Fh without a problem even when ; considering to remove the ram disk lateron. ; ; NOTE THAT THIS INTERRUPT HAS TO BE THE FIRST ROUTINE IN THE ; RESIDENT SECTION! ; ; Input: AX - Magic ID ; DX - segment following last usable byte ; Output: DX - new segment following last usable byte ; Registers changed: DX int2F: jmp short int2F1 ; this has to be a relative jump nop db 'RPL' ; magic ID string for DOS int2F1: cmp ax,0x4A06 ; check for magic ID jne int2F9 push cx mov dx,start_resident ; determine last usable segment mov cl,4 ; from segment and offset of shr dx,cl ; the resident section pop cx push ax push cs pop ax add dx,ax ; add offset to segment dec dx pop ax iret int2F9: cs jmp far [old2Fh] ; jump to old interrupt routine ;==================================================================== ; ; New interrupt F8h routine. It can be used to retrieve several ; values from the resident driver. ; Input: AX - function code ; Output: depends on function: ; ; Installation check (AX = 0x9C00) ; AX - contains 0x009C ; ; Return ramdisk size and address (AX = 0x9C01) ; BX:DX - address of ramdisk ; CX - size of ramdisk in kb ; ; Return size and address of BOOTP block (AX = 0x9C02) ; BX:DX - address of BOOTP block ; CX - size of BOOTP block ; ; Return miscellaneous values for handling the ramdisk (AX = 0x9C03) ; AX - XMS handle for ram disk ; BX:DX - address of old interrupt vector table ; CL - ramdisk id ; ; Remove ramdisk (AX = 0x9C04) ; AL - non-zero if error ; ; Registers changed: depends on function intF8: cmp ah,0x9C ; check for magic ID jne intF89 cmp al,01 ; check for function number jne intF81 cs mov bx,[rdaddr+2] ; return ramdisk address cs mov dx,[rdaddr+0] cs mov cx,[rdsize] ; return ramdisk size iret intF81: cmp al,0x02 jne intF82 mov bx,cs ; return address of BOOTP record mov dx,btpnew cs mov cx,[btplen] ; return BOOTP length iret intF82: cmp al,0x03 jne intF83 mov bx,cs ; return address of old interrupt mov dx,oldints ; vector table cs mov cl,[drvid] ; return drive id cs mov ax,[xmshdl] ; return XMS handle iret intF83: cmp al,0x04 jne intF88 call rmrd ; remove ramdisk iret intF88: or al,al jnz intF89 xchg al,ah ; return installation check code intF89: iret ;==================================================================== ; ; New interrupt 13h routine to handle disk accesses. It is different ; for simulating either a floppy drive or a hard disk. DOS provides ; a way for restoring this interrupt to its original value when we ; want to remove the ramdisk lateron. ; Input: AH - function code ; DL - driver number ; Output: carry flag set if error ; Registers changed: depends on function int13: sti ; we dont need interrupts disabled push ax cs mov ax,[xmsadr+0] cs or ax,[xmsadr+2] ; check if XMS already initialized jnz int13s mov ax,0x4300 ; check if XMS available int 0x2f cmp al,0x80 ; XMS not available jne int13s push bx push es mov ax,0x4310 ; get XMS driver address int 0x2f cs mov [xmsadr+0],bx ; save driver address cs mov [xmsadr+2],es pop es call inixms ; initialize XMS pop bx int13s: pop ax cs cmp dl,[drvid] ; check if its for us je int132 cs cmp byte [drvid],0x80 ; check if we are to simulate a hard jae int13h ; disk drive ; First comes the floppy drive redirector cmp dl,0x80 ; check if its for a hard disk jb int133 cs test byte [nohd],0xff ; check if hard disk accesses allowed jz int131 cmp ah,0x08 ; function 0x08 should not return error mov ah,0x80 ; return with error jne int135 xor dl,dl ; indicate no hard disk present jmp short int13f ; return without error ; Handle function 0x08 for disk drives other than the ramdisk. int133: cmp ah,0x08 jne int131 pushf cs ; function 0x08 has to return the call far [old13h] ; correct number of disk drives cs mov dl,[drvnum] int13f: xor ah,ah ; never return an error jmp short int136 ; Jump directly to the BIOS int131: cs jmp far [old13h] ; call the old interrupt routine ; Now handle all ramdisk functions. First check if the function number ; is correct. int132: cmp ah,0x18 jbe int134 mov ah,0x01 ; unknown command int135: stc int136: push ds jmp short int13e ; Determine the handlers address according to the function number in AH ; and jump to it. int134: push ds push cs pop ds ; set data segment push bx mov bl,ah xor bh,bh shl bx,1 ; compute pointer into routine table jmp [bx+fntab] ; Now comes the hard disk drive redirector int13h: cmp dl,0x80 ; check if its for a floppy drive jb int131 ; Handle function 0x08 for hard disk drives other than the ramdisk. cmp ah,0x08 jne int137 dec dl pushf cs ; function 0x08 has to return the call far [old13h] ; correct number of disk drives cs mov dl,[drvnum] jmp short int13f ; always return without error ; Handle function 0x15 for disk drives other than the ramdisk. This is ; the only function besides 0x08 which returns a value in DX and therefore ; has to have special handling. int137: push dx dec dl cmp ah,0x15 jne int138 pushf cs call far [old13h] ; call the BIOS for handling jc int139 cmp ah,0x03 ; DX is only used if AH = 0x03 jne int139 add sp,byte 0x0002 ; remove DX from stack if the BIOS jmp short int136 ; returned a value in it ; Handle all other functions for drives other than the ramdisk. This will ; just call the original BIOS handler. int138: pushf cs call far [old13h] ; simply call the old int 13h routine int139: pop dx jmp short int136 ; Save the return status into the BIOS data area and return to the caller ; while preserving the carry flag. int13e: push es ; return from function handler push bx ; this code is not allowed to change call getsts ; any register or flag es mov [bx],ah ; set disk operation status pop bx pop es intend: pop ds push ax ; general exit point for interrupts pushf pop ax push bp mov bp,sp mov [bp+8],al ; put the flags onto the stack pop bp pop ax iret ; Function table fntab: dw f1300 ; function 00: reset disk system dw f1301 ; function 01: return last error dw f1302 ; function 02: read disk dw f1303 ; function 03: write disk dw f1304 ; function 04: verify disk dw f1305 ; function 05: format disk dw f1306 ; function 06: format track dw f1307 ; function 07: format disk dw f1308 ; function 08: get drive parameters dw f1309 ; function 09: intialize controller dw f130A ; function 0A: read long sectors dw f130B ; function 0B: write long sectors dw f130C ; function 0C: seek for cylinder dw f130D ; function 0D: disk reset dw f130E ; function 0E: read sector buffer dw f130F ; function 0F: write sector buffer dw f1310 ; function 10: check if drive ready dw f1311 ; function 11: recalibrate drive dw f1312 ; function 12: controller ram diagnostic dw f1313 ; function 13: drive diagnostic dw f1314 ; function 14: controller int diagnostic dw f1315 ; function 15: get disk type dw f1316 ; function 16: detect disk change dw f1317 ; function 17: set media type for format dw f1318 ; function 18: set media type for format f13end: jmp short int13e ;==================================================================== ; ; Function 00 - reset disk system ; f1300: test word [syscnf],0x0001 ; check if we have physical floppy jz f13001 ; drives at all push dx xor dl,dl ; always reset the floppy system pushf call far [old13h] ; call old disk interrupt pop dx jc f13002 f13001: xor ah,ah ; no error f13002: pop bx jmp short f13end ;==================================================================== ; ; Function 01 - return last error status ; f1301: push es call getsts ; get offset to status byte es mov ah,[bx] ; get disk operation status pop es pop bx clc jmp short intend ;==================================================================== ; ; Function 02/03 - read from/write to disk f1302: f1303: pop bx ; get old BX from stack push dx push cx push bx push ax xchg cx,ax sub ch,2 ; ch=0 for reads, ch=1 for writes call cvtsec ; get linear sector number jnc f13026 pop ax mov ah,0x04 ; error: sector not found f13021: pop bx pop cx pop dx stc jmp short f13end ; terminate f13022: cmp dx,[secnumhi] ; check if sector is still correct jb f13024 cmp ax,[secnumlo] jbe f13024 mov ah,0x04 ; error: sector not found f13023: pop bx mov al,bl sub al,cl ; compute number of sectors processed jmp short f13021 f13024: call rwsect ; actually handle request jnc f13025 mov ah,0x20 ; error: disk controller error jmp short f13023 f13025: inc ax ; proceed with next linear sector adc dx,0 add bx,512 ; increment src/dst address dec cl ; decrement sector count f13026: or cl,cl jnz f13022 f13027: pop ax pop bx pop cx pop dx xor ah,ah ; no error f13e1: jmp short f13end ;==================================================================== ; ; Function 08 - get disk drive parameters f1308: pop bx ; get old BX from stack mov dl,[drvnum] ; get number of disk drives mov cl,[secptk] ; get sectors per track mov ch,[cylnum] ; number of cylinders mov dh,[heads] ; number of heads - 1 dec dh xor bx,bx ; ramdisk drive type xor ax,ax dec ch jmp short f13e1 ;==================================================================== ; ; Function 04, 05, 06, 07, 09, 0D, 10, 11, 12, 13, 14, 16 - no operation f1304: f1305: f1306: f1307: f1309: f130D: f1310: f1311: f1312: f1313: f1314: f1316: pop bx ; get old BX from stack xor ah,ah ; no error jmp short f13e1 ;==================================================================== ; ; Function 0A, 0B, 0E, 0F, 17, 18 - not implemented f130A: f130B: f130E: f130F: f1317: f1318: pop bx ; get old BX from stack mov ah,0x01 ; invalid opcode stc jmp short f13e1 ;==================================================================== ; ; Function 0C - seek for cylinder f130C: pop bx ; get old BX from stack push dx push cx push ax xchg ax,cx call cvtsec ; get linear sector number pop ax mov ah,0x00 ; no error jnc f130C1 mov ah,0x04 ; error: sector not found f130C1: pop cx pop dx jmp short f13e1 ; terminate ;==================================================================== ; ; Function 15 - get disk type f1315: pop bx ; get old BX from stack mov ah,0x02 ; indicate floppy disk cmp dl,0x80 ; check if floppy disk type requested jb f13159 inc ah ; indicate hard disk mov dx,[secnumlo] ; get number of sectors on disk mov cx,[secnumhi] f13159: clc jmp short f13e1 ;==================================================================== ; ; Convert Cyl/Sec/Head notation into a linear sector number ; Input: AH - cylinder number ; AL - sector number ; DH - head number ; Output: DX:AX - 32-bit linear sector number ; carry flag set if invalid sector number ; Registers changed: AX,DX cvtsec: push bx push cx mov bl,al ; move sector number into BL mov bh,dh ; move head number into BH xchg ah,al ; compute track number into AX, the mov cl,6 ; upper two bits of CL are the high shr ah,cl ; bits of the 10 bit cylinder number xor dx,dx xor cx,cx mov cl,[heads] mul cx ; compute track number from cylinders mov cl,bh add ax,cx adc dx,0 mov cl,[secptk] push bx xor bx,bx or dx,dx ; is 32-bit value > 16-bits yet? jz cvts6 push ax push dx mov ax,dx ; multiply upper 16-bits first mul cx mov bx,ax ; save result in bx pop dx pop ax cvts6: mul cx ; compute number of track starting add dx,bx ; fixup upper 16-bits pop bx mov cl,bl and cl,0x3F ; move sector number into AX cmp cl,[secptk] ; check if sector number is correct ja cvts8 dec cx ; sector numbers start with 1 js cvts8 ; therefore sector 0 does not exist add ax,cx ; compute final sector number adc dx,0 cmp dx,[secnumhi] ; check if the sector is valid jb cvts8 ja cvts7 cmp ax,[secnumlo] jb cvts8 cvts7: stc jmp short cvts9 cvts8: clc ; return with error cvts9: pop cx pop bx ret ;==================================================================== ; ; Read/write a sector from the ram disk. This routine requires a ; sector to be 512 bytes long. ; Input: CH - non-zero if write to ram disk ; DX:AX - logical sector number ; ES:BX - pointer to destination buffer ; Output: carry flag set if error ; Registers changed: AX rwsect: push si push di push cx push dx push ax mov si,rd_srcb mov di,rd_dstb or ch,ch ; check direction of transfer jz rwsec1 mov si,rd_dstb mov di,rd_srcb rwsec1: mov cx,512 ; compute linear ramdisk address mul cx ; from sector number add ax,[rdaddr+0] adc dx,[rdaddr+2] mov [si],ax ; set src for read, dst for write mov [si+2],dl mov [si+5],dh mov ax,es xor dx,dx mov cl,0x10 xor ch,ch mul cx ; compute linear buffer address add ax,bx adc dx,0 mov [di],ax ; set dst for read, src for write mov [di+2],dl mov [di+5],dh push es mov ax,cs mov es,ax mov si,rd_gdt mov cx,512/2 ; copy 512 bytes, e.g. 256 words mov ax,0x8700 ; let the BIOS move the sector int 0x15 pop es pop ax pop dx pop cx pop di pop si ret ;==================================================================== ; ; Return a pointer to the disk drive status byte. This routine should ; not change any flags! ; Input: none ; Output: ES:BX - pointer to disk drive status byte ; Registers changed: BX, ES getsts: mov bx,0x0040 ; status byte is in BIOS data mov es,bx ; segment cs mov bx,[statof] ret ;==================================================================== ; ; Initialize the XMS interface. This is necessary to prevent the ; ram disk from getting overwritten. The way this works is to ; first allocate all of the available XMS, then resize the memory ; block to end just above the ramdisk and lock it. Unfortunately ; we have to do it this complicated because there is no way of ; knowing how the XMS is going to allocate the available memory. ; Another problem is that at least HIMEM.SYS requires up to 256 ; bytes of stack, and we cannot assume the caller of INT 13 to ; provide that much so we have to change stacks. ; Input: none ; Output: none ; Registers changed: AX, BX inixms: call setstk ; set new stack ; First check that the XMS version number is correct. To support all ; necessary functions it has to be version 2.0+. xor ah,ah call callxm ; get version number cmp ah,0x02 jb inixm8 ; wrong XMS version ; Determine how much memory we can allocate. mov ah,0x08 xor bl,bl ; get amount of extended memory call callxm or bl,bl ; check for error jnz inixm8 mov bx,[rdsize] ; get size of ramdisk add bx,byte 65 ; care for a missing HMA cmp bx,ax ; check if enough memory for ram disk ja inixm8 ; Grab all of the extended memory. push bx mov dx,ax ; grab largest block - which is whole mov ah,0x09 ; memory because there should be no call callxm ; other process using XMS pop bx or ax,ax ; check for error jz inixm8 mov [xmshdl],dx ; save handle ; Now resize the memory block so that it will contain the ramdisk image. mov ah,0x0f ; reallocate memory block call callxm or ax,ax ; check for error jnz inixm1 inixm8: mov word [xmshdl],0 ; in case of error dont return handle jmp short inixm9 ; Now lock the memory block and check that the physical address of the ; memory block is correct. inixm1: mov dx,[xmshdl] mov ah,0x0c ; lock memory block call callxm add bx,0x03ff adc dx,byte 0x0001 ; add 65kb - maximum difference sub bx,[rdaddr+0] ; check that ramdisk address is below sbb dx,[rdaddr+2] jc inixm8 ; Thats it. Restore all registers and swap the stack back to its ; original state. inixm9: call rststk ; restore old stack ret ;==================================================================== ; ; Call XMS driver. ; Input: AH - function code ; other registers depending on function code ; Output: depends on called function ; Registers changed: depends on called function callxm: push ax push bp push ax mov bp,sp mov ax,[bp+6] mov [bp+4],ax ; make far return address from mov [bp+6],cs ; near call pop ax pop bp cs push word [xmsadr+2] ; push address of XMS driver cs push word [xmsadr+0] retf ; call XMS driver ;==================================================================== ; ; Set new stack ; Input: none ; Output: none ; Registers changed: AX, BX, DS, SS, SP setstk: cli pop bx ; get return address mov ax,sp cs mov [oldstk+0],ax cs mov [oldstk+2],ss ; save old stack pointer mov ax,cs mov ss,ax mov sp,newtos - 2 ; change to new stack sti push cx push dx push si ; save all registers push di push es push ds mov ax,cs ; set DS to current segment mov ds,ax jmp bx ; return to caller ;==================================================================== ; ; Reset stack to old stack ; Input: none ; Output: none ; Registers changed: all (reset to old state) rststk: pop bx ; get return address pop ds pop es pop di pop si ; restore all registers pop dx pop cx cli cs mov ax,[oldstk+0] ; restore old stack cs mov ss,[oldstk+2] mov sp,ax sti jmp bx ; return to caller ;==================================================================== ; ; Remove ramdisk from memory. This involves restoring all interrupt ; vectors, freeing all used memory and restoring the DOS drive para- ; meter table. Since we need to call the XMS drive, we have to switch ; stacks like with inixms. ; Input: none ; Output: AL - non-zero if error ; Registers changed: AX rmrd: push bx call setstk ; set new stack mov al,[drvid] cmp al,0x80 ; can only restore floppy drives jb rmrd1 rmrd8: call rststk ; return with error pop bx mov al,0x01 ret ; First determine the address of the DOS disk parameter block for the ; ramdisk and check that the open count is zero, i.e. no open file on ; the device. rmrd1: push ds mov ax,0x0803 int 0x2f ; get address of drive parameter mov ax,ds ; table from DOS mov es,ax pop ds rmrd2: es mov al,[di+4] ; get physical unit number cmp al,[drvid] ; is it our drive? je rmrd3 cmp di,0xffff ; error if we couldnt find the DPB je rmrd8 ; for the ramdisk es les di,[di] ; get pointer to next entry jmp short rmrd2 rmrd3: mov [dpb_addr+0],di mov [dpb_addr+2],es es mov ax,[di+0x20] ; get device open count or ax,ax jnz rmrd8 ; Next restore the interrupt vectors. Int 13h is special as it is ; redirected by DOS. However, DOS provides a function to restore ; that interrupt. Interrupt 2Fh doesnt have to get restored because ; DOS does never call the old interrupt again. xor ax,ax mov es,ax mov ax,cs es ; first check that nobody redirected cmp ax,[0xF8 * 4+2] ; our own interrupts. In that case jne rmrd8 ; there is no chance of removing the es ; ramdisk. mov ax,[0xF8 * 4+0] cmp ax,intF8 jne rmrd8 mov si,if1sig mov di,0xF1 * 4 mov cx,4 ; interrupt F1h contains a signature repz ; and no vector cmpsb jnz rmrd8 push ds les bx,[old13h] ; get old interrupt vector 13h mov dx,bx mov ax,es ; save it into DS:DX and ES:BX mov ds,ax mov ah,0x13 int 0x2f ; call DOS to restore vector mov ax,ds mov cx,cs cmp ax,cx ; check that its indeed our interrupt jne rmrd4 ; which we are replacing mov ax,es cmp ax,cx jne rmrd4 cmp bx,int13 jne rmrd4 cmp dx,int13 je rmrd5 rmrd4: mov ah,0x13 int 0x2f ; restore old interrupt pop ds ; someone redirected the interrupt jmp rmrd8 ; already, cant restore rmrd5: pop ds ; restore the other interrupts cli xor ax,ax mov es,ax mov ax,[oldF8h+0] es mov [0xF8 * 4+0],ax mov ax,[oldF8h+2] es mov [0xF8 * 4+2],ax sti ; OK, we can now setup the DOS drive parameter table to contain the ; correct values for the physical floppy drive. If we couldnt create ; a valid parameter table entry for this drive, simply mark the DOS ; entry as invalid. This will cause "Not Ready" errors in DOS. This ; doesnt work with DR-DOS 5.0! les di,[dpb_addr] ; get address of DPB es or word [di+0x1f],byte 80 ; mark drive as invalid test byte [dpb_valid],0xff ; check if DPB valid jz rmrd6 cld ; got correct table entry mov cx,dpb_end - dpb mov si,dpb add di,byte 4 rep movsb ; simply copy the DPB ; Next remove the ramdisk image from extended memory using the XMS driver. rmrd6: mov dx,[xmshdl] or dx,dx ; only free memory if we really jz rmrd7 ; assigned it with XMS push dx mov ah,0x0d call callxm ; unlock memory block pop dx or ax,ax ; dont free block if error jz rmrd7 mov ah,0x0a call callxm ; free memory block ; Finally we can remove the memory for the ramdisk driver. We only ; reset the owner field of the memory control block to 0 to indicate ; it as free. rmrd7: mov dx,start_resident ; determine last usable segment mov cl,4 ; from segment and offset of shr dx,cl ; the resident section mov ax,cs add dx,ax ; add offset to segment sub dx,byte 2 mov es,dx mov di,1 xor ax,ax ; set owner field to 0 stosw add di,byte 5 mov cx,4 ; clear owner name rep stosw ; Thats it. Return to caller. rmrd9: call rststk ; restore old stack pop bx xor al,al ; return without error ret ;==================================================================== ; ; Variables for the resident section align 2, db 0 oldints: old13h: dd 0 ; old interrupt 13h vector old2Fh: dd 0 ; old interrupt 2Fh vector oldF1h: dd 0 ; old interrupt F1h vector oldF8h: dd 0 ; old interrupt F8h vector ; Disk parameters for ram disk statof: dw 0x0041 ; offset to BIOS disk status byte rdaddr: dd 0 ; base address of ram disk rdsize: dw 0 ; size of ram disk in kb cylnum: dw 80 ; number of cylinders secnumlo: dw 2400 ; number of sectors on disk secnumhi: dw 0 secptk: dw 15 ; number of sectors per track heads: db 1 ; number of heads drvnum: db 1 ; number of disk drives drvid: db 0 ; ram disk drive id nohd: db 0 ; no-hard-disk flag syscnf: dw 0 ; system configuration from BIOS align 2, db 0 ; Variables used to access the XMS interface xmshdl: dw 0 ; XMS handle for ram disk xmsadr: dd 0 ; address of XMS driver interface ; Variables used to redirect the stack oldstk: dd 0 ; old stack pointer newstk: times 512 db 0 ; new stack for calling XMS driver newtos: ; new top of stack ; Signature to put into interrupt vector F1h if1sig: db 'NetB' align 16, db 0 ; has to be paragraph aligned ; Descriptor table to access ram disk using the BIOS rd_gdt: dw 0,0,0,0 dw 0,0,0,0 rd_src: dw 0xffff ; length rd_srcb: db 0,0,0 ; base db 0x93 ; typebyte db 0 ; limit16 =0 rd_srcbh: db 0 ; base24 rd_dst: dw 0xffff ; length rd_dstb: db 0,0,0 ; base db 0x93 ; typebyte db 0 ; limit16 =0 rd_dstbh: db 0 ; base24 dw 0,0,0,0 ; BIOS CS dw 0,0,0,0 ; BIOS DS ; DOS disk parameter block. It contains the definitions for the ; floppy disk drive which is redirected by the ramdisk, and used ; for removing the ramdisk drive. Note that this DPB is only ; valid for DOS 4.0 and higher. dpb_addr: dd 0 ; address of DPB in DOS data area dpb_valid: db 0 ; non-zero if DPB is valid dpb: dpb_phys: db 0 ; BIOS ID of physical drive dpb_log: db 0 ; logical DOS drive ID dpb_bpb_low: dw 512 ; BIOS param block for lowest capacity db 0xff dw 1 db 2 dw 64 dw 360 db 0x00 dw 2 dw 9 dw 1 dd 0 dd 0 dpb_fat: db 0 ; flag indicating 16-bit FAT dpb_open: dw 0 ; device open count dpb_type: db 0x01 ; device type dpb_flags: dw 0x0020 + 0x0040 ; flags describing drive dpb_cyls: dw 80 ; number of cylinders dpb_bpb_cur: dw 512 ; BIOS parameter block for current db 1 dw 1 db 2 dw 224 dw 2400 db 0xf9 dw 7 dw 15 dw 2 dd 0 dd 0 dpb_rsvd: db 0, 0, 0, 0, 0, 0 dpb_ltrack: db 0xff ; last accessed track dpb_lacc: dd 0xffffffff ; time of last disk access dpb_volname: db 'NO NAME ' ; volume name db 0 dpb_sernum: dd 0 ; volume serial number dpb_fsname: db 'FAT12 ' ; file system name db 0 dpb_end: ; Copy of bootp block from bootrom. This has to be last in the data area! btplen: dw 0 ; length of bootp block btpnew: ; bootp block has to be at the very end _end: times 4096-(_end-_start) db 0