/*
 * SIO - Serial port I/O
 * Copyright (c) 1990, 2000 Erick Engelke
 * Utilizes special interrupt handling to support
 *      - multiple COM ports per irq
 *      - low latency for other interrupts on system board
 *          - does not disable interrupts for long like most serial code
 */

#define TERM
#define NEW

#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <rtos.h>
#include <sio.h>
#include <mem.h>

#ifdef __DJGPP__
#include <go32.h>
#include <pc.h>
#endif

//#define FIFO    /* Use FIFO if found */

#define RBR 0   /* receive  buffer register */
#define THR 0   /* transmit buffer register */
#define IER 1   /* interrupt enable register */
#define IIR 2   /* interrupt ID register */
#define LCR 3   /* line control register */
#define MCR 4   /* modem control register */
#define LSR 5   /* line status register */
#define MSR 6   /* modem status register */

#define DLL 0   /* divisor latch LSB when DLAB=1 */
#define DLM 1   /* divisor latch MSB when DLAB=1 */

#define DLAB      0x80   /* divisor latch bit */
#define OUT2      0x8    /* interrupt enable on PC compatibles */
#define IIRMASK   0x7    /* valid bits in the IIR register */
#define IERENABLE 0xf    /* enable all SIO interrupt conditions */
#define FIFOMASK  0x8

#define DTR     0x01   /* data terminal ready */
#define RTS     0x02   /* ready to send */
#define CTS     0x10   /* ready to send */
#define DELTA_CTS	0x01	/* change in CTS	*/
#define DELTA_DSR	0x02	/* change in DSR	*/
#define SETBRK		0x40	/* Break control	*/

#define OCW1    0x21    /* 8259 */
#define OCW2    0x20

/* Line Status Register bits
 */
#define LSR_DR     0x01  /* receive data ready */
#define LSR_OVRRUN 0x02  
#define LSR_PARITY 0x04
#define LSR_FRAME  0x08
#define LSR_BREAK  0x10
#define LSR_THRE   0x20  /* transmit holding register empty */
#define LSR_TSRE   0x40
#define TX_READY   (LSR_THRE|LSR_TSRE)

#define MAXIRQS 15

#if defined(__DJGPP__)
  #define IntrHandler   struct irq_handler_info
  #define NULL_ISR      { {0,0}, {0,0} }

#elif defined(__TURBOC__)
  typedef void interrupt (*IntrHandler)(void);
  #define NULL_HANDLER  ((void far*)0)
  #define NULL_ISR      ((void far*)0)

#elif defined(__BORLANDC__)
  typedef void (cdecl interrupt *IntrHandler)(void);
  #define NULL_HANDLER  ((void far*)0)
  #define NULL_ISR      ((void far*)0)

#else
  #error Unsupported compiler
#endif

static IntrHandler oldisrs[ MAXIRQS + 1 ] =
   { NULL_ISR, NULL_ISR, NULL_ISR, NULL_ISR,
     NULL_ISR, NULL_ISR, NULL_ISR, NULL_ISR,
     NULL_ISR, NULL_ISR, NULL_ISR, NULL_ISR,
     NULL_ISR, NULL_ISR, NULL_ISR, NULL_ISR };


typedef struct _sio_str {
    WORD    sio_port;
    WORD    sio_irq;
    WORD    sio_int;    /* 8 + irq or ... */
    IntrHandler sio_previsr;

    bq_str *sio_rbq;    /* receive byte queue */
    bq_str *sio_tbq;    /* transmit byte queue */

    thread_x *sio_notify;
    int     sio_message;
    BYTE    sio_hwflow;             // Hardware flow control enable
    BYTE    sio_modemstatus;
    int     sio_highwater;  // Receive buffer high water mark
    int     sio_lowwater;   // Receive buffer low water mark
} sio_str;

static sio_str sio[ MAXSIO ] = {
        {0, 0, 0, NULL_ISR, NULL, NULL, NULL, 0, 0, 0, 0, 0},
        {0, 0, 0, NULL_ISR, NULL, NULL, NULL, 0, 0, 0, 0, 0},
        {0, 0, 0, NULL_ISR, NULL, NULL, NULL, 0, 0, 0, 0, 0},
        {0, 0, 0, NULL_ISR, NULL, NULL, NULL, 0, 0, 0, 0, 0}};

static char *sio_err = "SIO call with com port out of range";

static struct {
       DWORD num_rm_intr;
       DWORD num_pm_intr;
       DWORD num_thre;
       DWORD num_rx_intr;
       DWORD num_fifo_rx_intr;
       DWORD num_modem_stat;
       struct {
         DWORD general;
         DWORD overrun;
         DWORD parity;
         DWORD framing;
         DWORD breaking;
       } num_line_stat;
     } sio_stats [MAXSIO];

#define test_com_valid( n )  do {                      \
                               if (!n || (n > MAXSIO)) \
                                  rt_halt (sio_err);   \
                             } while (0)


#if defined(__BORLANDC__) || defined(__TURBOC__)
#pragma option -N-   /* disable stack checking */
#endif

static int sio_isr(int irq)
{
    int com;
    int handled = 0;
    sio_str *s;
    WORD intid;
    WORD base;
    BYTE b;
    BYTE status;

    for ( com = 0 ; com < MAXSIO ; ++com ) {
        s = &sio[ com ];
        base = s->sio_port;
        if ( base == 0 )
            continue;
        if ( s->sio_irq != irq )
            continue;

#ifdef __DJGPP__
        sio_stats[com].num_pm_intr++; /* to-do: use a bimodal handler */
#else
        sio_stats[com].num_rm_intr++;
#endif

        while ( 1 ) {

            intid = inportb( base + IIR );

            if ( intid & 1 )
                break;  /* no interrupt pending */

            switch ( intid & IIRMASK ) {

                case 0  :   /* change in modem status */
                            s->sio_modemstatus = inportb( base + MSR );
                            sio_stats[com].num_modem_stat++;

                            if (s->sio_notify)
                                ksendmessage (s->sio_notify , s->sio_message+2, s->sio_modemstatus);
                            if (s->sio_hwflow) {
                                if (s->sio_modemstatus & CTS) {
                                    if (inportb (base + LSR) & LSR_THRE) {
                                        if (bq_getbyte (s->sio_tbq, &b))
                                            outportb (base + THR, b);
                                    }
                                }
                            }
                            break;
                case 2  :   /* ready to transmit */
                            sio_stats[com].num_thre++;
                            if (s->sio_hwflow) {
                                if (!(s->sio_modemstatus & CTS))
                                    break;
                            }

                            if ( bq_getbyte( s->sio_tbq, &b ) )
                                outportb( base + THR, b );

                            break;
                case 4  :   /* received data */
                            if (intid & FIFOMASK)
                                 sio_stats[com].num_fifo_rx_intr++;
                            else sio_stats[com].num_rx_intr++;

                            b = inportb( base + RBR );

                            bq_sendbyte( s->sio_rbq , b );
#if 0
                            if (s->sio_notify)
                               ksend1message (s->sio_notify , s->sio_message, b);
#endif
                            if (s->sio_hwflow) {
                                if (bq_readcount (s->sio_rbq) >= s->sio_highwater) {
                                    status = inportb (base + MCR);
                                    outportb (base + MCR, status & ~RTS);
                                }
                            }

                            break;
                case 6  :   /* line status */
                            b = inportb( base + LSR );
                            sio_stats[com].num_line_stat.general++;
                            if (b & LSR_OVRRUN)
                               sio_stats[com].num_line_stat.overrun++;
                            if (b & LSR_PARITY)
                               sio_stats[com].num_line_stat.parity++;
                            if (b & LSR_FRAME)
                               sio_stats[com].num_line_stat.framing++;
                            if (b & LSR_BREAK)
                               sio_stats[com].num_line_stat.breaking++;

#if 0
                            if (s->sio_notify)
                              ksendmessage (s->sio_notify , s->sio_message+1, b);
#endif
                            break;
               default :    /* ! added */
                            (void) inportb (base + LSR);
                            (void) inportb (base + MSR);
                            handled--;
                            break;
            }
            handled++;
        }
   }
   return (handled);
}

//-----------------------------------------------------------------------
// ----------------------------------------------------------------------
#define IEC_PRIMARY 0x21
#define IEC_SECONDARY 0xA1

#if defined(__DJGPP__)
  #define do_chain(irq)  ((void)0) /* never chain */

#elif defined(__BORLANDC__) || defined(__TURBOC__)
  #define do_chain(irq)  if (oldisrs[irq]) (*oldisrs[irq])()
#endif

/* this code produces low latency for other interrupt handlers */

#define UART_INT( irqlevel, iec ) \
    void interrupt uart_int##irqlevel ( void ) \
    {           \
        kinisr++; \
        outportb( iec , inportb( iec ) | ( 1 << irqlevel )); \
        if ( iec == IEC_SECONDARY ) \
            outportb( 0xA0, 0x60|irqlevel); /* specific EOI */  \
        outportb( 0x20, 0x60|irqlevel); \
        enable(); \
                  \
        /*!! Shouldn't EOI be sent after our work is done? */ \
        if (!sio_isr( irqlevel )) \
           do_chain (irqlevel);   \
        else { \
          disable(); \
          outportb( iec, inportb( iec ) & ~ ( 1 << irqlevel )); \
        }  \
        kinisr--; \
    }

UART_INT( 3, IEC_PRIMARY )
UART_INT( 4, IEC_PRIMARY )
UART_INT( 5, IEC_PRIMARY )
UART_INT( 6, IEC_PRIMARY )
UART_INT( 7, IEC_PRIMARY )
UART_INT( 9, IEC_SECONDARY )
UART_INT(10, IEC_SECONDARY )
UART_INT(11, IEC_SECONDARY )
UART_INT(12, IEC_SECONDARY )
UART_INT(13, IEC_SECONDARY )
UART_INT(14, IEC_SECONDARY )
UART_INT(15, IEC_SECONDARY )


#if defined(__BORLANDC__) || defined(__TURBOC__)
#pragma option -N.   /* default stack checking */
#endif


//---------------------------------------------------------------------------
int uart_detect( WORD base )
{
    BYTE x, ov, scr = 1;

    // check for LCR
    ov = inportb( base + 3 );
    outportb( base + 3, 0x1b );
    if ( inportb( base + 3 ) != 0x1b ) return -1;

    outportb( base + 3, 0x03 );
    if ( inportb( base + 3 ) != 0x03 ) return -1;

    // restore its value
    outportb( base + 3, ov );

    // look for scratch register
    ov = inportb( base + 7 );
    outportb( base + 7, 0x55 );
    if ( inportb( base + 7 ) != 0x55 ) scr = 0;
    outportb( base + 7, 0xAA ) ;
    if ( inportb( base + 7 ) != 0xAA ) scr = 0;
    outportb( base + 7, ov );

    // look for FIFO
    outportb( base +2, 0x01);
    x = inportb( base + 2 );
    outportb( base + 2, 0 );
    if ( ! ( x & 0x80 ) ) return scr;
    if ( ! ( x & 0x40 ) ) return scr + 2;
    return scr + 4;
}

int uart_enable_fifo( WORD base, WORD trigger )
{
    // 0 on success or no FIFO
    // -1 on error
    BYTE x;

    outportb( base + 2, 1 );        // key to getting C0 right
    x = inportb( base + 2 ) & 0xC0 ;
    if ( x == 0 ) return( -1 );
    if ( x == 0x80 ) {
        outportb( base + 2, 0 );
        return -1;      // bad fifo, disabled it
    }
    outportb( base + 2, trigger & (0xc0 | 0x07) );
    return 0 ;
}
void uart_disable_fifo( WORD base )
{
    outportb( base + 2, 0 );
}
//--------------------------------------------------------------------

void sio_print_stats (int com)
{
  if (com < 1 || com >= MAXSIO-1)
     return;

  com--;
  printf ("COM%d stats: intr %lu/%lu, THRE %lu, Rx-intr %lu, FIFO %lu, "
          "modem %lu, line %lu\n",
          com+1, sio_stats[com].num_rm_intr, sio_stats[com].num_pm_intr,
          sio_stats[com].num_thre, sio_stats[com].num_rx_intr,
          sio_stats[com].num_fifo_rx_intr, sio_stats[com].num_modem_stat,
          sio_stats[com].num_line_stat.general);
  printf ("  (overrun %lu, parity %lu, framing %lu, breaking %lu)\n",
          sio_stats[com].num_line_stat.overrun,
          sio_stats[com].num_line_stat.parity,
          sio_stats[com].num_line_stat.framing,
          sio_stats[com].num_line_stat.breaking);
}

//----------------------------------------------------------------------


void
sio_init (int com, int port, int irq, int rbufsize, int tbufsize, thread_x *notify, int message)
{
    static int atexit_done = 0;
    WORD flags;
    sio_str *s;
    void interrupt (*isr)() = NULL;

    sio_close( com );   /* clean up any previous stuff */

    test_com_valid (com);

    rt_cpu_block( &flags );

    s = &sio[ com - 1 ];
    s->sio_port = port;
    s->sio_irq = irq;

    s->sio_notify = notify;
    s->sio_message = message;
    s->sio_highwater = (int)(((long)rbufsize * 90L)/100L);
    s->sio_lowwater = (int)(((long)rbufsize * 10L)/100L);
    s->sio_hwflow = 0;
    s->sio_modemstatus = 0;

    if ( s->sio_rbq != NULL ) bq_free( s->sio_rbq );
    if ( s->sio_tbq != NULL ) bq_free( s->sio_tbq );

    s->sio_rbq = bq_alloc( rbufsize );
    s->sio_tbq = bq_alloc( tbufsize );

    /* disable ints */
    outportb (port + IER , 0);

    switch ( irq ) {
        case  3 : isr = uart_int3;    break;
        case  4 : isr = uart_int4;    break;
        case  5 : isr = uart_int5;    break;
        case  6 : isr = uart_int6;    break;
        case  7 : isr = uart_int7;    break;
        case  9 : isr = uart_int9;    break;
        case 10 : isr = uart_int10;   break;
        case 11 : isr = uart_int11;   break;
        case 12 : isr = uart_int12;   break;
        case 13 : isr = uart_int13;   break;
        case 14 : isr = uart_int14;   break;
        case 15 : isr = uart_int15;   break;
    }
#ifdef OLD

	switch (com)
		{
		case 1:
		    isr = sio_isr1;
			break;
		case 2:
		    isr = sio_isr2;
			break;
		}
#endif

#ifdef __DJGPP__
    if ( oldisrs[ irq ].new_handler.pm_offset == 0 ) {
        irq_handler_info *inf = rt_enableirq( irq, isr );
        if (!inf)
        {
          fprintf (stderr,"called to hook IRQ %d\n", irq);
          return;
        }
        oldisrs[irq] = *inf;
    }
#else
    if ( oldisrs[ irq ] == NULL_HANDLER ) {
        oldisrs[irq] = rt_enableirq( irq, isr );
    }
//  s->sio_previsr = rt_enableirq (irq, isr);
#endif

    if (!atexit_done)
       atexit (sio_exit);
    atexit_done = 1;
    rt_cpu_unblock( &flags );
}

void sio_close( int com )
{
    WORD base;
    sio_str *s;
    int tempcom, count;

    test_com_valid( com );
    s = &sio[ com - 1 ];
    base = s->sio_port;

    if ( base ) {
        /* disable sio ints */
        outportb( base + IER, 0 );

        for ( tempcom = count = 0; tempcom < MAXSIO ; ++tempcom )
            if ( sio[ tempcom ].sio_irq == s->sio_irq ) count++;

        /* only remove ISR if we are the last one using it */
        if ( count == 1 )
        {
#ifdef __DJGPP__
           if ( oldisrs[ s->sio_irq].new_handler.pm_offset)
           {
             rt_disableirq ( s->sio_irq, &oldisrs[ s->sio_irq ] );
             oldisrs[ s->sio_irq ].new_handler.pm_offset = 0;
           }
#else
           if ( oldisrs[ s->sio_irq] != NULL_HANDLER )
           {
             rt_disableirq ( s->sio_irq, oldisrs[ s->sio_irq ] );
             oldisrs[ s->sio_irq ] = NULL_HANDLER;
           }
#endif
        }

        /* bring down com line  */
        outportb (base + MCR , 0);
#ifdef FIFO
        uart_disable_fifo( base );
#endif

        s->sio_port = 0;
        s->sio_irq = 0;     /* so last sio_close() on that IRQ knows it */
    }
    if ( s->sio_rbq != NULL ) {
        bq_free( s->sio_rbq );
        s->sio_rbq = NULL;
    }
    if ( s->sio_tbq != NULL ) {
        bq_free( s->sio_tbq );
        s->sio_tbq = NULL;
    }


}

void
sio_setup (int com, DWORD baud, int bits, int parity, int stop, BYTE hwflow)
{
    WORD flags;
    WORD divisor;
    WORD base;
    BYTE parmbyte;
    sio_str *s;
    BYTE b;

    test_com_valid( com );

#define LOW(  x ) ( x & 255 )
#define HIGH( x ) ( x >> 8 )
    s = &sio[ com - 1];
    divisor = (WORD) (115200L / baud);

    /* word length from [5,8] gives results [0,3] */
    if ( bits == 0 ) bits = 8;
    parmbyte = bits - 5;
    parmbyte |= (stop -1) << 2;
    if (parity) parmbyte |= 8;
    if (parity == SIO_PARITY_EVEN) parmbyte |= 16;

    rt_cpu_block( &flags );
    base = s->sio_port;
    s->sio_hwflow = hwflow; // establish hardware flow control

    /* disable sio int */
    outportb( base + IER, 0 );  /* disable all serial interrupts */
    /* disable adapter, DTR RTS */
    outportb( base + MCR , 0 );

    /* set DLAB for a moment */
    outportb( base + LCR , inportb( base + LCR ) | DLAB );

    /* pass the divisor */
    outportb( base + DLL , LOW( divisor ));
    outportb( base + DLM , HIGH( divisor ));

    /* clear DLAB */
    outportb( base + LCR , inportb( base + LCR ) & ~DLAB );

    /* set word length and parity */
    outportb( base + LCR, parmbyte );

    /* clear any trash */
    while (inportb (base + LSR) & LSR_DR)
         inportb (base + RBR);

    // Send the initial notification of the line status
    b = inportb (base + LSR);
    if (s->sio_notify)
       ksendmessage (s->sio_notify , s->sio_message+1, b);

    // Send the initial notification of the modem status
    s->sio_modemstatus = inportb( base + MSR );
    if (s->sio_notify)
       ksendmessage (s->sio_notify , s->sio_message+2,
                     s->sio_modemstatus | DELTA_CTS | DELTA_DSR);

    /* enable adapter, DTR RTS */
    outportb( base + MCR , DTR | RTS | OUT2 );

    /* enable sio int */
    outportb( base + IER, IERENABLE );  /* enable all serial interrupts */
#ifdef FIFO
    uart_enable_fifo( base, 0xC0 );
#endif
    rt_cpu_unblock( &flags );
}

int sio_recv_waiting( int com )
{
    test_com_valid( com );
    return( bq_readcount( sio[ com - 1 ].sio_rbq ) );
}

int sio_tran_waiting( int com )
{
    test_com_valid( com );
    return( bq_readcount( sio[ com - 1 ].sio_tbq ) );
}

BYTE sio_readbyte( int com )
{
    BYTE x;
    BYTE status;
    WORD base;
    sio_str *s;

    test_com_valid( com );
    s = &sio[ com - 1];
    base = s->sio_port;
    bq_readbyte( s->sio_rbq , &x ) ;
    if (s->sio_hwflow)
    {
      if (bq_readcount (s->sio_rbq) <= s->sio_lowwater)
      {
        status = inportb (base + MCR);
        outportb (base + MCR, status | RTS);
      }
    }
    return( x );
}

void sio_writebyte (int com, BYTE b)
{
    sio_str *s;
    BYTE status;
    WORD base;
    WORD flags;

    test_com_valid (com);
    s = &sio[com - 1];
    base = s->sio_port;

    /* always send it to the queue */
    bq_writebyte (s->sio_tbq, b);

    /* now check if we need to start the UART siphon */
    rt_cpu_block( &flags );

    if (!(s->sio_hwflow) || (s->sio_modemstatus & CTS)) {
        status = inportb (base + LSR);    /* get UART status */
        if (status & LSR_THRE) {
            /* holding register is empty, fill it if data is available */
            if (bq_getbyte (s->sio_tbq, &b))
                outportb (base + THR, b);
        }
    }
    rt_cpu_unblock (&flags);
}

void sio_exit (void)
{
    int i;
    for (i = 1 ; i < MAXSIO ; ++i)
    {
        if (kdebug > 2)
           sio_print_stats (i);
        sio_close (i);
    }
}

void sio_msg( int com, int xmit, int recv )
{
    sio_str *s;

    test_com_valid( com );
    s = &sio[ com - 1 ];

    if ( xmit ) bq_msg( s->sio_tbq, EMSG_BQ_AVAIL );
    if ( recv ) bq_msg( s->sio_rbq, EMSG_BQ_WAITING );
}

BYTE sio_getmcr (int com)
{
    sio_str *s;

    test_com_valid (com);
    s = &sio[com - 1];
    return (inportb (s->sio_port + MCR));
}

void sio_setmcr (int com, BYTE mcr)
{
    sio_str *s;

    test_com_valid (com);
    s = &sio[com - 1];
    outportb (s->sio_port + MCR, mcr);
}

void sio_setbreak (int com, BYTE brk)
{
    sio_str *s;

    test_com_valid (com);
    s = &sio[com - 1];
    outportb (s->sio_port + LCR,
              (inportb (s->sio_port + LCR) & ~SETBRK) | (brk & SETBRK));
}



