/********************************************************************
 *     DEVLOD.C - Jim Kyle - 08/20/90                               *
 *            Copyright 1990 by Jim Kyle - All Rights Reserved      *
 *     (minor revisions by Andrew Schulman - 9/12/90                *
 *     Dynamic loader for device drivers                            *
 *            Requires Turbo C; see DEVLOD.MAK also for ASM helpers.*
 ********************************************************************/
     
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>

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<cmdlinesz && nameptr[i]<'!'; i++) /* skip blanks      */
    ;
  dvrarg = (char *)&nameptr[i]; /* save to put in SI                */
  for ( j=0; i<cmdlinesz && nameptr[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; i<lastdrive; i++, ((BYTE far *)cds)+=CDSsize)
    if (! cds->flags)                /* 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." );
}
