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 'SDCDv11' ; 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