* Mask unimplemented IOCTL message. * Add build for 286+. * Optimized IO for 8086. * Adding pre-build drivers.
248 lines
9.4 KiB
NASM
248 lines
9.4 KiB
NASM
PAGE 60, 132
|
|
TITLE HEADER - Interface for C Device Drivers
|
|
SUBTTL Bob Armstrong [23-Jul-94]
|
|
|
|
|
|
|
|
; header.asm - MSDOS device driver header...
|
|
;
|
|
; Copyright (C) 1994 by Robert Armstrong
|
|
;
|
|
; 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
|
|
; (at your option) 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 MERCHANT-
|
|
; ABILITY 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, visit the website of the Free
|
|
; Software Foundation, Inc., www.gnu.org.
|
|
|
|
|
|
; This module receives the DOS device driver calls (there are only two!)
|
|
; and sets up an environment suitable for executing C code. The original
|
|
; DOS device driver request is then handed off to the C code.
|
|
;
|
|
; There are two requirements that drive the memory layout of a device
|
|
; driver: (1) DOS requires the device header to be the first thing in the
|
|
; memory image, and (2) it is desirable to free the memory allocated to
|
|
; startup code once the driver has been initialized. These requirements
|
|
; force a memory layout like this diagram:
|
|
;
|
|
; DS, SS -> +----------------------+ <- driver load address
|
|
; | |
|
|
; | (device header) |
|
|
; | |
|
|
; \ _DATA \
|
|
; | |
|
|
; |----------------------|
|
|
; | |
|
|
; \ _BSS \
|
|
; | |
|
|
; | (stack) | <- C language stack
|
|
; | |
|
|
; CS -> |----------------------|
|
|
; | |
|
|
; \ _TEXT \
|
|
; | | <- driver break address
|
|
; | |
|
|
; \ (startup code) \
|
|
; | |
|
|
; +----------------------+
|
|
;
|
|
; This is very similar to the TINY (.COM file) model _except_ that the
|
|
; code is at the end of the memory image rather than the start. This
|
|
; turns out to be a major problem because the code generated by TURBO
|
|
; assumes that the CS register contains the start of the _TEXT segment
|
|
; and NOT the DGROUP. This is contrary to the documentation in the
|
|
; Borland manual and _I_ think it's a bug. Note that this bug is
|
|
; asymptomatic in .COM files because the _TEXT segment is normally the
|
|
; first thing in the DGROUP.
|
|
;
|
|
; To get around this problem we use the SMALL model (i.e. CS != DS).
|
|
; Trouble is, when this driver is loaded the only thing we know is the
|
|
; address of the start of the driver and that's in the CS register.
|
|
; This means that we have to calculate an appropriate CS value to use
|
|
; in the C code.
|
|
;
|
|
; Another unrelated issue is the stack size. The stack DOS uses when
|
|
; a driver is called has room for about 20 PUSHes. This isn't enough
|
|
; for any serious C code, so we only use the DOS stack to save all the
|
|
; registers. After that we switch to our own stack, which is located
|
|
; in the _BSS segment. Of course we have to put the DOS stack pointer
|
|
; back before returning.
|
|
;
|
|
; In order to ensure that the device header appears first in the load
|
|
; image it must be defined in this module. We also define the BIOS
|
|
; Parameter Block (BPB) and the table of BPB pointers in this module.
|
|
; These things are unfortunate because those parts of this file must be
|
|
; modified for each different driver.
|
|
;
|
|
; The only interface between this module and the C code is a single
|
|
; far pointer which must be exported by the C code. This pointer would
|
|
; be declared as:
|
|
;
|
|
; void (far *c_driver) (rh_t far *);
|
|
;
|
|
; The offset part (first word!) of this pointer must be initialized at
|
|
; link time with the offset (relative to _TEXT) of the routine that
|
|
; will handle DOS driver calls. The segment part of this pointer is
|
|
; filled in by this module with the CS value we compute before the
|
|
; first call. The C driver routine is passed a far pointer to the
|
|
; DOS request header. Everything else is up to the C code.
|
|
;
|
|
; Bob Armstrong [22-July-1994]
|
|
|
|
.8086
|
|
|
|
; This is the size of the new stack we create for the C code...
|
|
STACKSIZE EQU 256 ; use whatever seems appropriate...
|
|
|
|
|
|
; This DGROUP, and the order the segments appear in this module, will
|
|
; force the load order to be as described above!
|
|
DGROUP GROUP _DATA, _BSS, _TEXT
|
|
|
|
|
|
; The _DATA segment (initialized data) for this module contains the
|
|
; MSDOS device header and the BIOS parameter blocks...
|
|
_DATA SEGMENT WORD PUBLIC 'DATA'
|
|
PUBLIC _header, _bpb, _bpbtbl
|
|
|
|
; Header attribute bits for block devices:
|
|
;
|
|
; 0002H (B1) - 32 bit sector addresses
|
|
; 0040H (B6) - Generic IOCTL, Get/Set logical device
|
|
; 0080H (B7) - IOCTL Query
|
|
; 0800H (B11) - Open/Close device, Removable media
|
|
; 2000H (B13) - IBM format
|
|
; 4000H (B14) - IOCTL read/write
|
|
; 8000H (B15) - zero for block device!
|
|
|
|
; The DOS device header...
|
|
_header DD -1 ; link to the next device
|
|
DW 00C0H ; block device, non-IBM format, generic IOCTL
|
|
DW DGROUP:STRATEGY ; address of the strategy routine
|
|
DW DGROUP:INTERRUPT; " " " interrupt "
|
|
DB 1 ; number of drives
|
|
DB 'SDCDv12' ; DOS doesn't really use these bytes
|
|
|
|
; The geometry (sectors/track, tracks/cylinder) defined in the BPB is rather
|
|
; arbitrary in the case of the TU58, but there are things to watch out for.
|
|
; First, the DOS FORMAT program has a bug which causes it to crash or worse
|
|
; yet, exit silently without doing anything for devices with large numbers of
|
|
; sectors per track. Experiments show that 64 sectors/track is safe (at least
|
|
; for DOS v5) but 128 is not. Second, the DRIVER.C module must calculate the
|
|
; number of cylinders by cylinders = device_size / (sectors * heads). This
|
|
; value must always come out to an integer.
|
|
|
|
; The BIOS Parameter Block...
|
|
_bpb DW 512 ; sector size
|
|
DB 1 ; cluster size
|
|
DW 1 ; number of reserved sectors
|
|
DB 2 ; number of FAT copies
|
|
DW 48 ; number of files in root directory
|
|
DW 512 ; total number of sectors
|
|
DB 0F0H ; media descriptor
|
|
DW 2 ; number of sectors per FAT
|
|
DW 64 ; sectors per track
|
|
DW 2 ; number of heads
|
|
DD 0 ; number of hidden sectors
|
|
DD 0 ; large sector count
|
|
|
|
; This table contains one BPB pointer for each physical drive...
|
|
_bpbtbl DW DGROUP:_bpb ; the BPB for unit 0
|
|
; DW DGROUP:_bpb ; " " " " 1
|
|
|
|
; This doubleword points to the entry point of the C driver code...
|
|
EXTRN _c_driver:WORD
|
|
|
|
_DATA ENDS
|
|
|
|
|
|
; The _BSS segment contains uninitialized static data...
|
|
_BSS SEGMENT WORD PUBLIC 'BSS'
|
|
PUBLIC _STACK
|
|
|
|
; Local variables for this module...
|
|
RHPTR DW 2 DUP (?) ; offset and segment of the request header
|
|
OLDSTK DW 2 DUP (?) ; original stack offset and segment
|
|
|
|
; This is the stack while the C code is running...
|
|
DB STACKSIZE DUP (?)
|
|
_STACK LABEL WORD ; the address of a stack is at its _top_ !
|
|
|
|
_BSS ENDS
|
|
|
|
|
|
; WARNING! The _TEXT segment must be paragraph aligned!
|
|
_TEXT SEGMENT PARA PUBLIC 'CODE'
|
|
ASSUME CS:DGROUP, DS:DGROUP, SS:DGROUP, ES:NOTHING
|
|
|
|
|
|
; These words give the offsets of the _TEXT segment and the stack, both
|
|
; relative to the DGROUP. Note that you can't define use "DGROUP:_TEXT"
|
|
; as this generates a segment relocation record in the .EXE file which
|
|
; will prevent you from converting to a .COM file. The way its written
|
|
; works, but assumes that it is the first thing in this module.
|
|
CSOFF DW DGROUP:CSOFF
|
|
NEWSTK DW DGROUP:_STACK
|
|
|
|
|
|
; This is the entry for the STRATEGY procedure. DOS calls this routine
|
|
; with the address of the request header, but all the work is expected
|
|
; to occur in the INTERRUPT procedure. All we do here is to save the
|
|
; address of the request for later processing. Since this function is
|
|
; trivial, we never call any C code...
|
|
PUBLIC STRATEGY
|
|
STRATEGY PROC FAR
|
|
MOV CS:RHPTR,BX ; save the request header offset
|
|
MOV CS:RHPTR+2,ES ; and its segment
|
|
RET ; that's all there is to do
|
|
STRATEGY ENDP
|
|
|
|
|
|
; And now the INTERRUPT routine. This routine has to save all the
|
|
; registers, switch to the new stack, set up the segment registers,
|
|
; and call the C language driver routine while passing it the address
|
|
; of the request header (saved by the strategy routine).
|
|
PUBLIC INTERRUPT
|
|
INTERRUPT PROC FAR
|
|
PUSH DS ES AX BX CX DX DI SI BP
|
|
|
|
CLI ; interrupts OFF while the stack is unsafe!
|
|
MOV CS:OLDSTK+2,SS ; save the current stack pointer
|
|
MOV CS:OLDSTK,SP ; ...
|
|
MOV BX,CS ; then setup the new stack
|
|
MOV SS,BX ; ...
|
|
MOV SP,NEWSTK ; ...
|
|
STI ; interrupts are safe once again
|
|
MOV AX,CSOFF ; compute the correct code segment address
|
|
SHR AX,4 ; ... for the _TEXT segment
|
|
ADD AX,BX ; ...
|
|
MOV _c_driver+2,AX ; fix the pointer appropriately
|
|
MOV DS,BX ; setup DS for the C code
|
|
CLD ; the C code will assume this state
|
|
|
|
PUSH CS:RHPTR+2 ; pass the request header
|
|
PUSH CS:RHPTR ; address as a parameter
|
|
CALL DWORD PTR _c_driver; call the C entry point
|
|
|
|
CLI ; interrupts OFF once again
|
|
MOV SS,CS:OLDSTK+2 ; and restore the original stack
|
|
MOV SP,CS:OLDSTK ; ...
|
|
STI ; ...
|
|
|
|
POP BP SI DI DX CX BX AX ES DS
|
|
RET
|
|
INTERRUPT ENDP
|
|
|
|
|
|
_TEXT ENDS
|
|
|
|
END
|