
;--------------------------------------------------------------------
; DRIVER.SYS	(a character device driver)
;		Copyright (c) 1985 by Dan Rollins
;
; Simple device driver illustrates the fundamental structures and
; actions of a DOS 2.0 installable device driver.
;
; Creates TESTDEV, an input and output character device (pseudo-device).
;
; To install: Assemble, link, then process with:
;		exe2bin driver driver.sys
;	      Then add this line to your CONFIG.SYS file:
;		 device=driver.sys
;	      Then reboot DOS and copy a file to TESTDEV

;=======================================================================
;---- equates for character device drivers ----
;=======================================================================

;-- equates for the data structures used by this driver

;-- offsets from the start of the Request Header
RH_LEN		 equ 0	 ;byte
RH_UNIT_CODE	 equ 1	 ;byte
RH_CMD_CODE	 equ 2	 ;byte
RH_STATUS	 equ 3	 ;word

;--- Request Header offsets for fields when the INIT function is executed
IN_UNIT_COUNT	 equ 13  ;byte
IN_END_ADDR	 equ 14  ;dword
IN_BPB_PTR	 equ 18  ;dword

;--- Request Header offsets for fields for INPUT or OUTPUT functions
IO_MEDIA_DESC	 equ 13  ;byte
IO_TRANS_ADDR	 equ 14  ;dword
IO_DATA_COUNT	 equ 18  ;word
IO_START_SECTOR  equ 20  ;word

;--- equates for the bit-flags used in the attribute field of the device header

CHAR_DEVICE	 equ 8000h   ;bit 15: 1 for character device, 0 block device
IOCTL		 equ 4000h   ;bit 14: 1 if IOCTL system calls are supported
NON_IBM_FORMAT	 equ 2000h   ;bit 13: 1 if block device is non-IBM format
			     ;bits 12 to 4 are reserved
CLOCK		 equ 8	     ;bit 3: 1 if this is the CLOCK$ device
NUL		 equ 4	     ;bit 2: 1 if this is the NUL device
STO		 equ 2	     ;bit 1: 1 if this is the Standard Output device
STI		 equ 1	     ;bit 0: 1 if this is the Standard Input device

;--- equates for the bit-flags used in the status record for each command

ERR_FLAG	 equ 8000h   ;bit 15: 1 if error occurred (see below)
			     ;bits 14 to 10 reserved
BUSY		 equ 0200h   ;bit 9: 1 if device is busy
DONE		 equ 0100h   ;bit 8: set to 1 before exiting to caller
;--- the following values occupy bits 7 thru 0 when an error occurs --
WRITE_PROTECT	 equ  0
UNKNOWN_UNIT	 equ  1
DEV_NOT_READY	 equ  2
UNKNOWN_CMD	 equ  3    ;<-- this is the only error used by this device ---
CRC_ERROR	 equ  4
BAD_LEN 	 equ  5
SEEK_ERROR	 equ  6
UNKNOWN_MEDIA	 equ  7
SECTOR_NOT_FND	 equ  8
OUT_OF_PAPER	 equ  9
WRITE_FAULT	 equ  0ah
READ_FAULT	 equ  0bh
GENERAL_FAILURE  equ  0ch

;===============================================================
;----------- data and code of the file begins here -------------
;===============================================================

dev_seg  segment
test_dev proc  far
	 assume cs:dev_seg, ds:dev_seg, es:dev_seg

;------------------------------
; The following lines are the device header that must exist for
;  every device.  This file has only one device, and it works with
;  character I/O.

header: 	    ;label for the start of this device driver

next_dev_ptr	    dd	-1	     ;this is the last (only) device defined
dev_attribute	    dw	 CHAR_DEVICE
strategy_ptr	    dw	 strategy    ;the proc that sets up for the functions
interrupt_ptr	    dw	 interrupt   ;the proc that handles function calls
device_name	    db	 'TESTDEV '  ;8-byte string of device name

request_ptr	    dd	?   ;STRATEGY stores ES:BX request header pointer here
			    ;INTERRUPT retrieves it

; this table is used to jump to the procedure that is indicated
; by DOS in the command code (third byte of the request header)

fn_table dw  init_fn	      ;  0: initialization procedure
	 dw  media_check_fn   ;  1: media check procedure
	 dw  build_bpb_fn     ;  2: build Bios Parameter Block
	 dw  ioctl_in_fn      ;  3: IOCTL Input
	 dw  input_fn	      ;  4: read from device
	 dw  nd_input_fn      ;  5: Non-Destructive, no-wait read
	 dw  input_status_fn  ;  6: return status of input device
	 dw  input_flush_fn   ;  7: flush (clear) the input buffer
	 dw  output_fn	      ;  8: write to device
	 dw  output_verify_fn ;  9: write and verify
	 dw  output_status_fn ; 10: return status of output device
	 dw  output_flush_fn  ; 11: flush (clear) the output buffer
	 dw  ioctl_out_fn     ; 12: IOCTL Output

;The following messages are displayed by TESTDEV when DOS
;makes a request.

CR	 equ	 0dh
LF	 equ	 0ah
fn0_msg  db 'Initializing TESTDEV Driver ',CR,LF,'$'
fn4_msg  db 'Input request : $'
fn8_msg  db 'Output request : $'
crlf	 db CR,LF,'$'

;=============================================================
; STRATEGY procedure
; Just saves the request header pointer for the INTERRUPT procedure

strategy proc far
	 assume cs:dev_seg
	 mov	 cs:word ptr request_ptr,bx
	 mov	 cs:word ptr request_ptr+2,es
	 ret				       ;FAR return to DOS
strategy endp

;=============================================================
; INTERRUPT procedure
; Processes the command indicated in the request header.

interrupt proc far
	 assume cs:dev_seg, ds:nothing, es:nothing
	 push	 ds		    ;preserve all registers
	 push	 es
	 push	 ax
	 push	 bx
	 push	 cx
	 push	 dx
	 push	 di
	 push	 si

	 push	 cs
	 pop	 ds		;DS addresses the program data area
	 les	 bx,request_ptr ;get the pointer saved by STRATEGY
	 mov	 ah,0
	 mov	 al,es:[bx+RH_CMD_CODE]  ;fetch the command
	 mov	 di,ax
	 shl	 di,1			 ; * 2 to point to jump vector
	 jmp	 fn_table[di]		 ;invoke that function procedure

;--- all command jumps except INIT, INPUT, and OUTPUT will end up here ----

nd_input_fn:		 ;requests for any of these commands
media_check_fn: 	 ; will result in an error message
build_bpb_fn:
ioctl_in_fn:
input_status_fn:
input_flush_fn:
output_verify_fn:
output_status_fn:
output_flush_fn:
ioctl_out_fn:

error_exit:
	 or	 word ptr es:[bx+RH_STATUS],ERR_FLAG	 ;set the ERROR_FLAG
	 or	 word ptr es:[bx+RH_STATUS],UNKNOWN_CMD  ;identify the error

;--- service request has been handled.
;--- Set the "done flag" and return to DOS.

common_exit:
	 or	 word ptr es:[bx+RH_STATUS],DONE  ;always indicate completion
	 pop	 si				  ; by setting the DONE bit
	 pop	 di
	 pop	 dx
	 pop	 cx
	 pop	 bx
	 pop	 ax
	 pop	 es
	 pop	 ds
	 ret			       ;FAR return

;--------------------------
; INTERRUPT procedure function calls
; Only three types of requests are handled by TESTDEV.	All other
;  requests are routed through the ERROR_EXIT.

;-------------------------------------------------------------------------
; INPUT_FN
; Reads (waits for) 1 character from the keyboard, and displays it,
; then returns it (within the I/O structure) to DOS.

input_fn proc near
	 mov	 ch,0
	 mov	 cl,es:[bx+IO_DATA_COUNT]  ;get number of bytes
	 jcxz	 if_exit     ;if no bytes, just exit
if_10:
	 mov	 dx,offset fn4_msg     ;"Input request: "
	 mov	 ah,9		       ;DOS print string service
	 int	 21H

	 mov	 ah,0	  ;ROM-BIOS AWAIT_KEY function
	 int	 16H	  ;get AL = ASCII character, AH = scan code

	 mov	 word ptr es:[bx+IO_DATA_COUNT],1 ;store the character count

	 push	 ds
	 lds	 di,es:[bx+IO_TRANS_ADDR]    ;get SEG:OFF of destination
	 mov	 [di],al		     ;store byte in destination
	 pop	 ds

	 mov	 dl,al
	 mov	 ah,2		 ;display the character
	 int	 21H

	 mov	 dx,offset crlf  ;start a new line
	 mov	 ah,9		 ;invoke DOS print string service
	 int	 21h
	 loop	 if_10		 ;do again, for multi-byte requests
if_exit:
	 jmp	 common_exit
input_fn endp

;--------------------------------------------------------------------
; OUTPUT_FN  sends the character passed to it from DOS out to the
; standard output device.

output_fn proc near
	 mov	 ch,0
	 mov	 cl,es:[bx+IO_DATA_COUNT]  ;get number of bytes
	 jcxz	 of_exit     ;if no bytes, just exit
of_10:
	 mov	 dx,offset fn8_msg     ;"Output request :"
	 mov	 ah,9		       ;DOS print string service
	 int	 21H

	 push	 ds
	 lds	 si,es:[bx+IO_TRANS_ADDR] ;get SEG:OFF of data
	 mov	 dl,[si]		  ;retrieve the character
	 pop	 ds			  ;unstack or die

	 mov	 ah,2	    ;display the character
	 int	 21H

	 mov	 dx,offset crlf  ;print new line characters
	 mov	 ah,9		 ;DOS print string service
	 int	 21H
	 loop	 of_10		 ;get next (if multi-byte request)
of_exit:
	 jmp	 common_exit
output_fn endp

;-------------------------,
; INIT_FN procedure
; Passes the end-of-driver address back to DOS.
; Note that the address of this procedure is passed back to DOS as the
;  address of the end of the driver.  After one invocation, INIT_FN
;  is never called again; thus, it doesn't need to be saved with the
;  rest of the function procedures.

init_fn  proc  near
	 mov	 dx,offset fn0_msg     ;"Initializing TESTDEV"
	 mov	 ah,9		       ;DOS print string service
	 int	 21H

;-- debugging tool:
;-- store the address so the code can be examined with DEBUG ---
	 push	 ds
	 mov	 ax,0
	 mov	 ds,ax
	 mov	 word ptr ds:[04f0h],offset interrupt
	 mov	 word ptr ds:[04f2h],cs
	 pop	 ds
;-- header address (offset,segment) is stored at address 0000:04F0
;-- which is known as the "intra-application communication area"

;-- specify the end of this driver and exit to DOS -------
	 mov	 word ptr es:[bx+IN_END_ADDR],offset init_fn
	 mov	 word ptr es:[bx+IN_END_ADDR+2],cs
	 jmp	 common_exit
init_fn  endp

interrupt endp
test_dev  endp
dev_seg   ends
	  end	   header   ;must specify for EXE2BIN
