/*
 * rmrd.S86  -  DOS program to remove ramdisk
 *
 * Copyright (C) 1996-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: rmrd.S86,v 1.7 2007/01/06 18:31:23 gkminix Exp $
 *
 *====================================================================
 */

#include <common.i86>
#include <system/bios.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 0x0100,
 * but we cant use the .org directive. The assembler would then
 * write 0x0100 times 0 values. Instead we have to tell the linker
 * using a ldscript file that this starts at an absolute address.
 */

	.text


	.global	_start

_start:	jmp	start



/*
 *====================================================================
 *
 * When using our own boot sector, the BIOS disk parameter block (DPB)
 * for the first floppy drive gets stored into the resident ramdisk
 * driver. Therefore, unless no other program has modified the DPB pointer
 * within the interrupt table, it will still point into the resident
 * area. When we remove the resident ramdisk drive we have to make
 * sure that the DPB remains in memory. In that case we copy the DPB
 * into the following data area and then make this program resident.
 * It then only contains the DPB and nothing else, but keeps the DPB
 * from getting overwritten.
 */
dpb:	.space	DPB_SIZE,0



/*
 *====================================================================
 *
 * Here the actual game starts. It is not as simple as calling the
 * resident driver to remove itself. First we have to check for
 * some error conditions:
 *
 *	- the DOS version version has to be 3.2 or above
 *	- the version of the ramdisk driver has to be 2.0 or above
 *	- the ramdisk can only get removed if it is simulating the
 *	  first floppy disk drive
 *	- at least one physical floppy disk drive has to be installed
 *	  in the system, because DOS allocates a driver entry for the
 *	  ramdisk, and we can't remove that but just change the para-
 *	  meter to fit the real drive
 *	- the ramdisk should not be the current drive
 */
start:	push	cs
	pop	ds

/*
 * Check for correct versions of DOS and ramdisk driver
 */

	mov	ax,DOS_FN_WORD(DOS_FN_GET_VERSION)
	xor	bx,bx
	int	INT_DOS			# get DOS version
	xchg	al,ah
	cmp	ax,0x0302		# has to be at least 3.2
	jb	1f
	cmp	bh,0xFD			# check for FreeDOS which doesnt
	jne	2f			# support some functions needed
1:	mov	si,offset invdos
	mov	al,1
	jmp	doerr

2:	mov	[dosver],ax		# save DOS version
	xor	ax,ax
	mov	es,ax
	mov	si,offset rdsig
	mov	di,0xF1 * 4		# check ramdisk driver signature
	mov	cx,2
	repz	cmpsw
	jnz	3f

	mov	ax,0x9C00
	int	0xF8			# check if a ramdisk driver is
	cmp	ax,0x009D		# installed
	jne	3f
	cmp	bl,2			# check that we have ramdisk driver
	jae	4f			# version 2.0 or higher
3:	mov	si,offset noramd
	mov	al,1
	jmp	doerr

/*
 * Next check that the ramdisk is simulating the first floppy disk drive, and
 * that there is a physical floppy drive installed in the system. Also check
 * that the ramdisk is not the current drive.
 */

4:	mov	ax,0x9C03
	int	0xF8			# get ramdisk ID
	or	cl,cl			# has to be drive A: (e.g. floppy)
	jz	5f
	mov	si,offset invdrv
	mov	al,2
	jmp	doerr

5:	test	ch,0x01			# check that there is at least one
	jnz	6f			# physical floppy drive
	mov	si,offset noflop
	mov	al,2
	jmp	doerr

6:	shr	ch,6
	inc	ch			# determine number of physical floppy
	mov	[dsknum],ch		# drives (max. number is 2)

	mov	ah,DOS_FN_GET_DRIVE
	int	INT_DOS			# get current default drive from DOS
	or	al,al			# should not be A:
	jz	7f
	cmp	byte ptr [dsknum],1
	ja	1f			# if we have just one physical disk
	dec	al			# drive, it should also not be B:
	jnz	1f
7:	mov	si,offset curdrv
	mov	al,2
	jmp	doerr

/*
 * Copy the DPB from the resident ramdisk driver into our own memory for
 * later reference.
 */

1:	mov	ax,0x9C05		# get address of ramdisk DPB from
	int	0xF8			# driver
	mov	word ptr [dpbptr + 0],dx
	mov	word ptr [dpbptr + 2],bx
	mov	si,ds
	mov	es,si
	mov	ds,bx
	xchg	si,dx
	mov	di,offset dpb
	mov	cx,DPB_SIZE		# copy DPB into our own memory
	rep	movsb
	mov	ds,dx

/*
 * Now we can ask the resident driver to remove itself. If the driver turned
 * off the ramdisk and could remove itself from memory, and nobody moved the
 * DPB, we have to use our own copy of the DPB.
 */

	mov	ax,0x9C04
	int	0xF8			# remove ramdisk
	or	al,al
	jz	8f
	cmp	al,2			# check for error code
	je	9f
	mov	si,offset rderr
	mov	al,3
	jmp	doerr

8:	xor	ax,ax
	mov	es,ax			# check if someone changed the
	mov	eax,es:[DPB_VECT]	# DPB already
	cmp	eax,[dpbptr]
	jne	9f
	mov	word ptr es:[DPB_VECT + 0],offset dpb
	mov	word ptr es:[DPB_VECT + 2],cs
	inc	byte ptr [resflg]

/*
 * Since the ramdisk driver has now been uninstalled, we can ask the BIOS
 * about the parameters of the physical floppy drive, and setup the DOS
 * IOCTL parameter block. Note that some BIOS incorrectly return this
 * information for floppy disk drives. MS-DOS assumes 360kB drives in
 * that case, so we do the same.
 */

9:	xor	di,di			# ES:DI = 0000:0000 to prevent
	mov	es,di			# lockups with buggy BIOS
	mov	ax,0x0800
	xor	dl,dl
	int	INT_DISK		# get drive parameters
	jc	2f
	or	ch,ch			# check for valid response
	jnz	3f
2:	mov	bx,0x0001
	mov	cx,0x2708		# set parameters for 360kB floppy
	mov	dx,0x0101

3:	movzx	ax,dh			# set number of heads
	inc	ax
	mov	bpb_headnum[bpb],ax
	mov	al,ch
	mov	ah,cl
	shr	ah,6			# set number of cylinders
	inc	ax
	mov	[cylnum],ax
	movzx	ax,cl
	and	ax,0x003F		# set number of sectors per track
	mov	bpb_sptrack[bpb],ax
	mov	[trknum],ax

	xor	bh,bh
	dec	bx
	cmp	bx,5			# determine disk drive ID
	jbe	4f
	xor	bx,bx
4:	mov	ax,bx
	shl	ax,2			# multiply by 5
	add	bx,ax
	add	bx,offset drvtab
	mov	al,[bx + 0]		# get device type
	mov	[devtyp],al
	mov	al,[bx + 1]		# get media ID
	mov	bpb_mediaid[bpb],al
	mov	al,[bx + 2]		# get sectors per cluster
	mov	bpb_spclust[bpb],al
	xor	ah,ah
	mov	al,[bx + 3]		# get number of sectors per FAT
	mov	bpb_spfat[bpb],ax
	mov	al,[bx + 4]		# get number of root dir entries
	mov	bpb_dirnum[bpb],ax

	mov	ax,bpb_headnum[bpb]
	mov	cx,bpb_sptrack[bpb]
	mul	cx			# compute total number of sectors (we
	mul	word ptr [cylnum]	# dont have to care for overflow here
	mov	bpb_totsect[bpb],ax	# as that should never happen ;-))

	cld
	mov	bx,1
	mov	ax,ds
	mov	es,ax
	mov	di,offset trktab
5:	mov	ax,bx			# setup track layout field
	stosw
	mov	ax,bpb_sectsize[bpb]
	stosw
	inc	bx
	loop	5b

	cmp	byte ptr [devtyp],0
	je	6f			# no changeline support for 360 kB
	mov	ax,0x15FF
	xor	dx,dx
	int	INT_DISK		# get floppy DASD type
	jc	6f
	cmp	ah,0x02			# check for changeline support
	jne	6f
	or	word ptr [attrib],0x0002

/*
 * Call DOS with the new parameter table. Then set the number of sectors
 * per track in the DPB, and reset the disk system.
 */

6:	mov	ax,DOS_FN_WORD(DOS_FN_FILE_IOCTL) + DOS_IOCTL_BLOCKREQ
	mov	bx,0x0001
	mov	cx,0x0840
	mov	dx,offset ioctl
	int	INT_DOS			# call DOS with new parameters

	cmp	byte ptr [dsknum],1
	ja	7f
	mov	ax,DOS_FN_WORD(DOS_FN_FILE_IOCTL) + DOS_IOCTL_BLOCKREQ
	mov	bx,0x0002		# if we have just one physical drive
	mov	cx,0x0840		# this is also mapped to B: by DOS, so
	mov	dx,offset ioctl		# we have to set the new parameters
	int	INT_DOS			# for B: as well in this case

7:	xor	ax,ax
	mov	es,ax
	les	di,es:[DPB_VECT]	# set number of sectors per track
	mov	dx,bpb_sptrack[bpb]	# in DPB
	mov	byte ptr es:[di + 4],dl

	xor	dx,dx
	int	INT_DISK		# reset the disk drive

/*
 * Now we have reconfigured the disk driver to the new floppy parameters,
 * but DOS itself doesnt know about the new parameters yet. Therefore we
 * still have to manipulate the DOS disk parameter blocks.
 */

	mov	ah,DOS_FN_GET_SYSVARS
	int	INT_DOS			# get the DOS list of lists
	les	bx,es:[bx]		# get pointer to first parameter block
	mov	ax,es
	or	ax,bx			# check for valid pointer
	jz	4f
	mov	si,0x0017
	cmp	word ptr [dosver],0x0400
	jb	1f			# set offset to "not accessed flag"
	inc	si			# depending on DOS version

1:	cmp	bx,0xFFFF		# check for end of list
	je	4f
	mov	al,es:[bx + 0]		# get logical drive number
	or	al,al
	jz	2f			# check if its drive A:
	cmp	byte ptr [dsknum],1
	ja	3f			# if we have only one physical drive,
	dec	al			# we have to set the parameters for
	jnz	3f			# drive B: as well
					# mark drive as never accessed
2:	mov	byte ptr es:[bx + si],0x80
3:	les	bx,es:[bx + si + 1]	# get pointer to next parameter block
	jmp	1b

/*
 * If the system loaded nbramdrv with the NEWDRIVE switch, the ramdisk still
 * exists but has been set readonly. The BIOS ramdisk driver has been removed
 * by now, so we can now activate nbramdrv.
 */

4:	mov	ax,DOS_FN_WORD(DOS_FN_FILE_OPEN) + 0x02
	mov	dx,offset rddev		# open ramdisk controlling device
	int	INT_DOS			# for reading and writing
	jc	6f			# no ramdisk controller

	mov	bx,ax
	push	bx
	mov	dx,offset rdcmd
	mov	cx,_I(rdcmd_size)
	mov	ax,DOS_FN_WORD(DOS_FN_FILE_IOCTL) + DOS_IOCTL_WRCHAR
	int	INT_DOS			# write command to start ramdisk
	jnc	5f			# driver to ramdisk controller

	mov	dx,offset acterr
	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS			# print warning message
	mov	dx,offset crlf
	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS			# print CR/LF

5:	pop	bx
	mov	ah,DOS_FN_FILE_CLOSE	# close file handle
	int	INT_DOS

/*
 * Thats it, so we can print a message to the user and terminate. However,
 * we have to check if we have to stay resident.
 */

6:	mov	dx,offset rdok
	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS			# print success message
	mov	dx,offset crlf
	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS			# print CR/LF

	test	byte ptr [resflg],0xFF
	jnz	8f
	mov	ax,DOS_FN_WORD(DOS_FN_EXIT)
	int	INT_DOS			# terminate program
	lret

8:	mov	ax,word ptr ds:[psp_env]
	or	ax,ax			# get segment of environment
	jz	9f
	mov	es,ax			# free environment memory block
	mov	ax,DOS_FN_WORD(DOS_FN_MEM_FREE)
	int	INT_DOS
9:	mov	dx,offset start
	add	dx,0x000F		# compute number of paragraphs to
	shr	dx,4			# keep
	mov	ax,DOS_FN_WORD(DOS_FN_TERM_STAY)
	int	INT_DOS			# terminate and stay resident
	lret



/*
 *====================================================================
 *
 * Print an error message and exit the program
 * Input:  SI  - Offset to error message
 *         AL  - Error code
 * Output: none
 * Registers changed: all (routine doesnt return)
 */
doerr:	push	ax
	push	si
	mov	dx,offset errmsg	# print leading message
	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS
	pop	dx
	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS			# print error message
	mov	dx,offset crlf
	mov	ah,DOS_FN_WRITE_STRING
	int	INT_DOS			# print CR/LF
	pop	ax
	mov	ah,DOS_FN_EXIT		# terminate program with
	int	INT_DOS			# error code in AL
	lret




/*
 *====================================================================
 *
 * Data area
 */
	.data


dpbptr:	.long	0			# ramdisk DPB pointer
resflg:	.byte	0			# flag if we have to remain resident
dsknum:	.byte	0			# number of physical floppy drives
dosver:	.word	0			# DOS version


/*
 * DOS parameter block for IOCTL function 0x40
 */

ioctl:	.byte	0x04			# special functions
devtyp:	.byte	0			# device type
attrib:	.word	0			# device attributes
cylnum:	.word	0			# number of cylinders
	.byte	0			# media type
bpb:	.word	SECTSIZE		# number of bytes per sector
	.byte	0			# number of sectors per cluster
	.word	1			# number of reserved sectors
	.byte	2			# number of FATs
	.word	0			# number of root directory entries
	.word	0			# total number of sectors
	.byte	0			# media ID
	.word	0			# number of sectors per FAT
	.word	0			# number of sectors per track
	.word	0			# number of heads
	.long	0			# number of hidden sectors
	.long	0			# total number of sectors
	.byte	0,0,0,0,0,0		# reserved
trknum:	.word	0			# number of tracks
trktab:	.space	(64 * 4),0		# track table




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


/*
 * Table with parameters for different floppy drive sizes
 * This table is organized as follows:
 *	0x00    - device type
 *      0x01    - media ID
 *      0x02    - sectors per cluster
 *      0x03    - number of sectors per FAT
 *	0x04    - number of root directory entries
 */

drvtab:	.byte	0x00, 0xFD, 2, 2, 112	# 360 kB
	.byte	0x01, 0xF9, 1, 7, 224	# 1.2 MB
	.byte	0x02, 0xF9, 2, 3, 112	# 720 kB
	.byte	0x07, 0xF0, 1, 9, 224	# 1.44 MB
	.byte	0x09, 0xF0, 2, 9, 224	# unknown
	.byte	0x09, 0xF0, 2, 9, 224	# 2.88 MB


/*
 * Strings needed to access nbramdrv
 */

rddev:	.asciz	"RAMCNTR0"		# name of ramdisk controller device
rdcmd:	.ascii	"RDS2"			# command string to activate ramdisk
rdcmd_size = . - rdcmd


/*
 * Text messages
 */

rdsig:	.ascii	"NetB"
errmsg:	.ascii	"Unable to remove ramdisk: $"
invdos:	.ascii	"Invalid DOS version$"
noramd:	.ascii	"No or invalid ramdisk driver$"
invdrv:	.ascii	"Ramdisk is not drive A:$"
noflop:	.ascii	"No physical floppy drive installed$"
curdrv:	.ascii	"Ramdisk is current default drive$"
rderr:	.ascii	"Can't remove ramdisk$"
acterr:	.ascii	"Warning: unable to activate nbramdrv$"
rdok:	.ascii	"Ramdisk removed$"
crlf:	.byte	0x0D,0x0A
	.ascii	"$"




/*
 *====================================================================
 */
	.end

