/*
 * File stolen from WAMPES 921229, modified for compatibility with JNOS and my
 * tastes, and left to sink or swim.  Blub!  ++bsa
 *
 * The actual structure is much closer to that of JNOS than to WAMPES.  The
 * reason is that WAMPES uses these weirdball I/O hooks... We will use the
 * "classic" interface, modified by the use of register_fd().
 *
 * This file should really be called posixasy.c, since it uses only POSIX entry
 * points (as far as I can tell).
 */
  
#include <sys/types.h>
  
#include <fcntl.h>
#include <termios.h>
#include <sys/time.h>
#include <errno.h>
  
#include "global.h"
#include "mbuf.h"
#include "proc.h"
#include "iface.h"
#include "asy.h"
#include "timer.h"
#include "lxasy.h"
#include "hardware.h"
#include "devparam.h"
  
static int find_speed __ARGS((long speed));
static void pasy __ARGS((struct asy *asyp));
static void asy_tx __ARGS((int, void *, void *));
static void asy_input __ARGS((int, void *, void *));
  
struct asy Asy[ASY_MAX];
  
/*---------------------------------------------------------------------------*/
  
static struct {
    long speed;
    speed_t flags;
} speed_table[] = {
#ifdef B50
    50, B50,
#endif
#ifdef B75
    75, B75,
#endif
#ifdef B110
    110, B110,
#endif
#ifdef B134
    134, B134,
#endif
#ifdef B150
    150, B150,
#endif
#ifdef B200
    200, B200,
#endif
#ifdef B300
    300, B300,
#endif
#ifdef B600
    600, B600,
#endif
#ifdef B900
    900, B900,
#endif
#ifdef B1200
    1200, B1200,
#endif
#ifdef B1800
    1800, B1800,
#endif
#ifdef B2400
    2400, B2400,
#endif
#ifdef B3600
    3600, B3600,
#endif
#ifdef B4800
    4800, B4800,
#endif
#ifdef B7200
    7200, B7200,
#endif
#ifdef B9600
    9600, B9600,
#endif
#ifdef B19200
    19200, B19200,
#endif
#ifdef B38400
    38400, B38400,
#endif
#ifdef B57600
    57600, B57600,
#endif
#ifdef B115200
    115200, B115200,
#endif
#ifdef B230400
    230400, B230400,
#endif
#ifdef B460800
    460800, B460800,
#endif
    -1, 0
};
  
/*---------------------------------------------------------------------------*/
  
static int
find_speed(speed)
long speed;
{
    int i;
  
    i = 0;
    while (speed_table[i].speed < speed && speed_table[i+1].speed > 0)
        i++;
    return i;
}
  
/*---------------------------------------------------------------------------*/
  
/* Initialize asynch port "dev" */
int
asy_init(dev,ifp,arg1,arg2,bufsize,trigchar,monitor,speed,force,triglevel)
int dev;
struct iface *ifp;
char *arg1,*arg2;       /* Attach args for address and vector */
int16 bufsize;
int trigchar;
char monitor;
long speed;
int force;
int triglevel;
{
    register struct asy *ap;
    char filename[80];
    char *ifn;
    int sp, fd;
    struct termios termios;
  
    ap = &Asy[dev];
  
    /* UUCP locking with ASCII pid */
    strcpy(ap->uulock, "/usr/spool/uucp/LCK..");
    strcat(ap->uulock, arg1);
    for (;;)
    {
        if ((fd = open(ap->uulock, O_WRONLY|O_CREAT|O_EXCL, 0644)) != -1 ||
            errno != EEXIST)
            break;
        /* read pid, unlink and retry if proc no longer exists */
        if ((fd = open(ap->uulock, O_RDONLY)) == -1)
            continue;   /* timing is everything */
        filename[read(fd, filename, 10)] = '\0';
        close(fd);
        sscanf(filename, "%d", &fd);
        if (kill(fd, 0) == -1 && errno == ESRCH)
        {
            tprintf("Removing stale lockfile for %s\n", arg1);
            unlink(ap->uulock);
            continue;
        }
        tprintf("/dev/%s is locked\n", arg1);
        ap->uulock[0] = '\0'; /* so it won't clobber existing lock */
        goto Fail;
    }
    if (fd == -1)
    {
        tprintf("Can't lock /dev/%s: %s\n", arg1, strerror(errno));
        ap->uulock[0] = '\0';
        goto Fail;
    }
    chmod(ap->uulock, 0644); /* beware of overly restrictive umask */
    sprintf(filename, "%10d\n", getpid());
    write(fd, filename, 11);
    close(fd);
  
    strcpy(filename, "/dev/");
    strcat(filename, arg1);
    if ((fd = open(filename, O_RDWR|O_NONBLOCK|O_NOCTTY, 0644)) == -1)
    {
        tprintf("Can't open port: %s\n", strerror(errno));
        goto Fail;
    }
    ap->iface = ifp;
    sp = find_speed(speed);
    ap->speed = speed_table[sp].speed;
    memset((char *) &termios, 0, sizeof(termios));
    termios.c_iflag = IGNBRK|IGNPAR;
    termios.c_cflag = CS8|CREAD|CLOCAL|speed_table[sp].flags;
    if (cfsetispeed(&termios, speed_table[sp].flags) == -1)
    {
        tprintf("Can't set speed: %s\n", strerror(errno));
        goto Fail;
    }
    if (cfsetospeed(&termios, speed_table[sp].flags) == -1)
    {
        tprintf("Can't set speed: %s\n", strerror(errno));
        goto Fail;
    }
    if (tcsetattr(fd, TCSANOW, &termios) == -1)
    {
        tprintf("Can't configure port: %s\n", strerror(errno));
        goto Fail;
    }
    /* security: port won't work until re-opened */
    if ((ap->fd = open(filename, O_RDWR|O_NONBLOCK|O_NOCTTY, 0644)) == -1)
    {
        tprintf("Can't reopen port: %s\n", strerror(errno));
        goto Fail;
    }
    close(fd);
  
    ifp->txproc = newproc(ifn = if_name(ifp," tx"),
    256, asy_tx, dev, ifp, NULL, 0);
    free(ifn);
    ap->rxproc = newproc(ifn = if_name(ifp, " asy rx"),
    256, asy_input, dev, ifp, NULL, 0);
    free(ifn);
  
    register_io(ap->fd, &ap->fd);
  
    return 0;
  
    Fail:
    rflush();       /* make sure the message gets out */
    if (fd != -1)
        close(fd);
    /* Unlock port */
    if (ap->uulock[0])
        unlink(ap->uulock);
    ap->uulock[0] = '\0';
    ap->iface = NULLIF;
    return -1;
}
  
/*---------------------------------------------------------------------------*/
  
int
asy_stop(ifp)
struct iface *ifp;
{
    register struct asy *ap;
  
    ap = &Asy[ifp->dev];
  
    if(ap->iface == NULLIF)
        return -1;      /* Not allocated */
  
    unregister_io(ap->fd);
  
    if (ifp->txproc)
        killproc(ifp->txproc);
    ifp->txproc = 0;
  
    if (ap->rxproc)
        killproc(ap->rxproc);
    ap->rxproc = 0;
  
    ap->iface = NULLIF;
  
    free_q(&ap->sndq);
    close(ap->fd);
  
    free_q(&ap->rcvq);
  
    if (ap->uulock[0])
        unlink(ap->uulock);
    ap->uulock[0] = '\0';
  
    return 0;
}
  
void
detach_all_asy()
{
    register struct asy *ap;
  
    for (ap = Asy; ap != Asy + ASY_MAX; ap++)
    {
        if(ap->iface == NULLIF)
            break;
        unregister_io(ap->fd);
        if (ap->iface->txproc)
            killproc(ap->iface->txproc);
        ap->iface->txproc = 0;
        if (ap->rxproc)
            killproc(ap->rxproc);
        ap->rxproc = 0;
        ap->iface = NULLIF;
        free_q(&ap->sndq);
        free_q(&ap->rcvq);
        close(ap->fd);
        if (ap->uulock[0])
            unlink(ap->uulock);
        ap->uulock[0] = '\0';
    }
}
  
  
/*---------------------------------------------------------------------------*/
  
/* Set asynch line speed */
int
asy_speed(dev,bps)
int dev;
long bps;
{
  
    struct asy *asyp;
    int sp;
    struct termios termios;
  
    if(bps <= 0 || dev >= ASY_MAX)
        return -1;
    asyp = &Asy[dev];
    if(asyp->iface == NULLIF)
        return -1;
  
    if(bps == 0)
        return -1;
    sp = find_speed(bps);
    if (tcgetattr(asyp->fd, &termios))
        return -1;
    if (cfsetispeed(&termios, speed_table[sp].flags))
        return -1;
    if (cfsetospeed(&termios, speed_table[sp].flags))
        return -1;
    termios.c_cflag &= ~CBAUD;
    termios.c_cflag |= speed_table[sp].flags;
    if (tcsetattr(asyp->fd, TCSANOW, &termios))
        return -1;
    asyp->speed = speed_table[sp].speed;
    return 0;
}
  
/*---------------------------------------------------------------------------*/
  
/* Asynchronous line I/O control */
int32
asy_ioctl(ifp,cmd,set,val)
struct iface *ifp;
int cmd;
int set;
int32 val;
{
    struct asy *ap = &Asy[ifp->dev];
  
    switch(cmd){
        case PARAM_SPEED:
            if(set)
                asy_speed(ifp->dev,val);
            return ap->speed;
    }
    return -1;
}
  
/*---------------------------------------------------------------------------*/
  
#define RXCHUNK 64
  
static void
asy_input(dev, arg1, arg2)
int dev;
void *arg1, *arg2;
{
    extern int errno;
    struct asy *ap;
    char *buf;
    int i;
  
    buf = mallocw(RXCHUNK);
    ap = &Asy[dev];
    for (;;)
    {
        do
        {
            if (pwait(&ap->fd) != 0)
            {
                free(buf);
                return;
            }
            ap->rxints++;
        }
        while ((i = read(ap->fd, buf, RXCHUNK)) == 0 ||
        (i == -1 && errno == EWOULDBLOCK));
    /* gang-reading destroys JNOS response; no benefits seen */
#if 0
        while (i > 0)
        {
#endif
            ap->rxchar += i;
            enqueue(&ap->rcvq, qdata(buf, i));
#if 0
            pwait(NULL);    /* process it to avoid monopolizing cycles */
            i = read(ap->fd, buf, RXCHUNK);
        }
#endif
        if (i == -1 && errno != EWOULDBLOCK)
        {
            free(buf);
            return;
        }
    }
}
  
int
get_asy(dev)
int dev;
{
    struct asy *ap;
  
    ap = &Asy[dev];
    if (ap->iface == NULLIF)
        return -1;
    while (!ap->rcvq)
    {
        if (pwait(&ap->rcvq) != 0)
            return -1;      /* may not be dead, e.g. alarm in dialer */
    }
    return PULLCHAR(&ap->rcvq);
}
  
/*---------------------------------------------------------------------------*/
  
int
doasystat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register struct asy *asyp;
    struct iface *ifp;
    int i;
  
    if(argc < 2){
        for(asyp = Asy;asyp < &Asy[ASY_MAX];asyp++){
            if(asyp->iface != NULLIF)
                pasy(asyp);
        }
        return 0;
    }
    for(i=1;i<argc;i++){
        if((ifp = if_lookup(argv[i])) == NULLIF){
            tprintf("Interface %s unknown\n",argv[i]);
            continue;
        }
        for(asyp = Asy;asyp < &Asy[ASY_MAX];asyp++){
            if(asyp->iface == ifp){
                pasy(asyp);
                break;
            }
        }
        if(asyp == &Asy[ASY_MAX])
            tprintf("Interface %s not asy\n",argv[i]);
    }
  
    return 0;
}
  
/*---------------------------------------------------------------------------*/
  
static void
pasy(asyp)
struct asy *asyp;
{
    tprintf("%s: %lu bps\n", asyp->iface->name, asyp->speed);
    tprintf("  RX: int %lu chars %lu\n",
    asyp->rxints, asyp->rxchar);
    tprintf("  TX: int %lu chars %lu\n",
    asyp->txints, asyp->txchar);
}
  
/*---------------------------------------------------------------------------*/
  
/* Serial transmit process, common to all protocols */
static void
asy_tx(dev, p1, p2)
int dev;
void *p1, *p2;
{
    register struct mbuf *bp;
    struct asy *asyp;
  
    asyp = &Asy[dev];
    for (;;)
    {
        while (asyp->sndq == NULLBUF)
        {
            if (pwait(&asyp->sndq) != 0)
                return;
            asyp->txints++;
        }
        bp = dequeue(&asyp->sndq);
        while (bp != NULLBUF)
        {
            write(asyp->fd, bp->data, bp->cnt);
            asyp->txchar += bp->cnt;
            bp = free_mbuf(bp);
        }
        pwait(NULL);
    }
}
  
/*---------------------------------------------------------------------------*/
  
/* Send a message on the specified serial line */
int
asy_send(dev,bp)
int dev;
struct mbuf *bp;
{
    struct asy *asyp;
  
    if(dev < 0 || dev >= ASY_MAX){
        free_p(bp);
        return -1;
    }
    asyp = &Asy[dev];
  
    if(asyp->iface == NULLIF)
        free_p(bp);
    else
        enqueue(&asyp->sndq, bp);
    return 0;
}
  
/* stub, CD not enabled at present */
  
int
carrier_detect(dev)
int dev;
{
    return 1;           /* assume always on, with CLOCAL it is! */
}
