# nbramdrv.S86 - DOS ramdisk device driver # # Copyright (C) 2002-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: nbramdrv.S86,v 1.1 2003/03/09 00:43:09 gkminix Exp $ # #==================================================================== # #include "common.i86" #include "boot.i86" .file "nbramdrv.s86" .line 30 # #==================================================================== # # Define the text segment. This segment has to start at 0x0000. It # starts with a device header. This program actually incorporates # two device driver. The first driver is a character device driver # which serves as an interface to the global ramdisk data (XMS handle, # ramdisk address etc.). It is also responsible for initializing # everything. By using a character device it is possible to avoid # DOS to insert a new drive into the device chain. Instead we later # want to let our block device driver replace the disk driver which # currently serves the ramdisk using interrupt 13h. # \(.text) .global _start _start: char_dev_header: .long -1 # last driver in list .word 0xC000 # character device .word char_strat # offset to strategy routine .word char_int # offset to interrupt routine .ascii "RAMCNTR0" # device driver name # Table containing offsets to handling routines. chrtab: .byte 0x0C # highest supported function number .word doinit # 0x00 - initialize device driver .word unsupp # 0x01 - media check .word unsupp # 0x02 - build BPB .word char_read # 0x03 - ioctl input .word char_read # 0x04 - read from device .word unsupp # 0x05 - non-destructive read .word donop # 0x06 - input status .word donop # 0x07 - input flush .word char_write # 0x08 - output .word char_write # 0x09 - output with verify .word donop # 0x0A - output status .word donop # 0x0B - output flush .word char_write # 0x0C - ioctl output .align 2 # #==================================================================== # # Data area for the ramdisk driver # chareq: .long 0 # pointer to char dev request header blkreq: .long 0 # pointer to block dev request header # Data values returned by the character device driver .align 2 chrdat: .word 0x0001 # version of this data structure drvhdr: .word rd_dev_header # offset of ramdisk device header .word 0 # segment of ramdisk device header rdsize: .long 0 # size of ramdisk in kB rdaddr: .long 0 # physical address of ramdisk rdofs: .long 0 # offset to ramdisk within XMS block xmshdl: .word 0 # XMS handle for ramdisk xmsadr: .long 0 # pointer to XMS handler hidden: .long 0 # number of hidden sectors sptrk: .word 0 # number of sectors per track trknum: .word 0 # number of tracks hdnum: .word 0 # number of heads opncnt: .word 0 # count of open files on device drvnum: .word 0 # number of logical drives drvmap: .long 0 # map of logical drives logdrv: .byte 0 # logical drive number of ramdisk devtyp: .byte 0 # DOS device type devatr: .word 0 # DOS device attributes accflg: .byte 1 # driver access flag rdonly: .byte 0 # readonly flag active: .byte 1 # active flag btphdl: .word 0 # XMS handle for DHCP/BOOTP info block btpsiz: .word 0 # size of BOOTP information block chrend: # Parameter block for moving a memory block using the XMS driver xmsemm: xmslen: .long 0 # number of bytes to transfer srchdl: .word 0 # XMS source handle srcofs: .long 0 # source offset dsthdl: .word 0 # XMS destination handle dstofs: .long 0 # destination offset # BIOS disk parameter block for ramdisk device. If we remove the resident # interrupt 13h driver, it still may contain the DPB for the ramdisk. In # that case we have to relocate the DPB so that it doesnt get lost. dpb: .space DPB_SIZE,0 # DOS BIOS parameter block for ramdisk device. This gets filled in by the # init routine by reading the boot sector of the ramdisk bpb: .space BPB_SIZE,0 bpbtab: .word bpb # list of BPB pointers .word 0 # File system name, volume number and volume name. These variables are just # needed to handle the corresponding IOCTL calls. We never write them back # into the ramdisk because its useless: on the next boot, the values will be # the old ones again. volnum: .long 0 # volume number volnam: .space VOLNAME_LEN + 1,0 # volume name fsname: .space FSNAME_LEN + 1,0 # file system name # #==================================================================== # # Character device strategy routine. It simply saves the request header # address. # Input: ES:BX - Address of request header # Output: none # Registers changed: none # char_strat: mov word ptr cs:[chareq + 0],bx mov word ptr cs:[chareq + 2],es lret # #==================================================================== # # Routine to call driver handler routine # Input: BX - Offset to handler table # ES:DI - Pointer to request header # Output: none # Registers changed: AX, BX, CX, DX, SI # dorequest: mov al,es:[di + 2] # get command value cmp al,[bx] # check if valid jbe chrin1 mov ax,0x8003 # function not supported jmp chrin2 chrin1: xor ah,ah shl ax,1 inc bx # get pointer into routine table add bx,ax call [bx] # call handler chrin2: or ax,0x0100 # indicate "request done" mov es:[di + 3],ax # return status ret # #==================================================================== # # Character device interrupt routine. This does the actual job of # dispatching the request. # Input: none # Output: none # Registers changed: none # char_int: push es push ds pushad # save all registers mov ax,cs mov ds,ax mov bx,offset chrtab les di,[chareq] # get pointer to request header call dorequest # handle character request popad pop ds pop es lret # return to caller # #==================================================================== # # Function not supported # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # unsupp: mov ax,0x8003 # return code: function unsupported ret # #==================================================================== # # Return ramdisk driver variables # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX, CX, SI # char_read: mov cx,offset chrend - offset chrdat cmp cx,word ptr es:[di + 18] jbe cread1 mov cx,word ptr es:[di + 18] cread1: mov ax,cx jcxz cread8 cld push es push di les di,es:[di + 14] mov si,offset chrdat rep movsb # move data into user buffer pop di pop es cread8: mov es:[di + 18],ax # return size of data donop: xor ax,ax # return without error ret # #==================================================================== # # Set ramdisk driver variables # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX, BX, CX, DL # char_write: cld push es push di mov cx,word ptr es:[di + 18] cmp cx,4 # length of command string has to jb cwrit8 # be between 4 and 6 (to care for cmp cx,6 # trailing end-of-line). jbe cwrit1 cwrit8: mov ax,0x800A # return with error xor cx,cx # indicate no characters written jmp cwrit9 cwrit1: les di,es:[di + 14] # get address of command buffer cmp word ptr es:[di + 0],0x4452 jne cwrit8 # check magic command value mov dl,1 # max argument value mov ax,es:[di + 2] # get command byte mov bx,offset rdonly cmp al,0x52 # check for READONLY command je cwrit2 mov bx,offset accflg cmp al,0x41 # check for ACCESS command je cwrit2 mov dl,2 mov bx,offset active cmp al,0x53 # check for START command jne cwrit8 # command not recognized cwrit2: sub ah,0x30 jb cwrit8 # check for correct argument cmp ah,dl # has to be between 0 and 1 ja cwrit8 mov [bx],ah # save new flag value xor ax,ax # return without error cwrit9: pop di pop es mov word ptr es:[di + 18],cx ret # #==================================================================== # # End of character driver section. # #==================================================================== # end_chardrv: # #==================================================================== # # Device header for ramdisk block device. This device header gets # inserted into the DOS chain by the character device init routine. # .align 16 rd_dev_header: .long -1 # last driver in list .word 0x48C2 # block device with IOCTL support .word rd_strat # offset to strategy routine .word rd_int # offset to interrupt routine nunits: .byte 0 # number of subunits supported # Table containing offsets to handling routines. blktab: .byte 0x19 # highest supported function number .word donop # 0x00 - initialize device driver .word media_check # 0x01 - media check .word build_bpb # 0x02 - build BPB .word char_read # 0x03 - ioctl input .word block_read # 0x04 - read from device .word unsupp # 0x05 - non-destructive read .word unsupp # 0x06 - input status .word unsupp # 0x07 - input flush .word block_write # 0x08 - output .word block_write # 0x09 - output with verify .word unsupp # 0x0A - output status .word unsupp # 0x0B - output flush .word char_write # 0x0C - ioctl output .word block_open # 0x0D - device open .word block_close # 0x0E - device close .word media_rem # 0x0F - removable media .word unsupp # 0x10 - output until busy .word unsupp # 0x11 .word unsupp # 0x12 .word gen_ioctl # 0x13 - generic ioctl .word unsupp # 0x14 .word unsupp # 0x15 .word unsupp # 0x16 .word get_logical # 0x17 - get logical device .word set_logical # 0x18 - set logical device .word check_ioctl # 0x19 - check generic IOCTL support .align 2 # #==================================================================== # # Block device strategy routine. It simply saves the request header # address. # Input: ES:BX - Address of request header # Output: none # Registers changed: none # rd_strat: mov word ptr cs:[blkreq + 0],bx mov word ptr cs:[blkreq + 2],es lret # #==================================================================== # # Block device interrupt routine. This does the actual job of # dispatching the request. # Input: none # Output: none # Registers changed: none # rd_int: push es push ds pushad # save all registers mov ax,cs mov ds,ax mov bx,offset blktab les di,[blkreq] # get pointer to request header call dorequest # handle block request popad pop ds pop es lret # return to caller # #==================================================================== # # Check for correct subunit number. Note that this routine terminates # the calling routine on error # Input: ES:DI - Pointer to request header # Output: none # Registers changed: none # chkdrv: push ax movzx ax,byte ptr es:[di + 1] cmp ax,[drvnum] # check subunit number of drive pop ax jb chkdr9 pop ax # remove callers address from stack mov ax,0x8001 # set error code chkdr9: ret # #==================================================================== # # Check if access to ramdisk allowed. Note that this routine terminates # the calling routine on error # Input: ES:DI - Pointer to request header # Output: none # Registers changed: none # chkacc: test byte ptr [accflg],0xFF # check access flag jnz chkac9 pop ax # remove callers address from stack mov ax,0x8002 # set error code chkac9: ret # #==================================================================== # # Check if writing into ramdisk allowed. Note that this routine terminates # the calling routine on error # Input: ES:DI - Pointer to request header # Output: none # Registers changed: none # chkrdo: test byte ptr [rdonly],0xFF # check readonly flag jnz chkrd1 test byte ptr [active],0xFF # check active flag jnz chkrd9 chkrd1: pop ax # remove callers address from stack mov ax,0x8000 # set error code chkrd9: ret # #==================================================================== # # Return media check status - ramdisk cant change # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # media_check: call chkdrv # check subunit number mov al,0x01 cmp byte ptr [active],2 # let DOS think that the medium jne mchk8 # has been changed to let it mov al,0xFF # reread the FAT mov byte ptr [active],1 mchk8: mov byte ptr es:[di + 14],al mov word ptr es:[di + 15],offset volnum mov word ptr es:[di + 17],cs xor ax,ax ret # #==================================================================== # # Return check if medium is removable # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # media_rem: call chkdrv # check subunit number xor ax,ax test word ptr [devatr],0x0001 jz mrem9 mov ax,0x0200 # not removable mrem9: ret # #==================================================================== # # Build BIOS parameter block. This routine builds a BPB from the boot # sector contents provided by the caller. Since the ramdisk boot sector # cant change, we always return the same BPB. # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # build_bpb: call chkdrv # check subunit number mov word ptr es:[di + 18],offset bpb mov word ptr es:[di + 20],cs xor ax,ax ret # #==================================================================== # # Open device # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # block_open: call chkdrv # check subunit number call chkacc # check access flag inc word ptr [opncnt] xor ax,ax ret # #==================================================================== # # Close device # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # block_close: call chkdrv # check subunit number call chkacc # check access flag test word ptr [opncnt],0xFFFF jz close9 dec word ptr [opncnt] close9: xor ax,ax ret # #==================================================================== # # Read from ramdisk device # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # block_read: xor ax,ax # indicate read access bread1: call chkdrv # check subunit number call chkacc # check access flag movzx32 edx,word ptr es:[di + 20] cmp byte ptr es:[di + 0],0x1E jne bread2 # Compaq DOS and DR-DOS use a different cmp dx,0xFFFF # layout of the request header jne bread3 mov edx,es:[di + 26] # get 32-bit starting sector number jmp bread3 bread2: cmp byte ptr es:[di + 0],0x18 jne bread3 mov edx,es:[di + 20] # for Compaq DOS and DR-DOS bread3: add edx,[hidden] mov cx,es:[di + 18] # get sector count mov bx,es:[di + 16] # get transfer address mov si,es:[di + 14] call rdrw # actually transfer the sectors mov es:[di + 18],cx # return the number of sectors ret # #==================================================================== # # Write into ramdisk device # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # block_write: call chkrdo # check if ramdisk is readonly mov ax,1 # indicate write access jmp bread1 # #==================================================================== # # Transfer a number of sectors into or out of the ramdisk. This routine # assumes a sector size of 512 bytes. Note that the sector number starts # at the beginning of the ramdisk, not at the beginning of the partition. # Input: EDX - Sector number # CX - Number of sectors # AX - Non-zero if writing into the ramdisk # BX:SI - Source / destination address # Output: CX - Number of sectors transferred # AX - Error code # Registers changed: EAX, EBX, ECX, EDX, SI # rdrw: push di mov di,ax and ecx,0x0000FFFF mov eax,[rdsize] shl eax,1 # determine size of ramdisk in sectors sub eax,edx jnc rdrw1 rdrw5: xor cx,cx rdrw6: mov ax,0x8006 # return seek error pop di ret rdrw1: cmp eax,ecx # check if going to read past end of jbe rdrw2 # ramdisk mov eax,ecx rdrw2: shl eax,9 # convert sector count into byte count mov [xmslen],eax mov ax,[xmshdl] shl edx,9 # convert sector number into offset add edx,[rdofs] # depending on the XMS driver the ram- or di,di # disk might not start at the beginning jz rdrw3 # of the XMS memory block mov [dstofs],edx # set destination address mov [dsthdl],ax xor ax,ax mov word ptr [srcofs + 0],si mov word ptr [srcofs + 2],bx mov word ptr [srchdl],ax jmp rdrw4 rdrw3: mov [srcofs],edx # set source address mov [srchdl],ax xor ax,ax mov word ptr [dstofs + 0],si mov word ptr [dstofs + 2],bx mov word ptr [dsthdl],ax rdrw4: push cx mov ah,0x0B mov si,offset xmsemm lcall [xmsadr] # call XMS driver to do the transfer pop cx or ax,ax # transfer error jz rdrw5 mov eax,[xmslen] shr eax,9 cmp cx,ax # check if all sectors transferred je rdrw7 mov cx,ax # return number of transferred sectors jmp rdrw6 # together with seek error rdrw7: xor ax,ax pop di ret # #==================================================================== # # End of simple block driver section. The following code offers # extended IOCTL services. # #==================================================================== # end_simple: # #==================================================================== # # Return logical drive ID # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX, DX # get_logical: call chkdrv # check subunit number xor dx,dx test word ptr [devatr],0x0001 jz getlo9 cmp word ptr [drvnum],1 jbe getlo9 mov dl,[logdrv] getlo9: mov es:[di + 1],dl # return logical drive number xor ax,ax ret # #==================================================================== # # Set logical drive ID # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX, DX # set_logical: call chkdrv # check subunit number xor dx,dx test word ptr [devatr],0x0001 jz setlo9 cmp word ptr [drvnum],1 jbe setlo9 mov dl,es:[di + 1] inc dl # set new logical drive mov [logdrv],dl setlo9: mov es:[di + 1],dl # return logical drive number xor ax,ax ret # #==================================================================== # # Generic IOCTL function table # gentab: .byte 0x40 # set device parameters .word set_params .byte 0x41 # write physical track .word write_track .byte 0x46 # set volume serial number .word set_volnum .byte 0x47 # set access flag .word set_access .byte 0x60 # get device parameters .word get_params .byte 0x61 # read physical track .word read_track .byte 0x62 # verify track address .word verify_track .byte 0x66 # get volume serial number .word get_volnum .byte 0x67 # get access flag .word get_access .byte 0x68 # get media type .word get_mediaid .byte 0x6F # get drive map information .word get_drvmap .byte 0 # #==================================================================== # # Find handler address for generic IOCTL command # Input: AX - Command code # Output: BX - Pointer to handler offset (zero if not found) # Registers changed: AX, BX # fndgen: cmp al,0x08 # check if category code for disk jne fndg8 mov bx,offset gentab + 1 fndg1: mov al,[bx - 1] or al,al # check if at end of list jz fndg8 cmp al,ah # check if command code found je fndg9 add bx,3 # check next command code jmp fndg1 fndg8: xor bx,bx fndg9: ret # #==================================================================== # # Check if generic IOCTL function supported # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX, BX # check_ioctl: call chkdrv # check subunit number mov ax,es:[di + 13] # get command code call fndgen # find handler address mov ax,0x8003 or bx,bx jz chkio9 xor ax,ax # return without error chkio9: ret # #==================================================================== # # Call generic IOCTL command handler # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: AX # gen_ioctl: call chkdrv # check subunit number mov ax,es:[di + 13] # get command code call fndgen # find handler address mov ax,0x8003 or bx,bx jz genio9 push es push di les di,es:[di + 19] # call handler with pointer to call [bx] # parameter block pop di pop es genio9: ret # #==================================================================== # # Convert track/sector/head into linear sector number (starting from # beginning of ramdisk, i.e. not relative to beginning of partition). # Input: BX - Track number # CX - Sector number # DX - Head number # Output: EDX - Linear sector number # AX - Error code, or zero if no error # Registers changed: AX, CX, EDX # cvtsec: cmp bx,[trknum] # check for valid track number jae cvts1 # starts with zero cmp cx,[sptrk] # check for valid sector number ja cvts1 # starts with one jcxz cvts1 # sector zero doesnt exist cmp dx,[hdnum] # check for valid head number jb cvts2 # starts with zero cvts1: mov ax,0x8006 # return seek error ret cvts2: dec cx mov ax,dx mul cx # this should never overflow mul bx # this can make DX non-zero shl edx,16 mov dx,ax # convert DX:AX into EDX xor ax,ax ret # #==================================================================== # # Read a number of sectors from the ramdisk # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: EAX, EBX, ECX, EDX, SI # read_track: xor si,si # indicate read access rdtrk1: call chkacc # check access flag mov bx,es:[di + 3] # get track number mov cx,es:[di + 5] # get first sector number mov dx,es:[di + 1] # get head number call cvtsec # convert into linear sector number xor cx,cx or ax,ax jnz rdtrk9 # check for error mov ax,si mov bx,es:[di + 11] # get transfer address mov si,es:[di + 9] mov cx,es:[di + 7] # get number of sectors call rdrw # actually transfer the data rdtrk9: mov es:[di + 7],cx # save number of sectors transferred ret # #==================================================================== # # Write a number of sectors into the ramdisk # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: EAX, EBX, ECX, EDX, SI # write_track: call chkrdo # check if ramdisk is readonly mov si,1 # indicate write access jmp rdtrk1 # #==================================================================== # # Verify a number of tracks # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: EAX, BX, CX, EDX # verify_track: call chkacc # check access flag mov bx,es:[di + 3] # get first track number mov cx,[sptrk] # check for last sector mov dx,es:[di + 1] # get head number call cvtsec # this will check for a valid sector or ax,ax mov dl,0x02 jnz vrfy9 test byte ptr es:[di + 0],0x01 jz vrfy8 # check if to verify multiple tracks mov cx,es:[di + 5] # get number of tracks jcxz vrfy8 dec cx add bx,cx # get last track number mov cx,[sptrk] # check for last sector mov dx,es:[di + 1] # get head number call cvtsec # check for a valid sector or ax,ax mov dl,0x02 jnz vrfy9 vrfy8: xor dl,dl vrfy9: mov es:[di + 0],dl # save result code xor ax,ax # indicate no error ret # #==================================================================== # # Return the drive parameters # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: AX, CX, SI # get_params: cld push di mov al,0x04 # set special functions flag stosb mov al,[devtyp] # set device type stosb mov ax,[devatr] # set device attributes stosw mov ax,[trknum] # set number of cylinders stosw xor ax,ax # set media type stosb mov si,offset bpb # copy BPB into parameter block mov cx,31 # only first 31 bytes of BPB get rep movsb # copied pop di xor ax,ax ret # #==================================================================== # # Set the drive parameters # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: AX, EBX, CX, EDX, SI # set_params: mov cx,0x800C # all sectors have to have same size test byte ptr es:[di + 0],0x04 jz setp9 test byte ptr es:[di + 0],0x02 jnz setp8 # use only track layout field mov ax,es:[di + 7 + BPB_HEADNUM] cmp ax,MAX_HEADS # check number of heads ja setp9 mov bx,es:[di + 7 + BPB_SPTRACK] cmp bx,MAX_SECTS # check number of sectors per track ja setp9 mul bx mov bx,es:[di + 4] cmp bx,MAX_CYLS # check number of tracks ja setp9 mul bx # compute total number of sectors shl edx,16 # get total number of sectors as a mov dx,ax # 32-bit value movzx32 ebx,word ptr es:[di + 7 + BPB_TOTSECT] or ebx,ebx jnz setp1 mov ebx,es:[di + 7 + BPB_TOTSECT32] setp1: cmp ebx,edx # check that total size is correct jne setp9 inc edx shr edx,1 # convert it into number of kB cmp edx,[rdsize] # check that max ramdisk size not ja setp9 # exceeded mov al,es:[di + 1] mov [devtyp],al # set device type mov [bpb + BPB_DEVTYPE],al mov ax,es:[di + 2] mov [devatr],ax # set device attributes mov [bpb + BPB_DEVATTR],ax mov ax,es:[di + 4] mov [trknum],ax # set number of tracks mov [bpb + BPB_CYLNUM],ax mov ax,es:[di + 7 + BPB_SPTRACK] mov [sptrk],ax # set number of sectors per track mov ax,es:[di + 7 + BPB_HEADNUM] mov [hdnum],ax # set number of heads cld push ds push es push di mov ax,es mov bx,ds mov es,bx mov ds,ax lea si,[di + 7] mov di,offset bpb # move first part of BPB out of mov cx,BPB_BOOT_SIZE # parameter block rep movsb pop di pop es pop ds setp8: xor cx,cx setp9: mov ax,cx # set return code ret # #==================================================================== # # Get volume serial number # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: EAX, CX, SI # get_volnum: cld push di add di,2 mov eax,[volnum] stosd # copy volume serial number mov si,offset volnam mov cx,VOLNAME_LEN rep movsb # copy volume name mov si,offset fsname mov cx,FSNAME_LEN rep movsb # copy file system name pop di xor ax,ax ret # #==================================================================== # # Set volume serial number. We just set the number in our local # copy and dont update the actual ramdisk image. # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: EAX, CX, SI # set_volnum: call chkrdo # check if ramdisk is readonly cld push ds push es push di mov ax,ds mov cx,es mov ds,cx mov es,ax lea si,[di + 2] lodsd # set volume serial number mov es:[volnum],eax mov di,offset volnam mov cx,VOLNAME_LEN rep movsb # copy volume name mov di,offset fsname mov cx,FSNAME_LEN rep movsb # copy file system name pop di pop es pop ds xor ax,ax ret # #==================================================================== # # Set access flag # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: AX # set_access: xor al,al test byte ptr es:[di + 1],0xFF jz setac1 inc al setac1: mov [accflg],al xor ax,ax ret # #==================================================================== # # Get access flag # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: AX # get_access: mov al,[accflg] mov es:[di + 1],al xor ax,ax ret # #==================================================================== # # Get media ID. This is the same as the device ID, because in the # ramdisk we always have the default media. # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: AX # get_mediaid: mov byte ptr es:[di + 0],0x01 mov al,[devtyp] mov es:[di + 1],al xor ax,ax ret # #==================================================================== # # Get drive map information # Input: ES:DI - Pointer to parameter block # Output: AX - Error code # Registers changed: EAX # get_drvmap: mov ax,0x800C cmp byte ptr es:[di + 0],16 jb getmp9 mov byte ptr es:[di + 1],16 mov byte ptr es:[di + 2],0 mov byte ptr es:[di + 3],0xFF mov eax,[drvmap] mov dword ptr es:[di + 4],eax mov eax,[hidden] mov dword ptr es:[di + 8],eax xor eax,eax mov dword ptr es:[di + 12],eax getmp9: ret # #==================================================================== # # End of resident section. This was the easy part, now comes the # difficult part: setup everything. This involves fiddeling around # within DOS internal data tables. # #==================================================================== # end_resident: # #==================================================================== # # Setup the ramdisk driver # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: EAX, EBX, ECX, EDX, SI # doinit: push es # save pointer to request header push di # Print startup message. Then check if we have enough memory for the # BSS and clear it. mov si,offset sigmsg # print startup message call prnstr mov ax,cs mov dx,word ptr es:[di + 16] sub dx,ax # determine number of bytes available jc doini1 # to driver and edx,0x0000FFFF shl edx,4 movzx32 eax,word ptr es:[di + 14] add edx,eax cmp edx,offset __bss_end + 16 jae doini2 # check if its enough doini1: mov si,offset memerr doerr1: push si mov si,offset errmsg call prnstr pop si call prnstr # print error message call prcrlf mov ax,0x800C # return with error jmp doini9 doini2: cld push es mov si,di mov ax,ds mov es,ax mov di,offset __bss_start mov cx,offset __bss_end sub cx,di inc cx shr cx,1 xor ax,ax # clear BSS area rep stosw pop es # Handle all command line arguments. After doing this we dont need the # pointer to the request header anymore. les si,es:[si + 18] # get pointer to command line call docmdl # and process it or ax,ax mov si,offset cmderr # print command line error message jnz doerr1 test byte ptr [debug],0xFF jz doiniK # check for debugger trap int3 # Check for correct versions of DOS and ramdisk driver. We require at least # DOS version 4.0 because of the layout of various internal data tables. doiniK: mov ax,0x3000 xor bx,bx int 0x21 # get DOS version xchg al,ah cmp ax,0x0400 # has to be at least DOS 4.0+ mov si,offset vererr # print version error message jb doerr1 mov [dosver],ax xor ax,ax mov es,ax # check ramdisk driver signature mov eax,[rdsig] cmp dword ptr es:[0xF1 * 4],eax jne doini5 mov ax,0x9C00 int 0xF8 # check if a ramdisk driver is cmp ax,0x009D # installed jne doini5 cmp bl,2 # check that we have ramdisk driver jae doini6 # version 2.0 or higher doini5: mov si,offset rderr jmp doerr1 # We need DRIVER.SYS support in order to change all physical drive IDs # lateron after the primary ramdisk driver has been removed. doini6: mov ax,0x0800 int 0x2F # check if DRIVER.SYS support is inc al # installed mov si,offset syserr jnz doerr1 push ds mov ax,0x0803 int 0x2F # get address of device data table mov bx,ds pop ds mov word ptr [ddtadr + 0],di mov word ptr [ddtadr + 2],bx # Find an XMS driver and get its function dispatcher entry address. mov ax,0x4300 int 0x2F # check if XMS services are available cmp al,0x80 mov si,offset xmserr jne doerr1 mov ax,0x4310 int 0x2F # get XMS entry address mov word ptr [xmsadr + 0],bx mov word ptr [xmsadr + 2],es # Ask the ramdisk driver about different parameters like the XMS handle # and the drive ID of the ramdisk. mov ax,0x9C03 int 0xF8 mov [drvid],cl # save ramdisk drive ID mov [xmshdl],ax # save XMS handle or ax,ax mov si,offset noxerr jz doerr1 cmp cl,DISKID_HD jb doiniE # adding the ramdisk as a new drive test byte ptr [newflag],0xFF # is only possible if it is simula- jz doiniE # ting a floppy drive, so that the mov si,offset newerr # old driver remains removable with jmp doerr1 # the rmrd.com utility. # Get the address of the BIOS DPB stored within the primary ramdisk # driver. When interrupt 1Eh has not been redirected by the boot sector, # it still points into the ramdisk driver, but when the driver gets # removed lateron we have to provide some new storage. We already # setup the new interrupt 1Eh vector here, so that when removing the # ramdisk lateron the driver doesnt have a reason to deny freeing its # own memory. However this means that we have to restore interrupt 1Eh # when an error occurs somewhere below. doiniE: mov ax,0x9C05 int 0xF8 # get address of ramdisk DPB mov word ptr [dpbadr + 0],dx mov word ptr [dpbadr + 2],bx push ds mov ax,ds mov es,ax mov ds,bx # copy ramdisk DPB mov si,dx mov di,offset dpb mov cx,DPB_SIZE cld rep movsb pop ds xor ax,ax mov es,ax mov eax,es:[DPB_VECT] # get old DPB vector and save it xchg eax,[dpbadr] cmp eax,[dpbadr] # does it point into ramdisk driver? jne doini3 # if so replace it with the new pointer mov word ptr es:[DPB_VECT + 0],offset dpb mov word ptr es:[DPB_VECT + 2],cs # Get the address and size of the ramdisk and then lock the XMS memory block # occupied by the ramdisk. By locking the memory block before removing the # primary ramdisk driver, we make sure that the ramdisk doesnt get freed. But # in case of an error we have to unlock the memory block again. doini3: mov ax,0x9C01 int 0xF8 mov [rdaddr],edx # get ramdisk size and address mov [rdsize],ecx mov ah,0x0C mov dx,[xmshdl] lcall [xmsadr] # call the XMS driver to lock or ax,ax # the memory block mov si,offset lckerr jz doini4 shl edx,16 # convert address into 32-bit mov dx,bx mov eax,[rdaddr] sub eax,edx # compute ramdisk offset into block jnc doini7 mov si,offset rdaerr doerr2: push si mov ah,0x0D # in case of an error unlock the mov dx,[xmshdl] # XMS memory block again lcall [xmsadr] pop si doini4: xor ax,ax # dont have to restore it. mov es,ax # in case of an error we also have mov eax,[dpbadr] # to restore the old DPB address. mov es:[DPB_VECT],eax jmp doerr1 doini7: mov [rdofs],eax # save ramdisk offset # Get the ramdisk geometry from the ramdisk driver. Then get the DOS BIOS # Parameter Block and the number of hidden sectors. call getgeo # get ramdisk geometry or si,si jnz doerr2 call getbpb # read BPB or si,si jnz doerr2 mov eax,[bpb + BPB_HIDDEN] mov [hidden],eax # save number of hidden sectors # Lateron we have to insert the ramdisk block driver somewhere into the # DOS driver chain. The only driver we know exists is the XMS driver. # Therefore, we now search the list of DOS drivers to find the driver # header of the XMS driver. Note that the offset to the NUL device # header within the list of lists depends on the DOS version. We assume # DOS version 4.0+ here! mov ah,0x52 int 0x21 # get the DOS list of lists lea bx,[bx + 0x0022] # get address of NUL device header doiniB: mov si,offset drverr # XMS driver not found mov ax,word ptr es:[bx + 0] or ax,word ptr es:[bx + 2] # check for valid pointer jz doerr2 cmp word ptr es:[bx],0xFFFF # check for end of list je doerr2 mov si,offset xmsstr lea di,[bx + 10] mov cx,8 # compare driver name repe cmpsb je doiniC les bx,es:[bx] # continue with next driver jmp doiniB doiniC: mov word ptr [xmshdr + 0],bx mov word ptr [xmshdr + 2],es # Determine the logical drive data from DOS. This is necessary to change all # DOS internal data structures lateron. call getdrv # get logical drive data from DOS or si,si jnz doerr2 # When the user requested to save the BOOTP/DHCP information block, we can now # allocate some XMS memory and copy the BOOTP information into it. However, when # an error occurs lateron, we have to remember to remove the XMS block again. test byte ptr [dhcpflg],0xFF # if the flag is zero, we should not save jz doiniD # the BOOTP information mov ax,0x9C02 # get address and size of BOOTP information int 0xF8 # from resident driver mov ax,bx or ax,dx # check for valid return parameters jz doiniD jcxz doiniD mov [btpsiz],cx add cx,3 # round size to multiple of 4-bytes and cx,0xFFFC mov [xmslen],cx # save return values for later mov word ptr [srchdl],0 mov word ptr [srcofs + 0],dx mov word ptr [srcofs + 2],bx mov dx,cx add dx,0x03FF # convert size into kB shr dx,10 mov ah,0x09 lcall [xmsadr] # allocate memory block or ax,ax jz doerr5 mov [btphdl],dx # save memory block handle mov [dsthdl],dx mov ah,0x0C lcall [xmsadr] # lock memory block or ax,ax jz doerr5 mov dword ptr [dstofs],0 mov ah,0x0B mov si,offset xmsemm lcall [xmsadr] # transfer the BOOTP/DHCP information or ax,ax # out of the resident driver into the jnz doiniD # XMS memory block doerr5: mov si,offset btperr doerr3: push si mov dx,[btphdl] or dx,dx jz doerr4 push dx mov ah,0x0D # in case of an error unlock the lcall [xmsadr] # DHCP/BOOTP memory block again pop dx mov ah,0x0A # remove the DHCP memory block lcall [xmsadr] doerr4: pop si jmp doerr2 # When we are going to add the ramdisk as a new drive, we should not remove # the old disk driver (because it is still needed to process the rest of # config.sys) and should also not modify any internal DOS tables. Instead # we install a new initialization routine into the ramdisk block device # driver. When this routine terminates DOS will go on in the chain of # device driver headers, and the call the ramdisk block device driver # initialization routine, and subsequently install that driver as a new # device. This new ramdisk block device is initially deactivated. This # is necessary so that no two drivers (the old one and the new one) are # able to access the same ramdisk. Lateron, when the rmrd.com utility # gets executed, we turn on the ramdisk block device driver. # Since the NEWDRIVE flag can only be set when the ramdisk is simulating # a floppy drive, and that size cant be larger than 32MB, we can remove # the 32-bit sector addressing flag from the block driver device attributes. doiniD: test byte ptr [newflag],0xFF jz doiniF mov bx,offset rd_dev_header mov si,offset char_dev_header mov word ptr [si + 0],bx mov word ptr [si + 2],cs and word ptr [bx + 4],0xFFFD mov word ptr [blktab + 1],offset blkini mov byte ptr [active],0 # disable write accesses temporarily jmp doini8 # Now we have checked all error conditions, so that we can now let the # primary ramdisk driver remove itself. After this no further error should # occur because we cant recover anymore. doiniF: mov ax,0x9C04 int 0xF8 # remove ramdisk mov si,offset rmverr cmp al,1 # check for error je doerr3 # First insert our ramdisk block driver into the device driver chain. We # insert it directly behind the XMS driver. Then setup all drive control # blocks to point to our new ramdisk driver. les bx,[xmshdr] # get address of XMS driver header mov ax,cs shl eax,16 mov ax,offset rd_dev_header mov edx,eax xchg eax,es:[bx] # set our new header address mov dword ptr [rd_dev_header],eax mov ax,[drvnum] # save number of subunits into mov [nunits],al # device header call setdcb # set all DCBs # Disable all drive data table entries within the DOS block device driver # which were previously serving the old ramdisk. call setddt # set device data table mov [opncnt],cx # save open count # Since we eventually changed the BIOS disk parameter table way ago, we # should now reset the BIOS disk driver to aknowledge the new DPB. xor ax,ax xor dx,dx int 0x13 # Check if the user asked for verbose output test byte ptr [verbose],0xFF jz doini8 call prnvrb # Finally setup the return code and replace the pointer to this init # routine in the function handler table with a NOP, so that it is no # longer possible to call it again. Also set the upper memory boundary # of the resident section. Remember that this is the initialization # code for a character device driver, so we dont need to return the # BPB address in the IOCTL request header. doini8: xor ax,ax doini9: mov word ptr [chrtab + 1],offset donop mov word ptr [drvhdr + 2],cs test ax,0x8000 # on error dont make the driver jz doiniG # resident xor bx,bx xor dl,dl jmp doiniA doiniG: mov dl,1 # number of subunits of char drv mov bx,offset __bss_end test byte ptr [newflag],0xFF jnz doiniA # setup correct end address call getend doiniA: pop di # restore pointer to request header pop es # and set the resident memory area mov byte ptr es:[di + 13],dl mov word ptr es:[di + 14],bx mov word ptr es:[di + 16],cs mov dword ptr es:[di + 18],0 cmp byte ptr es:[di + 0],25 jb doiniH mov word ptr es:[di + 23],0 doiniH: ret # #==================================================================== # # Setup the ramdisk block driver when we have to install the ramdisk # as an additional drive. Since almost everything has been setup by # the character driver initialization routine, we just have to return # the proper parameters to DOS so that it can install the driver # correctly. # Input: ES:DI - Pointer to request header # Output: AX - Result code # Registers changed: EAX, EBX, ECX, EDX, SI # blkini: test byte ptr [debug],0xFF jz blkin2 # check for debugger trap int3 blkin2: mov cl,es:[di + 22] mov [logdrv],cl # set logical driver number mov eax,0x00000001 shl eax,cl # compute drive map mov [drvmap],eax test byte ptr [verbose],0xFF jz blkin1 call prnvrb # print verbose information blkin1: call getend # get end address xor ax,ax mov byte ptr [nunits],1 mov word ptr [blktab + 1],offset donop mov byte ptr es:[di + 13],1 mov word ptr es:[di + 14],bx mov word ptr es:[di + 16],cs mov word ptr es:[di + 18],offset bpbtab mov word ptr es:[di + 20],cs cmp byte ptr es:[di + 0],25 jb blkin9 mov word ptr es:[di + 23],ax blkin9: ret # #==================================================================== # # Determine end address of ramdisk block driver depending on simple # driver flag. # Input: none # Output: BX - Offset to end of block driver # Registers changed: BX # getend: mov bx,offset end_resident test byte ptr [simple],0xFF jz geten9 mov bx,offset end_simple mov byte ptr [blktab + 0],0x0F # limit block driver functions and word ptr [rd_dev_header + 4],0xFF3F geten9: ret # #==================================================================== # # Determine the ramdisk geometry by calling the ramdisk driver using # interrupt 0x13. # Input: none # Output: SI - Offset to error message # Registers changed: AX, BX, CX, DX # getgeo: mov ah,0x08 mov dl,[drvid] int 0x13 # call ramdisk driver to get geometry mov si,offset geoerr jc getge9 mov al,ch mov ah,cl # save number of tracks shr ah,6 inc ax mov [trknum],ax mov al,cl and ax,0x003F # save number of sectors per track mov [sptrk],ax mov al,dh inc ax # save number of heads mov [hdnum],ax mov cx,0x0001 test byte ptr [newflag],0xFF jnz getge1 cmp byte ptr [drvid],DISKID_HD jae getge1 mov cx,0x0002 cmp bl,6 # convert BIOS drive type into jbe getge2 # DOS drive type getge1: xor bl,bl getge2: xor bh,bh mov al,[bx + typtab] mov byte ptr [devtyp],al mov word ptr [devatr],cx getge8: xor si,si # return without error getge9: ret # #==================================================================== # # Get DOS BIOS parameter block (BPB) by reading the boot sector of the # ramdisk. We setup the BPB as a standard BPB. An extended BPB gets only # used for FAT32 devices, and the ramdisk can never have a FAT32 file # system. # Input: none # Output: SI - Offset to error message # Registers changed: EAX, BX, CX, DX, SI # getbpb: push es push di mov ax,ds mov es,ax mov bx,offset bootbuf movzx dx,byte ptr [drvid] mov cx,0x0001 # read first sector of ramdisk mov ax,0x0201 int 0x13 jc getbp2 mov ax,[bootbuf + BOOT_SIG_OFS - BOOT_OFFSET] cmp ax,BOOT_SIG # check for boot signature jne getbp2 cmp dl,DISKID_HD # check if this was the partition jb getbp3 # table lea si,[bootbuf + BOOT_PART_OFS - BOOT_OFFSET] cmp byte ptr [si + 0],0x80 jne getbp2 # check if partition entry active mov al,[si + 4] # get type of partition cmp al,0x01 # check for 12-bit FAT je getbp1 cmp al,0x04 # check for small 16-bit FAT je getbp1 cmp al,0x06 # check for large 16-bit FAT jne getbp2 getbp1: mov dh,[si + 1] mov cx,[si + 2] # get parameters for first sector mov ax,0x0201 # of partition int 0x13 # read boot sector from ramdisk jc getbp2 mov ax,[bootbuf + BOOT_SIG_OFS - BOOT_OFFSET] cmp ax,BOOT_SIG # check for boot signature je getbp3 getbp2: mov si,offset boterr # return with error message jmp getbp9 getbp3: cld lea si,[bootbuf + BPB_OFFSET] mov di,offset bpb mov cx,BPB_BOOT_SIZE # copy DOS BPB out of boot sector rep movsb xor al,al mov cx,BPB_SIZE - BPB_BOOT_SIZE rep stosb mov eax,[bootbuf + VOLNUM_OFS] mov [volnum],eax # get volume number lea si,[bootbuf + VOLNAME_OFS] mov di,offset volnam # get volume name mov cx,VOLNAME_LEN rep movsb lea si,[bootbuf + FSNAME_OFS] mov di,offset fsname # get file system name mov cx,FSNAME_LEN rep movsb # do some sanity checks on the BPB mov ax,[bpb + BPB_SECTSIZE] cmp ax,SECT_SIZE jne getbp2 mov ax,[bpb + BPB_SPTRACK] cmp ax,[sptrk] jne getbp2 mov ax,[bpb + BPB_HEADNUM] cmp ax,[hdnum] jne getbp2 mov ax,[trknum] # save number of cylinders mov [bpb + BPB_CYLNUM],ax mov al,[devtyp] # save ramdisk device type mov [bpb + BPB_DEVTYPE],al mov ax,[devatr] # save ramdisk device attribute mov [bpb + BPB_DEVATTR],ax getbp8: xor si,si getbp9: pop di pop es ret # #==================================================================== # # Get the logical drive data of the ramdisk drive from DOS # Input: none # Output: SI - Offset to error message # Registers changed: EAX, BX, CX, DX, SI, DI # getdrv: # Find the ramdisk drives in the list of Device Control Blocks (also # known as DOS Device Parameter Blocks - but this name is also used # for a BIOS data structure, so we use a different name here to avoid # confusion with the abbreviations). These DCBs are needed lateron to # change the address of the driver which is responsible for ramdisk # accesses. In order to find all DCBs we have to use logical drive # numbers, and those can be retrieved from the DDT which stores the # mapping from the physical drive number (which is used by interrupt # 13h) to the logical drive number (A: to Z:). Note that we expect # the layout of DOS 4.0+ here! # We also determine the logical drive number. It is used in case the # ramdisk is simulating multiple drives. Then one logical drive "owns" # the physical ramdisk drive. # If we are adding the ramdisk as a new drive, we dont need to go # through the DOS DCB list, because we dont need to change anything # lateron. The number of drives and the logical drive number are always 1. push es mov ah,0x52 int 0x21 # get the DOS list of lists mov eax,es:[bx] # get pointer to first DCB or eax,eax # check for valid pointer jz getdr6 mov [dcbadr],eax test byte ptr [newflag],0xFF jz getdrB mov dx,1 # set number of drives jmp getdr8 getdrB: xor dx,dx # DX gets number of DCBs found les bx,[ddtadr] # find ramdisk physical unit number mov ch,[drvid] # in DDT mov si,offset dcblst xor di,di cmp word ptr [dosver],0x070A jb getdr1 # care for different DDT format with mov di,28 # DOS version 7.10+ getdr1: cmp bx,0xFFFF # check if at end of list je getdr8 cmp ch,es:[bx + 4] # check if physical unit found jne getdrA mov cl,es:[bx + 5] # get logical drive number cmp cl,MAX_DRIVES # should not exceed drive letter Z jae getdr6 test word ptr es:[bx + di + 35],0x0020 jz getdr2 # set new logical drive number if this mov [logdrv],cl # logical drive owns the physical drive getdr2: push es push bx les bx,[dcbadr] # get pointer to DCB list getdr3: cmp bx,0xFFFF # check if at end of list je getdr7 cmp cl,es:[bx + 0] # check if we found the ramdisk drive jne getdr4 mov eax,0x00000001 # compute map for this logical drive shl ax,cl test [drvmap],eax # check if drive already setup jnz getdr5 or [drvmap],eax # update drive map inc dx # increment drive counter cmp dx,MAX_DRIVES # should not exceed 26 entries jae getdr5 mov word ptr [si + 0],bx # save DCB address mov word ptr [si + 2],es add si,4 getdr4: les bx,es:[bx + 25] # get pointer to next DCB entry jmp getdr3 getdr5: pop bx pop es # return with error getdr6: mov si,offset dcberr jmp getdr9 getdr7: pop bx # if end of DCB list reached continue pop es # with next DDT entry getdrA: les bx,es:[bx + 0] # get pointer to next DDT entry jmp getdr1 getdr8: mov cx,dx jcxz getdr6 # check that we found at least one DCB mov [drvnum],cx # save device count xor si,si getdr9: pop es ret # #==================================================================== # # Setup DOS drive control blocks (aka drive parameter block) to point # to new ramdisk device # Input: EDX - Pointer to our new ramdisk driver header # Output: none # Registers changed: AX, BX, CX, DX, SI # setdcb: push es test byte ptr [newflag],0xFF # if we are adding the ramdisk as a jnz setdc9 # new device dont change the DCBs xor al,al mov cx,[drvnum] # get number of DCB entries mov si,offset dcblst setdc1: les bx,[si] # get address of DCB add si,4 mov es:[bx + 19],edx mov es:[bx + 1],al # set subunit number inc al loop setdc1 setdc9: pop es ret # #==================================================================== # # Setup DOS drive data table to support new ramdisk device # Input: none # Output: CX - Number of open files on ramdisk device # Registers changed: AX, BX, CX, DX # setddt: # By removing the old ramdisk driver interrupt 13h points back into the # BIOS. But the ramdisk driver told DOS already that we have one more # drive than physically installed (which was the ramdisk). Also, the old # driver did a remapping of the drive numbers. Therefore we now have to # go through the DOS disk driver table and restore all original BIOS # drive numbers. We should also mark the old entry as unusable. This way # the DOS disk driver should never again access the ramdisk using inter- # rupt 13h and will access all physical drives correctly again. # Note that the old ramdisk driver replaced the physical drive with # the ramdisk if it was to simulate a floppy drive. For a hard disk # simulation the physical drive numbers moved up by one. # We assume DOS version 4.0+ for the DDT format. push es push si xor cx,cx test byte ptr [newflag],0xFF # if we are adding the ramdisk as a jnz setdd9 # new drive dont change the DDT les bx,[ddtadr] # get device data table mov dh,[drvid] xor si,si cmp word ptr [dosver],0x070A jb setdd1 # the table entry format has been mov si,28 # changed with DOS version 7.10 setdd1: cmp bx,0xFFFF # check if at end of list je setdd9 mov al,es:[bx + 4] # get physical unit number cmp dh,al # if its the ramdisk mark the entry jne setdd2 # as unusable add cx,es:[bx + si + 32] # get open count mov byte ptr es:[bx + 4],0xFF or byte ptr es:[bx + si + 31],0x80 or word ptr es:[bx + si + 35],0x0200 mov word ptr es:[bx + si + 32],0 jmp setdd3 setdd2: cmp dh,DISKID_HD # check if we are simulating a hard jb setdd3 # disk drive cmp al,DISKID_HD # dont change anything for floppy jb setdd3 # disk drives dec al # normalize physical unit number mov es:[bx + 4],al setdd3: les bx,es:[bx + 0] # continue with next entry jmp setdd1 setdd9: pop si pop es ret # #==================================================================== # # Print verbose information onto the screen # Input: none # Output: none # Registers changed: EAX, EBX, ECX, EDX, SI # prnvrb: mov si,offset vmsg1 call prnstr # print header string test byte ptr [rdonly],0xFF jz prnvr6 mov si,offset vmsg4 call prnstr # print readonly message prnvr6: call prcrlf mov si,offset vmsg5 call prnstr mov ax,[trknum] # print number of tracks call prndec call prcrlf mov si,offset vmsg6 call prnstr mov ax,[hdnum] # print number of heads call prndec call prcrlf mov si,offset vmsg7 call prnstr mov ax,[sptrk] # print number of sectors per track call prndec call prcrlf mov si,offset vmsg2 # print size string call prnstr mov eax,[rdsize] cmp eax,1024 jae prnvr1 call prndec # print ramdisk size in kB mov si,vmsgkb call prnstr jmp prnvr3 prnvr1: xor edx,edx mov ebx,1024 div ebx # compute number of MB push edx call prndec # print full MB mov ax,0x3800 mov dx,offset country int 0x21 # get country information mov al,0x2E jc prnvr2 mov al,[country + 9] # get decimal seperator prnvr2: call prnchr # print decimal seperator pop eax # get remainder which is always mov ebx,9765 # smaller than 1024 mul ebx # EDX will always be zero add eax,99999 mov ebx,100000 # convert into fractional part div ebx # result will always be between 0 and call prndec # 100 mov si,offset vmsgmb call prnstr prnvr3: call prcrlf test byte ptr [newflag],0xFF jz prnvr8 mov si,offset vmsg11 # print new drive message call prnstr mov al,[logdrv] add al,0x41 call prnchr # print new drive letter jmp prnvr9 prnvr8: mov si,offset vmsg3 call prnstr # print drive message mov ebx,[drvmap] mov dx,0x0041 mov cx,MAX_DRIVES prnvr4: shr ebx,1 jnc prnvr5 or dh,dh jz prnvrA mov al,0x2C # print comma call prnchr mov al,0x20 # print space call prnchr prnvrA: mov al,dl call prnchr # print drive letter inc dh prnvr5: inc dl loop prnvr4 # continue with all drives prnvr9: call prcrlf mov si,offset vmsg8 call prnstr # print BOOTP/DHCP message mov ax,[btphdl] or ax,ax jnz prnvr7 mov si,offset vmsg9 call prnstr # print "not" prnvr7: mov si,offset vmsg10 call prnstr call prcrlf call prcrlf ret # #==================================================================== # # Print a character onto the screen # Input: AL - Character to print # Output: none # Registers changed: none # prnchr: push bx int 0x29 pop bx ret # #==================================================================== # # Print a CR/LF onto the screen # Input: none # Output: none # Registers changed: AX # prcrlf: push si mov si,offset crlf jmp prnst1 # #==================================================================== # # Print a zero-terminated string onto the screen # Input: DS:SI - Pointer to string # Output: none # Registers changed: AX # prnstr: push si prnst1: cld prnst2: lodsb or al,al jz prnst3 call prnchr jmp prnst2 prnst3: pop si ret # #==================================================================== # # Print a 16-bit number # Input: AX - Number to print # BX - Base # Output: none # Registers changed: AX # prnnum: push si push dx mov si,offset decbuf + 6 mov byte ptr [si + 1],0 mov byte ptr [si + 0],0x30 or ax,ax jz prnnm9 prnnm1: or ax,ax jz prnnm8 xor dx,dx div bx add dl,0x30 mov byte ptr [si],dl dec si jmp prnnm1 prnnm8: inc si prnnm9: pop dx call prnstr pop si ret # #==================================================================== # # Print a decimal number # Input: AX - Number to print # Output: none # Registers changed: AX # prndec: push bx mov bx,10 call prnnum pop bx ret # #==================================================================== # # Get next character from command line # Input: ES:SI - Pointer to command line # Output: AL - Next character, zero if end of line # Registers changed: AX, SI # getchr: xor ah,ah getch1: mov al,es:[si] # get next character cmp al,0x0D je getch2 cmp al,0x0A # check if it is any of the end je getch2 # of line characters or al,al jnz getch3 getch2: xor al,al # if so indicate end of line jmp getch9 getch3: inc si # increase character pointer cmp al,0x20 je getch4 cmp al,0x09 # check if any of the blank characters jne getch5 getch4: inc ah # if so increase blank counter jmp getch1 getch5: or ah,ah # do we had any blanks so far jz getch8 mov al,0x20 # return just a single blank and reset dec si # SI to first non-blank character getch8: cmp al,0x61 # check if lower case character jb getch9 cmp al,0x7A ja getch9 sub al,0x20 # convert into upper case character getch9: ret # #==================================================================== # # Get next token from command line # Input: ES:SI - Pointer to command line # Output: AX - Non-zero if error # BX - Pointer to token handler # ES:SI - Pointer to first character after token # Registers changed: AX, BX, CX, DX, SI, DI # gettok: call getchr # get first non-blank character or al,al # check if end of line jz gettk8 cmp al,0x20 # check if blank character je gettok cmp al,0x2F # token has to start with a slash jne gettkE xor cx,cx xor dx,dx # indicate no argument value mov di,offset curtok gettk1: cmp cx,32 # check if max token length reached jb gettk2 gettkE: mov ax,1 # return with error ret gettk2: call getchr # get next token character or al,al jz gettk4 cmp al,0x20 # check for end of token je gettk4 cmp al,0x3D # check for equal sign je gettk3 mov [di],al # continue with next character inc di inc cx jmp gettk1 gettk3: call getchr # get next character after equal sign or al,al jz gettkE # should not be end of line cmp al,0x3D je gettkE # should not be another equal sign cmp al,0x20 je gettk3 # skip blank characters after equal sign dec si inc dx # remember that we have an argument value jmp gettk6 gettk4: call getchr # get next character after blank or al,al jz gettk6 # no argument if at end of line cmp al,0x20 je gettk4 # skip blanks after token cmp al,0x3D je gettk3 # check for argument after equal sign gettk5: dec si gettk6: jcxz gettkE # token should not be empty mov byte ptr [di],0 # terminate token with zero cld push es push si mov si,offset argtab mov ax,ds mov es,ax mov bx,cx inc bx gettk7: lodsw or ax,ax # check if at end of token list jnz gettkA pop si pop es jmp gettkE # token not found gettkA: mov cx,bx mov di,offset curtok repe cmpsb # compare token with list string jz gettkC dec si gettkB: lodsb # find end of list string or al,al jz gettk7 # continue with next list entry jmp gettkB gettkC: pop si pop es mov bx,ax # remember handler address xor cx,cx mov di,offset curtok or dx,dx # check if we have an argument jz gettkG gettkD: cmp cx,32 # check if argument too long jae gettkE call getchr # get next argument character or al,al jz gettkG cmp al,0x20 # check if end of argument je gettkG mov [di],al # save character and continue with inc di # next character inc cx jmp gettkD gettkG: mov byte ptr [di],0 # terminate argument with zero mov [toklen],cx # save length of argument value jmp gettk9 gettk8: xor bx,bx # indicate end of command line gettk9: xor ax,ax # return without error ret # #==================================================================== # # Handle all command line arguments # Input: ES:SI - Pointer to command line # Output: AX - Non-zero means error # Registers changed: AX, BX, CX, DX, SI, DI # docmdl: call getchr # skip device driver name or al,al # check if end of line jz docmd8 cmp al,0x20 # skip until first blank or slash char je docmd1 cmp al,0x2F jne docmdl docmd1: call gettok # get next token from command line or ax,ax jnz docmd9 or bx,bx # check if at end of command line jz docmd8 push si push es call bx # call token handler pop es pop si or ax,ax # check if error jnz docmd9 jmp docmd1 # continue with next token docmd8: xor ax,ax docmd9: ret # #==================================================================== # # Compare command line argument value to a certain string # Input: SI - Offset to string to compare argument to # Output: Zero flag set if both strings are equal # Registers changed: CX, SI, DI # cmparg: cld push es mov cx,ds mov es,cx mov di,offset curtok mov cx,[toklen] inc cx repe cmpsb pop es ret # #==================================================================== # # Handle verbose argument # Input: none # Output: AX - Non-zero means error # Registers changed: AX, BX # arg_verbose: mov bx,offset verbose # set verbose flag argv1: xor ax,ax cmp byte ptr [bx],0 jne argv8 # should not be defined twice cmp byte ptr [curtok],0 jne argv8 # should not have any argument inc byte ptr [bx] jmp argv9 argv8: inc ax # return with error argv9: ret # #==================================================================== # # Handle readonly argument # Input: none # Output: AX - Non-zero means error # Registers changed: AX, BX # arg_readonly: mov bx,offset rdonly # set readonly flag jmp argv1 # #==================================================================== # # Handle newdrive argument # Input: none # Output: AX - Non-zero means error # Registers changed: AX, BX # arg_newdrv: mov bx,offset newflag # set newdrive flag jmp argv1 # #==================================================================== # # Handle simple driver argument # Input: none # Output: AX - Non-zero means error # Registers changed: AX, BX # arg_simple: mov bx,offset simple # set simple driver flag jmp argv1 # #==================================================================== # # Handle debugging argument # Input: none # Output: AX - Non-zero means error # Registers changed: AX, BX # arg_debug: mov bx,offset debug # set debugging flag jmp argv1 # #==================================================================== # # Handle DHCP argument # Input: none # Output: AX - Non-zero means error # Registers changed: AX, BX, CX, SI, DI # arg_dhcp: mov bx,offset dhcpflg # set DHCP flag argd1: xor ax,ax cmp byte ptr [bx],0xFF jne argd8 # should not be defined twice mov si,offset on_arg call cmparg # check if argument is ON jnz argd2 mov byte ptr [bx],0x01 jmp argd9 argd2: mov si,offset of_arg call cmparg # check if argument is OFF jnz argd8 mov byte ptr [bx],0x00 jmp argd9 argd8: inc ax # return with error argd9: ret # #==================================================================== # # Data segment # \(.data) # Copyright and startup message sigmsg: .byte 0x0D, 0x0A .ascii "Netboot DOS Ramdisk Driver " .ascii "\&VERSION" .byte 0x0D, 0x0A .ascii "\©RIGHT" .byte 0x0D, 0x0A crlf: .byte 0x0D, 0x0A .byte 0 # Table with command line arguments argtab: .word arg_verbose .asciz "VERBOSE" .word arg_readonly .asciz "READONLY" .word arg_dhcp .asciz "DHCP" .word arg_newdrv .asciz "NEWDRIVE" .word arg_simple .asciz "SIMPLE" .word arg_debug .asciz "DEBUG" .word 0 # Command line argument strings on_arg: .asciz "ON" of_arg: .asciz "OFF" # Flags for command line arguments verbose: .byte 0x00 # verbose flag simple: .byte 0x00 # simple driver flag debug: .byte 0x00 # debugging flag newflag: .byte 0x00 # new drive flag dhcpflg: .byte 0xFF # DHCP option flag # Verbose messages vmsg1: .asciz "Ramdisk driver loaded successfully" vmsg2: .asciz "Size of ramdisk: " vmsgkb: .asciz " kB" vmsgmb: .asciz " MB" vmsg3: .asciz "Simulated drives: " vmsg4: .asciz " (readonly)" vmsg5: .asciz "Number of tracks: " vmsg6: .asciz "Number of heads: " vmsg7: .asciz "Sectors per track: " vmsg8: .asciz "BOOTP/DHCP information " vmsg9: .asciz "not " vmsg10: .asciz "saved into XMS" vmsg11: .asciz "Ramdisk installed as drive " # Error messages errmsg: .asciz "ERROR: " memerr: .asciz "Not enough memory available for driver" cmderr: .asciz "Invalid command line argument" vererr: .asciz "Invalid DOS version" xmserr: .asciz "XMS driver not installed" syserr: .asciz "DRIVER.SYS not installed or DOS version too old" rderr: .asciz "No primary ramdisk driver found" noxerr: .asciz "Ramdisk not protected by XMS memory" newerr: .asciz "Option NEWDRIVE only allowed with floppy simulation" rdaerr: .asciz "Invalid ramdisk address" lckerr: .asciz "Unable to lock ramdisk memory block" geoerr: .asciz "Unable to get ramdisk geometry" boterr: .asciz "Unable to read boot sector from ramdisk" drverr: .asciz "Unable to find XMS driver" dcberr: .asciz "Unable to find ramdisk in DOS internal tables" rmverr: .asciz "Unable to remove primary ramdisk driver" btperr: .asciz "Unable to save BOOTP/DHCP information block" # Signature to identify interrupt 13h ramdisk driver rdsig: .ascii "NetB" # Table for converting the BIOS drive type into a DOS drive type typtab: .byte 0x05 .byte 0x00 .byte 0x01 .byte 0x02 .byte 0x07 .byte 0x09 .byte 0x09 # Header string of XMS driver xmsstr: .ascii "XMSXXXX0" # #==================================================================== # # BSS segment # \(.bss) # Variables for handling the command line. Note that no token is allowed # to be larger than 32 characters. .lcomm curtok,33 # currently read token .lcomm toklen,2 # length of current token # Buffer used to temporarily hold the ramdisk boot sector .align 2 .lcomm bootbuf,SECT_SIZE # Table of DCBs which correspond to the old ramdisk driver. This list # can have a maximum of 26 entries (from A: to Z:). The number of valid # entries corresponds with the number of drives the ramdisk is simulating # as set by the drvnum variable. .align 2 .lcomm dcblst,(4 * MAX_DRIVES) # DCB pointer table # Various other variables .lcomm dosver,2 # DOS version .lcomm xmshdr,4 # header of XMS driver .lcomm dpbadr,4 # address of ramdisk DPB .lcomm dcbadr,4 # address of first DCB .lcomm ddtadr,4 # address of drive data table .lcomm drvid,1 # ramdisk drive ID .lcomm decbuf,8 # temp buffer for decimal conversion .lcomm country,36 # country information buffer # #==================================================================== # .end