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

/*
 *	bayeppflex.c  --  FlexNet driver for Baycom EPPFLEX modem.
 *
 *	Copyright (C) 1999-2001
 *          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.
 *
 *
 *  History:
 *   0.1  23.06.1999  Created
 *   0.2  07.01.2000  Expanded to usbdevfs capabilities
 *
 */

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

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

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>

#include "flexdrv.h"
#include "resource.h"

#include "fpga.h"

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

#define DRIVER_NAME	"Baycom EPPFLEX"
#define DRIVER_VERSION  "0.2"

#define RXBUFFER_SIZE     ((MAXFLEN*6U/5U)+8U)
#define TXBUFFER_SIZE     8192U  /* must be a power of 2 and >= MAXFLEN*6/5+8 */

#define MODE_EPP     0
#define MODE_ECP     1
#define MODE_EPPAFSK 2
#define MODE_EPPCONV 3  /* conventional modem */

#define NRBIOSPORTS  4
#define BIOSLPTADDR  0x408

static struct state {
	volatile unsigned int active;
	volatile unsigned int terminate;
	volatile unsigned int rxactive;

	unsigned int verblevel;
	unsigned int mode;
	unsigned int ioaddr;
        unsigned int isnt;
        unsigned int w9xportmode;
        unsigned int portflags;
	struct adapter_config config;
	unsigned int txdelay;
	unsigned int fullduplex;
	unsigned int calib;
        unsigned int leds;
        
        byte baud;
	byte flexmode;
	byte status;

	unsigned int scale;

	HANDLE hdevlife;
	HANDLE htxmutex;

	L1FRAME txframe;
	L1FRAME rxframe;
	L1_STATISTICS stat;

	unsigned char rxbuf[512];
	unsigned int rxptr, rxlen;

	struct {
		unsigned rd, wr;
		unsigned char buf[TXBUFFER_SIZE];
	} htx;

	struct {
		unsigned int bitbuf, bitstream, numbits, state;
		unsigned char *bufptr;
		int bufcnt;
		unsigned char buf[RXBUFFER_SIZE];
	} hrx;

	unsigned short biosports[NRBIOSPORTS];
	HANDLE hlpt;
} state = { 0, };

#define REGISTRYPATH "SOFTWARE\\FlexNet\\BaycomUSB"
#define REGHKEY      HKEY_LOCAL_MACHINE

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

int lprintf(unsigned vl, const char *format, ...)
{
        va_list ap;
        char buf[512];
        int r;

	if (vl > state.verblevel)
                return 0;
	va_start(ap, format);
        r = vsnprintf(buf, sizeof(buf), format, ap);
	va_end(ap);
        OutputDebugString(buf);
        return r;
}

extern inline int iopl(unsigned int level)
{
        OSVERSIONINFO info;

        info.dwOSVersionInfoSize = sizeof(info);
        if (GetVersionEx(&info) &&
            (info.dwPlatformId == VER_PLATFORM_WIN32s ||
             info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS))
                return 0;
        return 1;
}

/* ---------------------------------------------------------------------- */
/*
 * 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
};

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

static 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;
}

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

static 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;
}

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

/*
 * 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 void hdlc_encode(unsigned char *pkt, unsigned int len)
{
        unsigned bitstream, notbitstream, bitbuf, numbit;
        unsigned wr = state.htx.wr;

        append_crc_ccitt(pkt, len);
        len += 2;
        bitstream = 0;
        bitbuf = 0x7e;
        numbit = 8; /* opening flag */
        while (numbit >= 8) {
                state.htx.buf[wr] = bitbuf;
                wr = (wr + 1) % TXBUFFER_SIZE;
                if (wr == state.htx.rd)
                        *(int *)0 = 0;  /* must not happen! */
                bitbuf >>= 8;
                numbit -= 8;
        }
        for (; len > 0; len--, pkt++) {
                bitstream >>= 8;
                bitstream |= ((unsigned int)*pkt) << 8;
                bitbuf |= ((unsigned int)*pkt) << numbit;
                notbitstream = ~bitstream;
                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)
                                *(int *)0 = 0;  /* must not happen! */
                        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)
                        *(int *)0 = 0;  /* must not happen! */
                bitbuf >>= 8;
                numbit -= 8;
        }
        state.htx.wr = wr;
}

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

static inline void do_rxpacket(void)
{
        if (state.hrx.bufcnt < 3 || state.hrx.bufcnt > MAXFLEN) 
                return;
        if (!check_crc_ccitt(state.hrx.buf, state.hrx.bufcnt)) 
                return;
        memcpy(state.rxframe.frame, state.hrx.buf, state.hrx.bufcnt-2);
        state.rxframe.len = state.hrx.bufcnt-2;
        state.rxframe.txdelay = 0;
        state.rxframe.kanal = 0;
        state.stat.rx_frames++;
}

#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 */        \
                st = 0;                                                                \
                goto enditer##j;                                                       \
        }                                                                              \
        if ((bitstream & (0x1fe << j)) != (0x0fc << j))   /* flag received */          \
                goto enditer##j;                                                       \
        if (st)                                                                        \
                do_rxpacket();                                                         \
        state.hrx.bufcnt = 0;                                                          \
        state.hrx.bufptr = state.hrx.buf;                                              \
        st = 1;                                                                        \
        numbits = 7-j;                                                                 \
        goto enditer##j;                                                               \
  stuff##j:                                                                            \
        numbits--;                                                                     \
        bitbuf = (bitbuf & ((~0xff) << j)) | ((bitbuf & ~((~0xff) << j)) << 1);        \
        goto enditer##j;                                                               \
})
        
static inline void hdlc_receive(void)
{
        unsigned bits, bitbuf, notbitstream, bitstream, numbits, st;

        /* start of HDLC decoder */
        numbits = state.hrx.numbits;
        st = state.hrx.state;
        bitstream = state.hrx.bitstream;
        bitbuf = state.hrx.bitbuf;
        while (state.rxptr < state.rxlen && !state.rxframe.len) {
                bits = state.rxbuf[state.rxptr++];
                bitstream >>= 8;
                bitstream |= ((unsigned int)bits) << 8;
                bitbuf >>= 8;
                bitbuf |= ((unsigned int)bits) << 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 (st && numbits >= 8) {
                        if (state.hrx.bufcnt >= RXBUFFER_SIZE) {
                                st = 0;
                        } else {
                                *(state.hrx.bufptr)++ = bitbuf >> (16-numbits);
                                state.hrx.bufcnt++;
                                numbits -= 8;
                        }
                }
        }
        state.hrx.numbits = numbits;
        state.hrx.state = st;
        state.hrx.bitstream = bitstream;
        state.hrx.bitbuf = bitbuf;
}

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

/*
 * Treiber-Init. Sofern mehrfach aufgerufen, kommt vorher jeweils l1_exit()
 * Hier also alle Ressourcen allokieren, aber noch nicht starten sofern
 * dazu die Parameter gebraucht werden. Die kommen spaeter per l1_init_kanal()
 */

static void saveparams(HKEY hKey)
{
	DWORD val;

	val = state.verblevel; 
	RegSetValueEx(hKey, "verblevel", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.mode; 
	RegSetValueEx(hKey, "mode", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.ioaddr; 
	RegSetValueEx(hKey, "ioaddr", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.portflags; 
	RegSetValueEx(hKey, "portflags", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.w9xportmode; 
	RegSetValueEx(hKey, "w9xportmode", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.fclk; 
	RegSetValueEx(hKey, "fclk", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.bitrate; 
	RegSetValueEx(hKey, "bitrate", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.intclk; 
	RegSetValueEx(hKey, "intclk", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.extmodem; 
	RegSetValueEx(hKey, "extmodem", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.loopback; 
	RegSetValueEx(hKey, "loopback", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.extstat; 
	RegSetValueEx(hKey, "extstat", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.pttmute; 
	RegSetValueEx(hKey, "pttmute", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.filtmode; 
	RegSetValueEx(hKey, "filtmode", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.config.gain; 
	RegSetValueEx(hKey, "gain", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.txdelay; 
	RegSetValueEx(hKey, "txdelay", 0, REG_DWORD, (void *)&val, sizeof(val));
	val = state.fullduplex; 
	RegSetValueEx(hKey, "fullduplex", 0, REG_DWORD, (void *)&val, sizeof(val));
}

int init_device(HKEY hKey)
{
	DWORD regtype, reglen, regval;
	unsigned int i;
	unsigned short baddr;
	
	/* read params from registry */
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "verblevel", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 10;
	state.verblevel = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "mode", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = MODE_EPP;
	state.mode = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "ioaddr", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.ioaddr = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "portflags", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.portflags = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "w9xportmode", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.w9xportmode = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "fclk", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 19666600;
	state.config.fclk = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "bitrate", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 9600;
	state.config.bitrate = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "intclk", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.config.intclk = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "extmodem", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.config.extmodem = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "loopback", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.config.loopback = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "extstat", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 1;
	state.config.extstat = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "pttmute", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 1;
	state.config.pttmute = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "filtmode", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.config.filtmode = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "gain", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 1;
	state.config.gain = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "txdelay", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.txdelay = regval;
	reglen = sizeof(regval);
	if (RegQueryValueEx(hKey, "fullduplex", NULL, &regtype, (void *)&regval, &reglen) != ERROR_SUCCESS || regtype != REG_DWORD)
		regval = 0;
	state.fullduplex = regval;
	/* save params to registry */
	saveparams(hKey);
	/* init hdlc */
	memset(&state.htx, 0, sizeof(state.htx));
	memset(&state.hrx, 0, sizeof(state.hrx));
	state.terminate = state.rxactive = state.active = 0;
        state.status = CH_DEAD;
	state.hdevlife = CreateEvent(NULL, FALSE, FALSE, "FlexNet Device State");
	state.htxmutex = CreateMutex(NULL, FALSE, NULL);
        state.isnt = iopl(3);
	memset(state.biosports, 0, sizeof(state.biosports));
	state.hlpt = INVALID_HANDLE_VALUE;
	if (!state.isnt) {
		for (i = 0; i < NRBIOSPORTS; i++) {
			if (!ReadProcessMemory(GetCurrentProcess(), (void *)(BIOSLPTADDR+i*sizeof(baddr)), &baddr, sizeof(baddr), &reglen) ||
			    reglen != sizeof(baddr))
				break;
			if (!baddr)
				break;
			state.biosports[i] = baddr;
			lprintf(10, "BIOS Port %u: 0x%04x\n", i, baddr);
		}
#if 0
		state.hlpt = CreateFile("\\\\.\\LPT1", GENERIC_READ | GENERIC_WRITE,
					FILE_SHARE_READ | FILE_SHARE_WRITE,
					NULL, OPEN_EXISTING, 0, NULL);
#endif
	}
        state.leds = 0;
        return 1;
}

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

void l1_exit(HKEY hKey)
{
	state.terminate = 1;
	saveparams(hKey);
	while (state.rxactive)
		Sleep(10);
	CloseHandle(state.hdevlife);
	CloseHandle(state.htxmutex);
        /* unload sys driver if NT */
        if (state.isnt || !state.w9xportmode)
                parport_stop_win();
	if (state.hlpt != INVALID_HANDLE_VALUE)
		CloseHandle(state.hlpt);
}

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

byte *config_info(byte kanal)
{
        if (state.active) {
                switch (state.mode) {
                case MODE_EPP:
                        return "EPPFLEX (EPP)";
                        
                case MODE_ECP:
                        return "EPPFLEX (ECP)";
                        
                case MODE_EPPAFSK:
                        return "EPPFLEX (AFSK)";
                        
                case MODE_EPPCONV:
                        return "EPP Conventional";
                }
        }
	return "EPPFLEX (inactive/error)";
}

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

static BOOL CALLBACK EdParmDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
        HWND hcombo;
        char buf[32];
	unsigned int i, j;

	switch (uMsg) {
	case WM_INITDIALOG:
                hcombo = GetDlgItem(hDlg, IDC_PORT);
                if (state.isnt) {
                        SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"LPT1");
                        SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"LPT2");
                        SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"LPT3");
                        if (state.ioaddr > 3) {
                                snprintf(buf, sizeof(buf), "LPT%u", state.ioaddr);
                                SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)buf);
                                ComboBox_SetCurSel(hcombo, 3);
                        } else
                                ComboBox_SetCurSel(hcombo, state.ioaddr-1);
                } else {
			for (j = !!state.ioaddr, i = 0; i < NRBIOSPORTS; i++) {
				if (!state.biosports[i])
					break;
				snprintf(buf, sizeof(buf), "LPT%u", i+1);
				SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)buf);
				if ((state.ioaddr == state.biosports[i] && state.w9xportmode) ||
                                    (state.ioaddr-1 == i && !state.w9xportmode)) {
					j = 0;
					ComboBox_SetCurSel(hcombo, i);
				}
			}
			if (j) {
                                if (state.w9xportmode)
                                        snprintf(buf, sizeof(buf), "0x%x", state.ioaddr);
                                else
                                        snprintf(buf, sizeof(buf), "LPT%u", state.ioaddr);
                                SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)buf);
                                ComboBox_SetCurSel(hcombo, i);
			}
                }
		hcombo = GetDlgItem(hDlg, IDC_MODE);
                SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"EPP");
                SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"ECP");
                SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"EPPAFSK");
                SendMessage(hcombo, CB_ADDSTRING, 0, (LPARAM)"EPP Conventional");
		ComboBox_SetCurSel(hcombo, state.mode);
		snprintf(buf, sizeof(buf), "%d", state.txdelay);
		SetDlgItemText(hDlg, IDC_TXDELAY, buf);
		CheckDlgButton(hDlg, IDC_FULLDUPLEX, state.fullduplex ? BST_CHECKED : BST_UNCHECKED);
		CheckDlgButton(hDlg, IDC_EXTMODEM, state.config.extmodem ? BST_CHECKED : BST_UNCHECKED);
		snprintf(buf, sizeof(buf), "%d", state.config.bitrate);
		SetDlgItemText(hDlg, IDC_BITRATE, buf);
		CheckDlgButton(hDlg, IDC_PTTMUTE, state.config.pttmute ? BST_CHECKED : BST_UNCHECKED);
		CheckDlgButton(hDlg, IDC_FORCEHWEPP, (state.portflags & PPFLAG_FORCEHWEPP) ? BST_CHECKED : BST_UNCHECKED);
		CheckDlgButton(hDlg, IDC_SWEMULEPP, (state.portflags & PPFLAG_SWEMULEPP) ? BST_CHECKED : BST_UNCHECKED);
		CheckDlgButton(hDlg, IDC_SWEMULECP, (state.portflags & PPFLAG_SWEMULECP) ? BST_CHECKED : BST_UNCHECKED);
		SetWindowText(hDlg, DRIVER_NAME" Configuration");
		break;
                
        case WM_COMMAND:
                switch (GET_WM_COMMAND_ID(wParam, lParam)) {
                case IDCANCEL:
                        EndDialog(hDlg, 0);
                        break;

                case IDOK:
                        GetDlgItemText(hDlg, IDC_PORT, buf, sizeof(buf));
                        if (state.isnt) {
                                if (strncmp(buf, "LPT", 3))
                                        state.ioaddr = 0;
                                else
                                        state.ioaddr = strtoul(buf+3, NULL, 0);
                        } else {
				if (buf[0] == 'L' && buf[1] == 'P' && buf[2] == 'T' && buf[4] == 0 &&
				    buf[3] >= '1' && buf[3] < '1' + NRBIOSPORTS) {
                                        if(state.w9xportmode)
                                                state.ioaddr = state.biosports[buf[3] - '1'];
                                        else
                                                state.ioaddr = buf[3] - '0';
				} else if (state.w9xportmode)
					state.ioaddr = strtoul(buf, NULL, 0);
                                else
                                        state.ioaddr = 0;
			}
                        GetDlgItemText(hDlg, IDC_MODE, buf, sizeof(buf));
			if (!strcmp(buf, "EPP Conventional"))
				state.mode = 3;
			else if (!strcmp(buf, "EPPAFSK"))
				state.mode = 2;
			else if (!strcmp(buf, "ECP"))
				state.mode = 1;
			else 
				state.mode = 0;
                        GetDlgItemText(hDlg, IDC_TXDELAY, buf, sizeof(buf));
			state.txdelay = strtoul(buf, NULL, 0);
			state.fullduplex = IsDlgButtonChecked(hDlg, IDC_FULLDUPLEX) == BST_CHECKED;
			state.config.extmodem = IsDlgButtonChecked(hDlg, IDC_EXTMODEM) == BST_CHECKED;
                        GetDlgItemText(hDlg, IDC_BITRATE, buf, sizeof(buf));
			state.config.bitrate = strtoul(buf, NULL, 0);
			state.config.pttmute = IsDlgButtonChecked(hDlg, IDC_PTTMUTE) == BST_CHECKED;
			if (IsDlgButtonChecked(hDlg, IDC_FORCEHWEPP) == BST_CHECKED)
				state.portflags |= PPFLAG_FORCEHWEPP;
			else
				state.portflags &= ~PPFLAG_FORCEHWEPP;
			if (IsDlgButtonChecked(hDlg, IDC_SWEMULEPP) == BST_CHECKED)
				state.portflags |= PPFLAG_SWEMULEPP;
			else
				state.portflags &= ~PPFLAG_SWEMULEPP;
			if (IsDlgButtonChecked(hDlg, IDC_SWEMULECP) == BST_CHECKED)
				state.portflags |= PPFLAG_SWEMULECP;
			else
				state.portflags &= ~PPFLAG_SWEMULECP;
			EndDialog(hDlg, 1);
                        break;
                        
                default:	
                        break;
                }
                break;
	
	default:
		return FALSE;
	}
	return TRUE;
}

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

int config_device(byte max_channels, HWND hDlg, byte channel)
{
	int restart = DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_DIALOG1), hDlg, EdParmDlgProc, 0);

	if (restart)
		state.active = 0;
	return restart;
}

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

u16 l1_get_ch_cnt(void)
{
	return 1;

}

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

byte get_txdelay(byte kanal)
{
	return state.txdelay / 10;
}

void set_txdelay(byte kanal, byte delay)
{
	state.txdelay = 10 * delay;
}

u16 get_mode(byte kanal)
{
	return (state.flexmode & ~MODE_d) | (state.fullduplex ? MODE_d : 0);
}

u16 get_baud(byte kanal)
{
	return state.baud;
}

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

byte l1_init_kanal(byte kanal, u16 chbaud, u16 chmode)
{
	state.flexmode = chmode;
	return (state.ioaddr != 0);
}

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

byte l1_ch_active(byte kanal)
{
	return (state.ioaddr != 0);
}

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

char far *l1_ident(byte kanal)
{
	return DRIVER_NAME;
}

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

char far *l1_version(byte kanal)
{
	return DRIVER_VERSION;
}

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

L1_STATISTICS far *l1_stat(byte kanal, byte delete)
{
	L1_STATISTICS *p = &state.stat;
	
	if (delete)
		p->tx_error = p->rx_overrun = p->rx_bufferoverflow =
		p->tx_frames = p->rx_frames = p->io_error = 0;
	return p;
}

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

void set_led(byte kanal, byte ledcode)
{
        state.leds = ledcode;
}

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

byte l1_ch_state(byte kanal)
{
	return state.status;
}

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

u16 l1_scale(byte kanal)
{
	return state.scale;
}

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

void l1_tx_calib(byte kanal, byte minutes)
{
	state.calib = minutes * state.config.bitrate * 60 / 8;
}


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

L1FRAME far *l1_get_framebuf(byte kanal)
{
	return &state.txframe;
}

byte l1_tx_frame(void)
{
        unsigned int i;

        if (state.txframe.kanal || !state.active)
                return 1;
	WaitForSingleObject(state.htxmutex, INFINITE);
	if (state.htx.rd == state.htx.wr) {
		/* need tx delay */
		i = state.txframe.txdelay * state.config.bitrate / 800;
		if (!i)
			i = 1;
		if (i > TXBUFFER_SIZE-1)
			i = TXBUFFER_SIZE-1;
		memset(state.htx.buf, 0x7e, i);
		state.htx.rd = 0;
		state.htx.wr = i;
	}
        i = (TXBUFFER_SIZE - 1 + state.htx.rd - state.htx.wr) % TXBUFFER_SIZE;
        if (i < state.txframe.len*6/5+32) {
                ReleaseMutex(state.htxmutex);
                return 0;
        }
	/* remove tail if possible */
	if (i < TXBUFFER_SIZE-8)
		state.htx.wr = (state.htx.wr + TXBUFFER_SIZE-8) % TXBUFFER_SIZE;
        hdlc_encode(state.txframe.frame, state.txframe.len);
	/* add tail */
	for (i = 0; i < 8; i++) {
		state.htx.buf[state.htx.wr] = 0x7e;
		state.htx.wr = (state.htx.wr + 1) % TXBUFFER_SIZE;
	}
	ReleaseMutex(state.htxmutex);
        state.stat.tx_frames++;
        return 1;
}

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

static int hdlc_transmit(byte *pkt, int cnt)
{
	int totcnt = 0;
	unsigned int xcnt;

	WaitForSingleObject(state.htxmutex, INFINITE);
	while (cnt > 0) {
		if (state.htx.rd == state.htx.wr) {
			if (!state.calib)
				break;
			xcnt = cnt;
			if (xcnt > state.calib)
				xcnt = state.calib;
			memset(pkt, 0x7e, xcnt);
			pkt += xcnt;
			totcnt += xcnt;
			cnt -= xcnt;
			state.calib -= xcnt;
			break;
		}
                if (state.htx.wr >= state.htx.rd)
                        xcnt = state.htx.wr - state.htx.rd;
                else
                        xcnt = TXBUFFER_SIZE - state.htx.rd;
                if (xcnt > cnt)
                        xcnt = cnt;
                if (!xcnt)
                        break;
                memcpy(pkt, &state.htx.buf[state.htx.rd], xcnt);
                pkt += xcnt;
		totcnt += xcnt;
                cnt -= xcnt;
                state.htx.rd = (state.htx.rd + xcnt) % TXBUFFER_SIZE;
	}
	ReleaseMutex(state.htxmutex);
	return totcnt;
}

/*
 * RX-Thread. Wartet auf RX-Bytes, verarbeitet sie und returned wenn
 * Handle zu oder Paket komplett
 */

L1FRAME far *l1_rx_frame(void)
{
	unsigned int i;
	unsigned char eio[2048];
        unsigned char ctreg;
	byte stat;
	byte statecp[6];
        SYSTEMTIME tm1, tm2;
	unsigned int msec, bitrate, bytecnt;
	int cnt, icnt, ocnt, icntx, ocntx;

	state.rxactive = 1;
	state.rxframe.len = 0;
	if (state.terminate) {
		state.rxactive = 0;
		return NULL;
	}
	/* check if startup is needed */
	if (!state.active) {
		if (!state.ioaddr)
			goto errwait;
		/* init parport; distinguish between NT and 9x case */
                if (state.isnt || !state.w9xportmode)
                        cnt = parport_init_win_flags(state.ioaddr-1, state.portflags);
                else if (state.w9xportmode == 1)
                        cnt = parport_init_w9xring0_flags(state.ioaddr, state.portflags);
                else
                        cnt = parport_init_direct_flags(state.ioaddr, state.portflags);
                if (cnt) {
			lprintf(10, "Cannot open Parport at address/number 0x%04x\n", state.ioaddr);
			state.ioaddr = 0;
			goto errwait;
		}
		bytecnt = 0;
		/* autoprobe bitrate */
		switch (state.mode) {
		case MODE_EPP:
			if (adapter_start_epp(&state.config)) {
				lprintf(0, "Error configuring EPP mode\n");
				goto errwait;
			}
			/* 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 errparport;
			GetSystemTime(&tm1);
			for (;;) {
				if (parport_epp_read_addr(&stat, 1) != 1)
					goto errparport;
				/* 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 errparport;
					if (parport_epp_read_addr(eio, 2) != 2)
						goto errparport;
					icnt = 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 errparport;
					icnt &= 0x7fff;
				} else {
				/* 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;
					}
				}
				/* rx */
				while (icnt > 0) {
					cnt = icnt;
					if (cnt > sizeof(eio))
						cnt = sizeof(eio);
					icnt -= cnt;
					bytecnt += cnt;
					if (parport_epp_read_data(eio, cnt) != cnt)
						goto errparport;
				}
				GetSystemTime(&tm2);
				msec = (60000 + tm2.wSecond * 1000U + tm2.wMilliseconds - tm1.wSecond * 1000U - tm1.wMilliseconds) % 60000;
				if (msec >= 500)
					break;
				Sleep(10);
				if (state.terminate) {
					state.rxactive = 0;
					return NULL;
				}
			}
			/* 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 errparport;
			break;

		case MODE_ECP:
			if (adapter_start_ecp(&state.config)) {
				lprintf(0, "Error configuring EPP mode\n");
				goto errwait;
			}
			eio[0] = 0xaf;  /* reset register: all reset */
			eio[1] = 0xa0;  /* reset register: terminate reset */
			eio[2] = 0xb0;
			if (parport_ecp_write_addr(eio, 3) != 3)
				goto errparport;
			GetSystemTime(&tm1);
			for (;;) {
				eio[0] = 0x81;
				if (parport_ecp_write_addr(eio, 1) != 1)
					goto errparport;
				if (parport_ecp_read_data(statecp, 6) != 6)
					goto errparport;
				eio[0] = 0x80;
				if (parport_ecp_write_addr(eio, 1) != 1)
					goto errparport;
				icnt = ((unsigned)statecp[2] << 8) | statecp[1];
				/* read */
				while (icnt > 0) {
					cnt = icnt;
					if (cnt > sizeof(eio))
						cnt = sizeof(eio);
					icnt -= cnt;
					bytecnt += cnt;
					eio[0] = 0xc0 | (cnt & 0xf);
					eio[1] = 0xd0 | ((cnt >> 4) & 0xf);
					eio[2] = 0xe0 | ((cnt >> 8) & 0xf);
					if (parport_ecp_write_addr(eio, 3) != 3)
						goto errparport;
					if (parport_ecp_read_data(eio, cnt) != cnt)
						goto errparport;
				}
				GetSystemTime(&tm2);
				msec = (60000 + tm2.wSecond * 1000U + tm2.wMilliseconds - tm1.wSecond * 1000U - tm1.wMilliseconds) % 60000;
				if (msec >= 500)
					break;
				Sleep(10);
				if (state.terminate) {
					state.rxactive = 0;
					return NULL;
				}
			}
			eio[0] = 0xaf;  /* reset register: all reset */
			eio[1] = 0xa0;  /* reset register: terminate reset */
			eio[2] = 0xb0;
			if (parport_ecp_write_addr(eio, 3) != 3)
				goto errparport;
			break;
			
		case MODE_EPPAFSK:
			state.config.bitrate = 1200;
			if (adapter_start_eppafsk(&state.config)) {
				lprintf(0, "Error configuring EPP mode\n");
				goto errwait;
			}
			/* reset modem */
			eio[0] = 0x00;
			eio[1] = 0x18;
			if (parport_epp_write_addr(eio, 2) != 2)
				goto errparport;
			GetSystemTime(&tm1);
			for (;;) {
				if (parport_epp_read_addr(&stat, 1) != 1)
					goto errparport;
				/* determine the FIFO count */
				eio[0] = 0x1a;
				if (parport_epp_write_addr(eio, 1) != 1)
					goto errparport;
				if (parport_epp_read_addr(eio, 1) != 1)
					goto errparport;
				icnt = eio[0] & 0x1f;
				eio[0] = 0x18;
				if (parport_epp_write_addr(eio, 1) != 1)
					goto errparport;
				/* rx */
				if (icnt > 0) {
					if (parport_epp_read_data(eio, icnt) != icnt)
						goto errparport;
					bytecnt += icnt;
				}
				GetSystemTime(&tm2);
				msec = (60000 + tm2.wSecond * 1000U + tm2.wMilliseconds - tm1.wSecond * 1000U - tm1.wMilliseconds) % 60000;
				if (msec >= 500)
					break;
				Sleep(10);
				if (state.terminate) {
					state.rxactive = 0;
					return NULL;
				}
			}
			/* reset modem */
			eio[0] = 0x00;
			eio[1] = 0x18;
			if (parport_epp_write_addr(eio, 2) != 2)
				goto errparport;
			break;

		case MODE_EPPCONV:
			/* 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 errparport;
			GetSystemTime(&tm1);
			for (;;) {
				if (parport_epp_read_addr(&stat, 1) != 1)
					goto errparport;
				/* try to determine the FIFO count if in extended mode */
				/* 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;
				}
				/* rx */
				while (icnt > 0) {
					cnt = icnt;
					if (cnt > sizeof(eio))
						cnt = sizeof(eio);
					icnt -= cnt;
					bytecnt += cnt;
					if (parport_epp_read_data(eio, cnt) != cnt)
						goto errparport;
				}
				GetSystemTime(&tm2);
				msec = (60000 + tm2.wSecond * 1000U + tm2.wMilliseconds - tm1.wSecond * 1000U - tm1.wMilliseconds) % 60000;
				if (msec >= 500)
					break;
				Sleep(10);
				if (state.terminate) {
					state.rxactive = 0;
					return NULL;
				}
			}
			for (icnt = 0; icnt < 256; icnt++) {
				if (parport_epp_read_addr(&stat, 1) != 1)
					goto errparport;
				if (!(stat & EPP_NREF))
					break;
				if (parport_epp_read_data(eio, 1) != 1)
					goto errparport;
				bytecnt++;
			}
			/* 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 errparport;
			break;
			
		default:
			lprintf(0, "Invalid mode requested\n");
			goto errwait;
		}
		bitrate = (bytecnt * 8000U + msec / 2U) / msec;
		lprintf(0, "Autoprobed bitrate: %u\n", bitrate);
		/* set variables */
		i = state.config.bitrate;
		if (!i)
			i = 1;
		state.scale = 614400 / i;
		state.baud = i / 100;
		state.status = 0;
		state.active = 1;
		SetEvent(state.hdevlife);			
	}
	/* actual driver */
	for (;;) {
		/* do IO */
		if (state.rxptr >= state.rxlen) {
			state.rxptr = state.rxlen = 0;
			switch (state.mode) {
			case MODE_EPP:
				if (parport_epp_read_addr(&stat, 1) != 1)
					goto errparport;
				/* 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 */
                                ctreg = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE;
                                if (state.leds & LED_CON)
                                        ctreg |= 1 << 6;
                                if (state.leds & LED_STA)
                                        ctreg |= 1 << 7;
				if (state.config.extstat) {
					eio[0] = ctreg|1;
					if (parport_epp_write_addr(eio, 1) != 1)
						goto errparport;
					if (parport_epp_read_addr(eio, 2) != 2)
						goto errparport;
					icnt = eio[0] | (((unsigned int)eio[1]) << 8);
					eio[0] = ctreg|2;
					if (parport_epp_write_addr(eio, 1) != 1)
						goto errparport;
					if (parport_epp_read_addr(eio, 2) != 2)
						goto errparport;
					ocnt = eio[0] | (((unsigned int)eio[1]) << 8);
					ocnt = 16384 - (ocnt & 0x7fff);
					icnt &= 0x7fff;
				} else {
					ocnt = ocntx;
					icnt = icntx;
				}
                                eio[0] = ctreg;
                                if (parport_epp_write_addr(eio, 1) != 1)
                                        goto errparport;
				if (stat & EPP_DCDBIT)
					state.status &= ~CH_DCD;
				else
					state.status |= CH_DCD;
				if (stat & EPP_PTTBIT)
					state.status |= CH_PTT;
				else
					state.status &= ~CH_PTT;
				/* rx */
				if (icnt > 0) {
					if (icnt > sizeof(state.rxbuf))
						icnt = sizeof(state.rxbuf);
					if (parport_epp_read_data(state.rxbuf, icnt) != icnt)
						goto errparport;
					state.rxlen = icnt;
				}
				/* 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 errparport;
				}
				break;

			case MODE_ECP:
				eio[0] = 0x81;
				if (parport_ecp_write_addr(eio, 1) != 1)
					goto errparport;
				if (parport_ecp_read_data(statecp, 6) != 6)
					goto errparport;
				eio[0] = 0x80;
				if (parport_ecp_write_addr(eio, 1) != 1)
					goto errparport;
				eio[0] = 0xb0;
                                if (state.leds & LED_CON)
                                        eio[0] |= 1 << 0;
                                if (state.leds & LED_STA)
                                        eio[0] |= 1 << 1;
				if (parport_ecp_write_addr(eio, 1) != 1)
					goto errparport;
				icnt = ((unsigned)statecp[2] << 8) | statecp[1];
				ocnt = ((unsigned)statecp[4] << 8) | statecp[3];
				/* update status */
				if (statecp[0] & 0x80)
					state.status &= ~CH_DCD;
				else
					state.status |= CH_DCD;
				if (statecp[0] & 0x40)
					state.status &= ~CH_PTT;
				else
					state.status |= CH_PTT;
				/* read */
				if (icnt > 0) {
					cnt = icnt;
					if (cnt > sizeof(state.rxbuf))
						cnt = sizeof(state.rxbuf);
					icnt -= cnt;
					eio[0] = 0xc0 | (cnt & 0xf);
					eio[1] = 0xd0 | ((cnt >> 4) & 0xf);
					eio[2] = 0xe0 | ((cnt >> 8) & 0xf);
					if (parport_ecp_write_addr(eio, 3) != 3)
						goto errparport;
					if (parport_ecp_read_data(state.rxbuf, cnt) != cnt)
						goto errparport;
					state.rxlen = cnt;
				}
				/* write */
				if (ocnt < 8192) { /* fixme */
					cnt = 8192 - ocnt;
					if (cnt > sizeof(eio))
						cnt = sizeof(eio);
					cnt = hdlc_transmit(eio, cnt);
					if (parport_ecp_write_data(eio, cnt) != cnt)
						goto errparport;
				}
				break;

			case MODE_EPPAFSK:
				if (parport_epp_read_addr(&stat, 1) != 1)
					goto errparport;
				/* determine the FIFO count */
                                ctreg = 0x18;
                                if (state.leds & LED_CON)
                                        ctreg |= 1 << 6;
                                if (state.leds & LED_STA)
                                        ctreg |= 1 << 7;
				eio[0] = ctreg | 1;
				if (parport_epp_write_addr(eio, 1) != 1)
					goto errparport;
				if (parport_epp_read_addr(eio, 1) != 1)
					goto errparport;
				ocnt = 0x10 - (eio[0] & 0x1f);
				eio[0] = ctreg | 2;
				if (parport_epp_write_addr(eio, 1) != 1)
					goto errparport;
				if (parport_epp_read_addr(eio, 1) != 1)
					goto errparport;
				icnt = eio[0] & 0x1f;
				eio[0] = ctreg;
				if (parport_epp_write_addr(eio, 1) != 1)
					goto errparport;
				/* update status */
				if (stat & 0x80)
					state.status &= ~CH_DCD;
				else
					state.status |= CH_DCD;
				if (stat & 0x40)
					state.status &= ~CH_PTT;
				else
					state.status |= CH_PTT;
				/* rx */
				if (icnt > 0) {
					if (parport_epp_read_data(state.rxbuf, icnt) != icnt)
						goto errparport;
					state.rxlen = icnt;
				}
				/* tx */
				if (ocnt > 0) {
					cnt = hdlc_transmit(eio, ocnt);
					if (parport_epp_write_data(eio, cnt) != cnt)
						goto errparport;
				}
				break;

			case MODE_EPPCONV:
				if (parport_epp_read_addr(&stat, 1) != 1)
					goto errparport;
				/* 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;
				}
				if (stat & EPP_DCDBIT)
					state.status &= ~CH_DCD;
				else
					state.status |= CH_DCD;
				if (stat & EPP_PTTBIT)
					state.status |= CH_PTT;
				else
					state.status &= ~CH_PTT;
                                /* set LEDs */
                                ctreg = EPP_TX_FIFO_ENABLE|EPP_RX_FIFO_ENABLE|EPP_MODEM_ENABLE;
                                if (state.leds & LED_CON)
                                        ctreg |= 1 << 6;
                                if (state.leds & LED_STA)
                                        ctreg |= 1 << 7;
                                eio[0] = ctreg;
                                if (parport_epp_write_addr(eio, 1) != 1)
                                        goto errparport;                                
				/* rx */
				if (icnt == 0 && state.config.bitrate < 100000) {
					i = (state.config.bitrate < 50000) ? 256 : 128;
					while (i > 0 && state.rxlen < sizeof(state.rxbuf) && stat & EPP_NREF) {
						if (parport_epp_read_data(state.rxbuf+state.rxlen, 1) != 1)
							goto errparport;
						state.rxlen++;
						i--;
						if (parport_epp_read_addr(&stat, 1) != 1)
							goto errparport;
					}
				} else if (icnt > 0) {
					cnt = icnt;
					if (cnt > sizeof(state.rxbuf))
						cnt = sizeof(state.rxbuf);
					icnt -= cnt;
					if (parport_epp_read_data(state.rxbuf, cnt) != cnt)
						goto errparport;
					state.rxlen = 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 errparport;
				}
				break;
			}
		}
		while (state.rxptr < state.rxlen) {
			hdlc_receive();
			if (state.rxframe.len) {
				state.rxactive = 0;
				return &state.rxframe;
			}
		}
		if (state.terminate) {
			state.rxactive = 0;
			return NULL;
		}
		Sleep(10);
	}

  errparport:
	lprintf(0, "Parport timeout\n");
  errwait:
	if (state.isnt || !state.w9xportmode)
	  parport_stop_win();
	state.stat.io_error++;
	state.active = 0;
	state.status = CH_DEAD;
	SetEvent(state.hdevlife);			
	for (i = 0; i < 30; i++) {
		if (state.terminate)
			break;
		Sleep(100);
	}
	state.rxactive = 0;
	return NULL;
}

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