_LOADING DEVICE DRIVERS FROM THE DOS COMMAND LINE_ by Jim Kyle [LISTING ONE] /******************************************************************** * DEVLOD.C - Copyright 1990 by Jim Kyle - All Rights Reserved * * (minor revisions by Andrew Schulman * * Dynamic loader for device drivers * * Requires Turbo C; see DEVLOD.MAK also for ASM helpers.* ********************************************************************/ #include #include #include typedef unsigned char BYTE; #define GETFLAGS __emit__(0x9F) /* if any error, quit right now */ #define FIXDS __emit__(0x16,0x1F)/* PUSH SS, POP DS */ #define PUSH_BP __emit__(0x55) #define POP_BP __emit__(0x5D) unsigned _stklen = 0x200; unsigned _heaplen = 0; char FileName[65]; /* filename global buffer */ char * dvrarg; /* points to char after name in cmdline buffer */ unsigned movsize; /* number of bytes to be moved up for driver */ void (far * driver)(); /* used as pointer to call driver code */ void far * drvptr; /* holds pointer to device driver */ void far * nuldrvr; /* additional driver pointers */ void far * nxtdrvr; BYTE far * nblkdrs; /* points to block device count in List of Lists*/ unsigned lastdrive; /* value of LASTDRIVE in List of Lists */ BYTE far * CDSbase; /* base of Current Dir Structure */ int CDSsize; /* size of CDS element */ unsigned nulseg; /* hold parts of ListOfLists pointer */ unsigned nulofs; unsigned LoLofs; #pragma pack(1) struct packet{ /* device driver's command packet */ BYTE hdrlen; BYTE unit; BYTE command; /* 0 to initialize */ unsigned status; /* 0x8000 is error */ BYTE reserv[8]; BYTE nunits; unsigned brkofs; /* break adr on return */ unsigned brkseg; /* break seg on return */ unsigned inpofs; /* SI on input */ unsigned inpseg; /* _psp on input */ BYTE NextDrv; /* next available drive */ } CmdPkt; typedef struct { /* Current Directory Structure (CDS) */ BYTE path[0x43]; unsigned flags; void far *dpb; unsigned start_cluster; unsigned long ffff; unsigned slash_offset; /* offset of '\' in current path field */ // next for DOS4+ only BYTE unknown; void far *ifs; unsigned unknown2; } CDS; extern unsigned _psp; /* established by startup code in c0 */ extern unsigned _heaptop; /* established by startup code in c0 */ extern BYTE _osmajor; /* established by startup code */ extern BYTE _osminor; /* established by startup code */ void _exit( int ); /* established by startup code in c0 */ void abort( void ); /* established by startup code in c0 */ void movup( char far *, char far *, int ); /* in MOVUP.ASM file */ void copyptr( void far *src, void far *dst ); /* in MOVUP.ASM file */ void exit(int c) /* called by startup code's sequence */ { _exit(c);} int Get_Driver_Name ( void ) { char *nameptr; int i, j, cmdlinesz; nameptr = (char *)0x80; /* check command line for driver name */ cmdlinesz = (unsigned)*nameptr++; if (cmdlinesz < 1) /* if nothing there, return FALSE */ return 0; for (i=0; i' '; i++) /* copy name */ FileName[j++] = nameptr[i]; FileName[j] = '\0'; return 1; /* and return TRUE to keep going */ } void Put_Msg ( char *msg ) /* replaces printf() */ { #ifdef INT29 /* gratuitous use of undocumented DOS */ while (*msg) { _AL = *msg++; /* MOV AL,*msg */ geninterrupt(0x29); /* INT 29h */ } #else _AH = 2; /* doesn't need to be inside loop */ while (*msg) { _DL = *msg++; geninterrupt(0x21); } #endif } void Err_Halt ( char *msg ) /* print message and abort */ { Put_Msg ( msg ); Put_Msg ( "\r\n" ); /* send CR,LF */ abort(); } void Move_Loader ( void ) /* vacate lower part of RAM */ { unsigned movsize, destseg; movsize = _heaptop - _psp; /* size of loader in paragraphs */ destseg = *(unsigned far *)MK_FP( _psp, 2 ); /* end of memory */ movup ( MK_FP( _psp, 0 ), MK_FP( destseg - movsize, 0 ), movsize << 4); /* move and fix segregs */ } void Load_Drvr ( void ) /* load driver file into RAM */ { unsigned handle; struct { unsigned LoadSeg; unsigned RelocSeg; } ExecBlock; ExecBlock.LoadSeg = _psp + 0x10; ExecBlock.RelocSeg = _psp + 0x10; _DX = (unsigned)&FileName[0]; _BX = (unsigned)&ExecBlock; _ES = _SS; /* es:bx point to ExecBlock */ _AX = 0x4B03; /* load overlay */ geninterrupt ( 0x21 ); /* DS is okay on this call */ GETFLAGS; if ( _AH & 1 ) Err_Halt ( "Unable to load driver file." ); } void Get_List ( void ) /* set up pointers via List */ { _AH = 0x52; /* find DOS List of Lists */ geninterrupt ( 0x21 ); nulseg = _ES; /* DOS data segment */ LoLofs = _BX; /* current drive table offset */ switch( _osmajor ) /* NUL adr varies with version */ { case 0: Err_Halt ( "Drivers not used in DOS V1." ); case 2: nblkdrs = NULL; nulofs = LoLofs + 0x17; break; case 3: if (_osminor == 0) { nblkdrs = (BYTE far *) MK_FP(nulseg, LoLofs + 0x10); lastdrive = *((BYTE far *) MK_FP(nulseg, LoLofs + 0x1b)); nulofs = LoLofs + 0x28; } else { nblkdrs = (BYTE far *) MK_FP(nulseg, LoLofs + 0x20); lastdrive = *((BYTE far *) MK_FP(nulseg, LoLofs + 0x21)); nulofs = LoLofs + 0x22; } CDSbase = *(BYTE far * far *)MK_FP(nulseg, LoLofs + 0x16); CDSsize = 81; break; case 4: case 5: nblkdrs = (BYTE far *) MK_FP(nulseg, LoLofs + 0x20); lastdrive = *((BYTE far *) MK_FP(nulseg, LoLofs + 0x21)); nulofs = LoLofs + 0x22; CDSbase = *(BYTE far * far *) MK_FP(nulseg, LoLofs + 0x16); CDSsize = 88; break; case 10: case 20: Err_Halt ( "OS2 DOS Box not supported." ); default: Err_Halt ( "Unknown version of DOS!"); } } void Fix_DOS_Chain ( void ) /* patches driver into DOS chn */ { unsigned i; nuldrvr = MK_FP( nulseg, nulofs+0x0A ); /* verify the drvr */ drvptr = "NUL "; for ( i=0; i<8; ++i ) if ( *((BYTE far *)nuldrvr+i) != *((BYTE far *)drvptr+i) ) Err_Halt ( "Failed to find NUL driver." ); nuldrvr = MK_FP( nulseg, nulofs ); /* point to NUL driver */ drvptr = MK_FP( _psp+0x10, 0 ); /* new driver's address */ copyptr ( nuldrvr, &nxtdrvr ); /* hold old head now */ copyptr ( &drvptr, nuldrvr ); /* put new after NUL */ copyptr ( &nxtdrvr, drvptr ); /* and old after new */ } // returns number of next free drive, -1 if none available int Next_Drive ( void ) { #ifdef USE_BLKDEV return (nblkdrs && (*nblkdrs < lastdrive)) ? *nblkdrs : -1; #else /* The following approach takes account of SUBSTed and network-redirector drives */ CDS far *cds; int i; /* find first unused entry in CDS structure */ for (i=0, cds=CDSbase; iflags) /* found a free drive */ break; return (i == lastdrive) ? -1 : i; #endif } int Init_Drvr ( void ) { unsigned tmp; #define INIT 0 CmdPkt.command = INIT; /* build command packet */ CmdPkt.hdrlen = sizeof (struct packet); CmdPkt.unit = 0; CmdPkt.inpofs = (unsigned)dvrarg; /* points into cmd line */ CmdPkt.inpseg = _psp; /* can't really check for next drive here, because don't yet know if this is a block driver or not */ CmdPkt.NextDrv = Next_Drive(); drvptr = MK_FP( _psp+0x10, 0 ); /* new driver's address */ tmp = *((unsigned far *)drvptr+3); /* STRATEGY pointer */ driver = MK_FP( FP_SEG( drvptr ), tmp ); _ES = FP_SEG( (void far *)&CmdPkt ); _BX = FP_OFF( (void far *)&CmdPkt ); (*driver)(); /* set up the packet address */ tmp = *((unsigned far *)drvptr+4); /* COMMAND pointer */ driver = MK_FP( FP_SEG( drvptr ), tmp ); (*driver)(); /* do the initialization */ /* check status code in command packet */ return (! ( CmdPkt.status & 0x8000 )); } int Put_Blk_Dev ( void ) /* TRUE if Block Device failed */ { int newdrv; int retval = 1; /* pre-set for failure */ int unit = 0; BYTE far *DPBlink; CDS far *cds; int i; if ((Next_Drive() == -1) || CmdPkt.nunits == 0) return retval; /* cannot install block driver */ if (CmdPkt.brkofs != 0) /* align to next paragraph */ { CmdPkt.brkseg += (CmdPkt.brkofs >> 4) + 1; CmdPkt.brkofs = 0; } while( CmdPkt.nunits-- ) { if ((newdrv = Next_Drive()) == -1) return 1; (*nblkdrs)++; _AH = 0x32; /* get last DPB and set poiner */ _DL = newdrv; geninterrupt ( 0x21 ); _AX = _DS; /* save segment to make the pointer */ FIXDS; DPBlink = MK_FP(_AX, _BX); (unsigned) DPBlink += (_osmajor < 4 ? 24 : 25 ); _SI = *(unsigned far *)MK_FP(CmdPkt.inpseg, CmdPkt.inpofs); _ES = CmdPkt.brkseg; _DS = CmdPkt.inpseg; _AH = 0x53; PUSH_BP; _BP = 0; geninterrupt ( 0x21 ); /* build the DPB for this unit */ POP_BP; FIXDS; *(void far * far *)DPBlink = MK_FP( CmdPkt.brkseg, 0 ); /* set up the Current Directory Structure for this drive */ cds = (CDS far *) (CDSbase + (newdrv * CDSsize)); cds->flags = 1 << 14; /* PHYSICAL DRIVE */ cds->dpb = MK_FP(CmdPkt.brkseg, 0); cds->start_cluster = 0xFFFF; cds->ffff = -1L; cds->slash_offset = 2; if (_osmajor > 3) { cds->unknown = 0; cds->ifs = (void far *) 0; cds->unknown2 = 0; } /* set up pointers for DPB, driver */ DPBlink = MK_FP( CmdPkt.brkseg, 0); *DPBlink = newdrv; *(DPBlink+1) = unit++; if (_osmajor > 3) DPBlink++; /* add one if DOS 4 */ *(long far *)(DPBlink+0x12) = (long)MK_FP( _psp+0x10, 0 ); *(long far *)(DPBlink+0x18) = 0xFFFFFFFF; CmdPkt.brkseg += 2; /* Leave two paragraphs for DPB */ CmdPkt.inpofs += 2; /* Point to next BPB pointer */ } /* end of nunits loop */ return 0; /* all went okay */ } void Get_Out ( void ) { unsigned temp; temp = *((unsigned far *)drvptr+2); /* attribute word */ if ((temp & 0x8000) == 0 ) /* if block device, set up tbls */ if (Put_Blk_Dev() ) Err_Halt( "Could not install block device" ); Fix_DOS_Chain (); /* else patch it into DOS */ _ES = *((unsigned *)MK_FP( _psp, 0x002C )); _AH = 0x49; /* release environment space */ geninterrupt ( 0x21 ); /* then set up regs for KEEP function, and go resident */ temp = (CmdPkt.brkofs + 15); /* normalize the offset */ temp >>= 4; temp += CmdPkt.brkseg; /* add the segment address */ temp -= _psp; /* convert to paragraph count */ _AX = 0x3100; /* KEEP function of DOS */ _DX = (unsigned)temp; /* paragraphs to retain */ geninterrupt ( 0x21 ); /* won't come back from here! */ } void main ( void ) { if (!Get_Driver_Name() ) Err_Halt ( "Device driver name required."); Move_Loader (); /* move code high and jump */ Load_Drvr (); /* bring driver into freed RAM */ Get_List(); /* get DOS internal variables */ if (Init_Drvr ()) /* let driver do its thing */ Get_Out(); /* check init status, go TSR */ else Err_Halt ( "Driver initialization failed." ); } [LISTING TWO] NAME movup ;[]------------------------------------------------------------[] ;| MOVUP.ASM -- helper code for DEVLOD.C | ;| Copyright 1990 by Jim Kyle - All Rights Reserved | ;[]------------------------------------------------------------[] _TEXT SEGMENT BYTE PUBLIC 'CODE' _TEXT ENDS _DATA SEGMENT WORD PUBLIC 'DATA' _DATA ENDS _BSS SEGMENT WORD PUBLIC 'BSS' _BSS ENDS DGROUP GROUP _TEXT, _DATA, _BSS ASSUME CS:_TEXT, DS:DGROUP _TEXT SEGMENT BYTE PUBLIC 'CODE' ;----------------------------------------------------------------- ; movup( src, dst, nbytes ) ; src and dst are far pointers. area overlap is NOT okay ;----------------------------------------------------------------- PUBLIC _movup _movup PROC NEAR push bp mov bp, sp push si push di lds si,[bp+4] ; source les di,[bp+8] ; destination mov bx,es ; save dest segment mov cx,[bp+12] ; byte count cld rep movsb ; move everything to high ram mov ss,bx ; fix stack segment ASAP mov ds,bx ; adjust DS too pop di pop si mov sp, bp pop bp pop dx ; Get return address push bx ; Put segment up first push dx ; Now a far address on stack retf _movup ENDP ;------------------------------------------------------------------- ; copyptr( src, dst ) ; src and dst are far pointers. ; moves exactly 4 bytes from src to dst. ;------------------------------------------------------------------- PUBLIC _copyptr _copyptr PROC NEAR push bp mov bp, sp push si push di push ds lds si,[bp+4] ; source les di,[bp+8] ; destination cld movsw movsw pop ds pop di pop si mov sp, bp pop bp ret _copyptr ENDP _TEXT ENDS end [LISTING THREE] NAME c0 ;[]------------------------------------------------------------[] ;| C0.ASM -- Start Up Code | ;| based on Turbo-C startup code, extensively modified | ;[]------------------------------------------------------------[] _TEXT SEGMENT BYTE PUBLIC 'CODE' _TEXT ENDS _DATA SEGMENT WORD PUBLIC 'DATA' _DATA ENDS _BSS SEGMENT WORD PUBLIC 'BSS' _BSS ENDS DGROUP GROUP _TEXT, _DATA, _BSS ; External References EXTRN _main : NEAR EXTRN _exit : NEAR EXTRN __stklen : WORD EXTRN __heaplen : WORD PSPHigh equ 00002h PSPEnv equ 0002ch MINSTACK equ 128 ; minimal stack size in words ; At the start, DS, ES, and SS are all equal to CS ;/*-----------------------------------------------------*/ ;/* Start Up Code */ ;/*-----------------------------------------------------*/ _TEXT SEGMENT BYTE PUBLIC 'CODE' ASSUME CS:_TEXT, DS:DGROUP ORG 100h STARTX PROC NEAR mov dx, cs ; DX = GROUP Segment address mov DGROUP@, dx mov ah, 30h ; get DOS version int 21h mov bp, ds:[PSPHigh]; BP = Highest Memory Segment Addr mov word ptr __heaptop, bp mov bx, ds:[PSPEnv] ; BX = Environment Segment address mov __version, ax ; Keep major and minor version number mov __psp, es ; Keep Program Segment Prefix address ; Determine the amount of memory that we need to keep mov dx, ds ; DX = GROUP Segment address sub bp, dx ; BP = remaining size in paragraphs mov di, __stklen ; DI = Requested stack size ; ; Make sure that the requested stack size is at least MINSTACK words. ; cmp di, 2*MINSTACK ; requested stack big enough ? jae AskedStackOK ; yes, use it mov di, 2*MINSTACK ; no, use minimal value mov __stklen, di ; override requested stack size AskedStackOK: add di, offset DGROUP: edata jb InitFailed ; DATA segment can NOT be > 64 Kbytes add di, __heaplen jb InitFailed ; DATA segment can NOT be > 64 Kbytes mov cl, 4 shr di, cl ; $$$ Do not destroy CL $$$ inc di ; DI = DS size in paragraphs cmp bp, di jnb TooMuchRAM ; Enough to run the program ; All initialization errors arrive here InitFailed: jmp near ptr _abort ; Set heap base and pointer TooMuchRAM: mov bx, di ; BX = total paragraphs in DGROUP shl di, cl ; $$$ CX is still equal to 4 $$$ add bx, dx ; BX = seg adr past DGROUP mov __heapbase, bx mov __brklvl, bx ; ; Set the program stack down into RAM that will be kept. ; cli mov ss, dx ; DGROUP mov sp, di ; top of (reduced) program area sti mov bx,__heaplen ; set up heap top pointer add bx,15 shr bx,cl ; length in paragraphs add bx,__heapbase mov __heaptop, bx ; ; Clear uninitialized data area to zeroes ; xor ax, ax mov es, cs:DGROUP@ mov di, offset DGROUP: bdata mov cx, offset DGROUP: edata sub cx, di rep stosb ; ; exit(main()); ; call _main ; the real C program push ax call _exit ; part of the C program too ;---------------------------------------------------------------- ; _exit() ; Restore interrupt vector taken during startup. ; Exit to DOS. ;---------------------------------------------------------------- PUBLIC __exit __exit PROC NEAR push ss pop ds ; Exit to DOS ExitToDOS: mov bp,sp mov ah,4Ch mov al,[bp+2] int 21h ; Exit to DOS __exit ENDP STARTX ENDP ;[]------------------------------------------------------------[] ;| Miscellaneous functions | ;[]------------------------------------------------------------[] ErrorDisplay PROC NEAR mov ah, 040h mov bx, 2 ; stderr device int 021h ret ErrorDisplay ENDP PUBLIC _abort _abort PROC NEAR mov cx, lgth_abortMSG mov dx, offset DGROUP: abortMSG MsgExit3 label near push ss pop ds call ErrorDisplay CallExit3 label near mov ax, 3 push ax call __exit ; _exit(3); _abort ENDP ; The DGROUP@ variable is used to reload DS with DGROUP PUBLIC DGROUP@ DGROUP@ dw ? _TEXT ENDS ;[]------------------------------------------------------------[] ;| Start Up Data Area | ;[]------------------------------------------------------------[] _DATA SEGMENT WORD PUBLIC 'DATA' abortMSG db 'Quitting program...', 13, 10 lgth_abortMSG equ $ - abortMSG ; ; Miscellaneous variables ; PUBLIC __psp PUBLIC __version PUBLIC __osmajor PUBLIC __osminor __psp dw 0 __version label word __osmajor db 0 __osminor db 0 ; Memory management variables PUBLIC ___heapbase PUBLIC ___brklvl PUBLIC ___heaptop PUBLIC __heapbase PUBLIC __brklvl PUBLIC __heaptop ___heapbase dw DGROUP:edata ___brklvl dw DGROUP:edata ___heaptop dw DGROUP:edata __heapbase dw 0 __brklvl dw 0 __heaptop dw 0 _DATA ENDS _BSS SEGMENT WORD PUBLIC 'BSS' bdata label byte edata label byte ; mark top of used area _BSS ENDS END STARTX [LISTING FOUR] # makefile for DEVLOD.COM # can substitute other assemblers for TASM c0.obj : c0.asm tasm c0 /t/mx/la; movup.obj: movup.asm tasm movup /t/mx/la; devlod.obj: devlod.c tcc -c -ms devlod devlod.com: devlod.obj c0.obj movup.obj tlink c0 movup devlod /c/m,devlod if exist devlod.com del devlod.com exe2bin devlod.exe devlod.com del devlod.exe