home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Current Shareware 1994 January
/
SHAR194.ISO
/
dos_util
/
v12n19.zip
/
DRVASM.ZIP
/
DRVMAIN.ASM
< prev
next >
Wrap
Assembly Source File
|
1993-10-11
|
39KB
|
865 lines
;---------------------------------------------------------------
;drvmain.asm - Main module for DRVLOAD Utility |
;--------------------------------------------------------------|
;DRVLOAD Copyright (c) 1993 |
; |
;Rick Knoblaugh All rights reserved. |
;First Appeared in PC MAGAZINE, US Edition, |
;November 9, 1993 |
;--------------------------------------------------------------|
; 7/04/93 Rick Knoblaugh |
;--------------------------------------------------------------|
;include files |
;---------------------------------------------------------------
include drvequ.inc
include drvstruc.inc
code segment public 'CODE'
assume cs:code, ds:code, es:code
;--------------------------------------------------------------
;externals |
;--------------------------------------------------------------
extrn get_driver:near, relocate:near
extrn get_parms:near, check_driver:near
extrn look_win:near
;--------------------------------------------------------------
;publics |
;--------------------------------------------------------------
public driver_file, dos_ver, driver_p_off
public drv_stack_ptr
org 100h
startup:
jmp start_util
;--------------------------------------------------------------
;Variables |
;--------------------------------------------------------------
include drvdata.inc
;--------------------------------------------------------------
start_util proc near
cld ;all strings forward
mov ah, DOS_GET_VER ;check unlikely event
int 21h ;that user has really
mov dx, offset invalid_dos_msg
;
;DRVLOAD requires DOS version 3.0 or better becuase some of the
;DOS data structures used by the program (e.g. the CDS),
;didn't exist prior that version (some of this
;information could be extracted from other areas, but since
;undocumented DOS is used, it might not be 100% reliable).
cmp al, 3 ;need DOS 3.0 or better
jnb start_u050
start_u040:
jmp start_u900
start_u050:
mov cs:dos_ver, ax
push cs
pop word ptr cs:psp_seg
call look_win ;look for Windows
mov dx, offset no_win_msg
jc start_u040 ;if so, can't run
xor si, si ;offset zero from psp
sub ch, ch
mov bp, [si].psp_nextpar ;hold end of mem
mov cl, [si].psp_cmd_len
lea si, [si].psp_cmd_tail
call get_driver ;retrieve driver name
mov dx, offset nodriver_msg
jc start_u070 ;if no driver
call get_parms ;retrieve driver parms
call check_driver ;get size and ensure valid
jnc start_u100 ;continue if found
mov dx, offset cant_open_msg
start_u070:
jmp start_u900 ;if can't open
;
;size returned in dx:ax
;
start_u100:
mov driver_size, ax ;save driver size
mov si, ds ;psp segment
mov di, offset drv_stack_ptr ;end of loader
add di, 0fh ;round up to nearest para
mov cl, 4
shr di, cl ;size in paragraphs
;
;Calculate segment to which loader will be relocated. This is the
;maximum ceiling to which driver could climb.
;
;
sub bp, di ;new "top of mem"
mov bx, bp
sub bx, si ;max available paragraphs
sub bx, 10h ;count psp itself
;bp has segment to which loader will be relocated.
;bx has maximum paragraphs available for driver being loaded. Next,
;test to determine if driver will fit. Driver should always fit. Just
;about the only case where it possibly couldn't, is if DRVLOAD is
;being executed under LOADHIGH and there is insufficient upper memory.
add ax, 0fh ;round up
adc dx, 0
mov si, dx ;save high size
shr dx, cl ;convert to paragraphs
shr ax, cl
mov cl, 0ch
shl si, cl
or ax, si
cmp bx, ax ;compare with max
jae start_u200 ;if it's >, can't
mov dx, offset too_big_msg
jmp start_u900 ;driver >= 1 Meg
start_u200:
call relocate ;move loader up
start_u220:
call load_driver
mov dx, offset err_load_msg
jc start_u900 ;if error loading driver
;
;Do a little sanity checking on driver header. At least ensure that
;the strategy and interrupt routines offsets are realistic values.
;
xor bx, bx
mov ax, driver_size
cmp es:[bx].dev_stratr, size dev_header
jb start_u230 ;offset must be past header
cmp es:[bx].dev_stratr, ax ;and < total driver size
jb start_u240
start_u230:
mov dx, offset bad_drv_msg
jmp short start_u900 ;if driver not right
start_u240:
cmp es:[bx].dev_intr, size dev_header
jbe start_u230
cmp es:[bx].dev_intr, ax
jae start_u230
mov dx, offset sign_on_msg ;say hello to user
mov ah, DOS_PRT_STRING
int 21h
push es ;save ptr to header
push bx
call get_list_ptr
call get_num_blk ;get # blk devices
mov num_blk_devices, al ;save
pop bx
pop es
test es:[bx].dev_attrib, (mask char_dev) ;char drv?
jnz start_u300
mov dx, offset no_drives_msg
jcxz start_u900 ;exit if no drives
start_u300:
call do_drv_init ;do driver INIT cmd
mov dx, offset init_fail_msg
jnz start_u900 ;exit if INIT failed
mov mem_to_keep, ax ;save # mem paragraphs
;
;Also, if driver returned with no error, but is keeping no memory,
;treat as initialization failure (very few drivers should ever
;do this).
;
or ax, ax
jz start_u900
;
;Next, if driver is a character device, simply add it to the driver
;chain and go TSR. For block devices, must create DPB and CDS entries
;first.
;
;
test es:[bx].dev_attrib, (mask char_dev)
jnz start_u800 ;if so, go add it
start_u500:
;
;Before generating DPBs for the block device, inspect the BPB returned
;by the driver and at least see it makes some sense. If a bad BPB is
;passed to the undocumented function used to translate from BPB to
;DPB, the system may hang. For example, when RAMDRIVE.SYS is
;executed with unrealistic parameters (e.g. RAMDRIVE.SYS 32767 128),
;it won't return an error from the INIT call, and the BPB returned
;has zero for the total number of sectors and sectors per cluster. If
;anything like this is seen, fail the install (DOS itself would).
;
;
call check_bpb ;return nz if ok
mov dx, offset init_fail_msg
jz start_u900
call make_drives ;create DPBs, CDSs
;
;The previous call should always succeed. The only way it would return
;with carry set is if there is something very wrong with DOS (i.e. DPB
;chain corrupted, etc.)
;
jnc start_u800
mov dx, offset corrupt_msg
mov ah, DOS_PRT_STRING
int 21h
jmp short start_u880
start_u800:
call add_to_chain
call go_tsr ;should never return
start_u880:
mov dx, offset warning_msg
start_u900:
push cs
pop ds ;get data segment
start_u950:
mov ah, DOS_PRT_STRING
int 21h
start_u999:
mov ax, (DOS_TERM SHL 8) ;terminate
int 21h
ret
start_util endp
;--------------------------------------------------------------
;load_driver - Use DOS load overlay function to load device |
; driver just after PSP. |
; |
; Enter: |
; psp_seg=original PSP for loader |
; driver_file=file spec for driver file |
; over_args=structure to pass for overlay call |
; |
; es, ds=local data segment |
; |
; |
; Exit: |
; es=seg where loaded |
; CY=set if error loading (ax=error) |
; |
;--------------------------------------------------------------
load_driver proc near
mov dx, offset driver_file
mov ax, psp_seg
add ax, 10h ;load past PSP
push ax ;save load seg
mov over_args.over_seg, ax
mov over_args.over_relo_fac, ax
mov bx, offset over_args
mov ax, DOS_LOAD_OVER
int 21h
pop es ;ret load seg
ret
load_driver endp
;--------------------------------------------------------------
;get_list_ptr - Use Undocumented DOS funcntion 52h to |
; retrieve "list of lists" ptr. Based on DOS |
; version, load variables with pointers to |
; DPB, CDS, and NUL driver header. |
; |
; Enter: |
; ds=local data segment |
; dos_ver=DOS version from DOS version check |
; Exit: |
; es:bx=list of lists ptr |
; cds_ptr=ptr to first CDS entry |
; cds_size=size of each CDS entry |
; dpb_ptr=ptr to first DPB entry |
; dpb_size=size of each DPB entry |
; dpb_drv_off=offset of address of device driver |
; in DPB entry |
; |
; dpb_link_off=offset of link go next DPB |
; in DPB entry |
; |
; num_blk_off=offset in list of lists for number |
; of block devices. |
; |
;--------------------------------------------------------------
get_list_ptr proc near
mov ah, DOS_LIST_LISTS
int 21h ;get list of lists
mov cds_size, (size cds_4_0)
mov dpb_size, (size dpb_format_4)
mov di, offset dos31_ver_off ;default to 3.1 >
mov dpb_link_off, dpb_ptr_nxt_4
mov dpb_drv_off, dpb_driver_4
mov num_blk_off, num_blk31
cmp byte ptr dos_ver, 4 ;maj ver 4 >?
jae get_list100
mov cds_size, (size cds_3_0)
mov dpb_size, (size dpb_format_3)
mov dpb_link_off, dpb_ptr_nxt_3 ;DOS 3 dpb ptr
mov dpb_drv_off, dpb_driver_3
cmp dos_ver, 300h ;is it 3.0?
jne get_list100
mov di, offset dos30_ver_off ;offsets for 3.0
mov num_blk_off, num_blk30
get_list100:
mov si, [di].vcds_ptr ;offset to CDS ptr
mov ax, es:[bx + si].d_segment ;get seg CDS ptr
mov dx, es:[bx + si].d_offset ;get offset CDS ptr
mov cds_ptr.d_segment, ax
mov cds_ptr.d_offset, dx
mov si, [di].vdpb_ptr ;offset to DPB ptr
mov ax, es:[bx + si].d_segment ;get seg DPB ptr
mov dx, es:[bx + si].d_offset ;get offset DPB ptr
mov dpb_ptr.d_segment, ax
mov dpb_ptr.d_offset, dx
mov si, [di].vnul_dev_ptr ;offset to NUL ptr
mov ax, es ;get seg NUL dev
mov nul_dev_ptr.d_segment, ax
add si, bx
mov nul_dev_ptr.d_offset, si ;offset of NUL dev
mov si, [di].vlast_drive ;offset to LASTDRIVE
mov al, es:[bx + si] ;get LASTDRIVE
mov last_drive, al
get_list999:
ret
get_list_ptr endp
;--------------------------------------------------------------
;get_num_blk - Using lists of lists ptr. Use ptr to Current |
; Directory Structures, CDSs, to determine the |
; next available driver letter. |
; |
; Enter: |
; ds=local data segment |
; cds_ptr=ptr to first CDS entry |
; cds_size=size of each CDS entry |
; last_drive=max number of CDS entries |
; |
; Exit: |
; al=unit number |
; cx=0 if no drives available |
; cds_ptr=ptr to first available CDS entry |
; max_avail_units=maximum units that can be added |
; given the number of free CDSs |
; |
; es, bx destroyed |
;--------------------------------------------------------------
get_num_blk proc near
les bx, cds_ptr
sub ch, ch
mov cl, last_drive ;max to search
xor al, al ;unit counter
get_num_b100:
mov cds_ptr.d_offset, bx ;point 1st available
cmp es:[bx].cds_drv_stat4, 0 ;is it free?
je get_num_b500
inc al ;advance unit count
add bx, cds_size ;get to next entry
loop get_num_b100
get_num_b500:
mov max_avail_units, cl ;# available units
ret
get_num_blk endp
;--------------------------------------------------------------
;do_drv_init - Format an INIT cmd blk and call the driver |
; strategy and interrupt routines. |
; |
; Enter: |
; ds=local data segment |
; es:bx=ptr to driver header |
; driver_p_off=offset of any cmd line parms |
; (these are preceeded by driver |
; file name in driver_file) |
; al=number of block devices on system |
; |
; Exit: |
; ax=number of paragraphs to keep |
; es:bx=ptr to driver header |
; NZ=if INIT failed. |
; |
; ds saved |
;--------------------------------------------------------------
do_drv_init proc near
push ds
push es ;save driver header ptr
push bx
mov si, bx ;offset of driver
mov bx, driver_p_off
mov byte ptr [bx - 1], ' ' ;file spec was ASCIIZ
mov dx, offset driver_file ;offset of cmd line
mov bx, offset init_cmd_blk ;point to cmd block
mov [bx].rh_len, (size init_req) ;len cmd blk
mov [bx].init_bpb_tbo, dx ;offset cmd line
mov [bx].init_drv_first, al ;next available drive
mov [bx].init_bpb_tbs, cs ;seg for cmd line
mov [bx].init_brk_seg, cs ;end of avail mem
mov ax, ds
mov dx, es
assume ds:nothing
mov ds, dx ;ds=driver header
mov es, ax ;es=local data
;es:bx point to driver cmd request block
push cs ;far
mov ax, offset do_drv_i100 ;return address
push ax
push ds ;far "call"
push word ptr [si].dev_stratr ;address
retf ;make strategy call
do_drv_i100:
push cs ;far
mov ax, offset do_drv_i200 ;return address
push ax
push ds ;far "call"
push word ptr [si].dev_intr ;address
;
;One poorly designed driver we tested relied on ah being equal
;to zero. Set it to zero just to make drivers of that nature
;happy.
;
xor ax, ax
retf ;make int call
do_drv_i200:
test es:[bx].rh_status, (mask err_bit) ;error?
jnz do_drv_i900
mov dx, ds ;start of driver
mov ax, es:[bx].init_brk_seg
sub ax, dx ;difference if any
mov dx, es:[bx].init_brk_ofs
add dx, 0fh ;paragraph round
mov cl, 4
shr dx, cl ;convert to paragraphs
add ax, dx ;total paragraphs
sub cl, cl ;zero flag set
assume ds:code
do_drv_i900:
pop bx
pop es ;driver header seg
pop ds
ret
do_drv_init endp
;--------------------------------------------------------------
;check_bpb - Inspect the BPB returned from the driver to |
; see if it at least has a correct value for |
; total number of sectors. |
; |
; Enter: |
; ds=local data segment |
; |
; Exit: |
; ZR=if values are bad. |
; |
; ds saved |
;--------------------------------------------------------------
check_bpb proc near
push bx
push ds
lds bx, dword ptr cs:init_cmd_blk.init_bpb_tbo
mov bx, [bx] ;get BPB offset
cmp [bx].bpb_ts, 0 ;total sectors > 0?
pop ds
pop bx
ret
check_bpb endp
;--------------------------------------------------------------
;make_drives - Create the DPB entries and fill in CDS entries |
; for each unit supported by the device driver. |
; Also, update number of block devices field in |
; the DOS list of lists. |
; |
; Enter: |
; ds=local data segment |
; cds_ptr=ptr to next available CDS entry |
; cds_size=size of each CDS entry |
; dpb_ptr=ptr to first DPB entry |
; dpb_size=size of each DPB entry |
; last_drive=max number of CDS entries |
; es:bx=ptr to driver header |
; dpb_link_off=offset of link to next device driver|
; dpb_drv_off=offset of address of device driver |
; in DPB entry |
; max_avail_units=maximum number of driver letters |
; available for drives |
; Exit: |
; CY=set if for some reason, DOS data |
; areas corrupted, etc. (this should |
; never happen) |
; |
; mem_to_keep=adjusted to add in DPBs placed |
; at end of driver (break address) |
; |
; |
; ds, es, bx preserved |
;--------------------------------------------------------------
make_drives proc near
push bx
push ds
push es
;
;dpb_size contains DOS version specific size of DPB entry. Convert
;this to paragraphs --used to determine the number of additional
;paragraphs we need to keep resident.
;
;
mov ax, dpb_size ;size each DPB
add ax, 0fh ;round up
mov cl, 4
shr ax, cl
mov dpb_para, ax
sub ch, ch
mov cl, es:[bx].dev_num_units ;number of units
cmp cl, max_avail_units ;enough drive letters?
jbe make_dr050
mov ah, 9
mov dx, offset units_msg ;let user know that
int 21h ;we can't do all of them
mov cl, max_avail_units ;only do what we can
make_dr050:
call get_last_dpb ;find last DPB
jnc make_dr070
jmp make_dr999 ;exit if error
make_dr070:
;
;dx:si holds ptr to DPB for previous drive. Save the pointer.
;
mov prev_dpb_ptr.d_segment, dx
mov prev_dpb_ptr.d_offset, si
;
;Save device driver header address
;
mov cs:drv_head_offset, bx ;save drv header
mov dx, es
;get pointer to BPB table
lds bx, dword ptr cs:init_cmd_blk.init_bpb_tbo
mov al, cs:init_cmd_blk.init_drv_first ;drive
sub ah, ah ;unit counter
make_dr100:
push ax ;save unit count/drive #
mov ax, dx ;base of driver
add ax, cs:mem_to_keep ;set for DPB
mov es, ax
;
;Clear the area where DPB will reside. BPB to DPB function doesn't
;initialize all of the fields. This ensures that the drive access
;field is clear initially.
;
;
push cx ;save unit count
mov cx, cs:dpb_size ;clear the DPB area
xor al, al
xor di, di
rep stosb
pop cx
xor bp, bp ;es:bp ptr to new DPB
mov si, [bx] ;get BPB offset
;
;Use undocumented DOS call to translate BPB to DPB
;
mov ah, DOS_XLAT_BPB
int 21h
xor di, di
pop ax ;count/drive #
push ax ;save again
mov es:[di], ax ;put into DPB
;
;Put address of device driver into DPB. Offset for this DPB entry
;is DOS version specific.
;
push bx
mov bx, cs:dpb_drv_off
mov es:[di + bx].d_segment, dx ;&device driver
mov ax, cs:drv_head_offset
mov es:[di + bx].d_offset, ax
mov bx, cs:dpb_link_off
mov bp, bx ;save link offset
;
;Mark this as last DPB
;
mov ax, 0ffffh ;no link to next DPB
mov es:[di + bx].d_segment, ax
mov es:[di + bx].d_offset, ax
pop bx
inc bx ;next BPB table entry
inc bx
call fill_cds
;
;Link previous DPB to this one
;
mov ax, es ;DPB segment
mov si, di ;DPB offset
les di, cs:prev_dpb_ptr ;previous DPB
add di, bp ;link offset in DPB
mov es:[di].d_segment, ax
mov es:[di].d_offset, si
;
;Save pointer to DPB just created (in case more than one unit supported,
;it can be linked to the next DPB created).
;
mov cs:prev_dpb_ptr.d_segment, ax
mov cs:prev_dpb_ptr.d_offset, si
mov ax, cs:dpb_para ;# paragraphs DPB uses
add cs:mem_to_keep, ax ;add to total to keep
pop ax ;save unit count/drive #
inc ah ;unit counter
inc al ;drive counter
loop make_dr100
push ax ;save unit count
mov ah, DOS_LIST_LISTS
int 21h ;get list of lists ptr
pop ax ;restore it
;
;Increase the number of block devices variable kept in the DOS
;list of lists. The number will now reflect all block devices on
;the system (including any drive for which a CDS was found --which
;may have not been previously reflected in the variable). It was found
;to be necessary to include all block devices in this value. For
;example, if a CD-ROM drive existed on the system as drive e:, this
;variable would be a 4 (doesn't reflect the "network" drive). Simply
;adding the number of units installed by driver being loaded, was not
;sufficient to make DOS happy --the value needed to be the total
;number of block devices (including the CD-ROM drive).
;
mov si, cs:num_blk_off ;offset for # drives
mov al, cs:num_blk_devices ;original number
add al, ah
mov es:[bx + si], al
clc ;success
make_dr999:
pop es
pop ds
pop bx
ret
make_drives endp
;--------------------------------------------------------------
;fill_cds - fill in a CDS entry. |
; |
; Enter: |
; ds=driver BPB segment |
; es:di=ptr to DPB |
; cs:cds_ptr=ptr to first available CDS entry |
; cs:cds_size=size of each CDS entry |
; Exit: |
; |
; cs:cds_ptr=ptr to next CDS |
; |
; ds, di preserved (cx, dx not used) |
;--------------------------------------------------------------
fill_cds proc near
push ds
push di
mov ax, di ;offset of DPB
lds di, cs:cds_ptr ;get ptr to CDS
mov [di].cds_dbp_ptr4.d_offset, ax ;ptr to DPB
mov [di].cds_dbp_ptr4.d_segment, es
;indicate it's a physical drive
mov [di].cds_drv_stat4, (mask physical)
mov [di].cds_slash4, 2 ;slash in path
mov ax, 0ffffh
mov [di].cds_start_clus4, ax ;starting cluster
mov [di].cds_dk14.d_offset, ax ;ffs in this field
mov [di].cds_dk14.d_segment, ax
cmp byte ptr cs:dos_ver, 4 ;below DOS 4?
jb fill_cds500
inc ax ;zero
mov [di].cds_ifs.d_segment, ax ;no install file sys
mov [di].cds_ifs.d_offset, ax ;no install file sys
mov [di].cds_dk24, al ;zero unknown fields
mov [di].cds_dk34, ax
fill_cds500:
mov ax, cs:cds_size
add cs:cds_ptr.d_offset, ax ;next CDS entry
pop di
pop ds
ret
fill_cds endp
;--------------------------------------------------------------
;get_last_dpb - Search DPB chain and find last DPB entry. |
; It's possible to use DOS function 32h to |
; return a ptr to a DPB, but the call requires |
; an access to the drive and don't want |
; critical error if drive isn't ready (could |
; be removable media, etc.). |
; |
; Enter: |
; ds=local data segment |
; dpb_ptr=ptr to first DPB entry |
; last_drive=max number of CDS entries |
; |
; Exit: |
; |
; dx:si=ptr to last used DPB |
; CY=set if can't find ending entry |
; (this should never occur, since |
; we've already confirmed there |
; are enough CDS entries, etc.) |
; |
; ds, bx, cx, saved. |
;--------------------------------------------------------------
get_last_dpb proc near
push ds
push bx
push cx
sub ch, ch
mov cl, last_drive
mov bx, dpb_link_off ;ver specific offset
lds si, dpb_ptr
mov ax, 0ffffh ;end of list flag
get_last_100:
cmp [si + bx], ax ;at the end?
je get_last_900
lds si, [si + bx]
loop get_last_100
stc
get_last_900:
mov dx, ds ;save seg of DPB
pop cx
pop bx
pop ds
ret
get_last_dpb endp
;--------------------------------------------------------------
;add_to_chain - Add driver to driver chain. |
; |
; Enter: |
; ds=local data segment |
; es:bx=ptr to driver header |
; nul_dev_ptr=ptr to NUL device header |
; |
; Exit: |
; ds saved |
;--------------------------------------------------------------
add_to_chain proc near
push ds
lds si, nul_dev_ptr ;point to NUL header
mov ax, [si].dev_chain.d_offset ;ptr to next drvr
mov dx, [si].dev_chain.d_segment
cli
mov [si].dev_chain.d_offset, bx ;put ours in list
mov [si].dev_chain.d_segment, es
mov es:[bx].dev_chain.d_offset, ax ;link to
mov es:[bx].dev_chain.d_segment, dx ;old 1st drvr
sti
pop ds
ret
add_to_chain endp
;--------------------------------------------------------------
;go_tsr - Free our environment and then terminate and stay |
; resident, keeping the PSP and all paragraphs |
; of memory requested by the device driver. |
; |
; Enter: |
; ds=local data segment |
; mem_to_keep=paragraphs needed by driver |
; |
; Exit: |
; SHOULD NEVER RETURN |
; |
; if for some reason the TSR function |
; fails, return with CY set |
; |
;--------------------------------------------------------------
go_tsr proc near
mov dx, offset tsr_ok_msg ;report installed
mov ah, DOS_PRT_STRING
int 21h
mov ax, es ;driver segment
sub ax, 10h ;back to the PSP
mov es, ax
mov bx, psp_environ_seg
mov ax, es:[bx] ;get environment segment
mov es, ax
mov ah, DOS_RELEASE_MEM
int 21h
mov dx, mem_to_keep
add dx, 10h ;add in PSP length
mov ax, (DOS_TSR_FUNC SHL 8) ;exit code 0
int 21h ;Go TSR
ret
go_tsr endp
even
drv_stack dw DRV_STACK_SIZE dup('SS')
drv_stack_ptr label word
code ends
end startup