/*********************************************************************/
/*                                                                   */
/*  This Program Written by Paul Edwards, 3:711/934@fidonet.         */
/*  Released to the Public Domain                                    */
/*                                                                   */
/*********************************************************************/
/*********************************************************************/
/*                                                                   */
/*  pdcommd - com handlers for MSDOS                                 */
/*                                                                   */
/*  This program is designed to do interrupt-driven handling of the  */
/*  com port.                                                        */
/*                                                                   */
/*********************************************************************/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "pdcommd.h"
#include "pdcommda.h"
#include "uart.h"
#include "lldos.h"

static PDCOMM *actcomm = 0;

static void pdcommIenable(PDCOMM *pdcomm);
static void pdcommIdisable(PDCOMM *pdcomm);
static void pdcommEnableCTSRTS(PDCOMM *pdcomm);
static void pdcommBuffers(PDCOMM *pdcomm, size_t txBufSiz, size_t recBufSiz);
static void pdcommOpenPort(PDCOMM *pdcomm, int port, int intrpt);
static void pdcommClosePort(PDCOMM *pdcomm);

void pdcommDefaults(PDCOMM *pdcomm)
{
    pdcomm->txBuf = NULL;
    pdcomm->recBuf = NULL;
    return;
}

/* Supports the following syntaxes:
   COM2:02F8,3,19200
*/

void pdcommInit(PDCOMM *pdcomm, char *name)
{
    int port;
    int intrpt;
    unsigned long speed;
    char *p;
    
    p = strchr(name, ':');
    if (p == NULL) return;
    p++;
    port = (int)strtol(p, NULL, 16);
    p = strchr(p, ',');
    if (p == NULL) return;
    p++;
    intrpt = atoi(p);
    p = strchr(p, ',');
    if (p == NULL) return;
    p++;
    speed = atol(p);
    pdcommBuffers(pdcomm, BUFSIZ, BUFSIZ);
    uartInit(&pdcomm->uart);
    pdcommOpenPort(pdcomm, port, intrpt);
    pdcommSetSpeed(pdcomm, speed);
    pdcommSetParms(pdcomm, PDCOMM_NO_PARITY, 8, 1);
    return;
}

static void pdcommOpenPort(PDCOMM *pdcomm, int port, int intrpt)
{
    uartAddress(&pdcomm->uart, port);

    pdcomm->intrpt = intrpt;
    pdcomm->txempty = 1;

    actcomm = pdcomm;    
    pdcommIenable(pdcomm);
    uartRaiseDTR(&pdcomm->uart);
    uartRaiseRTS(&pdcomm->uart);
    pdcommEnableCTSRTS(pdcomm);
    return;
}

static void pdcommClosePort(PDCOMM *pdcomm)
{
    uartDropRTS(&pdcomm->uart);
    uartDropDTR(&pdcomm->uart);
    pdcommIdisable(pdcomm);
    uartReset(&pdcomm->uart);
    return;
}

static void pdcommIenable(PDCOMM *pdcomm)
{
    int ch;
    
    /* Interrupts 0-7 use the PIC at 20H, whilst interrupts 8-15 use
       the PIC at A0H.  In both cases, the IMR is one location further */
    if (pdcomm->intrpt < 8)
    {
        pdcomm->a8259 = 0x20;
        pdcomm->imr = 0x21;
    }
    else
    {
        pdcomm->a8259 = 0xa0;
        pdcomm->imr = 0xa1;
    }

    /* The PIC has a number that is added on to the IRQ to get a vector
       number.  */
    ch = PREADB(pdcomm->a8259);
    if (pdcomm->intrpt < 8) ch = 8;
    else ch = 104;

    pdcomm->vector = pdcomm->intrpt + ch;

    /* Disable all interrupts on the 8250 */
    
    uartDisableInts(&pdcomm->uart); 
    
    /* Save the old interrupt address */
    INTSTOP();
    pdcomm->oldIRQ[0] = getfar((long)pdcomm->vector * 4);
    pdcomm->oldIRQ[1] = getfar((long)pdcomm->vector * 4 + 1);
    pdcomm->oldIRQ[2] = getfar((long)pdcomm->vector * 4 + 2);
    pdcomm->oldIRQ[3] = getfar((long)pdcomm->vector * 4 + 3);
    INTALLOW();

    /* Install the interrupt */
    pdcommdaInstallInt((long)pdcomm->vector * 4);

    
    /* Tell the 8259 that you want to receive IRQs for this IRQ */
    ch = PREADB(pdcomm->imr);
    ch &= ~(1 << (pdcomm->intrpt % 8));
    PWRITEB(pdcomm->imr, ch);

    /* Enable GPO2 on the 8250 */
    uartEnableGPO2(&pdcomm->uart);
    
    /* Tell the 8250 that you want to receive RxRDY + TBE interrupts */

    uartEnableRxRDY(&pdcomm->uart);
    uartEnableTBE(&pdcomm->uart);
    uartEnableModem(&pdcomm->uart);
    
    return;
}

static void pdcommIdisable(PDCOMM *pdcomm)
{
    int ch;

    /* switch of Interrupts */
    uartDisableInts(&pdcomm->uart);
    
    /* disable GPO2 on the 8250 */
    uartDisableGPO2(&pdcomm->uart);
    
    /* switch off this interrupt on the PIC */
    ch = PREADB(pdcomm->imr);
    ch |= (1 << (pdcomm->intrpt % 8));
    PWRITEB(pdcomm->imr, ch);
    
    /* restore the old interrupt vector */
    INTSTOP();
    putfar((long)pdcomm->vector * 4, pdcomm->oldIRQ[0]);
    putfar((long)pdcomm->vector * 4 + 1, pdcomm->oldIRQ[1]);
    putfar((long)pdcomm->vector * 4 + 2, pdcomm->oldIRQ[2]);
    putfar((long)pdcomm->vector * 4 + 3, pdcomm->oldIRQ[3]);
    INTALLOW();
    return;
}

void pdcommSetSpeed(PDCOMM *pdcomm, unsigned long speed)
{
    uartSetBaud(&pdcomm->uart, speed);
    return;
}

void pdcommSetParms(PDCOMM *pdcomm, int parity, int data, int stop)
{
    uartSetParity(&pdcomm->uart, parity);
    uartSetDataBits(&pdcomm->uart, data);
    uartSetStopBits(&pdcomm->uart, stop);
    return;
}

int pdcommWriteCh(PDCOMM *pdcomm, int ch)
{
    char *newpos;
    
    INTSTOP();
    if (pdcomm->txempty && pdcomm->ctsOK)
    {
        INTALLOW();
        pdcomm->txempty = 0;
        uartTxCh(&pdcomm->uart, ch);
    }
    else
    {
        newpos = pdcomm->txWritePos + 1;
        if (newpos == pdcomm->txEndPos)
        {
            newpos = pdcomm->txStartPos;
        }
        if (newpos != pdcomm->txReadPos)
        {
            *pdcomm->txWritePos = ch;
            pdcomm->txWritePos = newpos;
            INTALLOW();
        }
        else
        {
            /* uh oh, we're about to overrun the buffer.  Spin around
               in a hard loop, exactly like we shouldn't */
            INTALLOW();
            while (pdcomm->txReadPos == newpos) ;
            *pdcomm->txWritePos = ch;
            pdcomm->txWritePos = newpos;
        }
    }
    return (ch);
}

size_t pdcommWriteBuf(PDCOMM *pdcomm, void *vbuf, size_t nelem)
{
    size_t x;
    unsigned char *buf = (unsigned char *)vbuf;
    
    for (x = 0; x < nelem; x++)
    {
        pdcommWriteCh(pdcomm, buf[x]);
    }
    return (nelem);
}

int pdcommReadCh(PDCOMM *pdcomm)
{
    int ch;
    
    if (pdcomm->recReadPos == pdcomm->recWritePos)
    { 
        ch = EOF;
    }
    else
    {
        ch = (unsigned char)*pdcomm->recReadPos;
        pdcomm->recReadPos++;
        if (pdcomm->recReadPos == pdcomm->recEndPos)
        {
            pdcomm->recReadPos = pdcomm->recStartPos;
        }
    }
    return (ch);
}

size_t pdcommReadBuf(PDCOMM *pdcomm, void *vbuf, size_t nelem)
{
    unsigned char *buf = (unsigned char *)vbuf;
    size_t         count = 0;
    size_t         first;
    size_t         second;

    if (pdcomm->recWritePos >= pdcomm->recReadPos)
    {
        first = (size_t)(pdcomm->recWritePos - pdcomm->recReadPos);
        if (first > nelem)
        {
            first = nelem;
        }
        memcpy(buf, pdcomm->recReadPos, first);
        pdcomm->recReadPos += first;
        count = first;
    }
    else
    {
        first = (size_t)(pdcomm->recEndPos - pdcomm->recReadPos);
        if (first > nelem)
        {
            first = nelem;
        }
        memcpy(buf, pdcomm->recReadPos, first);
        pdcomm->recReadPos += first;
        count = first;
        if (pdcomm->recReadPos == pdcomm->recEndPos)
        {
            pdcomm->recReadPos = pdcomm->recStartPos;
        }
        if (count < nelem)
        {
            second = (size_t)(pdcomm->recWritePos - pdcomm->recReadPos);
            if ((second + count) > nelem)
            {
                second = nelem - count;
            }
            memcpy(buf + first, pdcomm->recReadPos, second);
            pdcomm->recReadPos += second;
            count += second;
        }
    }
    return (count);
}

void pdcommTerm(PDCOMM *pdcomm)
{
    pdcommClosePort(pdcomm);
    uartTerm(&pdcomm->uart);
    free(pdcomm->txBuf);
    free(pdcomm->recBuf);
    pdcomm->txBuf = NULL;
    pdcomm->recBuf = NULL;
    return;
}


/* pdcommInt is the interrupt handler */

void DOSPREF pdcommInt(void)
{
    PDCOMM *pdcomm;
    int id;

    pdcomm = actcomm;
    id = uartGetIntType(&pdcomm->uart);
    while (id != UART_NO_PENDING)
    {
        switch (id)
        {
            case UART_RxRDY:
                *pdcomm->recWritePos++ = uartRecCh(&pdcomm->uart);
                if (pdcomm->recWritePos == pdcomm->recEndPos)
                {
                    pdcomm->recWritePos = pdcomm->recStartPos;
                }
                break;
                
            case UART_TBE:
                if ((pdcomm->txReadPos != pdcomm->txWritePos) 
                    && pdcomm->ctsOK)
                {
                    uartTxCh(&pdcomm->uart, *pdcomm->txReadPos);
                    pdcomm->txReadPos++;
                    if (pdcomm->txReadPos == pdcomm->txEndPos)
                    {
                         pdcomm->txReadPos = pdcomm->txStartPos;
                    }
                }
                else
                {
                    pdcomm->txempty = 1;
                }
                break;
                 
            case UART_ERRBRK:
                uartGetLineStatus(&pdcomm->uart);
                break;
                
            case UART_RS232:
                uartGetInputStatus(&pdcomm->uart);
                if (uartCTS(&pdcomm->uart) && pdcomm->ctsShake)
                {
                    pdcomm->ctsOK = 1;
                    if (pdcomm->txempty)
                    {
                        if (pdcomm->txReadPos != pdcomm->txWritePos) 
                        {
                            uartTxCh(&pdcomm->uart, *pdcomm->txReadPos);
                            pdcomm->txReadPos++;
                            if (pdcomm->txReadPos == pdcomm->txEndPos)
                            {
                                 pdcomm->txReadPos = pdcomm->txStartPos;
                            }
                        }
                    }
                }
                else if (pdcomm->ctsShake)
                {
                    pdcomm->ctsOK = 0;
                }
                break;
                
            default :
                /* shit, something dire has happened */
                /* we will disable interrupts to avoid a hang */
                /* better than hanging, anyway */
                uartDisableInts(&pdcomm->uart);
                    
        }
        id = uartGetIntType(&pdcomm->uart);
    }
    
    /* to signal the work is complete, tell the PIC(s) that you have
       finished */
    PWRITEB(0x20, 0x20);
    if (pdcomm->intrpt >= 8)
    {
        PWRITEB(pdcomm->a8259, 0x20); 
    }
    return;
}

static void pdcommEnableCTSRTS(PDCOMM *pdcomm)
{
    pdcomm->ctsShake = 0;
    pdcomm->ctsOK = 1;
    if (uartCTS(&pdcomm->uart))
    {
        pdcomm->ctsShake = 1;
    }
    return;
}

static void pdcommBuffers(PDCOMM *pdcomm, size_t txBufSiz, size_t recBufSiz)
{
    pdcomm->txBufSiz = txBufSiz;
    pdcomm->recBufSiz = recBufSiz;
    pdcomm->txBuf = realloc(pdcomm->txBuf, pdcomm->txBufSiz);
    pdcomm->recBuf = realloc(pdcomm->recBuf, pdcomm->recBufSiz);
    pdcomm->txStartPos = pdcomm->txBuf;
    pdcomm->txEndPos = pdcomm->txBuf + pdcomm->txBufSiz;
    pdcomm->txReadPos = pdcomm->txBuf;
    pdcomm->txWritePos = pdcomm->txBuf;
    pdcomm->recStartPos = pdcomm->recBuf;
    pdcomm->recEndPos = pdcomm->recBuf + pdcomm->recBufSiz;
    pdcomm->recReadPos = pdcomm->recBuf;
    pdcomm->recWritePos = pdcomm->recBuf;
    return;
}

