/*****************************************************************************/

/*
 *      drvresident.c  -- HDLC packet radio modem for EPP/ECP using FPGA usermode driver.
 *
 *      Copyright (C) 1998-2000, 2002  Thomas Sailer (sailer@ife.ee.ethz.ch)
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Please note that the GPL allows you to use the driver, NOT the radio.
 *  In order to use the radio, you need a license from the communications
 *  authority of your country.
 *
 */

/*****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/uio.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <dlfcn.h>

#include "drv.h"
#include "parport.h"

/* ---------------------------------------------------------------------- */

int logging = 0;
int logpackets = 0;
struct baycomepp_state state;
const char *progname = "baycomepp";

/* --------------------------------------------------------------------- */
/*
 * Logging functions
 */

void errprintf(int severity, const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	if (logging) {
		char tmp[512];
		vsnprintf(tmp, sizeof(tmp), fmt, args);
		syslog(severity, tmp);
	} else {
		fprintf(stderr, "baycomepp[%u]: ", getpid());
		vfprintf(stderr, fmt, args);
	}
	va_end(args);
	if (severity <= SEV_FATAL)
		exit(1);
}

/* --------------------------------------------------------------------- */

void errstr(int severity, const char *st)
{
	errprintf(severity, "%s: %s\n", st, strerror(errno));
}

/* --------------------------------------------------------------------- */

void epp_error(const char *msg)
{
	errprintf(SEV_FATAL, msg);
}

/* --------------------------------------------------------------------- */
/*
 * the CRC routines are stolen from WAMPES
 * by Dieter Deyke
 */

const u_int16_t crc_ccitt_table[0x100] = {
        0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
        0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
        0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
        0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
        0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
        0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
        0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
        0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
        0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
        0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
        0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
        0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
        0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
        0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
        0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
        0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
        0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
        0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
        0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
        0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
        0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
        0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
        0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
        0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
        0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
        0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
        0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
        0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
        0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
        0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
        0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
        0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};

/* --------------------------------------------------------------------- */

extern inline u_int16_t calc_crc_ccitt(const u_int8_t *buffer, int len)
{
        u_int16_t crc = 0xffff;

        for (;len>0;len--)
                crc = (crc >> 8) ^ crc_ccitt_table[(crc ^ *buffer++) & 0xff];
        crc ^= 0xffff;
	return crc;
}

extern inline void append_crc_ccitt(u_int8_t *buffer, int len)
{
        u_int16_t crc = calc_crc_ccitt(buffer, len);
        *buffer++ = crc;
        *buffer++ = crc >> 8;
}

extern inline int check_crc_ccitt(const u_int8_t *buffer, int len)
{
        u_int16_t crc = calc_crc_ccitt(buffer, len);
        return (crc & 0xffff) == 0x0f47;
}

/* ---------------------------------------------------------------------- */

#define ADDRXCH(x)                        \
do {                                      \
	state.kissout.b[wr] = (x);           \
	wr = (wr + 1) % KISSOUTBUF_SIZE;  \
	if (wr == rd)                     \
		goto errout;              \
        total++;                          \
} while (0);

static size_t rxpacket(const struct iovec *vector, size_t count)
{
	u_int8_t *bp;
	size_t len;
	size_t total = 0;
	unsigned wr = state.kissout.wr;
	unsigned rd = state.kissout.rd;

	ADDRXCH(KISS_FEND);
	for (; count > 0; count--, vector++) {
		for (bp = vector->iov_base, len = vector->iov_len; len > 0; len--, bp++) {
			if (*bp == KISS_FEND) {
				ADDRXCH(KISS_FESC);
				ADDRXCH(KISS_TFEND);
			} else if (*bp == KISS_FESC) {
				ADDRXCH(KISS_FESC);
				ADDRXCH(KISS_TFESC);
			} else {
				ADDRXCH(*bp);
			}
		}
	}
	if (total <= 1)
		return 0;
	ADDRXCH(KISS_FEND);
	state.kissout.wr = wr;
	state.stat.kiss_out++;
	return total;

  errout:
	state.stat.kiss_outerr++;
	return 0;
}

#undef ADDRXCH

/* --------------------------------------------------------------------- */
/*
 * high performance HDLC encoder
 * yes, it's ugly, but generates pretty good code
 */

#define ENCODEITERA(j)                         \
({                                             \
        if (!(notbitstream & (0x1f0 << j)))    \
                goto stuff##j;                 \
  encodeend##j:                                \
})

#define ENCODEITERB(j)                                          \
({                                                              \
  stuff##j:                                                     \
        bitstream &= ~(0x100 << j);                             \
        bitbuf = (bitbuf & (((2 << j) << numbit) - 1)) |        \
                ((bitbuf & ~(((2 << j) << numbit) - 1)) << 1);  \
        numbit++;                                               \
        notbitstream = ~bitstream;                              \
        goto encodeend##j;                                      \
})

static int hdlc_enqueue(u_int8_t *pkt, int len)
{
        unsigned bitstream, notbitstream, bitbuf, numbit, crc;
	u_int8_t crcarr[2];
	unsigned wr = state.htx.wr;

	state.htx.buf[wr] = 0x7e;
	wr = (wr + 1) % TXBUFFER_SIZE;
	if (wr == state.htx.rd)
		return -1;
	crc = calc_crc_ccitt(pkt, len);
	crcarr[0] = crc;
	crcarr[1] = crc >> 8;
	bitstream = bitbuf = numbit = 0;
	while (len > -2) {
		bitstream >>= 8;
		bitstream |= ((unsigned int)*pkt) << 8;
		bitbuf |= ((unsigned int)*pkt) << numbit;
		notbitstream = ~bitstream;
		pkt++;
		len--;
		if (!len)
			pkt = crcarr;
		ENCODEITERA(0);
		ENCODEITERA(1);
		ENCODEITERA(2);
		ENCODEITERA(3);
		ENCODEITERA(4);
		ENCODEITERA(5);
		ENCODEITERA(6);
		ENCODEITERA(7);
		goto enditer;
		ENCODEITERB(0);
		ENCODEITERB(1);
		ENCODEITERB(2);
		ENCODEITERB(3);
		ENCODEITERB(4);
		ENCODEITERB(5);
		ENCODEITERB(6);
		ENCODEITERB(7);
	enditer:
		numbit += 8;
		while (numbit >= 8) {
			state.htx.buf[wr] = bitbuf;
			wr = (wr + 1) % TXBUFFER_SIZE;
			if (wr == state.htx.rd)
				return -1;
			bitbuf >>= 8;
			numbit -= 8;
		}
	}
	bitbuf |= 0x7e7e << numbit;
	numbit += 16;
	while (numbit >= 8) {
		state.htx.buf[wr] = bitbuf;
		wr = (wr + 1) % TXBUFFER_SIZE;
		if (wr == state.htx.rd)
			return -1;
		bitbuf >>= 8;
		numbit -= 8;
	}
	state.htx.wr = wr;
	return 0;
}

/* ---------------------------------------------------------------------- */

static void do_rxpacket(void)
{
	static const u_int8_t kissdata = KISS_CMD_DATA;
	struct iovec iov[2];
	char buf[512];

	if (state.hrx.bufcnt < 4) 
		return;
	if (!check_crc_ccitt(state.hrx.buf, state.hrx.bufcnt)) 
		return;
	iov[0].iov_len = 1;
	iov[0].iov_base = &kissdata;
	iov[1].iov_len = state.hrx.bufcnt-2;
	iov[1].iov_base = state.hrx.buf;
	rxpacket(iov, 2);
	state.stat.pkt_in++;
	if (!logpackets)
		return;
	if (snprintpkt(buf, sizeof(buf), state.hrx.buf, state.hrx.bufcnt-2) != -1)
		printf("rx: %s", buf);
}

#define DECODEITERA(j)                                                        \
({                                                                            \
        if (!(notbitstream & (0x0fc << j)))              /* flag or abort */  \
                goto flgabrt##j;                                              \
        if ((bitstream & (0x1f8 << j)) == (0xf8 << j))   /* stuffed bit */    \
                goto stuff##j;                                                \
  enditer##j:                                                                 \
})

#define DECODEITERB(j)                                                                 \
({                                                                                     \
  flgabrt##j:                                                                          \
        if (!(notbitstream & (0x1fc << j))) {              /* abort received */        \
                stat = 0;                                                              \
                goto enditer##j;                                                       \
        }                                                                              \
        if ((bitstream & (0x1fe << j)) != (0x0fc << j))   /* flag received */          \
                goto enditer##j;                                                       \
        if (stat)                                                                      \
                do_rxpacket();                                                         \
        state.hrx.bufcnt = 0;                                                          \
        state.hrx.bufptr = state.hrx.buf;                                              \
        stat = 1;                                                                      \
        numbits = 7-j;                                                                 \
        goto enditer##j;                                                               \
  stuff##j:                                                                            \
        numbits--;                                                                     \
        bitbuf = (bitbuf & ((~0xff) << j)) | ((bitbuf & ~((~0xff) << j)) << 1);        \
        goto enditer##j;                                                               \
})
        
/* --------------------------------------------------------------------- */

static void hdlc_receive(u_int8_t *pkt, int len)
{
        unsigned int bitbuf, notbitstream, bitstream, numbits, stat;

	state.stat.bytes_in += len;
	numbits = state.hrx.numbits;
	stat = state.hrx.state;
	bitstream = state.hrx.bitstream;
	bitbuf = state.hrx.bitbuf;
	for (; len > 0; len--, pkt++) {
		bitstream >>= 8;
		bitstream |= ((unsigned int)*pkt) << 8;
		bitbuf >>= 8;
		bitbuf |= ((unsigned int)*pkt) << 8;
		numbits += 8;
		notbitstream = ~bitstream;
		DECODEITERA(0);
		DECODEITERA(1);
		DECODEITERA(2);
		DECODEITERA(3);
		DECODEITERA(4);
		DECODEITERA(5);
		DECODEITERA(6);
		DECODEITERA(7);
		goto enddec;
		DECODEITERB(0);
		DECODEITERB(1);
		DECODEITERB(2);
		DECODEITERB(3);
		DECODEITERB(4);
		DECODEITERB(5);
		DECODEITERB(6);
		DECODEITERB(7);
	enddec:
		while (stat && numbits >= 8) {
			if (state.hrx.bufcnt >= RXBUFFER_SIZE) {
				stat = 0;
			} else {
				*(state.hrx.bufptr)++ = bitbuf >> (16-numbits);
				state.hrx.bufcnt++;
				numbits -= 8;
			}
		}
	}
        state.hrx.numbits = numbits;
	state.hrx.state = stat;
	state.hrx.bitstream = bitstream;
	state.hrx.bitbuf = bitbuf;
}

/* ---------------------------------------------------------------------- */

static u_int16_t random_seed;

extern inline u_int16_t random_num(void)
{
	random_seed = 28629 * random_seed + 157;
	return random_seed;
}

/* --------------------------------------------------------------------- */

/*
 * here we do the channel access algorithm, and process calibration bytes
 */

extern inline void arbitrate(void)
{
	if (state.htx.rd == state.htx.wr)
		return;
	if (!state.chaccesspar.fulldup) {
		if (state.access.slotcnt > 0)
			return;
		state.access.slotcnt = state.chaccesspar.slottime;
		if (state.access.dcd)
			return;
		if ((random_num() % 256) > state.chaccesspar.ppersist)
			return;
	}
	state.access.state = tx_keyup;
	state.access.flagcnt = (state.access.bitrateby25 * state.chaccesspar.txdelay) >> 5;
	state.stat.ptt_keyed++;
}

static int hdlc_transmit(u_int8_t *pkt, int len)
{
	int ret = 0, i;

	while (len > 0) {
		switch (state.access.state) {
		case tx_keyup:
			i = state.access.flagcnt;
			if (i <= 0) {
				state.access.state = tx_data;
				break;
			}
			if (i > len)
				i = len;
			state.access.flagcnt -= i;
			memset(pkt, 0x7e, i);
			ret += i;
			pkt += i;
			len -= i;
			break;
			
		case tx_data:
			if (state.htx.rd == state.htx.wr) {
				state.access.state = tx_tail;
				state.access.flagcnt = (state.access.bitrateby25 * state.chaccesspar.txtail) >> 5;
				break;
			}
			i = ((state.htx.rd > state.htx.wr) ? TXBUFFER_SIZE : state.htx.wr) - state.htx.rd;
			if (i > len)
				i = len;
			memcpy(pkt, state.htx.buf + state.htx.rd, i);
			state.htx.rd = (state.htx.rd + i) % TXBUFFER_SIZE;
			ret += i;
			pkt += i;
			len -= i;
			break;			

		case tx_tail:
			i = state.access.flagcnt;
			if (i <= 0) {
				if (state.access.ptt)
					return ret;
				state.access.state = tx_idle;
				state.access.slotcnt = state.chaccesspar.slottime;
				break;
			}
			if (i > len)
				i = len;
			state.access.flagcnt -= i;
			memset(pkt, 0x7e, i);
			ret += i;
			pkt += i;
			len -= i;
			break;

		default:
			arbitrate();
			if (state.access.state == tx_keyup)
				break;
			i = state.access.calib;
			if (i <= 0)
				return ret;
			if (i > len)
				i = len;
			state.access.calib -= i;
			memset(pkt, 0, i);
			ret += i;
			pkt += i;
			len -= i;
			break;
		}
	}
	return ret;
}

/* --------------------------------------------------------------------- */

void sighandler(int sig)
{
	errprintf(LOG_INFO, "terminating on receiving signal %i\n", sig);
	reset_modem();
	exit(0);
}

/* --------------------------------------------------------------------- */

void sig_usr2(int sig)
{
	if (state.access.calib > 0) {
		errprintf(LOG_DEBUG, "terminating calibration\n");
		state.access.calib = 0;
	} else {
		errprintf(LOG_DEBUG, "calibrating for 1min\n");
		state.access.calib = state.access.bitrate * (60/8);
	}
	if (signal(SIGUSR2, sig_usr2) == SIG_ERR)
		errstr(SEV_ERROR, "signal");
}

/* --------------------------------------------------------------------- */

void sig_usr1(int sig)
{
	printf("Statistics:\n  KISS out: %ld errors: %ld  in: %ld errors: %ld\n"
	       "  Packets out: %ld  in: %ld\n"
	       "  Bytes out: %ld  in: %ld\n"
	       "  PTT:%c  DCD:%c  PTT keyed: %ld\n"
	       "  ch par: txdel %u slot %u ppersist %u txtail %u fulldup %u\n"
	       "  status 0x%02x  icnt %d %d %d  ocnt %d %d %d  IntRate: %d\n",
	       state.stat.kiss_out, state.stat.kiss_outerr,
	       state.stat.kiss_in, state.stat.kiss_inerr,
	       state.stat.pkt_out, state.stat.pkt_in, 
	       state.stat.bytes_out, state.stat.bytes_in,
	       state.access.ptt ? 'P' : '-', state.access.dcd ? 'D' : '-', state.stat.ptt_keyed,
	       state.chaccesspar.txdelay, state.chaccesspar.slottime, state.chaccesspar.ppersist,
	       state.chaccesspar.txtail, state.chaccesspar.fulldup,
	       state.stat.status, state.stat.icnt1, state.stat.icnt2, state.stat.icnt3,
	       state.stat.ocnt1, state.stat.ocnt2, state.stat.ocnt3, state.stat.intcnt);

	printf("TX: rd %d wr %d  st: %d  slotcnt: %d\n",
	       state.htx.rd, state.htx.wr, state.access.state, state.access.slotcnt);

	if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
		errstr(SEV_ERROR, "signal");
}

/* ---------------------------------------------------------------------- */

/*
 * Process KISS io
 */

extern inline void process_pkt(u_int8_t *pkt, unsigned int len)
{
	char buf[512];

	if (len < 2)
		return;
	switch (pkt[0]) {
	case KISS_CMD_DATA:
		if (hdlc_enqueue(pkt+1, len-1))
			state.stat.pkt_outerr++;
		else
			state.stat.pkt_out++;
		if (!logpackets)
			return;
		if (snprintpkt(buf, sizeof(buf), pkt+1, len-1) != -1)
			printf("tx: %s", buf);
		return;

	case KISS_CMD_TXDELAY:
		state.chaccesspar.txdelay = pkt[1];
		errprintf(SEV_INFO, "kiss: txdelay = %ums\n", state.chaccesspar.txdelay*10);
		return;
			
	case KISS_CMD_SLOTTIME:
		state.chaccesspar.slottime = pkt[1];
		errprintf(SEV_INFO, "kiss: slottime = %ums\n", state.chaccesspar.slottime*10);
		return;

	case KISS_CMD_PPERSIST:
		state.chaccesspar.ppersist = pkt[1];
		errprintf(SEV_INFO, "kiss: ppersist = %u/256\n", state.chaccesspar.ppersist);
		return;

	case KISS_CMD_TXTAIL:
		state.chaccesspar.txtail = pkt[1];
		errprintf(SEV_INFO, "kiss: txtail = %ums\n", state.chaccesspar.txtail*10);
		return;

	case KISS_CMD_FULLDUP:
		state.chaccesspar.fulldup = !!pkt[1];
		errprintf(SEV_INFO, "kiss: %sduplex\n", state.chaccesspar.fulldup ? "full" : "half");
		return;

	default:
		errprintf(SEV_WARNING, "unknown kiss packet: 0x%02x 0x%02x\n", pkt[0], pkt[1]);
		return;
	}
}

/* --------------------------------------------------------------------- */

/* we decode on the fly in place */

extern inline void kiss_decode(u_int8_t *b, int len)
{
	int nlen = 0;
	u_int8_t *p1 = b, *p2 = b;

      	while (len > 0) {
		if (*p1 != KISS_FESC) {
			*p2++ = *p1++;
			nlen++;
			len--;
			continue;
		}
		if (len < 2)
			goto err; /* invalid escape */
		if (p1[1] == KISS_TFEND)
			*p2++ = KISS_FEND;
		else if (p1[1] == KISS_TFESC)
			*p2++ = KISS_FESC;
		else
			goto err; /* invalid escape */
		nlen++;
		p1 += 2;
		len -= 2;
	}
	process_pkt(b, nlen);
	state.stat.kiss_in++;
	return;
 err:
	state.stat.kiss_inerr++;
}

/* --------------------------------------------------------------------- */

extern inline void kiss_read(void)
{
	char *cp1, *cp2, *endp;
	int i;

	if (state.kissin.ptr >= KISSINBUF_SIZE)  /* discard oversized packets */
		state.kissin.ptr = 0;
	i = read(state.kissfd, state.kissin.b + state.kissin.ptr, KISSINBUF_SIZE - state.kissin.ptr);
	if (i < 0) {
		if (errno == EAGAIN)
			return;
		errstr(SEV_FATAL, "read");
	}
	if (i == 0)
		return;
	state.kissin.ptr += i;
	endp = state.kissin.b + state.kissin.ptr;
	cp1 = memchr(state.kissin.b, KISS_FEND, state.kissin.ptr);
	while (cp1) {
		cp2 = memchr(cp1+1, KISS_FEND, endp - cp1 - 1);
		if (!cp2) {
			state.kissin.ptr = endp-cp1;
			memmove(state.kissin.b, cp1, state.kissin.ptr);
			return;
		}
		kiss_decode(cp1+1, cp2-cp1-1);
		cp1 = cp2;
	}
}

/* ---------------------------------------------------------------------- */

extern inline void kiss_write(void)
{
	struct iovec iov[2];
	int i;

	if (state.kissout.rd == state.kissout.wr)
		return;
	if (state.kissout.rd > state.kissout.wr) {
		iov[0].iov_len = KISSOUTBUF_SIZE - state.kissout.rd;
		iov[0].iov_base = state.kissout.b + state.kissout.rd;
		iov[1].iov_len = state.kissout.wr;
		iov[1].iov_base = state.kissout.b;
		i = writev(state.kissfd, iov, 2);
	} else
		i = write(state.kissfd, state.kissout.b + state.kissout.rd, 
			  state.kissout.wr - state.kissout.rd);
	if (i < 0) {
		if (errno == EAGAIN)
			return;
		errstr(SEV_FATAL, "read");
	}
	state.kissout.rd = (state.kissout.rd + i) % KISSOUTBUF_SIZE;
}

/* ---------------------------------------------------------------------- */

static void handle_kiss(void)
{
	kiss_read();
	kiss_write();
	/* reschedule */
	usleep(8000); /* must be more than 2ms, Linux busy-waits up to 2ms! */
	state.stat.intcnt++;
	state.access.slotcnt--;
}

/* ---------------------------------------------------------------------- */

/*
 * EPP Mode
 */

/* _MUST_ not return! */

static void epp_modem(void)
{
	unsigned char eio[1024];
	u_int8_t stat;
	int cnt, icnt, ocnt, icntx, ocntx;
	
	/* reset modem */
	eio[0] = 0;
	eio[1] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE;
	if (parport_epp_write_addr(eio, 2) != 2)
		goto errret;
	for (;;) {
		if (parport_epp_read_addr(&stat, 1) != 1)
			goto errret;
		/* determine TX fifo size from status bits */
		switch (stat & (EPP_NTAEF|EPP_NTHF)) {
		case EPP_NTHF:
			ocntx = 2048 - 256;
			break;

		case EPP_NTAEF:
			ocntx = 2048 - 1793;
			break;

		case 0:
			ocntx = 0;
			break;

		default:
			ocntx = 2048 - 1025;
			break;
		}
		/* determine RX fifo size from status bits */
		switch (stat & (EPP_NRAEF|EPP_NRHF)) {
		case EPP_NRHF:
			icntx = 0;
			break;
                
		case EPP_NRAEF:
			icntx = 1025;
			break;
                
		case 0:
			icntx = 1793;
			break;
                
		default:
			icntx = 256;
			break;
		}
		/* try to determine the FIFO count if in extended mode */
		if (state.config.extstat) {
			eio[0] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE|1;
			if (parport_epp_write_addr(eio, 1) != 1)
				goto errret;
			if (parport_epp_read_addr(eio, 2) != 2)
				goto errret;
			icnt = eio[0] | (((unsigned int)eio[1]) << 8);
			eio[0] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE|2;
			if (parport_epp_write_addr(eio, 1) != 1)
				goto errret;
			if (parport_epp_read_addr(eio, 2) != 2)
				goto errret;
			ocnt = eio[0] | (((unsigned int)eio[1]) << 8);
			eio[0] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE;
			if (parport_epp_write_addr(eio, 1) != 1)
				goto errret;
			ocnt = 16384 - (ocnt & 0x7fff);
			icnt &= 0x7fff;
		} else {
			ocnt = ocntx;
			icnt = icntx;
		}
		/* update status */
		state.stat.icnt3 = state.stat.icnt1;
		state.stat.icnt1 = icnt;
		state.stat.icnt2 = icntx;
		state.stat.ocnt3 = state.stat.ocnt1;
		state.stat.ocnt1 = ocnt;
		state.stat.ocnt2 = ocntx;
		state.stat.status = stat;
		state.access.dcd = !(stat & EPP_DCDBIT);
		state.access.ptt = !!(stat & EPP_PTTBIT);
		/* rx */
		while (icnt > 0) {
			cnt = icnt;
			if (cnt > sizeof(eio))
				cnt = sizeof(eio);
			icnt -= cnt;
			if (parport_epp_read_data(eio, cnt) != cnt)
				goto errret;
			hdlc_receive(eio, cnt);
		}
		/* tx */
		if (ocnt > 0) {
			if (ocnt > sizeof(eio))
				ocnt = sizeof(eio);
			cnt = hdlc_transmit(eio, ocnt);
			if (parport_epp_write_data(eio, cnt) != cnt)
				goto errret;
		}
		/* KISS and reschedule */
		handle_kiss();
	}
errret:
	errprintf(SEV_FATAL, "Modem timeout\n");	
}

/* ---------------------------------------------------------------------- */

/*
 * ECP Mode
 */

/* _MUST_ not return! */

static void ecp_modem(void)
{
	u_int8_t data[1024];
	unsigned char stat[6];
        int cnt, icnt, ocnt;
	
	data[0] = 0xaf;  /* reset register: all reset */
	data[1] = 0xa0;  /* reset register: terminate reset */
	data[2] = 0xb0;
	if (parport_ecp_write_addr(data, 3) != 3)
		goto errret;
	for (;;) {
		data[0] = 0x81;
		if (parport_ecp_write_addr(data, 1) != 1)
			goto errret;
		if (parport_ecp_read_data(stat, 6) != 6)
			goto errret;
		data[0] = 0x80;
		if (parport_ecp_write_addr(data, 1) != 1)
			goto errret;
		icnt = ((unsigned)stat[2] << 8) | stat[1];
		ocnt = ((unsigned)stat[4] << 8) | stat[3];
		/* update status */
		state.stat.icnt3 = state.stat.icnt2;
		state.stat.icnt2 = state.stat.icnt1;
		state.stat.icnt1 = icnt;
		state.stat.ocnt3 = state.stat.ocnt2;
		state.stat.ocnt2 = state.stat.ocnt1;
		state.stat.ocnt1 = ocnt;
		state.stat.status = stat[0];
		state.access.dcd = !(stat[0] & 0x80);
		state.access.ptt = !(stat[0] & 0x40);
		/* read */
		while (icnt > 0) {
			cnt = icnt;
			if (cnt > sizeof(data))
				cnt = sizeof(data);
			icnt -= cnt;
			data[0] = 0xc0 | (cnt & 0xf);
			data[1] = 0xd0 | ((cnt >> 4) & 0xf);
			data[2] = 0xe0 | ((cnt >> 8) & 0xf);
			if (parport_ecp_write_addr(data, 3) != 3)
				goto errret;
			if (parport_ecp_read_data(data, cnt) != cnt)
				goto errret;
			hdlc_receive(data, cnt);
		}
		/* write */
		if (ocnt < 8192) { /* fixme */
			cnt = 8192 - ocnt;
			if (cnt > sizeof(data))
				cnt = sizeof(data);
			cnt = hdlc_transmit(data, cnt);
			if (parport_ecp_write_data(data, cnt) != cnt)
				goto errret;
		}
		/* KISS and reschedule */
		handle_kiss();
	}
errret:
	errprintf(SEV_FATAL, "Modem timeout\n");	
}

/* ---------------------------------------------------------------------- */

/*
 * EPP AFSK Mode
 */

/* _MUST_ not return! */

static void eppafsk_modem(void)
{
	unsigned char eio[32];
	u_int8_t stat;
        int cnt, icnt, ocnt;

	/* reset modem */
	eio[0] = 0x00;
	eio[1] = 0x18;
	if (parport_epp_write_addr(eio, 2) != 2)
		goto errret;
	for (;;) {
		if (parport_epp_read_addr(&stat, 1) != 1)
			goto errret;
		/* determine the FIFO count */
		eio[0] = 0x19;
		if (parport_epp_write_addr(eio, 1) != 1)
			goto errret;
		if (parport_epp_read_addr(eio, 1) != 1)
			goto errret;
		ocnt = 0x10 - (eio[0] & 0x1f);
		eio[0] = 0x1a;
		if (parport_epp_write_addr(eio, 1) != 1)
			goto errret;
		if (parport_epp_read_addr(eio, 1) != 1)
			goto errret;
		icnt = eio[0] & 0x1f;
		eio[0] = 0x18;
		if (parport_epp_write_addr(eio, 1) != 1)
			goto errret;
		/* update status */
		state.stat.icnt3 = state.stat.icnt2;
		state.stat.icnt2 = state.stat.icnt1;
		state.stat.icnt1 = icnt;
		state.stat.ocnt3 = state.stat.ocnt2;
		state.stat.ocnt2 = state.stat.ocnt1;
		state.stat.ocnt1 = ocnt;
		state.stat.status = stat;
		state.access.dcd = !(stat & 0x80);
		state.access.ptt = !(stat & 0x40);
		/* rx */
		if (icnt > 0) {
			if (parport_epp_read_data(eio, icnt) != icnt)
				goto errret;
			hdlc_receive(eio, icnt);
		}
		/* tx */
		if (ocnt > 0) {
			cnt = hdlc_transmit(eio, ocnt);
			if (parport_epp_write_data(eio, cnt) != cnt)
				goto errret;
		}
		/* KISS and reschedule */
		handle_kiss();
	}
errret:
	errprintf(SEV_FATAL, "Modem timeout\n");	
}

/* ---------------------------------------------------------------------- */

/*
 * EPP conventional Mode
 */

/* _MUST_ not return! */

static void eppconv_modem(void)
{
	unsigned char eio[1024];
        u_int8_t stat;
        int cnt, i, icnt, ocnt;
        
        /* reset modem */
	eio[0] = 0;
	eio[1] = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE;
	if (parport_epp_write_addr(eio, 2) != 2)
		goto errret;
        for (;;) {
		if (parport_epp_read_addr(&stat, 1) != 1)
			goto errret;
                /* determine TX fifo size from status bits */
                switch (stat & (EPP_NTAEF|EPP_NTHF)) {
                case EPP_NTHF:
                        ocnt = 2048 - 256;
                        break;

                case EPP_NTAEF:
                        ocnt = 2048 - 1793;
                        break;

                case 0:
                        ocnt = 0;
                        break;

                default:
                        ocnt = 2048 - 1025;
                        break;
                }
                /* determine RX fifo size from status bits */
                switch (stat & (EPP_NRAEF|EPP_NRHF)) {
                case EPP_NRHF:
                        icnt = 0;
                        break;
                
                case EPP_NRAEF:
                        icnt = 1025;
                        break;
                
                case 0:
                        icnt = 1793;
                        break;
                
                default:
                        icnt = 256;
                        break;
                }
                /* update status */
                state.stat.icnt3 = state.stat.icnt2;
                state.stat.icnt2 = state.stat.icnt1;
                state.stat.icnt1 = icnt;
                state.stat.ocnt3 = state.stat.ocnt2;
                state.stat.ocnt2 = state.stat.ocnt1;
                state.stat.ocnt1 = ocnt;
                state.stat.status = stat;
                state.access.dcd = !(stat & EPP_DCDBIT);
                state.access.ptt = !!(stat & EPP_PTTBIT);
                /* rx */
                if (icnt == 0 && state.access.bitrate < 100000) {
                        cnt = 0;
                        i = (state.access.bitrate < 50000) ? 256 : 128;
                        while (i > 0 && cnt < sizeof(eio) && stat & EPP_NREF) {
				if (parport_epp_read_data(eio+cnt, 1) != 1)
					goto errret;
				cnt++;
                                i--;
				if (parport_epp_read_addr(&stat, 1) != 1)
					goto errret;
                        }
                        hdlc_receive(eio, cnt);
                }
                while (icnt > 0) {
                        cnt = icnt;
                        if (cnt > sizeof(eio))
                                cnt = sizeof(eio);
                        icnt -= cnt;
 			if (parport_epp_read_data(eio, cnt) != cnt)
				goto errret;
                        hdlc_receive(eio, cnt);
                }
                /* tx */
                if (ocnt > 0) {
                        if (ocnt > sizeof(eio))
                                ocnt = sizeof(eio);
                        cnt = hdlc_transmit(eio, ocnt);
			if (parport_epp_write_data(eio, cnt) != cnt)
				goto errret;
                }
                /* KISS and reschedule */
                handle_kiss();
        }
errret:
	errprintf(SEV_FATAL, "Modem timeout\n");	
}

/* ---------------------------------------------------------------------- */

int main(int argc, char *argv[])
{
	void *so;
	int (*drvinit)(int argc, char *argv[]);
	int mode;

	if (!(so = dlopen("baycomfpga_init.so", RTLD_NOW)) || !(drvinit = dlsym(so, "driver_init"))) {
		fprintf(stderr, "baycomepp: cannot load initialization code from baycomfpga_init.so\n");
		exit(1);
	}
	mode = drvinit(argc, argv);
	dlclose(so);
	switch(mode) {
	case MODE_EPP:
		epp_modem();
	case MODE_ECP:
		ecp_modem();
	case MODE_EPPAFSK:
		eppafsk_modem();
	case MODE_EPPCONV:
		eppconv_modem();
	}
	exit(1);
}

/* ---------------------------------------------------------------------- */
