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

/*
 *      drvinit.c  -- HDLC packet radio modem for EPP/ECP using FPGA usermode driver.
 *                    Init code (discarded at runtime).
 *
 *      Copyright (C) 1998-2000  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/time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>

#ifdef HAVE_LINUX_IF_H
#include <linux/if.h>
#elif defined(HAVE_NET_IF_H)
#include <net/if.h>
#endif

#ifdef HAVE_SCHED_H
#include <sched.h>
#endif

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#ifdef HAVE_PTY_H
#include <pty.h>
#else
extern int openpty(int *amaster, int *aslave, char *name, struct termios *termp, struct  winsize *winp);
#endif

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

#ifdef HAVE_MKISS
#include <sys/socket.h>
/* glibc2.0 does not have sockaddr_ax25, ifr.ifr_newname and SIOCSIFNAME */
#include <linux/ax25.h>
#include <linux/sockios.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif

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

#define PIDFILE LOCALSTATEDIR "/run/baycomepp.pid"

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

static unsigned verboselevel = 0;

#ifdef HAVE_MKISS

struct ifconfig {
	char ifname[IFNAMSIZ];
	ax25_address hwaddr;
	struct in_addr ipaddr, netmask, broadcast;
};

#define IFCONFIGINIT { "", { "" }, { INADDR_NONE }, { INADDR_NONE }, { INADDR_NONE } }

#else /* !HAVE_MKISS */

struct ifconfig {
	char ifname[PATH_MAX];
};

#define IFCONFIGINIT { "" }

#endif /* !HAVE_MKISS */

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

int lprintf(unsigned vl, const char *format, ...)
{
        static const int logprio[] = { LOG_ERR, LOG_INFO };
        va_list ap;
        int r = 0;

        if (vl > verboselevel)
                return 0;
	va_start(ap, format);
	if (logging)
		vsyslog((vl > 1) ? LOG_DEBUG : logprio[vl], format, ap);
	else {
		r = fprintf(stderr, "baycomepp[%u]: ", getpid());
		r += vfprintf(stderr, format, ap);
	}
	va_end(ap);
        return r;
}

int tprintf(const char *format, ...)
{
	va_list ap;
	int i;

        va_start(ap, format);
        i = vprintf(format, ap);
        va_end(ap);
	fflush(stdout);
	return i;
}

int idle_callback(unsigned us_delay)
{
	return 1;
}

/* --------------------------------------------------------------------- */
#ifdef HAVE_MKISS

static inline void ifconfigure(struct ifconfig *cfg)
{
	int master, slave, fd, disc = N_AX25, encap = 4;
	char mbuf[512], *mptr = mbuf;
	struct sockaddr_ax25 sax25;
	struct sockaddr_in sin;
	struct termios tm;
        struct ifreq ifr;
	unsigned i;

	openpty(&master, &slave, NULL, NULL, NULL);
	/* set pty/tty pair to raw */
	memset(&tm, 0, sizeof(tm));
	tm.c_cflag = CS8 | CREAD | CLOCAL;
	if (tcsetattr(master, TCSANOW, &tm))
		errstr(SEV_WARNING, "master: tcsetattr");
	memset(&tm, 0, sizeof(tm));
	tm.c_cflag = CS8 | CREAD | CLOCAL;
	if (tcsetattr(slave, TCSANOW, &tm))
		errstr(SEV_WARNING, "slave: tcsetattr");
        if (ioctl(master, TIOCSETD, &disc) == -1)
                errstr(SEV_FATAL, "ioctl: TIOCSETD");
	if (ioctl(master, SIOCGIFNAME, ifr.ifr_name) == -1)
                errstr(SEV_FATAL, "ioctl: SIOCGIFNAME");
        if (ioctl(master, SIOCSIFENCAP, &encap) == -1)
                errstr(SEV_FATAL, "ioctl: SIOCSIFENCAP");
	/* configure interface */
        if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
                errstr(SEV_FATAL, "socket");
#ifdef HAVE_IFRNEWNAME
	if (cfg->ifname[0]) {
		strncpy(ifr.ifr_newname, cfg->ifname, sizeof(ifr.ifr_newname));
		if (ioctl(fd, SIOCSIFNAME, &ifr) == -1) {
			errstr(SEV_WARNING, "ioctl: SIOCSIFNAME");
			errprintf(SEV_WARNING, "baycomepp: cannot set ifname to %s, using %s (old kernel version?)\n",
				  cfg->ifname, ifr.ifr_name);
		} else {
			memcpy(ifr.ifr_name, ifr.ifr_newname, sizeof(ifr.ifr_name));
		}
	}
#endif /* HAVE_IFRNEWNAME */
	mptr += sprintf(mptr, "ifname %s mtu 256", ifr.ifr_name);
	ifr.ifr_mtu = 256;
        if (ioctl(fd, SIOCSIFMTU, &ifr) < 0)
                errstr(SEV_FATAL, "ioctl: SIOCSIFMTU");
	if (cfg->hwaddr.ax25_call[0]) {
		sax25.sax25_family = AF_AX25;
		memcpy(&sax25.sax25_call, &cfg->hwaddr, sizeof(sax25.sax25_call));
		sax25.sax25_ndigis = 0;
		memcpy(&ifr.ifr_hwaddr, &sax25, sizeof(ifr.ifr_hwaddr));
		if (ioctl(fd, SIOCSIFHWADDR, &ifr) == -1)
			errstr(SEV_WARNING, "ioctl: SIOCSIFHWADDR");
		else {
			mptr += sprintf(mptr, " hwaddr ");
			for (i = 0; i < 6; i++)
				if (sax25.sax25_call.ax25_call[i] != (' ' << 1))
					*mptr++ = (sax25.sax25_call.ax25_call[i] >> 1) & 0x7f;
			mptr += sprintf(mptr, "-%d", (sax25.sax25_call.ax25_call[6] >> 1) & 0x7f);
		}
	}
	if (cfg->ipaddr.s_addr != INADDR_NONE) {
		sin.sin_family = AF_INET;
		sin.sin_addr = cfg->ipaddr;
		memcpy(&ifr.ifr_addr, &sin, sizeof(ifr.ifr_addr));
		if (ioctl(fd, SIOCSIFADDR, &ifr) == -1)
			errstr(SEV_WARNING, "ioctl: SIOCSIFADDR");
		else
			mptr += sprintf(mptr, " ipaddr %s", inet_ntoa(cfg->ipaddr));
	}
	if (cfg->netmask.s_addr != INADDR_NONE) {
		sin.sin_family = AF_INET;
		sin.sin_addr = cfg->netmask;
		memcpy(&ifr.ifr_netmask, &sin, sizeof(ifr.ifr_netmask));
		if (ioctl(fd, SIOCSIFNETMASK, &ifr) == -1)
			errstr(SEV_WARNING, "ioctl: SIOCSIFNETMASK");
		else
			mptr += sprintf(mptr, " netmask %s", inet_ntoa(cfg->netmask));
	}
	if (cfg->broadcast.s_addr != INADDR_NONE) {
		sin.sin_family = AF_INET;
		sin.sin_addr = cfg->broadcast;
		memcpy(&ifr.ifr_broadaddr, &sin, sizeof(ifr.ifr_broadaddr));
		if (ioctl(fd, SIOCSIFBRDADDR, &ifr) == -1)
			errstr(SEV_WARNING, "ioctl: SIOCSIFBRDADDR");
		else
			mptr += sprintf(mptr, " broadcast %s", inet_ntoa(cfg->broadcast));
	}
        if (ioctl(fd, SIOCGIFFLAGS, &ifr) == -1)
                errstr(SEV_FATAL, "ioctl: SIOCGIFFLAGS");
        ifr.ifr_flags &= ~IFF_NOARP;
        ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
        if (ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) {
		printf("flags: 0x%x name %s\n", ifr.ifr_flags, ifr.ifr_name);
                errstr(SEV_FATAL, "ioctl: SIOCSIFFLAGS");
	}
        close(fd);
	/* prepare modem state etc. */
	state.kissfd = slave;
	/* don't close the master side! */
	fcntl(state.kissfd, F_SETFL, fcntl(state.kissfd, F_GETFL, 0) | O_NONBLOCK);
	errprintf(SEV_INFO, "baycomepp: %s\n", mbuf);
}

#else /* !HAVE_MKISS */

static inline void ifconfigure(struct ifconfig *cfg)
{
	int master, slave;
	struct termios tm;
	char ttyname[32];

	openpty(&master, &slave, ttyname, NULL, NULL);
	/* set pty/tty pair to raw */
	memset(&tm, 0, sizeof(tm));
	tm.c_cflag = CS8 | CREAD | CLOCAL;
	if (tcsetattr(master, TCSANOW, &tm))
		errstr(SEV_WARNING, "master: tcsetattr");
	memset(&tm, 0, sizeof(tm));
	tm.c_cflag = CS8 | CREAD | CLOCAL;
	if (tcsetattr(slave, TCSANOW, &tm))
		errstr(SEV_WARNING, "slave: tcsetattr");
	/* make link */
	if (cfg->ifname[0]) {
		//fchmod(slave, 0600);
		unlink(cfg->ifname);
		if (symlink(ttyname, cfg->ifname)) 
			errprintf(SEV_WARNING, "baycomepp: symlink error: %s -> %s\n", ttyname, cfg->ifname);
	}
	/* prepare modem state etc. */
	state.kissfd = master;
	close(slave);
	/* don't close the master side! */
	fcntl(state.kissfd, F_SETFL, fcntl(state.kissfd, F_GETFL, 0) | O_NONBLOCK);
	errprintf(SEV_INFO, "baycomepp: starting AX.25 interface %s\n", cfg->ifname ? cfg->ifname : ttyname);
}

#endif /* !HAVE_MKISS */

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

static void write_pid(pid_t pid)
{
	FILE *f = fopen(PIDFILE, "a");
	if (!f) {
		errprintf(SEV_INFO, "cannot open PID file %s\n", PIDFILE);
		return;
	}
	fprintf(f, "%i\n", pid);
	fclose(f);
}

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

static void kill_baycomeppd(void)
{
	FILE *f = fopen(PIDFILE, "r");
	pid_t pid;

	if (!f) {
		errprintf(SEV_INFO, "cannot open PID file %s\n", PIDFILE);
		return;
	}
	while (fscanf(f, " %i \n", &pid) == 1) 
		if (pid <= 0 || pid == getpid())
			errprintf(SEV_INFO, "invalid pid %i\n", pid);
		else {
			kill(pid, SIGTERM);
			errprintf(SEV_INFO, "terminating process %i\n", pid);
		}
	fclose(f);
	unlink(PIDFILE);
}

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

static void init(int mode)
{
	unsigned char eio[2048];
	u_int8_t stat;
	u_int8_t statecp[6];
	int cnt, icnt;
	struct timeval tv1, tv2;
	unsigned long usec;
	unsigned bytecnt = 0;

	/* we need this here so that the bitrate autoprobe works correctly */
	/*
	 * set the process to realtime privs
	 */
#ifdef HAVE_SCHED_H
	{
		struct sched_param schp;
		memset(&schp, 0, sizeof(schp));
		schp.sched_priority = sched_get_priority_min(SCHED_RR)+1;
#if 1
		if (sched_setscheduler(0, SCHED_RR, &schp) != 0)
			errstr(SEV_ERROR, "sched_setscheduler");
#endif
	}
#endif /* HAVE_SCHED_H */
	/*
	 * lock memory down
	 */
	if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) 
		errstr(SEV_ERROR, "mlockall");
       	switch(mode) {
	case MODE_EPP:
		if (adapter_start_epp(&state.config))
			exit(1);
		/* 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;
		gettimeofday(&tv1, NULL);
		for (;;) {
			if (parport_epp_read_addr(&stat, 1) != 1)
				goto errret;
			/* 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;
				if (parport_epp_write_addr(eio, 1) != 1)
					goto errret;
				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 errret;
			}
			gettimeofday(&tv2, NULL);
			usec = ((60 + tv2.tv_sec - tv1.tv_sec) % 60) * 1000000 + tv2.tv_usec - tv1.tv_usec;
			if (usec >= 500000)
				break;
			usleep(8000); /* must be more than 2ms, Linux busy-waits up to 2ms! */
		}
		break;

	case MODE_ECP:
		if (adapter_start_ecp(&state.config))
			exit(1);
		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 errret;
		gettimeofday(&tv1, NULL);
		for (;;) {
			eio[0] = 0x81;
			if (parport_ecp_write_addr(eio, 1) != 1)
				goto errret;
			if (parport_ecp_read_data(statecp, 6) != 6)
				goto errret;
			eio[0] = 0x80;
			if (parport_ecp_write_addr(eio, 1) != 1)
				goto errret;
			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 errret;
				if (parport_ecp_read_data(eio, cnt) != cnt)
					goto errret;
			}
			gettimeofday(&tv2, NULL);
			usec = ((60 + tv2.tv_sec - tv1.tv_sec) % 60) * 1000000 + tv2.tv_usec - tv1.tv_usec;
			if (usec >= 500000)
				break;
			usleep(8000); /* must be more than 2ms, Linux busy-waits up to 2ms! */
		}
		break;

	case MODE_EPPAFSK:
		state.config.bitrate = 1200;
		if (adapter_start_eppafsk(&state.config))
			exit(1);
		/* reset modem */
		eio[0] = 0x00;
		eio[1] = 0x18;
		if (parport_epp_write_addr(eio, 2) != 2)
			goto errret;
		gettimeofday(&tv1, NULL);
		for (;;) {
			if (parport_epp_read_addr(&stat, 1) != 1)
				goto errret;
			/* determine the FIFO count */
			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;
			/* rx */
			if (icnt > 0) {
				if (parport_epp_read_data(eio, icnt) != icnt)
					goto errret;
				bytecnt += icnt;
			}
			gettimeofday(&tv2, NULL);
			usec = ((60 + tv2.tv_sec - tv1.tv_sec) % 60) * 1000000 + tv2.tv_usec - tv1.tv_usec;
			if (usec >= 500000)
				break;
			usleep(8000); /* must be more than 2ms, Linux busy-waits up to 2ms! */
		}
		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 errret;
		gettimeofday(&tv1, NULL);
		for (;;) {
			if (parport_epp_read_addr(&stat, 1) != 1)
				goto errret;
			/* 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 errret;
			}
			gettimeofday(&tv2, NULL);
			usec = ((60 + tv2.tv_sec - tv1.tv_sec) % 60) * 1000000 + tv2.tv_usec - tv1.tv_usec;
			if (usec >= 500000)
				break;
			usleep(8000); /* must be more than 2ms, Linux busy-waits up to 2
					 ms! */
		}
		for (icnt = 0; icnt < 256; icnt++) {
			if (parport_epp_read_addr(&stat, 1) != 1)
				goto errret;
			if (!(stat & EPP_NREF))
				break;
			if (parport_epp_read_data(eio, 1) != 1)
				goto errret;
			bytecnt++;
		}
		break;

	default:
		exit(1);
	}
	state.access.bitrate = (bytecnt * 8000000ULL + usec / 2ULL) / usec;
	state.access.bitrateby25 = (state.access.bitrate + 12) / 25;
	errprintf(SEV_INFO, "autoprobed bitrate: %u\n", state.access.bitrate);
	return;

errret:
	errprintf(SEV_FATAL, "Modem timeout\n");	
}

static void init2(struct ifconfig *ifcfg)
{
	ifconfigure(ifcfg);
	/*
	 * sigusr1 is used to display the current state
	 */
	if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
		errstr(SEV_ERROR, "signal");
	if (signal(SIGUSR2, sig_usr2) == SIG_ERR)
		errstr(SEV_ERROR, "signal");
	if (signal(SIGHUP, SIG_IGN) == SIG_ERR)
		errstr(SEV_ERROR, "signal");
	if (signal(SIGTERM, sighandler) == SIG_ERR)
		errstr(SEV_ERROR, "signal");
	/* start the modem */
	state.access.state = tx_idle;
	state.access.slotcnt = 1;
}

/* --------------------------------------------------------------------- */
/*
 * convert_call_entry is taken from the ax25 library
 */

static inline int convert_call_entry(const char *name, char *buf)
{
        int ct = 0;
        int ssid = 0;
        const char *p = name;
        char c;

        while (ct < 6) {
                c = toupper(*p);
                if (c == '-' || c == '\0')
                        break;
                if (!isalnum(c)) {
			fprintf(stderr, "baycomepp: invalid symbol in callsign %s\n", name);
                        return -1;
                }
                buf[ct] = c << 1;
                p++;
                ct++;
        }
        for ( ; ct < 6; ct++)
                buf[ct] = ' ' << 1;
        if (*p != '\0') {
                p++;
                if (sscanf(p, "%d", &ssid) != 1 || ssid < 0 || ssid > 15) {
                        fprintf(stderr, "baycomepp: SSID must follow '-' and "
				  "be numeric in the range 0-15 - %s\n", name);
                        return -1;
                }
        }
        buf[6] = ((ssid + '0') << 1) & 0x1E;
        return 0;
}

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

int driver_init(int argc, char *argv[])
{
        static const struct option long_options[] = {
		{ "epp", 0, 0, 0x400+MODE_EPP },
		{ "ecp", 0, 0, 0x400+MODE_ECP },
		{ "eppafsk", 0, 0, 0x400+MODE_EPPAFSK },
		{ "eppconv", 0, 0, 0x400+MODE_EPPCONV },
		{ "log", 0, 0, 'l' },
		{ "kill", 0, 0, 'k' },
		{ "mode", 1, 0, 'm' },
		{ "port", 1, 0, 'p' },
		{ "verbose", 0, 0, 'v' },
		{ "no-daemon", 0, 0, 'n' },
#ifdef HAVE_MKISS
#ifdef HAVE_IFRNEWNAME
		{ "ifname", 1, 0, 0x500 },
#endif /* HAVE_IFRNEWNAME */
		{ "hwaddr", 1, 0, 0x501 },
		{ "ipaddr", 1, 0, 0x502 },
		{ "netmask", 1, 0, 0x503 },
		{ "broadcast", 1, 0, 0x504 },
#else /* !HAVE_MKISS */
		{ "iflink", 1, 0, 0x600 },
#endif /* !HAVE_MKISS */
		{ 0, 0, 0, 0}
        };
	struct ifconfig ifcfg = IFCONFIGINIT;
	int c;
	int err = 0;
	pid_t pid;
	int daemon = 1;
	int mode = MODE_EPP;
	int pfds[2];
	unsigned iobase = 0x378;

	printf("baycomepp v" VERSION " (c) 1998-2000,2002 by Thomas Sailer, HB9JNX/AE4WA"
#ifdef EPPLONG
	       "  (epplong)"
#endif
#ifdef EPPEMUL
	       "  (eppemul)"
#endif
	       "\n");
	/* initialize state struct */
	memset(&state, 0, sizeof(state));
	state.hrx.bufptr = state.hrx.buf;
	state.config = (struct adapter_config) { 19666600, 9600, 0, 0, 0, 1, 1, 0, 1 };
	state.chaccesspar = (struct chaccesspar) { 10, 10, 40, 3, 0 };
	/* parse arguments */
	while ((c = getopt_long(argc, argv, "lknvm:p:caz", long_options, NULL)) != EOF)
		switch (c) {
		case 'l':
			logging = 1;
			break;

		case 'k':
			kill_baycomeppd();
			exit(0);

		case 'n':
			daemon = 0;
			logpackets = 1;
			break;

		case 'v':
			verboselevel++;
			break;

		case 'm':
			parseconfig(&state.config, optarg);
			state.config.extstat = 1;
			break;

		case 'p': 
			iobase = strtoul(optarg, NULL, 0);
			if (iobase <= 0 || iobase >= 0x3f8)
				err++;
			break;

		case 0x400+MODE_EPPAFSK:
		case 'a':
			mode = MODE_EPPAFSK;
			break;

		case 0x400+MODE_ECP:
		case 'c':
			mode = MODE_ECP;
			break;

		case 0x400+MODE_EPPCONV:
		case 'z':
			mode = MODE_EPPCONV;
			break;

		case 0x400+MODE_EPP:
			mode = MODE_EPP;
			break;

#ifdef HAVE_MKISS
		case 0x500: /* ifname */
			if (strlen(optarg) >= IFNAMSIZ) {
				fprintf(stderr, "baycomepp: ifname \"%s\"too long\n", optarg);
				err++;
			}
			strncpy(ifcfg.ifname, optarg, sizeof(ifcfg.ifname)-1);
			ifcfg.ifname[sizeof(ifcfg.ifname)-1] = 0;
			break;

		case 0x501: /* hwaddr */
			if (convert_call_entry(optarg, ifcfg.hwaddr.ax25_call))
				err++;
			break;

		case 0x502: /* ipaddr */
			if (!inet_aton(optarg, &ifcfg.ipaddr)) {
				fprintf(stderr, "baycomepp: invalid ip address \"%s\"\n", optarg);
				err++;
			}
			break;

		case 0x503: /* netmask */
			if (!inet_aton(optarg, &ifcfg.netmask)) {
				fprintf(stderr, "baycomepp: invalid netmask \"%s\"\n", optarg);
				err++;
			}
			break;

		case 0x504: /* broadcast */
			if (!inet_aton(optarg, &ifcfg.broadcast)) {
				fprintf(stderr, "baycomepp: invalid broadcast address \"%s\"\n", optarg);
				err++;
			}
			break;

#else /* !HAVE_MKISS */
		case 0x600: /* iflink */
			strncpy(ifcfg.ifname, optarg, sizeof(ifcfg.ifname)-1);
			ifcfg.ifname[sizeof(ifcfg.ifname)-1] = 0;
			break;
#endif /* !HAVE_MKISS */

		default:
			err++;
			break;
		}
	if (err) {
		fprintf(stderr, "usage: baycomepp [-c <cfgfilename>] [-l] [-k] [-n] [-v] [-p <portaddr>] [-m <modestr>]\n"
#ifdef HAVE_MKISS
			"                 [--ifname=<name>] [--hwaddr=<call>] [--ipaddr=<ip>]\n"
			"                 [--netmask=<ip>] [--broadcast=<ip>]\n"
#else /* !HAVE_MKISS */
			"                 [--iflink=<name>]\n"
#endif /* !HAVE_MKISS */
			"  mode options:\n"
			"    intclk    uses FPGA internal clock; DON'T use if an external osc is on board\n"
			"    extclk    uses the external oscillator; recommended if the internal modem is used\n"
			"    intmodem  uses the internal modem\n"
			"    extmodem  uses a DF9IC type modem connected to the high speed modem connector\n"
			"    fclk=     sets the FPGA clock frequency (default 19.6666 MHz)\n"
			"    bitrate=  sets the bitrate for the FSK modem\n"
			"    loopback  sets an internal modem loopback\n"
			"    extstat   allows extended status readback\n"
			"    noextstat disallows extended status readback\n");
		exit(1);
	}
	if (parport_init_direct(iobase))
		errprintf(SEV_FATAL, "no parport found at 0x%x\n", iobase);	
	init(mode);
	if (!daemon) {
		setsid(); /* become a process group leader and drop controlling terminal */
		fclose(stdin); /* no more standard in */
		if (logging)
			openlog(progname, LOG_PID, LOG_DAEMON);
		init2(&ifcfg);
		return mode;
	}
	if (pipe(pfds)) {
		errstr(SEV_FATAL, "pipe\n");
		exit(1);
	}
	switch (pid = fork()) {
	case -1:
		errstr(SEV_FATAL, "fork\n");
		exit(1);

	case 0: /* child process */
		close(pfds[0]);
		setsid(); /* become a process group leader and drop controlling terminal */
		fclose(stdin); /* no more standard in */
		if (logging)
			openlog(progname, LOG_PID, LOG_DAEMON);
		init2(&ifcfg);
		if (write(pfds[1], &mode, sizeof(mode)) != sizeof(mode))
			errstr(SEV_ERROR, "write\n");
		close(pfds[1]);
		return mode;

	default:
		close(pfds[1]);
		err = read(pfds[0], &c, sizeof(c));
		if (err != sizeof(c))
			errprintf(SEV_FATAL, "baycomepp: modem init failed (read: %d errno: %d %s)\n",
				  err, errno, strerror(errno));
		write_pid(pid);
		fprintf(stderr, "baycomepp: child pid: %u\n", pid);
		break;
	}
	exit(0);
}
