# first.S86 - primary boot loader for DOS # # Copyright (C) 1996-2003 Gero Kuhlmann # # 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. # # $Id: first.S86,v 1.2 2003/03/30 10:02:36 gkminix Exp $ # #==================================================================== # #include "common.i86" #include "first.i86" .file "first.S86" .line 30 # # Care for debugging symbols # .ifndef ASM_DEBUG ASM_DEBUG = 0 .endif .ifndef DRV_DEBUG DRV_DEBUG = 0 .endif .ifndef ASM_FREEZE_AFTER_INIT ASM_FREEZE_AFTER_INIT = 0 .endif .ifndef ASM_KEYPRESS_AFTER_INIT ASM_KEYPRESS_AFTER_INIT = 0 .endif .ifgt ASM_KEYPRESS_AFTER_INIT ASM_FREEZE_AFTER_INIT = 0 .endif # #==================================================================== # # Define some external routines from the i386 library # \(.text) .extern nbentry # routines in nbentry.s86 .extern nbexit .extern doerr .extern ldrlen # routines in ldrec.s86 .extern ldradr .extern ldrfnd .extern prnstr # routines in scnio.s86 .extern prcrlf .extern prndwd .extern prnwrd .extern prnbyt .extern prnchr .extern getext # routines in misc.s86 .extern getmem .extern movmem # #==================================================================== # # Now start the game # .global _start _start: jmp start1 .word __text_end # end of text segment .word __bss_end + MIN_STK_SIZE # specify total size of data # and BSS segments # Call the i386 library startup routine. It will automatically care # for all arguments given by the bootrom, save all registers and setup # the load record and BOOTP information. start1: mov ax,offset sigmsg mov dx,offset vmagic call nbentry # setup everything # Get the size of extended memory for later call getext mov cs:[extsiz],eax # Find the load record for this boot loader and get the vendor flags. mov si,offset recerr mov al,VENDOR_BOOTL call ldrfnd # find load record for boot loader jc doerr mov bl,fs:BOOT_LD_LENGTH[di] test bl,0xF0 # get offset of vendor information jz doerr and bx,0x000F # within load record shl bx,2 mov al,fs:[bx + di] # get boot loader flags mov cs:[ldflags],al # Next get the address of the ramdisk and its size. # When starting DOS we have to make sure that the ramdisk doesnt get # overwritten by someone. If an XMS driver like HIMEM.SYS gets loaded, # we can protect the ramdisk using it. However, when we are not able # or dont want to use XMS, we have to use some other means, and this # is by using the interrupt 15h method. We redirect interrupt 15h so # that it returns an extended memory size reduced by the ramdisk size. # However, for this to work, the ramdisk has to be at the end of the # physical memory. So, now determine the size of extended memory and # move the ramdisk at the end. If the ramdisk is very large this will # take some time, but thats life... mov si,offset recerr mov al,VENDOR_RAMDISK call ldrfnd # find load record for ramdisk jc doerr call ldradr # get base address if ramdisk jc doerr test eax,0xFFF00000 # must be in extended memory jz doerr mov edx,eax mov ecx,fs:BOOT_LD_MLENGTH[di] cmp ecx,MAX_RD_SIZE # ramdisk should not be larger jae doerr # than 64 MB add ecx,0x000003FF # round up to full kB shr ecx,10 # get ramdisk size in kB mov cs:[rdsize],ecx test byte ptr cs:[ldflags],BOOT_FLAG_INT15 jz start3 # check using int 15 method mov ebx,cs:[extsiz] # get size of extended memory add ebx,0x00000400 # add size of conventional memory sub ebx,ecx # compute new start of ramdisk shl ebx,10 # multiply by 1024 mov eax,edx mov edx,ebx mov ecx,fs:BOOT_LD_ILENGTH[di] call movmem # move the ramdisk mov si,offset moverr jc doerr mov eax,edx shr eax,10 # convert address into kB sub eax,0x00000401 # subtract conventional memory mov cs:[extsiz],eax # save it for later start3: mov cs:[rdaddr],edx # save new ramdisk address .ifgt ASM_DEBUG mov si,offset rdmsg call prnstr mov eax,edx call prndwd # let the use know mov al,0x28 call prnchr mov eax,cs:[rdsize] shl eax,10 call prndwd mov ax,0x29 call prnchr call prcrlf .endif # Get the disk geometry out of the vendor information block in the # load record mov si,offset geoerr mov bl,fs:BOOT_LD_LENGTH[di] test bl,0xF0 jz doerr and bx,0x000F # get offset of vendor infor- shl bx,2 # mation within load record mov eax,fs:BOOT_LD_SECNUM[bx + di] # get number of sectors cmp eax,MAX_RD_SIZE / SECT_SIZE jae doerr mov cs:[secnum],eax mov ax,fs:BOOT_LD_SPT[bx + di] # get sectors per track cmp ax,MAX_SPT_NUM ja doerr mov cs:[secptk],ax mov ax,fs:BOOT_LD_CYL[bx + di] # get number of cylinders cmp ax,MAX_CYL_NUM ja doerr mov cs:[cylnum],ax mov ax,fs:BOOT_LD_HDNUM[bx + di] # get number of heads cmp ax,MAX_HD_NUM ja doerr mov cs:[hdnum],ax mov al,fs:BOOT_LD_NOHD[bx + di] # get no-hard-disk flag mov cs:[nohd],al mov al,fs:BOOT_LD_DRIVE[bx + di] # get ram disk drive id cmp al,BOOT_ID_FD je start2 cmp al,BOOT_ID_HD jne doerr start2: mov cs:[drvid],al # Determine the BIOS drive type according to the total number of # sectors in ramdisk. push ax xor dl,dl cmp al,BOOT_ID_HD # drive type zero for jae start5 # hard disk or for disks test word ptr cs:[secnum + 2],0xFFFF # which are too big jnz start5 cld mov si,offset typtab start4: inc dl lodsw # get next sector number cmp ax,-1 je start4 cmp ax,word ptr cs:[secnum + 0] # save drive type number je start5 or ax,ax # check if at end of table jnz start4 xor dl,dl start5: mov cs:[drvtyp],dl pop ax # Set the address of the BIOS disk status byte mov bx,BIOS_FDSTAT cmp al,BOOT_ID_HD jb setsts mov bx,BIOS_HDSTAT setsts: mov cs:[statof],bx # Get the number of floppy or hard disk drives in the system. mov cx,BIOS_SEG mov es,cx mov cx,es:[BIOS_SYSCNF] # get system configuration word mov cs:[syscnf],cx # from BIOS data area cmp al,BOOT_ID_HD jae getnm2 test cl,0x01 # if at least one floppy drive is jnz getnm4 # installed dont touch the BIOS or cl,0x01 # configuration word and cl,0x3F # otherwise indicate one floppy drive mov es:[BIOS_SYSCNF],cx # in system configuration word getnm4: 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 gotnum getnm1: mov dx,cs:[syscnf] # if int 13h didnt work try it with test dl,0x01 # the mainboard dip switch config jz getnm3 shr dl,6 and dl,0x03 # determine number of floppy disk inc dl # drives jmp gotnum getnm2: mov ah,0x08 mov dl,BOOT_ID_HD int 0x13 # get the number of hard disk jc getnm3 # drives from the BIOS inc dl jmp gotnum getnm3: mov dl,1 # we have at least one drive gotnum: mov cs:[drvnum],dl # save number of disk drives # Save the BIOS disk parameter block for the first floppy drive. This is # necessary so that our own boot block can use it as scratch area. cld push ds mov ax,cs mov es,ax xor ax,ax # save old BIOS disk parameter block mov ds,ax lds si,[I1E_INT] mov di,offset new_dpb mov cx,DPB_SIZE rep movsb pop ds mov ax,cs:[secptk] # prepare DPB for boot sector mov byte ptr cs:[new_dpb + 4],al mov byte ptr cs:[new_dpb + 9],0x0f # 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. push ds mov ax,ds mov es,ax mov ax,cs mov ds,ax mov bx,offset tmpbuf xor al,al # indicate read xor edx,edx # first sector call rwsect # read boot sector pop ds mov si,offset rderr jc doerr cmp byte ptr cs:[drvid],BOOT_ID_HD jb chkbot # there is no partition table for # floppy disks cld mov si,offset tmpbuf mov di,offset partbuf # save partition table for later mov cx,SECT_SIZE / 2 rep movsw mov si,offset dskerr cmp word ptr BOOT_SIG_OFS[partbuf],BOOT_SIGNATURE jne doerr cmp byte ptr PART_STATUS[partbuf],PART_ACTIVE jne doerr cmp byte ptr PART_TYPE[partbuf],PART_FAT16_SM je partok cmp byte ptr PART_TYPE[partbuf],PART_FAT16_LG je partok cmp byte ptr PART_TYPE[partbuf],PART_FAT12 jne doerr partok: mov edx,PART_ABS_SECT[partbuf] push ds mov ax,cs mov ds,ax xor al,al call rwsect # read boot sector pop ds mov si,offset rderr jc doerr chkbot: mov si,offset dskerr cmp word ptr BOOT_SIG_OFS[bx],BOOT_SIGNATURE jne doerr mov al,cs:[drvid] cmp byte ptr DISK_BOOT[bx],al # check boot disk number jne doerr cmp word ptr DISK_BPS[bx],SECT_SIZE # check sector size jne doerr mov ax,cs:[hdnum] cmp word ptr DISK_HEADS[bx],ax # check number of heads jne doerr mov ax,cs:[secptk] cmp word ptr DISK_SPT[bx],ax # check sectors per track jne doerr # Everything is right, so we can now move the resident section to the # end of the conventional memory. With some older bootroms there is # no chance of returning from this point on. Only newer PXE compatible # bootroms set the top of memory correctly. # Note that the resident section doesnt start at offset 0, so we have # to set the segment address to somewhere lower. # Also note that the BOOTP record immediately follows the resident # section, so we have to copy it out of our data area as well. .ifgt ASM_DEBUG mov si,offset resmsg call prnstr call prcrlf .endif call getmem # get memory size in kB cld shl ax,6 # compute last usable segment mov bx,offset btpnew mov cx,bx mov dx,[bootplen] # save length of BOOTP record into mov cs:[btplen],dx # resident area add bx,dx shr bx,4 # compute size of code segment in inc bx # paragraphs sub ax,bx # compute new code segment mov es,ax # set source and destination ptrs push ds mov ax,cs mov ds,ax mov si,offset start_resident mov di,si sub cx,si # compute size of resident area rep movsb # move it pop ds mov si,offset bootpbuf mov cx,dx rep movsb # copy the BOOTP record # Setup all interrupt vectors. Remember to set interrupt 15h only if # required to protect the ramdisk. cli xor ax,ax mov gs,ax # save old interrupt vectors in mov eax,gs:[I13_INT] # copy of resident area mov es:[old13h],eax mov eax,gs:[I15_INT] mov es:[old15h],eax mov eax,gs:[I2F_INT] mov es:[old2Fh],eax mov eax,gs:[IF1_INT] mov es:[oldF1h],eax mov eax,gs:[IF8_INT] mov es:[oldF8h + 0],eax mov bx,es test byte ptr cs:[ldflags],BOOT_FLAG_INT15 jz seti1 mov word ptr gs:[I15_INT + 2],bx mov word ptr gs:[I15_INT + 0],offset int15 seti1: mov word ptr gs:[I13_INT + 2],bx mov word ptr gs:[I13_INT + 0],offset int13 mov word ptr gs:[I2F_INT + 2],bx mov word ptr gs:[I2F_INT + 0],offset int2F mov word ptr gs:[IF8_INT + 2],bx mov word ptr gs:[IF8_INT + 0],offset intF8 mov eax,es:[if1sig] # copy installation check string mov gs:[IF1_INT],eax # into interrupt vector F1h sti # With older DOS versions or FreeDOS it is not possible to use function # 0x4A06 in order to reserve the memory for the resident area. With these # DOS versions we have to decrease the amount of conventional memory so # that DOS does not overwrite this area. Lateron, it is no longer possible # to remove the resident area when removing the ramdisk. It will remain # as lost memory. # Note that we should not overwrite ES here! It has to remain 0. test byte ptr cs:[ldflags],BOOT_FLAG_NORPL jz userpl mov ax,offset start_resident shr ax,4 add ax,bx shr ax,6 mov gs:[BIOS_SEG * 16 + BIOS_BASEMEM],ax userpl: # Output some debugging messages by simply calling the interrupt vectors # which we just created. .ifgt DRV_DEBUG mov ax,0x4A06 int $2F mov si,offset cnvmsg call prnstr # print amount of conventional movzx32 eax,dx # memory call prndwd call prcrlf mov si,offset extmsg call prnstr # print amount of extended memory mov eax,cs:[extsiz] call prndwd call prcrlf .endif # The boot sector had to be placed into a temporary buffer 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. Then call the boot sector with all required parameters. # Note that ES is still 0 at this point. cld xor ax,ax mov es,ax mov si,offset tmpbuf mov di,BOOT_OFFSET mov cx,SECT_SIZE / 2 rep movsw .ifgt ASM_DEBUG mov si,offset debug2 call prnstr call prcrlf .endif .ifgt ASM_FREEZE_AFTER_INIT lop1: jmp lop1 .else .ifgt ASM_KEYPRESS_AFTER_INIT mov si,offset keymsg call prnstr xor ah,ah int 0x16 call prcrlf .endif mov dl,cs:[drvid] mov si,offset [partbuf + PART_STATUS] xor ax,ax mov ds,ax ljmp 0,BOOT_OFFSET .endif # #==================================================================== # # String and constants definitions # \(.data) # Startup signature sigmsg: .byte 0x0D, 0x0A .ascii "DOS Net Boot Image Loader " .ascii "\&VERSION" .byte 0x0D, 0x0A .ascii "\©RIGHT" .byte 0x0D, 0x0A .byte 0x0D, 0x0A .byte 0 # Boot image header vendor magic ID vmagic: .asciz "\&VENDOR_MAGIC" # Ramdisk drive type table: total number of sectors typtab: .word 720 # 0x01 .word 2400 # 0x02 .word 1440 # 0x03 .word 2880 # 0x04 .word -1 # 0x05 .word 5760 # 0x06 .word 0 # Error messages recerr: .asciz "Error in load record data" rderr: .asciz "Error while accessing ramdisk" geoerr: .asciz "Invalid ramdisk geometry" dskerr: .asciz "Wrong ramdisk image" moverr: .asciz "Failed to move ramdisk to end of memory" .ifgt ASM_DEBUG # Some debugging messages rdmsg: .asciz "Ramdisk address = " resmsg: .asciz "Making driver resident" .ifgt ASM_KEYPRESS_AFTER_INIT keymsg: .asciz "Press any key to continue" .endif .ifgt ASM_FREEZE_AFTER_INIT dbgmsg: .asciz "Freezing!" .else dbgmsg: .asciz "Calling boot block" .endif .endif .ifgt DRV_DEBUG cnvmsg: .asciz "RAMDISK: reporting conventional memory size: " extmsg: .asciz "RAMDISK: reporting extended memory size: " .endif # #==================================================================== # # Variable definitions # \(.bss) .extern bootplen # space to hold BOOTP record .extern bootpbuf # length of BOOTP record .lcomm tmpbuf,SECT_SIZE # temporary buffer for boot sector .lcomm partbuf,SECT_SIZE # partition table buffer # #==================================================================== # # Start of resident section. This will be placed at the end of the # conventional RAM area. # # In order to be able to copy this in one piece with all offsets # set correctly by the assembler, we have to put the data values # of the resident section into the text section. Also, to seperate # this area from all the other text sections, we use subsection # number 1 for the resident area. # #==================================================================== # \(.text 1) .align 16 # 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 int2F1 # this has to be a relative jump nop .ascii "RPL" # magic ID string for DOS int2F1: cmp ax,0x4A06 # check for magic ID jne int2F9 mov dx,offset start_resident shr dx,4 # determine last usable segment push ax # from segment and offset of push cs # the resident section pop ax add dx,ax # add offset to segment dec dx pop ax iret int2F9: ljmp cs:[old2Fh] # jump to old interrupt routine # #==================================================================== # # New interrupt 15h routine. This routine gets only installed if # we dont want or cant use XMS to protect the ramdisk. It catches # the "get extended memory" calls and returns a size reduced to the # start of the ramdisk. # Input: AH - Function code # Output: depends on function # Registers changed: depends on function # int15: cmp ah,0x88 jne int151 push ebx mov ebx,cs:[extsiz] # get size of extended memory mov ax,bx # move it into AX cmp ebx,0x00010000 # we cant return more than 64MB, so pop ebx # check if size if larger jae int15e mov ax,0xFFFF # else return maximum amount jmp int15e int151: cmp ax,0xE801 jne int153 push edx mov edx,cs:[extsiz] # get size of extended memory mov ax,dx xor bx,bx cmp edx,0x00003C00 # above 16 MB? jbe int152 mov ax,0x3C00 # determine amount of memory above sub edx,0x00003C00 # 16 MB in 64 kB chunks shr edx,5 mov bx,dx int152: pop edx mov cx,ax # prepare return registers mov dx,bx jmp int15e int153: cmp ax,0xE820 jne int154 stc mov ah,0x86 # function not supported jmp int15f int154: cmp ax,0xE881 jne int159 mov eax,cs:[extsiz] # get size of extended memory xor ebx,ebx cmp eax,0x00003C00 # above 16 MB? jbe int155 mov ebx,0x00003C00 # determine amount of memory above sub eax,ebx # 16 MB in 64 kB chunks shr eax,5 xchg eax,ebx int155: mov ecx,eax # prepare return registers mov edx,ebx int15e: clc int15f: push ax pushf pop ax push bp mov bp,sp mov [bp + 8],al # put the flags onto the stack pop bp pop ax iret int159: ljmp cs:[old15h] # jump to old interrupt routine # #==================================================================== # # New interrupt F8h routine. It can be used to retrieve several # values from the resident driver. This is version 2 of the # interface specification. # Input: AX - function code # Output: depends on function: # # Installation check (AX = 0x9C00) # AX - contains 0x009D # BL - major version of this resident driver # BH - minor version of this resident driver # # Return ramdisk size and address (AX = 0x9C01) # EDX - address of ramdisk # ECX - 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 # CH - low byte of old system configuration word # # Remove ramdisk (AX = 0x9C04) # AL - non-zero if error: # 1 = unable to remove ramdisk driver, ramdisk # remains active # 2 = unable to remove ramdisk driver, ramdisk # becomes unusable # # Return address of disk parameter block (AX = 0x9C05) # BX:DX - address of new disk parameter block # # Registers changed: depends on function # intF8: cmp ah,0x9C # check for magic ID jne intF89 cmp al,0x01 # check for function number jne intF81 mov edx,cs:[rdaddr] # return ramdisk address mov ecx,cs:[rdsize] # return ramdisk size jmp intF89 intF81: cmp al,0x02 jne intF82 mov bx,cs # return address of BOOTP record mov dx,offset btpnew mov cx,cs:[btplen] # return BOOTP length jmp intF89 intF82: cmp al,0x03 jne intF83 mov bx,cs # return address of old interrupt mov dx,offset oldints # vector table mov ax,word ptr cs:[xmshdl] # return XMS handle mov cl,byte ptr cs:[drvid] # return drive id mov ch,byte ptr cs:[syscnf] # return system configuration jmp intF89 intF83: cmp al,0x04 jne intF84 call rmrd # remove ramdisk jmp intF89 intF84: cmp al,0x05 jne intF88 mov bx,cs # return address of new DPB mov dx,offset new_dpb jmp intF89 intF88: or al,al jnz intF89 xchg al,ah # return installation check code inc ax mov bx,RES_VERSION # return interface version number 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. # Note that we have to be careful when using 32-bit registers so that # the high word of a register doesnt get clobbered without being saved # first. # 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 test byte ptr cs:[disabled],0xFF jnz int131 # check if ramdisk has been disabled push ax # check if XMS already initialized test byte ptr cs:[ldflags],BOOT_FLAG_INT15 jnz int13s mov ax,cs:[xmsadr + 0] or ax,cs:[xmsadr + 2] 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 mov cs:[xmsadr + 0],bx # save driver address mov cs:[xmsadr + 2],es pop es call inixms # initialize XMS pop bx int13s: pop ax # Check that this call is for us, and wether we are to simulate a hard # disk or a floppy drive. cmp dl,byte ptr cs:[drvid] je int132 cmp byte ptr cs:[drvid],BOOT_ID_HD jae int13h ############################################# # # # First comes the floppy drive redirector # # # ############################################# cmp dl,BOOT_ID_HD # check if its for a hard disk jb int133 test byte ptr cs:[nohd],0xFF # check if hard disk accesses allowed jz int131 cmp ah,0x08 # function $08 should not return error mov ah,BOOT_ID_HD # return with error jne int135 xor dl,dl # indicate no hard disk present jmp int13f # return without error # Handle function 0x08 for disk drives other than the ramdisk. int133: cmp ah,0x08 jne int131 pushf # function 0x08 has to return the lcall cs:[old13h] # correct number of disk drives mov dl,cs:[drvnum] int13f: xor ah,ah # never return an error jmp int136 # Jump directly to the BIOS int131: ljmp cs:[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 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 movzx bx,ah shl bx,1 # compute pointer into routine table jmp fntab[bx] ############################################# # # # Now comes the hard disk drive redirector # # # ############################################# int13h: cmp dl,BOOT_ID_HD # check if its for a floppy drive jb int131 # then directly call the BIOS # Handle function 0x08 for hard disk drives other than the ramdisk. cmp ah,0x08 jne int137 dec dl pushf # function $08 has to return the lcall cs:[old13h] # correct number of disk drives mov dl,cs:[drvnum] jmp 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 lcall cs:[old13h] # call the BIOS for handling jc int139 cmp ah,0x03 # DX is only used if AH = $03 jne int139 add sp,0x0002 # remove DX from stack if the BIOS jmp 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 lcall cs:[old13h] # simply call the old int 13h routine int139: pop dx jmp 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 mov es:[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 byte ptr [bp + 8],al # put the flags onto the stack or byte ptr [bp + 9],0x02 # set interrupt flag on stack pop bp pop ax iret # Function table fntab: .word f1300 # function 00: reset disk system .word f1301 # function 01: return last error .word f1302 # function 02: read disk .word f1303 # function 03: write disk .word f1304 # function 04: verify disk .word f1305 # function 05: format disk .word f1306 # function 06: format track .word f1307 # function 07: format disk .word f1308 # function 08: get drive parameters .word f1309 # function 09: intialize controller .word f130A # function 0A: read long sectors .word f130B # function 0B: write long sectors .word f130C # function 0C: seek for cylinder .word f130D # function 0D: disk reset .word f130E # function 0E: read sector buffer .word f130F # function 0F: write sector buffer .word f1310 # function 10: check if drive ready .word f1311 # function 11: recalibrate drive .word f1312 # function 12: controller ram diagnostic .word f1313 # function 13: drive diagnostic .word f1314 # function 14: controller int diagnostic .word f1315 # function 15: get disk type .word f1316 # function 16: detect disk change .word f1317 # function 17: set media type for format .word f1318 # function 18: set media type for format f13end: jmp int13e # #==================================================================== # # Function 00 - reset disk system # f1300: test word ptr [syscnf],0x0001 jz f13001 # check if we have physical floppy push dx # drives at all xor dl,dl # always reset the floppy system pushf lcall [old13h] # call old disk interrupt pop dx jc f13002 f13001: xor ah,ah # no error f13002: pop bx jmp f13end # #==================================================================== # # Function 01 - return last error status # # f1301: push es call getsts # get offset to status byte mov ah,es:[bx] # get disk operation status pop es pop bx clc jmp intend # #==================================================================== # # Function 02/03 - read/write from disk # f1302: f1303: pop bx # get old BX from stack push edx push ax call cvtsec # get linear sector number pop ax jnc f13021 mov ah,0x04 # error: sector not found f13028: pop edx stc jmp f13end # terminate f13021: push cx push bx push ax movzx cx,al # move number of sectors into CX f13022: jcxz f13026 cmp edx,[secnum] # check if sector is still correct jb f13023 pop ax mov ah,0x04 # error: sector not found f13027: sub al,cl # compute number of sectors processed pop bx pop cx jmp f13028 f13023: pop ax push ax xor al,al cmp ah,0x02 # check if read or write sector je f13024 inc al f13024: call rwsect # actually handle request jnc f13025 pop ax mov ah,0x20 # error: disk controller error jmp f13027 f13025: inc dx dec cx # proceed with next sector add bx,SECT_SIZE jmp f13022 f13026: pop ax pop bx pop cx pop edx xor ah,ah # no error jmp f13end # #==================================================================== # # Function 08 - get disk drive parameters # f1308: pop bx # get old BX from stack mov dx,[hdnum] # get number of heads dec dx mov dh,dl mov dl,byte ptr [drvnum] # get number of disk drives mov cx,[cylnum] dec cx xchg cl,ch # put low order 8 bits into CH shl cl,6 # put high order 2 bits into CL mov ax,[secptk] # get number of sectors per track or cl,al # put it into CL mov bl,[drvtyp] # ramdisk drive type xor ax,ax jmp f13end # #==================================================================== # # 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 f13end # #==================================================================== # # 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 f13end # #==================================================================== # # Function 0C - seek for cylinder # f130C: pop bx # get old BX from stack push edx push ax call cvtsec # get linear sector number pop ax mov ah,0x00 # no error jnc f130C1 mov ah,0x04 # error: sector not found f130C1: pop edx jmp f13end # terminate # #==================================================================== # # Function 15 - get disk type # f1315: pop bx # get old BX from stack mov ah,0x02 # indicate floppy disk cmp dl,BOOT_ID_HD # check if floppy disk type requested jb f13159 inc ah # indicate hard disk mov dx,word ptr [secnum + 0] mov cx,word ptr [secnum + 2] f13159: clc jmp f13end # #==================================================================== # # Convert Cyl/Sec/Head notation into a linear sector number # Input: CH - cylinder number # CL - sector number, high bits of cylinder number # DH - head number # Output: EDX - linear sector number # carry flag set if invalid sector number # Registers changed: AX,EDX # cvtsec: push bx movzx bx,dh # move current head number into BX cmp bx,[hdnum] # check if head number is correct jae cvts8 # maximum number of heads is always 2 mov al,ch # compute track number into AX, the mov ah,cl # upper two bits of CL are the high shr ah,6 # bits of the 10 bit cylinder number cmp ax,[cylnum] jae cvts8 mul word ptr [hdnum] # compute track number from cylinders add ax,bx adc dx,0 mov bx,ax mov ax,dx mul word ptr [secptk] # compute number of first sector on xchg bx,ax # track mul word ptr [secptk] add dx,bx mov bl,cl and bx,0x003F # move sector number into AX cmp bx,[secptk] # check if sector number is correct ja cvts8 dec bx # sector numbers start with 1 js cvts8 # therefore sector 0 does not exist add ax,bx # compute final sector number adc dx,0 shl edx,16 # convert into 32-bit value mov dx,ax cmp edx,[secnum] # check if the sector is valid jae cvts8 clc # no error jmp cvts9 cvts8: stc # return with error cvts9: pop bx ret # #==================================================================== # # Read/write a sector from the ram disk. This routine requires a # sector to be 512 bytes long. # Input: AL - non-zero if write to ram disk # EDX - logical sector number # ES:BX - pointer to destination buffer # Output: carry flag set if error # Registers changed: AX # rwsect: push cx push dx mov ch,al # save direction indicator mov dx,es mov ax,dx shr ax,12 shl dx,4 # compute linear buffer address add dx,bx adc ax,0 or ch,ch # check direction of transfer jz rwsec1 # set source address for write mov word ptr [rd_srcb + 0],dx mov byte ptr [rd_srcb + 2],al mov byte ptr [rd_srcc],0 jmp rwsec2 # set destination address for read rwsec1: mov word ptr [rd_dstb + 0],dx mov byte ptr [rd_dstb + 2],al mov byte ptr [rd_dstc],0 rwsec2: pop dx push edx shl edx,9 # compute linear ramdisk address add edx,[rdaddr] # from sector number or ch,ch # check direction of transfer jz rwsec3 # set destination address for write mov word ptr [rd_dstb + 0],dx shr edx,16 mov byte ptr [rd_dstb + 2],dl mov byte ptr [rd_dstc],dh jmp rwsec4 # set source address for read rwsec3: mov word ptr [rd_srcb + 0],dx shr edx,16 mov byte ptr [rd_srcb + 2],dl mov byte ptr [rd_srcc],dh rwsec4: push es push si mov ax,cs mov es,ax mov si,offset rd_gdt mov cx,SECT_SIZE / 2 # copy 512 bytes, e.g. 256 words mov ax,0x8700 # let the BIOS move the sector int 0x15 pop si pop es pop edx pop cx 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,BIOS_SEG # status byte is in BIOS data mov es,bx # segment mov bx,cs:[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. Note that we # have to provide two different versions of this routine depending # on the version of the XMS handler. # 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 cmp ah,0x03 jb inixm1 # call XMS 2.0 routine # XMS version 3.0 can handle more than 64MB of memory # First determine the amount of total memory installed. Then grab all # memory which is available, and finally resize it to fit the ramdisk. push eax # save upper 16 bits of EAX mov ah,0x88 xor bl,bl # get amount of extended memory call callxm mov edx,eax pop eax or bl,bl jz inixm2 # this can happen with himem.sys used # by OpenDOS even if it says V3.0 mov edx,[rdsize] # in this case just say that we have add edx,HMA_SIZE # enough for the ramdisk inixm2: push edx mov ah,0x89 call callxm # grab largest memory block pop ecx or ax,ax # check for error jz inixm1 # if error, try 16-bit routine mov [xmshdl],dx # save handle mov ebx,[rdsize] # get ramdisk size add ebx,HMA_SIZE # care for missing HMA cmp ebx,ecx # check if enough memory for ram disk jae inixm7 # if not, leave all memory allocated mov ah,0x8F # handle is still in DX call callxm # resize memory block jmp inixm7 # Now handle the XMS version 2.0 protocol. This protocol does not allow # more memory than 64MB. inixm1: mov ah,0x08 xor bl,bl # get amount of extended memory call callxm mov dx,ax or bl,bl # check for error jnz inixm8 push dx # grab largest block - which is whole mov ah,0x09 # memory because there should be no call callxm # other process using XMS pop cx or ax,ax # check for error jz inixm8 mov [xmshdl],dx # save handle mov ebx,[rdsize] # get size of ramdisk add ebx,HMA_SIZE # care for a missing HMA cmp ebx,0x00010000 # if ram disk is larger than 64 MB jae inixm7 # we dont have to check memory amount cmp bx,cx # check if enough memory for ram disk jae inixm7 # if not leave all memory allocated mov ah,0x0F # handle is still in DX call callxm # resize memory block # Now lock the memory block and check that the physical address of the # memory block is correct. inixm7: mov dx,[xmshdl] mov ah,0x0C # lock memory block call callxm add bx,0x03FF adc dx,0x0001 # add 65kb - maximum difference sub bx,word ptr [rdaddr + 0] sbb dx,word ptr [rdaddr + 2] jnc inixm9 # Thats it. Restore all registers and swap the stack back to its # original state. inixm8: mov word ptr [xmshdl],0 # in case of error dont return handle 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 push dword ptr cs:[xmsadr] lret # call XMS driver # #==================================================================== # # Set new stack. After calling this routine we can use EBX, ECX and EDX. # Input: none # Output: none # Registers changed: AX, BX, DS, SS, SP setstk: cli pop bx # get return address mov ax,sp # save old stack pointer mov word ptr cs:[oldstk + 0],ax mov word ptr cs:[oldstk + 2],ss mov ax,cs mov ss,ax mov sp,offset newtos - 2 # change to new stack sti push ebx # we have to save the upper 16 push ecx # bits of EBX too! push edx 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 ax # get return address pop ds pop es pop di pop si # restore all registers pop edx pop ecx pop ebx mov bx,ax cli mov ax,word ptr cs:[oldstk + 0] mov ss,word ptr cs:[oldstk + 2] mov sp,ax sti jmp bx # return to caller # #==================================================================== # # Remove ramdisk from memory. This involves restoring all interrupt # vectors and freeing all used memory. The DOS drive table has to # get restored by the caller in order to make the physical floppy # drive accessible again. Note that this is different from the # previous version of this routine. # Since we need to call the XMS driver, we have to switch stacks like # with inixms. # Input: none # Output: AL - non-zero if error: # 1 = ramdisk remains active # 2 = ramdisk becomes unusable # Registers changed: EAX # rmrd: push bx call setstk # set new stack # First 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. If we cant restore # interrupt 15h it doesnt matter. The ramdisk then just remains # physically in memory unless we used the XMS driver. xor ax,ax mov es,ax # first check that nobody redirected mov ax,cs # our own interrupts cmp word ptr es:[IF8_INT + 2],ax jne rmrd8 cmp word ptr es:[IF8_INT + 0],offset intF8 jne rmrd8 mov eax,es:[IF1_INT] # interrupt F1h contains a signature cmp eax,[if1sig] # and no vector je rmrd1 rmrd8: call rststk # return with error pop bx mov al,0x01 ret rmrd1: 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,offset int13 jne rmrd4 cmp dx,offset int13 je rmrd5 rmrd4: or byte ptr [ldflags],BOOT_FLAG_KEEP inc byte ptr cs:[disabled] # if unable to restore interrupt 13h, mov ah,0x13 # set it back again and disable the int 0x2f # ramdisk rmrd5: pop ds cli # restore the other interrupts xor ax,ax mov es,ax mov eax,[oldF1h] mov es:[IF1_INT],eax mov eax,[oldF8h] mov es:[IF8_INT],eax test byte ptr [ldflags],BOOT_FLAG_INT15 jz rmrd2 mov ax,cs cmp word ptr es:[I15_INT + 2],ax jne rmrdB cmp word ptr es:[I15_INT + 0],offset int15 jne rmrdB mov eax,[old15h] mov es:[I15_INT],eax jmp rmrd2 rmrdB: or byte ptr [ldflags],BOOT_FLAG_KEEP rmrd2: sti # Restore the BIOS system configuration flag cmp byte ptr [drvid],BOOT_ID_HD jae rmrdA # only necessary with floppy drive mov ax,BIOS_SEG mov es,ax mov ax,[syscnf] and ax,0x00C1 # isolate the floppy relevant bits mov bx,es:[BIOS_SYSCNF] and bx,0xFF3E or ax,bx # set old floppy flags and restore mov es:[BIOS_SYSCNF],ax # old configuration # Next remove the ramdisk image from extended memory using the XMS driver. # If we cant remove it, well thats just bad luck because it will remain # eating up memory, but nothing else. rmrdA: 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 push dx mov ax,0x0E call callxm # get lock count pop dx or ax,ax jz rmrd7 or bh,bh # dont try to free the block if jnz rmrd7 # the lock count is not zero mov ah,0x0A call callxm # free memory block # Finally we can remove the memory used by the ramdisk driver. We only # reset the owner field of the memory control block to 0 to indicate # it as free. However, this is only possible if the DOS version # supports RPL loading. If the ramdisk has been disabled, then interrupt # 13h couldnt get restored, so dont remove this code. rmrd7: test byte ptr [ldflags],BOOT_FLAG_NORPL + BOOT_FLAG_KEEP jnz rmrd3 cld mov dx,offset start_resident shr dx,4 mov ax,cs add dx,ax sub dx,2 mov es,dx mov di,0x0001 mov al,es:[di - 1] # get MCB type cmp al,0x4D je rmrd6 # has to be either M or Z cmp al,0x5A jne rmrd3 rmrd6: xor ax,ax # set owner field to 0 stosw add di,5 mov cx,4 # clear owner name rep stosw rmrd9: call rststk # restore old stack pop bx xor al,al # return without error ret rmrd3: call rststk pop bx # indicate that we removed the mov al,0x02 # ramdisk but not the driver ret # #==================================================================== # # Variables for the resident section. This has to be within the text # segment of the resident section. # .align 2 oldints: old13h: .long 0 # old interrupt 13h vector old15h: .long 0 # old interrupt 15h vector old2Fh: .long 0 # old interrupt 2Fh vector oldF1h: .long 0 # old interrupt F1h vector oldF8h: .long 0 # old interrupt F8h vector # Disk parameters for ram disk syscnf: .word 0 # system configuration from BIOS statof: .word BIOS_FDSTAT # offset to BIOS disk status byte rdaddr: .long 0 # base address of ram disk rdsize: .long 0 # size of ram disk in kb cylnum: .word 80 # number of cylinders secnum: .long 2400 # number of sectors on disk secptk: .word 15 # number of sectors per track hdnum: .word 2 # number of heads drvnum: .byte 1 # number of disk drives drvid: .byte 0 # ramdisk drive id drvtyp: .byte 0 # ramdisk drive type nohd: .byte 0 # no-hard-disk flag disabled: .byte 0 # non-zero if ramdisk disabled .align 2 # Variables used to access the XMS interface xmshdl: .word 0 # XMS handle for ram disk xmsadr: .long 0 # address of XMS driver interface extsiz: .long 0 # new size of extended memory in kB # for interrupt 15h interface # Variables used to redirect the stack .align 2 oldstk: .long 0 # old stack pointer newstk: .space 512,0x90 # new stack for calling XMS driver newtos: # new top of stack # Signature to put into interrupt vector F1h if1sig: .ascii "NetB" # Flags passed to the boot loader ldflags: .byte 0 # Descriptor table to access ram disk using the BIOS rd_gdt: .word 0,0,0,0 .word 0,0,0,0 rd_src: .word 0xFFFF # length rd_srcb: .byte 0,0,0 # base .byte 0x93 # typebyte .byte 0 # limit16 rd_srcc: .byte 0 # base24 rd_dst: .word 0xFFFF # length rd_dstb: .byte 0,0,0 # base .byte 0x93 # typebyte .byte 0 # limit16 rd_dstc: .byte 0 # base24 .word 0,0,0,0 # BIOS CS .word 0,0,0,0 # BIOS DS # BIOS disk parameter block. This is actually just a scratch area for # our own boot sector. new_dpb: .space DPB_SIZE,0 # Copy of bootp block from bootrom. This has to be last in the data area# btplen: .word 0 # length of bootp block btpnew: # bootp block has to be at the very end # #==================================================================== # .end