/*
 * nbramdrv.S86  -  DOS ramdisk device driver
 *
 * Copyright (C) 2002-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  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.7 2007/01/06 18:31:23 gkminix Exp $
 *
 *====================================================================
 */

#include <common.i86>
#include <i386/ctype.i86>
#include <system/bios.i86>
#include <system/partition.i86>
#include <dos/functions.i86>
#include <dos/ioctl.i86>
#include "headers/boot.i86"
#include "headers/dos.i86"
#include "headers/fat.i86"

.file __FILE__
.line __LINE__


/*
 *====================================================================
 *
 * 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:devreq_command[di]
	cmp	al,[bx]			# check if command is valid
	jbe	1f
	mov	ax,DEVERR_COMMAND	# function not supported
	jmp	2f

1:	xor	ah,ah
	shl	ax,1
	inc	bx			# get pointer into routine table
	add	bx,ax
	call	[bx]			# call handler
2:	or	ax,DEVERR_BUSY		# indicate "request done"
	mov	es:devreq_status[di],ax
	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,DEVERR_COMMAND	# 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:dev3_buflen[di]
	jbe	1f
	mov	cx,word ptr es:dev3_buflen[di]
1:	mov	ax,cx
	jcxz	8f

	cld
	push	es
	push	di
	les	di,es:dev3_txaddr[di]
	mov	si,offset chrdat
	rep	movsb			# move data into user buffer
	pop	di
	pop	es
8:	mov	es:dev3_buflen[di],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:dev3_buflen[di]
	cmp	cx,4			# length of command string has to
	jb	8f			# be between 4 and 6 (to care for
	cmp	cx,6			# trailing end-of-line).
	jbe	1f
8:	mov	ax,DEVERR_WRITE		# return with error
	xor	cx,cx			# indicate no characters written
	jmp	9f

1:	les	di,es:dev3_txaddr[di]	# get address of command buffer
	cmp	word ptr es:[di + 0],0x4452
	jne	8b			# 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	2f
	mov	bx,offset accflg
	cmp	al,0x41			# check for ACCESS command
	je	2f
	mov	dl,2
	mov	bx,offset active
	cmp	al,0x53			# check for START command
	jne	8b			# command not recognized

2:	sub	ah,0x30
	jb	8b			# check for correct argument
	cmp	ah,dl			# has to be between 0 and 1
	ja	8b
	mov	[bx],ah			# save new flag value
	xor	ax,ax			# return without error
9:	pop	di
	pop	es
	mov	word ptr es:dev3_buflen[di],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:devreq_subunit[di]
	cmp	ax,[drvnum]		# check subunit number of drive
	pop	ax
	jb	9f
	pop	ax			# remove callers address from stack
	mov	ax,DEVERR_UNKNOWN	# set error code
9:	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	9f
	pop	ax			# remove callers address from stack
	mov	ax,DEVERR_NOTREADY	# set error code
9:	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	1f
	test	byte ptr [active],0xFF	# check active flag
	jnz	9f
1:	pop	ax			# remove callers address from stack
	mov	ax,DEVERR_PROTECT	# set error code
9:	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	8f			# has been changed to let it
	mov	al,0xFF			# reread the FAT
	mov	byte ptr [active],1
8:	mov	byte ptr es:dev1_status[di],al
	mov	word ptr es:dev1_volume[di + 0],offset volnum
	mov	word ptr es:dev1_volume[di + 2],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	9f
	mov	ax,DEVERR_REMOVE	# not removable
9:	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:dev2_bpb[di + 0],offset bpb
	mov	word ptr es:dev2_bpb[di + 2],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	9f
	dec	word ptr [opncnt]
9:	xor	ax,ax
	ret



/*
 *====================================================================
 *
 * Read from ramdisk device
 * Input:  ES:DI  -  Pointer to request header
 * Output: AX     -  Result code
 * Registers changed: AX, BX, CX, EDX, SI
 */
block_read:

	xor	ax,ax			# indicate read access

1:	call	chkdrv			# check subunit number
	call	chkacc			# check access flag
	movzx32	edx,word ptr es:dev4_sector[di]
	cmp	byte ptr es:devreq_length[di],DEV4_LENGTH
	jne	2f			# Compaq DOS and DR-DOS use a different
	cmp	dx,0xFFFF		# layout of the request header
	jne	3f
	mov	edx,es:dev4_sect32[di]	# get 32-bit starting sector number
	jmp	3f

2:	cmp	byte ptr es:[di + 0],DEV4_COMPAQ_LEN
	jne	3f
	mov	edx,es:dev4_sector[di]	# for Compaq DOS and DR-DOS
3:	add	edx,[hidden]
	mov	cx,es:dev4_count[di]	# get sector count and address
	mov	bx,es:dev4_txaddr[di + 2]
	mov	si,es:dev4_txaddr[di + 0]
	call	rdrw			# actually transfer the sectors
	mov	dx,cs
	shl	edx,16			# return pointer to volume ID
	mov	dx,offset volnum
	mov	es:dev4_volume[di],edx
	mov	es:dev4_count[di],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	1b



/*
 *====================================================================
 *
 * 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
	movzx32	ecx,cx
	mov	eax,[rdsize]
	shl	eax,1			# determine size of ramdisk in sectors
	sub	eax,edx
	jnc	1f
5:	xor	cx,cx
6:	mov	ax,DEVERR_SEEK		# return seek error
	pop	di
	ret

1:	cmp	eax,ecx			# check if going to read past end of
	jbe	2f			# ramdisk
	mov	eax,ecx
2:	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	3f			# 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	4f

3:	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
4:	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	5b
	mov	eax,[xmslen]
	shr	eax,9
	cmp	cx,ax			# check if all sectors transferred
	je	7f
	mov	cx,ax			# return number of transferred sectors
	jmp	6b			# together with seek error

7:	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	9f
	cmp	word ptr [drvnum],1
	jbe	9f
	mov	dl,[logdrv]		# return logical drive number
9:	mov	es:devreq_subunit[di],dl
	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	9f
	cmp	word ptr [drvnum],1
	jbe	9f
	mov	dl,es:devreq_subunit[di]
	inc	dl			# set new logical drive
	mov	[logdrv],dl		# return logical drive number
9:	mov	es:devreq_subunit[di],dl
	xor	ax,ax
	ret



/*
 *====================================================================
 *
 * Generic IOCTL function table
 */
gentab:	.byte	DOS_GEN_SETPARAMS	# set device parameters
	.word	set_params
	.byte	DOS_GEN_WRITETRACK	# write physical track
	.word	write_track
	.byte	DOS_GEN_SETSERIAL	# set volume serial number
	.word	set_volnum
	.byte	DOS_GEN_SETACCESS	# set access flag
	.word	set_access
	.byte	DOS_GEN_GETPARAMS	# get device parameters
	.word	get_params
	.byte	DOS_GEN_READTRACK	# read physical track
	.word	read_track
	.byte	DOS_GEN_VERIFYTRACK	# verify track address
	.word	verify_track
	.byte	DOS_GEN_GETSERIAL	# get volume serial number
	.word	get_volnum
	.byte	DOS_GEN_GETACCESS	# get access flag
	.word	get_access
	.byte	DOS_GEN_GETMEDIAID	# get media type
	.word	get_mediaid
	.byte	DOS_GEN_DRIVEMAP	# 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,DOS_CAT_DISK		# check if category code for disk
	jne	8f
	mov	bx,offset gentab + 1
1:	mov	al,[bx - 1]
	or	al,al			# check if at end of list
	jz	8f
	cmp	al,ah			# check if command code found
	je	9f
	add	bx,3			# check next command code
	jmp	1b

8:	xor	bx,bx
9:	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	al,es:dev13_category[di]
	mov	ah,es:dev13_function[di]
	call	fndgen			# find handler address
	mov	ax,DEVERR_COMMAND
	or	bx,bx
	jz	9f
	xor	ax,ax			# return without error
9:	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	al,es:dev13_category[di]
	mov	ah,es:dev13_function[di]
	call	fndgen			# find handler address
	mov	ax,DEVERR_COMMAND
	or	bx,bx
	jz	9f
	push	es
	push	di
	les	di,es:dev13_params[di]	# call handler with pointer to
	call	[bx]			# parameter block
	pop	di
	pop	es
9:	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	1f			# starts with zero
	cmp	cx,[sptrk]		# check for valid sector number
	ja	1f			# starts with one
	jcxz	1f			# sector zero doesnt exist
	cmp	dx,[hdnum]		# check for valid head number
	jb	2f			# starts with zero
1:	mov	ax,DEVERR_SEEK		# return seek error
	ret

2:	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
1:	call	chkacc				# check access flag
	mov	bx,es:dos_gen41_cylinder[di]	# get track number
	mov	cx,es:dos_gen41_sector[di]	# get first sector number
	mov	dx,es:dos_gen41_head[di]	# get head number
	call	cvtsec				# convert into linear sector no.
	xor	cx,cx
	or	ax,ax
	jnz	9f				# check for error
	mov	ax,si				# get transfer address
	mov	bx,es:dos_gen41_transfer[di + 2]
	mov	si,es:dos_gen41_transfer[di + 0]
	mov	cx,es:dos_gen41_sectnum[di]	# get number of sectors
	call	rdrw				# actually transfer the data
9:	mov	es:dos_gen41_sectnum[di],cx	# save number of sectors
	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	1b



/*
 *====================================================================
 *
 * 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:dos_gen42_cylinder[di]	# get first track number
	mov	cx,[sptrk]			# check for last sector
	mov	dx,es:dos_gen42_head[di]	# get head number
	call	cvtsec				# check for a valid sector
	or	ax,ax
	mov	dl,0x02
	jnz	9f

	test	byte ptr es:dos_gen42_special[di],0x01
	jz	8f				# verify multiple tracks?
	mov	cx,es:dos_gen42_tracknum[di]	# get number of tracks
	jcxz	8f
	dec	cx
	add	bx,cx				# get last track number
	mov	cx,[sptrk]			# check for last sector
	mov	dx,es:dos_gen42_head[di]	# get head number
	call	cvtsec				# check for a valid sector
	or	ax,ax
	mov	dl,0x02
	jnz	9f
8:	xor	dl,dl
9:	mov	es:dos_gen42_special[di],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,DOS_GEN_BPBSIZE	# 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,DEVERR_GENERAL	# all sectors have to have same size
	test	byte ptr es:dos_gen40_special[di],0x04
	jz	9f
	test	byte ptr es:dos_gen40_special[di],0x02
	jnz	8f			# use only track layout field

	mov	ax,es:dos_gen40_bpb[di + bpb_headnum]
	cmp	ax,MAX_HEADS		# check number of heads
	ja	9f
	mov	bx,es:dos_gen40_bpb[di + bpb_sptrack]
	cmp	bx,MAX_SECTS		# check number of sectors per track
	ja	9f
	mul	bx
	mov	bx,es:dos_gen40_cylinders[di]
	cmp	bx,MAX_CYLS		# check number of tracks
	ja	9f
	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:dos_gen40_bpb[di + bpb_totsect]
	or	ebx,ebx
	jnz	1f
	mov	ebx,es:dos_gen40_bpb[di + bpb_totsect32]
1:	cmp	ebx,edx			# check that total size is correct
	jne	9f
	inc	edx
	shr	edx,1			# convert it into number of kB
	cmp	edx,[rdsize]		# check that max ramdisk size not
	ja	9f			# exceeded

	mov	al,es:dos_gen40_type[di]
	mov	[devtyp],al		# set device type
	mov	bpb_devtype[bpb],al
	mov	ax,es:dos_gen40_attrib[di]
	mov	[devatr],ax		# set device attributes
	mov	bpb_devattr[bpb],ax
	mov	ax,es:dos_gen40_cylinders[di]
	mov	[trknum],ax		# set number of tracks
	mov	bpb_cylnum[bpb],ax
	mov	ax,es:dos_gen40_bpb[di + bpb_sptrack]
	mov	[sptrk],ax		# set number of sectors per track
	mov	ax,es:dos_gen40_bpb[di + 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,dos_gen40_bpb[di]
	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
8:	xor	cx,cx
9:	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,_I(dos_gen46_serial)
	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,dos_gen46_serial[di]
	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:dos_gen47_flag[di],0xFF
	jz	1f
	inc	al
1:	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:dos_gen47_flag[di],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:dos_gen68_special[di],0x01
	mov	al,[devtyp]
	mov	es:dos_gen68_mediaid[di],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,DEVERR_GENERAL
	cmp	byte ptr es:dos_gen6F_size[di],DOS_GEN6F_LENGTH
	jb	9f
	mov	byte ptr es:dos_gen6F_len[di],DOS_GEN6F_LENGTH
	mov	byte ptr es:dos_gen6F_flags[di],0
	mov	byte ptr es:dos_gen6F_drvnum[di],0xFF
	mov	eax,[drvmap]
	mov	dword ptr es:dos_gen6F_drvmap[di],eax
	mov	eax,[hidden]
	mov	dword ptr es:dos_gen6F_partition[di + 0],eax
	xor	eax,eax
	mov	dword ptr es:dos_gen6F_partition[di + 4],eax
9:	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
	movzx32	edx,word ptr es:dev0_memavail[di + 2]
	sub	dx,ax			# determine number of bytes available
	jc	1f			# to driver
	shl	edx,4
	movzx32	eax,word ptr es:dev0_memavail[di + 0]
	add	edx,eax
	cmp	edx,offset __bss_end + 16
	jae	2f			# check if its enough

1:	mov	si,offset memerr
doerr1:	push	si
	mov	si,offset errmsg
	call	prnstr
	pop	si
	call	prnstr			# print error message
	call	prcrlf
	mov	ax,DEVERR_GENERAL	# return with error
	jmp	9f

2:	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:dev0_cmdline[si]	# 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	3f			# 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.
 */

3:	mov	ax,DOS_FN_WORD(DOS_FN_GET_VERSION)
	xor	bx,bx
	int	INT_DOS			# 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	5f

	mov	ax,0x9C00
	int	0xF8			# check if a ramdisk driver is
	cmp	ax,0x009D		# installed
	jne	5f
	cmp	bl,2			# check that we have ramdisk driver
	jae	1f			# version 2.0 or higher
5:	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.
 */

1:	mov	ax,0x0800
	int	INT_MULTI		# check if DRIVER.SYS support is
	inc	al			# installed
	mov	si,offset syserr
	jnz	doerr1

	push	ds
	mov	ax,0x0803
	int	INT_MULTI		# 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	INT_XMS			# check if XMS services are available
	cmp	al,0x80
	mov	si,offset xmserr
	jne	doerr1
	mov	ax,0x4310
	int	INT_XMS			# 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	2f			# adding the ramdisk as a new drive
	test	byte ptr [newflag],0xFF	# is only possible if it is simula-
	jz	2f			# 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.
 */

2:	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	3f			# 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.
 */

3:	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	4f

	shl	edx,16			# convert address into 32-bit
	mov	dx,bx
	mov	eax,[rdaddr]
	sub	eax,edx			# compute ramdisk offset into block
	jnc	5f

	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
4:	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

5:	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_hidden[bpb]
	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,DOS_FN_GET_SYSVARS
	int	INT_DOS			# get the DOS list of lists
	lea	bx,[bx + 0x0022]	# get address of NUL device header

1:	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	2f
	les	bx,es:[bx]		# continue with next driver
	jmp	1b

2:	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	3f			# 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	3f
	jcxz	3f
	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	5f
	mov	[btphdl],dx		# save memory block handle
	mov	[dsthdl],dx
	mov	ah,0x0C
	lcall	[xmsadr]		# lock memory block
	or	ax,ax
	jz	5f

	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	3f			# XMS memory block

5:	mov	si,offset btperr
doerr3:	push	si
	mov	dx,[btphdl]
	or	dx,dx
	jz	4f
	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]
4:	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.
 */

3:	test	byte ptr [newflag],0xFF
	jz	6f

	mov	bx,offset rd_dev_header
	mov	si,offset char_dev_header
	mov	word ptr devhdr_next[si + 0],bx
	mov	word ptr devhdr_next[si + 2],cs
	and	word ptr devhdr_attr[bx],0xFFFD
	mov	word ptr [blktab + 1],offset blkini
	mov	byte ptr [active],0	# disable write accesses temporarily
	jmp	8f

/*
 * 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.
 */

6:	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	INT_DISK

/*
 * Check if the user asked for verbose output
 */

	test	byte ptr [verbose],0xFF
	jz	8f
	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.
 */

8:	xor	ax,ax
9:	mov	word ptr [chrtab + 1],offset donop
	mov	word ptr [drvhdr + 2],cs

	test	ax,DEVERR_ERRFLAG	# on error dont make the driver
	jz	1f			# resident
	xor	bx,bx
	xor	dl,dl
	jmp	2f

1:	mov	dl,1			# number of subunits of char drv
	mov	bx,offset __bss_end
	test	byte ptr [newflag],0xFF
	jnz	2f			# setup correct end address
	call	getend
2:	pop	di			# restore pointer to request header
	pop	es			# and set the resident memory area
	mov	byte ptr es:dev0_units[di],dl
	mov	word ptr es:dev0_free[di + 0],bx
	mov	word ptr es:dev0_free[di + 2],cs
	mov	dword ptr es:dev0_bpb[di],0
	cmp	byte ptr es:devreq_length[di],DEV0_LENGTH
	jb	3f
	mov	word ptr es:dev0_errflag[di],0
3:	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	2f			# check for debugger trap
	int3

2:	mov	cl,es:dev0_firstdrv[di]
	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	1f
	call	prnvrb			# print verbose information

1:	call	getend			# get end address
	xor	ax,ax
	mov	byte ptr [nunits],1
	mov	word ptr [blktab + 1],offset donop
	mov	byte ptr es:dev0_units[di],1
	mov	word ptr es:dev0_free[di + 0],bx
	mov	word ptr es:dev0_free[di + 2],cs
	mov	word ptr es:dev0_bpb[di + 0],offset bpbtab
	mov	word ptr es:dev0_bpb[di + 2],cs
	cmp	byte ptr es:devreq_length[di],DEV0_LENGTH
	jb	9f
	mov	word ptr es:dev0_errflag[di],ax
9:	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	9f
	mov	bx,offset end_simple
	mov	byte ptr [blktab + 0],0x0F	# limit block driver functions
	and	word ptr [rd_dev_header + 4],0xFF3F
9:	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	INT_DISK		# call ramdisk driver to get geometry
	mov	si,offset geoerr
	jc	9f
	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	1f
	cmp	byte ptr [drvid],DISKID_HD
	jae	1f
	mov	cx,0x0002
	cmp	bl,6			# convert BIOS drive type into
	jbe	2f			# DOS drive type
1:	xor	bl,bl
2:	xor	bh,bh
	mov	al,typtab[bx]
	mov	byte ptr [devtyp],al
	mov	word ptr [devatr],cx
8:	xor	si,si			# return without error
9:	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	INT_DISK
	jc	2f
	mov	ax,bootbuf[BOOT_SIG_OFS]
	cmp	ax,BOOT_SIG			# check for boot signature
	jne	2f

	cmp	dl,DISKID_HD			# check if this was the
	jb	3f				# partition table
	lea	si,bootbuf[PART_TABLE_OFS]
	cmp	byte ptr part_status[si],PART_ACTIVE
	jne	2f				# check if entry active
	mov	al,part_type[si]		# get type of partition
	cmp	al,PART_TYPE_12			# check for 12-bit FAT
	je	1f
	cmp	al,PART_TYPE_16_SMALL		# check for small 16-bit FAT
	je	1f
	cmp	al,PART_TYPE_16_LARGE		# check for large 16-bit FAT
	jne	2f
1:	mov	dh,part_first_head[si]
	mov	cx,part_first_sector[si]	# get parameters for first
	mov	ax,0x0201			# sector of partition
	int	INT_DISK			# read boot sector from ramdisk
	jc	2f
	mov	ax,bootbuf[BOOT_SIG_OFS]
	cmp	ax,BOOT_SIG			# check for boot signature
	je	3f
2:	mov	si,offset boterr		# return with error message
	jmp	9f

3:	cld
	lea	si,bootbuf[bootrec_bpb]
	mov	di,offset bpb
	mov	cx,BPB_BOOT_SIZE		# copy DOS BPB out of boot
	rep	movsb				# sector
	xor	al,al
	mov	cx,BPB_SIZE - BPB_BOOT_SIZE
	rep	stosb

	mov	eax,bootbuf[bootrec_vol_num]
	mov	[volnum],eax			# get volume number
	lea	si,bootbuf[bootrec_vol_name]
	mov	di,offset volnam		# get volume name
	mov	cx,VOLNAME_LEN
	rep	movsb
	lea	si,bootbuf[bootrec_fat_name]
	mov	di,offset fsname		# get file system name
	mov	cx,FSNAME_LEN
	rep	movsb
						# do some sanity checks
	mov	ax,bpb_sectsize[bpb]		# on the BPB
	cmp	ax,SECTSIZE
	jne	2b
	mov	ax,bpb_sptrack[bpb]
	cmp	ax,[sptrk]
	jne	2b
	mov	ax,bpb_headnum[bpb]
	cmp	ax,[hdnum]
	jne	2b

	mov	ax,[trknum]			# save number of cylinders
	mov	bpb_cylnum[bpb],ax
	mov	al,[devtyp]			# save ramdisk device type
	mov	bpb_devtype[bpb],al
	mov	ax,[devatr]			# save ramdisk device attribute
	mov	bpb_devattr[bpb],ax

8:	xor	si,si
9:	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,DOS_FN_GET_SYSVARS
	int	INT_DOS			# get the DOS list of lists
	mov	eax,es:[bx]		# get pointer to first DCB
	or	eax,eax			# check for valid pointer
	jz	6f
	mov	[dcbadr],eax

	test	byte ptr [newflag],0xFF
	jz	5f
	mov	dx,1			# set number of drives
	jmp	8f

5:	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	1f			# care for different DDT format with
	mov	di,28			# DOS version 7.10+
1:	cmp	bx,0xFFFF		# check if at end of list
	je	8f
	cmp	ch,es:[bx + 4]		# check if physical unit found
	jne	0f
	mov	cl,es:[bx + 5]		# get logical drive number
	cmp	cl,MAX_DRIVES		# should not exceed drive letter Z
	jae	6f

	test	word ptr es:[bx + di + 35],0x0020
	jz	2f			# set new logical drive number if this
	mov	[logdrv],cl		# logical drive owns the physical drive
2:	push	es
	push	bx
	les	bx,[dcbadr]		# get pointer to DCB list
3:	cmp	bx,0xFFFF		# check if at end of list
	je	7f
	cmp	cl,es:[bx + 0]		# check if we found the ramdisk drive
	jne	4f

	mov	eax,0x00000001		# compute map for this logical drive
	shl	ax,cl
	test	[drvmap],eax		# check if drive already setup
	jnz	5f
	or	[drvmap],eax		# update drive map
	inc	dx			# increment drive counter
	cmp	dx,MAX_DRIVES		# should not exceed 26 entries
	jae	5f
	mov	word ptr [si + 0],bx	# save DCB address
	mov	word ptr [si + 2],es
	add	si,4
4:	les	bx,es:[bx + 25]		# get pointer to next DCB entry
	jmp	3b

5:	pop	bx
	pop	es			# return with error
6:	mov	si,offset dcberr
	jmp	9f

7:	pop	bx			# if end of DCB list reached continue
	pop	es			# with next DDT entry
0:	les	bx,es:[bx + 0]		# get pointer to next DDT entry
	jmp	1b

8:	mov	cx,dx
	jcxz	6b			# check that we found at least one DCB
	mov	[drvnum],cx		# save device count
	xor	si,si
9:	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	9f			# new device dont change the DCBs
	xor	al,al
	mov	cx,[drvnum]		# get number of DCB entries
	mov	si,offset dcblst
1:	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	1b
9:	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	9f			# 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	1f			# the table entry format has been
	mov	si,28			# changed with DOS version 7.10
1:	cmp	bx,0xFFFF		# check if at end of list
	je	9f
	mov	al,es:[bx + 4]		# get physical unit number
	cmp	dh,al			# if its the ramdisk mark the entry
	jne	2f			# 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	3f

2:	cmp	dh,DISKID_HD		# check if we are simulating a hard
	jb	3f			# disk drive
	cmp	al,DISKID_HD		# dont change anything for floppy
	jb	3f			# disk drives
	dec	al			# normalize physical unit number
	mov	es:[bx + 4],al
3:	les	bx,es:[bx + 0]		# continue with next entry
	jmp	1b

9:	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	6f
	mov	si,offset vmsg4
	call	prnstr			# print readonly message
6:	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	1f

	call	prndec			# print ramdisk size in kB
	mov	si,vmsgkb
	call	prnstr
	jmp	3f

1:	xor	edx,edx
	mov	ebx,1024
	div	ebx			# compute number of MB
	push	edx
	call	prndec			# print full MB
	mov	ax,DOS_FN_WORD(DOS_FN_COUNTRY_INFO)
	mov	dx,offset country
	int	INT_DOS			# get country information
	mov	al,0x2E
	jc	2f
	mov	al,country[9]		# get decimal seperator
2:	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
3:	call	prcrlf

	test	byte ptr [newflag],0xFF
	jz	8f
	mov	si,offset vmsg11	# print new drive message
	call	prnstr
	mov	al,[logdrv]
	add	al,0x41
	call	prnchr			# print new drive letter
	jmp	9f

8:	mov	si,offset vmsg3
	call	prnstr			# print drive message
	mov	ebx,[drvmap]
	mov	dx,0x0041
	mov	cx,MAX_DRIVES
4:	shr	ebx,1
	jnc	5f
	or	dh,dh
	jz	0f
	mov	al,0x2C			# print comma
	call	prnchr
	mov	al,0x20			# print space
	call	prnchr
0:	mov	al,dl
	call	prnchr			# print drive letter
	inc	dh
5:	inc	dl
	loop	4b			# continue with all drives
9:	call	prcrlf

	mov	si,offset vmsg8
	call	prnstr			# print BOOTP/DHCP message
	mov	ax,[btphdl]
	or	ax,ax
	jnz	7f
	mov	si,offset vmsg9
	call	prnstr			# print "not"
7:	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	INT_OUTCHAR
	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	1f



/*
 *====================================================================
 *
 * Print a zero-terminated string onto the screen
 * Input:  DS:SI  -  Pointer to string
 * Output: none
 * Registers changed: AX
 */
prnstr:	push	si
1:	cld
2:	lodsb
	or	al,al
	jz	3f
	call	prnchr
	jmp	2b
3:	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	9f

1:	or	ax,ax
	jz	8f
	xor	dx,dx
	div	bx
	add	dl,0x30
	mov	byte ptr [si],dl
	dec	si
	jmp	1b

8:	inc	si
9:	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
1:	mov	al,es:[si]		# get next character
	cmp	al,CHR_CR
	je	2f
	cmp	al,CHR_LF		# check if it is any of the end
	je	2f			# of line characters
	or	al,al
	jnz	3f
2:	xor	al,al			# if so indicate end of line
	jmp	9f

3:	inc	si			# increase character pointer
	cmp	al,CHR_BLANK
	je	4f
	cmp	al,CHR_TAB		# check if any of the blank characters
	jne	5f
4:	inc	ah			# if so increase blank counter
	jmp	1b

5:	or	ah,ah			# do we had any blanks so far
	jz	8f
	mov	al,CHR_BLANK		# return just a single blank and reset
	dec	si			# SI to first non-blank character

8:	cmp	al,0x61			# check if lower case character
	jb	9f
	cmp	al,0x7A
	ja	9f
	sub	al,0x20			# convert into upper case character
9:	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	8f
	cmp	al,CHR_BLANK		# check if blank character
	je	gettok
	cmp	al,0x2F			# token has to start with a slash
	jne	7f

	xor	cx,cx
	xor	dx,dx			# indicate no argument value
	mov	di,offset curtok
1:	cmp	cx,32			# check if max token length reached
	jb	2f
7:	mov	ax,1			# return with error
	ret

2:	call	getchr			# get next token character
	or	al,al
	jz	4f
	cmp	al,CHR_BLANK		# check for end of token
	je	4f
	cmp	al,0x3D			# check for equal sign
	je	3f
	mov	[di],al			# continue with next character
	inc	di
	inc	cx
	jmp	1b

3:	call	getchr			# get next character after equal sign
	or	al,al
	jz	7b			# should not be end of line
	cmp	al,0x3D
	je	7b			# should not be another equal sign
	cmp	al,CHR_BLANK
	je	3b			# skip blank characters after equal sign
	dec	si
	inc	dx			# remember that we have an argument value
	jmp	6f

4:	call	getchr			# get next character after blank
	or	al,al
	jz	6f			# no argument if at end of line
	cmp	al,CHR_BLANK
	je	4b			# skip blanks after token
	cmp	al,0x3D
	je	3b			# check for argument after equal sign
5:	dec	si
6:	jcxz	7b			# 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
1:	lodsw
	or	ax,ax			# check if at end of token list
	jnz	2f
	pop	si
	pop	es
	jmp	7b			# token not found

2:	mov	cx,bx
	mov	di,offset curtok
	repe	cmpsb			# compare token with list string
	jz	4f
	dec	si
3:	lodsb				# find end of list string
	or	al,al
	jz	1b			# continue with next list entry
	jmp	3b

4:	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	6f
5:	cmp	cx,32			# check if argument too long
	jae	7b
	call	getchr			# get next argument character
	or	al,al
	jz	6f
	cmp	al,CHR_BLANK		# check if end of argument
	je	6f
	mov	[di],al			# save character and continue with
	inc	di			# next character
	inc	cx
	jmp	5b

6:	mov	byte ptr [di],0		# terminate argument with zero
	mov	[toklen],cx		# save length of argument value
	jmp	9f

8:	xor	bx,bx			# indicate end of command line
9:	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	8f
	cmp	al,CHR_BLANK		# skip until first blank or slash char
	je	1f
	cmp	al,0x2F
	jne	docmdl

1:	call	gettok			# get next token from command line
	or	ax,ax
	jnz	9f
	or	bx,bx			# check if at end of command line
	jz	8f
	push	si
	push	es
	call	bx			# call token handler
	pop	es
	pop	si
	or	ax,ax			# check if error
	jnz	9f
	jmp	1b			# continue with next token

8:	xor	ax,ax
9:	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
1:	xor	ax,ax
	cmp	byte ptr [bx],0
	jne	8f			# should not be defined twice
	cmp	byte ptr [curtok],0
	jne	8f			# should not have any argument
	inc	byte ptr [bx]
	jmp	9f

8:	inc	ax			# return with error
9:	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	1b



/*
 *====================================================================
 *
 * 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	1b



/*
 *====================================================================
 *
 * 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	1b



/*
 *====================================================================
 *
 * 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	1b



/*
 *====================================================================
 *
 * 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
	xor	ax,ax
	cmp	byte ptr [bx],0xFF
	jne	8f			# should not be defined twice
	mov	si,offset on_arg
	call	cmparg			# check if argument is ON
	jnz	2f
	mov	byte ptr [bx],0x01
	jmp	9f

2:	mov	si,offset of_arg
	call	cmparg			# check if argument is OFF
	jnz	8f
	mov	byte ptr [bx],0x00
	jmp	9f

8:	inc	ax			# return with error
9:	ret




/*
 *====================================================================
 *
 * Strings and constant data segment
 */
	_rodata


/*
 * Copyright and startup message
 */
sigmsg:	.byte	0x0D, 0x0A
	.ascii	"Netboot DOS Ramdisk Driver "
	.ascii	VERSION
	.byte	0x0D, 0x0A
	.ascii	COPYRIGHT
	.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"


/*
 * 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"




/*
 *====================================================================
 *
 * Data segment
 */
	.data


/*
 * 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




/*
 *====================================================================
 *
 * 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,SECTSIZE


/*
 * 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

