/* file sample.c
   sample OS/2 serial device driver
*/

#include "drvlib.h"
#include "uart.h"
#include "serial.h"

extern void near STRAT(); /* name of strat rout.*/
extern void near TIM_HNDLR(); /* timer handler  */
extern int  near INT_HNDLR(); /* interrupt hand */

DEVICEHDR devhdr = {
 (void far *) 0xFFFFFFFF,  /* link              */
 (DAW_CHR | DAW_OPN | DAW_LEVEL1),/* attribute  */
 (OFF) STRAT,              /* &strategy         */
 (OFF) 0,                  /* &IDCroutine       */
 "DEVICE1 "
  };

CHARQUEUE   rx_queue;      /* receiver queue    */
CHARQUEUE   tx_queue;      /* transmitter queue */
FPFUNCTION  DevHlp=0;      /* for DevHlp calls  */
LHANDLE     lock_seg_han;  /* handle for locking*/
PHYSADDR    appl_buffer=0; /* address of caller */
PREQPACKET  p=0L;          /* Request Packet ptr*/
ERRCODE     err=0;         /* error return      */
void        far *ptr;      /* temp far pointer  */
DEVICEHDR   *hptr;         /* pointer to Device */
USHORT      i;             /* general counter   */
UARTREGS    uart_regs;     /* uart registers    */
ULONG       WriteID=0L;    /* ID for write Block*/
ULONG       ReadID=0L;     /* ID for read Block */
PREQPACKET  ThisReadRP=0L; /* for read Request  */
PREQPACKET  ThisWriteRP=0L;/* for write Request */
char        inchar,outchar;/* temp chars        */
USHORT      baud_rate;     /* current baud rate */
unsigned    int savepid;   /* PID of driver own */
UCHAR       opencount;     /* number of times   */
ULONG       tickcount;     /* for timeouts      */
unsigned    int com_error_word; /* UART status  */
USHORT      port;          /* port variable     */
USHORT      temp_bank;     /* holds UART bank   */
QUEUE       rqueue;        /* receive queue info*/

void near init();
void near enable_write();
void near disable_write();
void near set_dlab();
void near reset_dlab();
void near config_82050();

char   IntFailMsg[] = " interrupt handler failed to install.\r\n";
char   MainMsg[] = " OS/2 Serial Device Driver V1.0 installed.\r\n";

/* common entry point to strat routines */

int main(PREQPACKET rp, int dev )
{
   void far *ptr;
   int far *pptr;
   PLINFOSEG liptr;    /* pointer to local info */
   int i;
   ULONG addr;

   switch(rp->RPcommand)
    {
    case RPINIT:       /* 0x00                  */

        /* init called by kernel in prot mode */

        return Init(rp,dev);

    case RPOPEN:       /* 0x0d                  */

        /* get current processes id */

        if (GetDOSVar(2,&ptr))
            return (RPDONE|RPERR|ERROR_BAD_COMMAND);

        /* get process info */

        liptr = *((PLINFOSEG far *) ptr);

        /* if this device never opened */

        if (opencount == 0) /* 1st time dev op'd*/
        {
            ThisReadRP=0L;
            ThisWriteRP=0L;
            opencount=1;  /* set open counter   */
            savepid = liptr->pidCurrent; /* PID */
            QueueInit(&rx_queue);/* init driver */
            QueueInit(&tx_queue);
        }
        else
            {
            if (savepid != liptr->pidCurrent)
                return (RPDONE | RPERR | RPBUSY );
            ++opencount;       /* bump counter  */
        }
        return (RPDONE);

    case RPCLOSE:        /* 0x0e                */

        /* get process info of caller */

        if (GetDOSVar(2,&ptr))
            return (RPDONE|RPERR|ERROR_BAD_COMMAND); /* no info  */

        /* get process info from os/2 */

        liptr= *((PLINFOSEG far *) ptr); /* PID */
        if (savepid != liptr->pidCurrent ||
           opencount == 0)
        return (RPDONE|RPERR|ERROR_BAD_COMMAND);
        --opencount;   /* close counts down open*/

        if (ThisReadRP !=0 && opencount == 0) {
            Run((ULONG) ThisReadRP); /* dangling*/
            ThisReadRP=0L;
        }
        return (RPDONE);     /* return 'done'   */

    case RPREAD:             /* 0x04            */

        /*  Try to read a character */

        ThisReadRP = rp;
        if (opencount == 0)/* drvr was closed   */
        {
            rp->s.ReadWrite.count = 0;  /* EOF  */
            return(RPDONE);
        }
        com_error_word=0;/* start off no errors */
        ReadID = (ULONG) rp;
        if (Block(ReadID, -1L, 0, &err))
            if (err == 2)       /* interrupted  */
               return(RPDONE|RPERR|ERROR_CHAR_CALL_INTERRUPTED);

        if (rx_queue.qcount == 0) {
           rp->s.ReadWrite.count=0;
           return (RPDONE|RPERR|ERROR_NOT_READY);
           }

        i=0;
        do {
          if (MoveData((FARPOINTER)&inchar,
          (FARPOINTER) (rp->s.ReadWrite.buffer+i),
          1,
			 MOVE_VIRTTOPHYS))
            return(RPDONE|RPERR|ERROR_GEN_FAILURE);
            }
        while (++i < rp->s.ReadWrite.count
            && !QueueRead(&rx_queue,&inchar));
        rp->s.ReadWrite.count = i;
        QueueInit(&rx_queue);
        return(rp->RPstatus);

    case RPWRITE:        /* 0x08                */

        ThisWriteRP = rp;

        /* transfer characters from user buffer */

        addr=rp->s.ReadWrite.buffer;/* get addr */
        for (i = rp->s.ReadWrite.count; i; --i,++addr)
        {
          if (MoveData((FARPOINTER)addr,
             (FARPOINTER)&outchar,
				 1,
				 MOVE_PHYSTOVIRT))
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

          if (QueueWrite(&tx_queue,outchar))
              return (RPDONE|RPERR|ERROR_GEN_FAILURE);
          }
        WriteID = (ULONG) rp;
        enable_write();

        if (Block(WriteID, -1L, 0, &err))
            if (err == 2)   /* interrupted      */
                return(RPDONE|RPERR|ERROR_CHAR_CALL_INTERRUPTED);

        tickcount=MIN_TIMEOUT; /* reset timeout */
        QueueInit(&tx_queue);
        return (rp->RPstatus);

    case RPINPUT_FLUSH:      /* 0x07            */

        QueueFlush(&rx_queue);
        return (RPDONE);

    case RPOUTPUT_FLUSH:     /* 0x0b            */

        QueueFlush(&tx_queue);
        return (RPDONE);

    case RPIOCTL:            /* 0x10            */

        if (!((rp->s.IOCtl.category == SAMPLE_CAT)
           || (rp->s.IOCtl.category == 0x01)))
              return (RPDONE);

        switch (rp->s.IOCtl.function)
        {
        case 0x41:    /* set baud rate          */
        /* set baud rate to 1.2, 2.4, 9.6, 19.2 */
        /* verify caller owns the buffer area   */

        if(VerifyAccess(
         SELECTOROF(rp->s.IOCtl.parameters),
         OFFSETOF(rp->s.IOCtl.parameters),
         2,             /* two bytes            */
         1) )           /* read/write           */
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

         /* lock the segment down temp */

         if(LockSeg(
         SELECTOROF(rp->s.IOCtl.parameters),
         0,          /* lock for < 2 sec      */
         0,          /* wait for seg lock     */
         (PLHANDLE) &lock_seg_han)) /* handle */
             return (RPDONE|RPERR|ERROR_GEN_FAILURE);

         /* get physical address of buffer */
         if (VirtToPhys(
         (FARPOINTER) rp->s.IOCtl.parameters,
         (FARPOINTER) &appl_buffer))
             return (RPDONE|RPERR|ERROR_GEN_FAILURE);

         /* move data to local driver buffer */

         if(MoveData(
         (FARPOINTER) appl_buffer,  /* source             */
         (FARPOINTER)&baud_rate,    /* destination        */
         2,                         /* 2 bytes            */
			MOVE_PHYSTOVIRT))
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

         if (UnPhysToVirt()) /* release selector*/
                return(RPDONE|RPERR|ERROR_GEN_FAILURE);

         /* unlock segment */

         if(UnLockSeg(lock_seg_han))
              return(RPDONE|RPERR|ERROR_GEN_FAILURE);

         switch (baud_rate)
            {
            case 1200:

                uart_regs.Bal=0xe0;
                uart_regs.Bah=0x01;
                break;

            case 2400:

                uart_regs.Bal=0xf0;
                uart_regs.Bah=0x00;
                break;

            case 9600:

                uart_regs.Bal=0x3c;
                uart_regs.Bah=0x00;
                break;

            case 19200:

                uart_regs.Bal=0x1e;
                uart_regs.Bah=0x00;
                break;

            case 38400:

                uart_regs.Bal=0x0f;
                uart_regs.Bah=0x00;
                break;

error:
            return (RPDONE|RPERR|ERROR_BAD_COMMAND);

            }
            init();      /* reconfigure uart    */
            return (RPDONE);

        case 0x68:       /* get number of chars */

            /* verify caller owns the buffer    */

            if(VerifyAccess(
            SELECTOROF(rp->s.IOCtl.buffer),
            OFFSETOF(rp->s.IOCtl.buffer),
            4,            /* 4 bytes            */
            1) )          /* read/write         */
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

            /* lock the segment down temp */

            if(LockSeg(
            SELECTOROF(rp->s.IOCtl.buffer),
            0,           /* lock for < 2 sec    */
            0,           /* wait for seg lock   */
            (PLHANDLE) &lock_seg_han)) /* handle*/
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

            /* get physical address of buffer */

            if (VirtToPhys(
            (FARPOINTER) rp->s.IOCtl.buffer,
            (FARPOINTER) &appl_buffer))
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

            rqueue.cch=rx_queue.qcount;
            rqueue.cb=rx_queue.qsize;

            /* move data to local driver buffer */

            if(MoveData(
            (FARPOINTER)&rx_queue,  /* source   */
            (FARPOINTER) appl_buffer, /* dest   */
            4,           /* 4 bytes             */		 
				MOVE_PHYSTOVIRT))
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

            if (UnPhysToVirt())
                return(RPDONE|RPERR|ERROR_GEN_FAILURE);

            /* unlock segment */

            if(UnLockSeg(lock_seg_han))
                return(RPDONE|RPERR|ERROR_GEN_FAILURE);

            return (RPDONE);

        case 0x6d:    /* get COM error info     */

            /* verify caller owns the buffer    */

            if(VerifyAccess(
            SELECTOROF(rp->s.IOCtl.buffer),
            OFFSETOF(rp->s.IOCtl.buffer),
            2,        /* two bytes              */
            1) )      /* read/write             */
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

            /* lock the segment down temp */

            if(LockSeg(
            SELECTOROF(rp->s.IOCtl.buffer),
            0,        /* lock for < 2 sec       */
            0,        /* wait for seg lock      */
            (PLHANDLE) &lock_seg_han)) /* handle*/
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

            /* get physical address of buffer */

            if (VirtToPhys(
            (FARPOINTER) rp->s.IOCtl.buffer,
            (FARPOINTER) &appl_buffer))
               return (RPDONE|RPERR|ERROR_GEN_FAILURE);

            /* move data to application buffer */

            if(MoveData(
            (FARPOINTER)&com_error_word, /* source */
            (FARPOINTER) appl_buffer,    /* dest   */
            2,            /* 2 bytes               */
				MOVE_VIRTTOPHYS))
                return (RPDONE|RPERR|ERROR_GEN_FAILURE);

            if (UnPhysToVirt())
                return(RPDONE|RPERR|ERROR_GEN_FAILURE);

            /* unlock segment */

            if(UnLockSeg(lock_seg_han))
                return(RPDONE|RPERR|ERROR_GEN_FAILURE);

            return (RPDONE);

        default:
            return(RPDONE|RPERR|ERROR_GEN_FAILURE);
        }

        /* don't allow deinstall */

    case RPDEINSTALL:  /* 0x14                  */
        return(RPDONE|RPERR|ERROR_BAD_COMMAND);

    /* all other commands are ignored */

    default:
        return(RPDONE);

    }
}

void enable_write()

/* enable write interrupts on uart */

{
    int  port;
    int  reg_val;

    port=UART_PORT_ADDRESS;
    reg_val=inp(port+2) & 0x60;
    set_bank(00);
    outp((port+1),inp(port+1) | 0x12);
    outp((port+2),reg_val);

}
void disable_write()

/* turn off write interrupts on uart */

{
    int  port;
    int  reg_val;

    port=UART_PORT_ADDRESS;
    reg_val=inp(port+2) & 0x60;
    set_bank(00);
    outp((port+1),inp(port+1) & 0xed);
    outp((port+2),reg_val);

}

void init ()

/* intializes software and configures 82050 */

{
    config_82050 ();    /* Configure 82050      */
    set_bank(01);
}

void config_82050()

/*  Configure the 82050      */

{
    int  port;
    int inval;

    Disable();                          /* disable interrupts    */
    port=UART_PORT_ADDRESS;

    /* set stick bit */

    set_bank(01);                       /* stick bit            */
    outp((port+7),0x10);                /* reset port           */
    outp ((port+1), uart_regs.Txf);     /* stick bit            */

    set_bank (02);                      /* general config       */
    outp ((port + 4), uart_regs.Imd);   /*auto rupt             */
    outp ((port + 7), uart_regs.Rmd);
    outp ((port + 5), uart_regs.Acr1);  /* cntl-z               */
    outp ((port + 3), uart_regs.Tmd);   /* no 9 bit             */
    outp ((port + 1), uart_regs.Fmd);   /* rx fifo              */
    outp ((port + 6), uart_regs.Rie);   /* enable               */

    set_bank (03);                      /* modemconfiguration   */

    outp ((port + 0), uart_regs.Clcf);  /* clock                */
    set_dlab (03);                      /*                      */
    outp ((port + 0), uart_regs.Bbl);   /* BRGB lsb             */
    outp ((port + 1), uart_regs.Bbh);   /* BRGB msb             */
    reset_dlab (03);                    /*                      */
    outp ((port + 3), uart_regs.Bbcf);  /* BRGB                 */
    outp ((port + 6), uart_regs.Tmie);  /* timer b              */

    set_bank (00);                      /* general cfg          */
    outp ((port + 1), uart_regs.Ger);   /* enable               */
    outp ((port + 3), uart_regs.Lcr);   /* 8 bit                */
    outp ((port + 7), uart_regs.Acr0);  /* CR                   */
    outp ((port + 4), uart_regs.Mcr_0); /* no DTR               */
    set_dlab (00);                      /*                      */
    outp ((port + 0), uart_regs.Bal);   /* BRGA lsb             */
    outp ((port + 1), uart_regs.Bah);   /* BRGA msb             */
    reset_dlab (00);
    set_bank(01);

    Enable();                           /* turn on              */
}

void set_dlab (bank)

/*  Set DLAB bit to allow access to divisior registers  */

int bank;
{
    int  inval;
    int  port;

    port=UART_PORT_ADDRESS;
    set_bank (00);
    inval=inp(port +3);
    inval =inval | 0x80;                 /* set dlab in LCR     */
    outp ((port+3),inval);
    set_bank (bank);
}

getsrc()

{
    int   v,src;
    int   port;

    port=UART_PORT_ADDRESS;             /* get base address     */
    v=inp(port+2);                      /* get data             */
    src=v & 0x0e;                       /* mask bits            */
    src=src/2;                          /* divide by 2          */
    return(src);                        /* and pass it back     */
}

set_bank(bank_num)

/* set bank of 82050 uart */

int  bank_num;

{
    int reg_val;
    int   port;

    reg_val=bank_num*0x20;              /* select bank numb */
    port=UART_PORT_ADDRESS;             /* get real port    */
    outp(port+gir_addr,reg_val);        /* output      */
}

void reset_dlab (bank)

/*  Reset DLAB bit of LCR   */

int bank;

{
    int  inval;
    int  port;

    port=UART_PORT_ADDRESS;
    set_bank (00);
    inval=inp (port +3);
    inval = (inval & 0x7f);             /* dlab = 0 in LCR      */
    outp ((port+3),inval);
    set_bank (bank);
}

/* 82050 interrupt handler */

void interrupt_handler ()
{
    int  rupt_dev;
    int  source;
    int  cmd_b;
    int  st_b;
    int  port;
    int  temp;
    int  rxlevel;


    port=UART_PORT_ADDRESS;
    outp((port+2),0x20);                /* switch to bank 1     */
    source = getsrc ();                 /* get vector           */
    switch (source)
    {

    /* optional timer service routine */

    case timer :

        st_b=inp (port+3);              /* dec transmit count   */
        if ( ThisReadRP == 0)           /* nobody waiting       */
            break;
        ThisReadRP->RPstatus=(RPDONE|RPERR|ERROR_NOT_READY);
        Run ((ULONG)  ThisWriteRP);     /* run thread           */
        ThisWriteRP=0;
        break;

    case txm   :
    case txf   :

        /* spurious write interrupt */

        if ( ThisWriteRP == 0) {
            temp=inp(port+2);
            break;
        }

        /* keep transmitting until no data left */

        if  (!(QueueRead(&tx_queue,&outchar)))
        {
             outp((port), outchar);
             tickcount=MIN_TIMEOUT;
             break;
        }

        /* done writing, run blocked thread     */

        tickcount=MIN_TIMEOUT;
        disable_write();
        ThisWriteRP->RPstatus = (RPDONE);
        Run ((ULONG)  ThisWriteRP);
        ThisWriteRP=0;
        break;

    case ccr   :

        /* control character, treat as normal   */

        inchar=inp(port+5);

    case rxf   :

        /* rx fifo service routine */

        if ( ThisReadRP == 0)
            inchar=inp (port); /* get character */
        else
        {
        temp=inp(port+4);
        rxlevel=(temp & 0x70) / 0x10;

         /* empty out chip FIFO */

         while (rxlevel !=0) {

           inchar=inp (port); /* get character */
           rxlevel--;
           tickcount=MIN_TIMEOUT;

           /* write input data to queue */

           if(QueueWrite(&rx_queue,inchar))

             /* error, queue must be full */

             {
             ThisReadRP->RPstatus=(RPDONE|RPERR|ERROR_GEN_FAILURE);
             Run ((ULONG) ThisReadRP);
             ThisReadRP=0;
             break;
             }
           com_error_word |= inp(port+5);

        } /* while rxlevel */
     } /* else */
  } /* switch (source) */
}
void timer_handler()
{
  if (ThisReadRP == 0)
        return;

  tickcount--;
  if(tickcount == 0)  {
    ThisReadRP->RPstatus=(RPDONE);
    Run ((ULONG) ThisReadRP);
    ThisReadRP=0L;
    tickcount=MIN_TIMEOUT;
    }
}

/* Device Initialization Routine */

int Init(PREQPACKET rp, int dev)
{
    register char far *p;

    /* store DevHlp entry point */

    DevHlp = rp->s.Init.DevHlp;

    /* install interrupt hook in vector */

    if (SetTimer((PFUNCTION)TIM_HNDLR))
                goto fail;

    rx_queue.qsize=QUEUE_SIZE;
    tx_queue.qsize=QUEUE_SIZE; /* init queue    */
    init();                    /* init the port */
    tickcount=MIN_TIMEOUT;     /* set timeout   */

    if(SetIRQ(5,(PFUNCTION)INT_HNDLR,0)) {

     /* if we failed, deinstall driver cs+ds=0 */
fail:
     DosPutMessage(1, 8, devhdr.DHname);
     DosPutMessage (1,strlen(IntFailMsg),IntFailMsg);
     rp->s.InitExit.finalCS = (OFF) 0;
     rp->s.InitExit.finalDS = (OFF) 0;
     return (RPDONE | RPERR | ERROR_BAD_COMMAND);
     }

/* output initialization message */

DosPutMessage(1, 8, devhdr.DHname);
DosPutMessage(1, strlen(MainMsg), MainMsg);

/* send back our cs and ds values to os/2 */

if (SegLimit(HIUSHORT((void far *) Init),&rp->s.InitExit.finalCS)
    || SegLimit(HIUSHORT((void far *) MainMsg),
    &rp->s.InitExit.finalDS))
     Abort();
   return(RPDONE);
}


