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

/*
 *      baycom_usb.c  -- baycom USB radio modem driver.
 *
 *      Copyright (C) 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.
 *
 *  History:
 *   0.1  09.07.2000  Started
 */


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

#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/string.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/usb.h>
#include <linux/kmod.h>
#include <linux/if_arp.h>
#include <linux/smp_lock.h>

#include "baycom_usb.h"

#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
/* prototypes for ax25_encapsulate and ax25_rebuild_header */
#include <net/ax25.h> 
#endif /* CONFIG_AX25 || CONFIG_AX25_MODULE */

#define __KERNEL_SYSCALLS__
#include <linux/unistd.h>

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

#if LINUX_VERSION_CODE < 0x20400

#if 0
static int exec_usermodehelper(const char *path, char *argv[], char *envp[])
{
	int i;

	exit_files(current);
        set_fs(KERNEL_DS);      /* Allow execve args to be in kernel space. */
        current->uid = current->euid = current->fsuid = 0;
        if (execve(path, argv, envp) < 0)
                return -errno;
        return 0;
}
#endif
extern int exec_usermodehelper(char *program_path, char *argv[], char *envp[]);


#define rw_semaphore semaphore
#define init_rwsem init_MUTEX
#define down_read down
#define down_write down
#define up_read up
#define up_write up

struct usb_interface *usb_ifnum_to_if(struct usb_device *dev, unsigned ifnum)
{
        int i;

        for (i = 0; i < dev->actconfig->bNumInterfaces; i++)
                if (dev->actconfig->interface[i].altsetting[0].bInterfaceNumber == ifnum)
                        return &dev->actconfig->interface[i];

        return NULL;
}

#endif

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

#define BAYCOMUSB_VENDORID  0xbac0
#define BAYCOMUSB_PRODUCTID 0x6134

#define MODE_NONE       0
#define MODE_FSK        1
#define MODE_EXTERNAL   2
#define MODE_AFSK       3
#define MODE_AUDIO      4
#define MODE_BSCAN      5

#define MAXFLEN   512

#define NUMRX   1
#define NUMTX   1

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

struct baycomusb_priv {
        struct rw_semaphore devsem;    /* protects from disconnect */ 
	spinlock_t lock;               /* protects tx */
	struct usb_device *usbdev;

	struct {
		urb_t *urb[NUMRX];
		void *buf[NUMRX];
	} rx;

	struct {
		unsigned int flgrun, flgready;
		urb_t *urb[NUMTX];
		struct sk_buff *skb[NUMTX];
	} tx;

	urb_t *irqurb;
	unsigned char irqbuf[20];

	unsigned int mode;
	unsigned int bitrate;

	int slotcnt;

	struct channel_params {
		unsigned int txdelay;   /* the transmitter keyup delay in in flags */
		unsigned int slottime;  /* the slottime in ms; usually 100ms */
		unsigned int ppersist;  /* the p-persistence 0..255 */
		unsigned int fulldup;   /* setting this just makes them send even if DCD is on */
	} chpar;

	struct {
		unsigned int wr;
		unsigned int rd;
		unsigned char buf[64];
	} uart;

	struct net_device_stats stats;
};

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

static const char *bc_drvname = "baycomusb";

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

#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))

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

#define KISS_VERBOSE

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

#define PARAM_TXDELAY   1
#define PARAM_PERSIST   2
#define PARAM_SLOTTIME  3
#define PARAM_TXTAIL    4
#define PARAM_FULLDUP   5
#define PARAM_HARDWARE  6
#define PARAM_RETURN    255

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

/*
 *    eppconfig_path should be setable  via /proc/sys.
 */

static char baycomusb_path[256] = "/usr/sbin/baycomusb";

static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/usr/bin:/bin", NULL };

static int errno;

struct bayusbpar {
	unsigned int busnr;
	unsigned int devnr;
	unsigned char ifname[IFNAMSIZ];
};

static int exec_baycomusb(void *__p)
{
        struct bayusbpar *p = (struct bayusbpar *)__p;
        char devarg[64];
	char ifarg[128];
        char *argv[] = { baycomusb_path, "-s", devarg, ifarg, NULL};
        int i;

       /* set up arguments */
        sprintf(devarg, "--device=%u:%u", p->busnr, p->devnr);
	if (!p->ifname[0]) {
		ifarg[0] = 0;
		argv[3] = NULL;
	} else
	       	sprintf(ifarg, "--ifname=%s", p->ifname);
        printk(KERN_DEBUG "%s: %s -s %s %s\n", bc_drvname, baycomusb_path, devarg, ifarg);
        i = exec_usermodehelper(baycomusb_path, argv, envp);
        if (i < 0) {
                printk(KERN_ERR "%s: failed to exec %s -s %s %s, errno = %d\n",
                       bc_drvname, baycomusb_path, devarg, ifarg, i);
                return i;
        }
        return 0;
}

static int run_baycomusb(struct usb_device *usbdev, struct net_device *dev)
{
        int i, pid, r;
        mm_segment_t fs;
	struct bayusbpar p;

	p.busnr = usbdev->bus->busnum;
	p.devnr = usbdev->devnum;
	p.ifname[0] = 0;
	if (dev) {
		strncpy(p.ifname, dev->name, sizeof(p.ifname));
		p.ifname[sizeof(p.ifname)-1] = 0;
	}
        pid = kernel_thread(exec_baycomusb, &p, 0);
        if (pid < 0) {
                printk(KERN_ERR "%s: fork failed, errno %d\n", bc_drvname, -pid);
                return pid;
        }
        fs = get_fs();
        set_fs(KERNEL_DS);      /* Allow i to be in kernel space. */
        r = waitpid(pid, &i, __WCLONE);
        set_fs(fs);
        if (r != pid) {
                printk(KERN_ERR "%s: waitpid(%d) failed, returning %d\n",
                       bc_drvname, pid, r);
                return -1;
        }
        printk(KERN_DEBUG "%s: baycomusb returned %d\n", bc_drvname, i);
        return i;
}

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

static void inline do_kiss_params(struct baycomusb_priv *priv, unsigned char *data, unsigned long len)
{
#ifdef KISS_VERBOSE
#define PKP(a,b) printk(KERN_INFO "%s: channel params: " a "\n", bc_drvname, b)
#else /* KISS_VERBOSE */
#define PKP(a,b) 
#endif /* KISS_VERBOSE */
        if (len < 2)
                return;
        switch(data[0]) {
        case PARAM_TXDELAY:
                priv->chpar.txdelay = (data[1] * priv->bitrate + 799) / 800;
                if (!priv->chpar.txdelay)
			priv->chpar.txdelay = 1;
                PKP("TX delay = %ums", 10 * data[1]);
                break;
	case PARAM_PERSIST:   
                priv->chpar.ppersist = data[1];
                PKP("p persistence = %u", priv->chpar.ppersist);
                break;
        case PARAM_SLOTTIME:  
                priv->chpar.slottime = 10 * data[1];
                PKP("slot time = %ums", priv->chpar.slottime);
                break;
        case PARAM_TXTAIL:    
                PKP("TX tail = %ums", 10 * data[1]);
                break;
        case PARAM_FULLDUP:   
                priv->chpar.fulldup = !!data[1];
                PKP("%s duplex", priv->chpar.fulldup ? "full" : "half");
                break;
        default:
                break;
        }
#undef PKP
}

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

static unsigned short random_seed;

static inline unsigned short random_num(void)
{
        random_seed = 28629 * random_seed + 157;
        return random_seed;
}

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

static void txsubmitready(struct net_device *dev)
{
	struct baycomusb_priv *priv = dev->priv;
	unsigned long flags;
	unsigned int strt, i;
	struct sk_buff *skb;
	int err;

	spin_lock_irqsave(&priv->lock, flags);
	strt = priv->tx.flgready & ~priv->tx.flgrun;
	priv->tx.flgrun |= strt;
	spin_unlock_irqrestore(&priv->lock, flags);
	if (!strt)
		return;
	for (i = 0; i < NUMTX; i++) {
		if (!(strt & (1 << i)))
			continue;
		if ((err = usb_submit_urb(priv->tx.urb[i]))) {
			printk(KERN_ERR "%s: txsubmitready: submit error %d\n", bc_drvname, err);
			spin_lock_irqsave(&priv->lock, flags);
			priv->tx.flgready &= ~(1 << i);
			priv->tx.flgrun &= ~(1 << i);
			skb = priv->tx.skb[i];
			priv->tx.skb[i] = NULL;
			spin_unlock_irqrestore(&priv->lock, flags);
			dev_kfree_skb(skb);
			netif_start_queue(dev);
		}
	}
}

static void baycomusb_irq(struct urb *urb)
{
	struct net_device *dev = urb->context;
	struct baycomusb_priv *priv = dev->priv;
	unsigned int strtable, i;
	
	if (urb->status) {
		return;
	}
	if (urb->actual_length < 5) {
		return;
	}
	spin_lock(&priv->lock);
	for (i = 5; i < urb->actual_length; i++) {
		priv->uart.buf[priv->uart.wr] = priv->irqbuf[i];
		priv->uart.wr = (priv->uart.wr + 1) % sizeof(priv->uart.buf);
		if (priv->uart.wr == priv->uart.rd)
			priv->uart.rd = (priv->uart.rd + 1) % sizeof(priv->uart.buf);
	}
	strtable = priv->tx.flgready & ~priv->tx.flgrun;
	spin_unlock(&priv->lock);
	if (!strtable)
		priv->slotcnt = priv->chpar.slottime;
	else {
		priv->slotcnt -= 8;  /* 8ms IRQ interval */
		if (priv->slotcnt <= 0) {
			priv->slotcnt = priv->chpar.slottime;
			if (!(priv->irqbuf[0] & 0x08) && (random_num() % 256) > priv->chpar.ppersist)
				txsubmitready(dev);
		}
	}
}

static void baycomusb_txpacket(struct urb *urb)
{
	struct net_device *dev = urb->context;
	struct baycomusb_priv *priv = dev->priv;
	struct sk_buff *skb;
	unsigned int flgrdy, i;

	for (i = 0; i < NUMTX && priv->tx.urb[i] != urb; i++);
	if (i >= NUMTX) {
		printk(KERN_ERR "%s: txpacket unknown urb %p\n", bc_drvname, urb);
		return;
	}
	spin_lock(&priv->lock);
	skb = priv->tx.skb[i];
	priv->tx.skb[i] = NULL;
	priv->tx.flgrun &= ~(1 << i);
	priv->tx.flgready &= ~(1 << i);
	flgrdy = priv->tx.flgready;
	spin_unlock(&priv->lock);
	if (skb)
		dev_kfree_skb(skb);
	if (urb->status == USB_ST_URB_KILLED)
		return;
	if (urb->status) {
		printk(KERN_DEBUG "%s: txpacket status %d\n", bc_drvname, urb->status);	
		priv->stats.tx_errors++;
	} else
		priv->stats.tx_packets++;
	if (flgrdy != ((1 << NUMTX)-1))
		netif_wake_queue(dev);
}

static void baycomusb_rxpacket(struct urb *urb)
{
	struct net_device *dev = urb->context;
	struct baycomusb_priv *priv = dev->priv;
	struct sk_buff *skb;
	unsigned int pktlen, i;
	unsigned char *cp;
	int err;

	if (urb->status == USB_ST_URB_KILLED)
		return;
	for (i = 0; i < NUMRX && priv->rx.urb[i] != urb; i++);
	if (i >= NUMRX) {
		printk(KERN_ERR "%s: rxpacket unknown urb %p\n", bc_drvname, urb);
		return;
	}
	if (urb->status) {
		printk(KERN_DEBUG "%s: rxpacket status %d\n", bc_drvname, urb->status);
		priv->stats.rx_errors++;
	} else if (urb->actual_length > 2) {
		pktlen = urb->actual_length-2; /* remove CRC */
		if (!(skb = dev_alloc_skb(pktlen+1))) {
			printk(KERN_DEBUG "%s: memory squeeze, dropping packet\n", dev->name);
			priv->stats.rx_dropped++;
		} else {
			cp = skb_put(skb, pktlen+1);
			*cp++ = 0; /* KISS kludge */
			memcpy(cp, urb->transfer_buffer, pktlen);
			skb->dev = dev;
			skb->protocol = htons(ETH_P_AX25);
			skb->mac.raw = skb->data;
			skb->pkt_type = PACKET_HOST;
			netif_rx(skb);
			priv->stats.rx_packets++;
		}
	}
	if ((err = usb_submit_urb(urb)))
		printk(KERN_DEBUG "%s: rxpacket error %d resubmitting rx urb\n", bc_drvname, err);
}

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


/*
 * Open/initialize the board. This is called (in the current kernel)
 * sometime after booting when the 'ifconfig' program is run.
 *
 * This routine should set everything up anew at each open, even
 * registers that "should" only need to be set once at boot, so that
 * there is non-reboot way to recover if something goes wrong.
 */

static int baycomusb_open(struct net_device *dev)
{
	struct baycomusb_priv *priv = dev->priv;
	int ret = -ENOMEM;
	unsigned int i;

	for (i = 0; i < NUMTX; i++) {
		if (!(priv->tx.urb[i] = usb_alloc_urb(0)))
			goto out;
		priv->tx.urb[i]->dev = priv->usbdev;
		priv->tx.urb[i]->pipe = usb_sndbulkpipe(priv->usbdev, 2);
		priv->tx.urb[i]->complete = baycomusb_txpacket;
		priv->tx.urb[i]->context = dev;
		priv->tx.urb[i]->transfer_flags = (NUMTX > 1) ? USB_QUEUE_BULK : 0;
	}
	for (i = 0; i < NUMRX; i++) {
		if (!(priv->rx.urb[i] = usb_alloc_urb(0)) ||
		    !(priv->rx.buf[i] = kmalloc(MAXFLEN+2, GFP_KERNEL)))
			goto out;
		priv->rx.urb[i]->dev = priv->usbdev;
		priv->rx.urb[i]->pipe = usb_rcvbulkpipe(priv->usbdev, 2);
		priv->rx.urb[i]->transfer_buffer = priv->rx.buf[i];
		priv->rx.urb[i]->transfer_buffer_length = MAXFLEN+2;
		priv->rx.urb[i]->complete = baycomusb_rxpacket;
		priv->rx.urb[i]->context = dev;
		priv->rx.urb[i]->transfer_flags = (NUMRX > 1) ? USB_QUEUE_BULK : 0;
	}
	for (i = 0; i < NUMRX; i++)
		if ((ret = usb_submit_urb(priv->rx.urb[i]))) {
			printk(KERN_ERR "%s: error %d submitting receiver urb\n", bc_drvname, ret);
			ret = -EIO;
			goto out2;
		}
#if LINUX_VERSION_CODE < 0x20400
	dev->start = 1;
#endif
	netif_start_queue(dev);
	MOD_INC_USE_COUNT;
        return 0;

  out2:
	for (i = 0; i < NUMRX; i++) 
		usb_unlink_urb(priv->rx.urb[i]);
  out:
	for (i = 0; i < NUMRX; i++) {
		if (priv->rx.urb[i])
			usb_free_urb(priv->rx.urb[i]);
		if (priv->rx.buf[i])
			kfree(priv->rx.buf[i]);
		priv->rx.urb[i] = NULL;
		priv->rx.buf[i] = NULL;
	}
	for (i = 0; i < NUMTX; i++) {
		if (priv->tx.urb[i])
			usb_free_urb(priv->tx.urb[i]);
		priv->tx.urb[i] = NULL;
	}
	return ret;
}

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

static int baycomusb_close(struct net_device *dev)
{
	struct baycomusb_priv *priv = dev->priv;
	unsigned int i;
	unsigned long flags;

	netif_stop_queue(dev);
	spin_lock_irqsave(&priv->lock, flags);
	priv->tx.flgrun = 0;
	priv->tx.flgready = 0;
	spin_unlock_irqrestore(&priv->lock, flags);
	for (i = 0; i < NUMTX; i++) {
		usb_unlink_urb(priv->tx.urb[i]);
		usb_free_urb(priv->tx.urb[i]);
		priv->tx.urb[i] = NULL;
		priv->tx.skb[i] = NULL;
	}
	for (i = 0; i < NUMRX; i++) {
		usb_unlink_urb(priv->rx.urb[i]);
		usb_free_urb(priv->rx.urb[i]);
		kfree(priv->rx.buf[i]);
		priv->rx.urb[i] = NULL;
		priv->rx.buf[i] = NULL;
	}
#if LINUX_VERSION_CODE < 0x20400
	dev->start = 0;
#endif
        MOD_DEC_USE_COUNT;
        return 0;
}

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

static int baycomusb_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
	struct baycomusb_priv *priv = dev->priv;
	union {
		struct baycomusb_status stat;
		struct baycomusb_device dev;
		struct baycomusb_receiveuart rxuart;
		struct baycomusb_transmituart txuart;
		struct baycomusb_modemdisc mdisc;
		struct baycomusb_t7fcontrol t7f;
		struct baycomusb_forceptt fptt;
		struct baycomusb_setleds leds;
	} u;
	unsigned char buf[8];
	unsigned long flags;
	int i;

        if (cmd != SIOCDEVPRIVATE)
                return -ENOIOCTLCMD;
        if (get_user(cmd, (int *)ifr->ifr_data))
                return -EFAULT;
	switch (cmd) {
	case BAYCOMUSB_GETSTATUS:
		u.stat.cmd = cmd;
		spin_lock_irqsave(&priv->lock, flags);
		u.stat.ptt = !!(priv->irqbuf[0] & 0x04);
		u.stat.dcd = !!(priv->irqbuf[0] & 0x08);
		u.stat.rssi = priv->irqbuf[3];
		u.stat.uartchars = (sizeof(priv->uart.buf) + priv->uart.rd - priv->uart.wr) % sizeof(priv->uart.buf);
		spin_unlock_irqrestore(&priv->lock, flags);
		if (copy_to_user(ifr->ifr_data, &u.stat, sizeof(u.stat)))
                        return -EFAULT;
                return 0;

	case BAYCOMUSB_GETDEVICE:
		u.dev.cmd = cmd;
		u.dev.bitrate = priv->bitrate;
		u.dev.busnr = priv->usbdev->bus->busnum;
		u.dev.devnr = priv->usbdev->devnum;
		memset(u.dev.serial, 0, sizeof(u.dev.serial));
		down_read(&priv->devsem);
		i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 0xc9, 0xc0,
				    0, 0, u.dev.serial, sizeof(u.dev.serial)-1, 500);
		up_read(&priv->devsem);
		if (i < 0 || i >= sizeof(u.dev.serial))
			i = 0;
		u.dev.serial[i] = 0;
		if (copy_to_user(ifr->ifr_data, &u.dev, sizeof(u.dev)))
                        return -EFAULT;
		return 0;

	case BAYCOMUSB_RECEIVEUART:
		if (!capable(CAP_NET_ADMIN))
			return -EACCES;
		u.rxuart.cmd = cmd;
		u.rxuart.len = 0;
		memset(u.rxuart.buffer, 0, sizeof(u.rxuart.buffer));
		spin_lock_irqsave(&priv->lock, flags);
		while (u.rxuart.len < sizeof(u.rxuart.buffer) && priv->uart.rd != priv->uart.wr) {
			if (priv->uart.wr >= priv->uart.rd)
				i = priv->uart.wr - priv->uart.rd;
			else
				i = sizeof(u.rxuart.buffer) - priv->uart.rd;
			if (i + u.rxuart.len > sizeof(u.rxuart.buffer))
				i = sizeof(u.rxuart.buffer) - u.rxuart.len;
			memcpy(&u.rxuart.buffer[u.rxuart.len], &priv->uart.buf[priv->uart.rd], i);
			u.rxuart.len += i;
			priv->uart.rd = (priv->uart.rd + (unsigned int)i) % sizeof(priv->uart.buf);
		}
		spin_unlock_irqrestore(&priv->lock, flags);
		if (copy_to_user(ifr->ifr_data, &u.rxuart, sizeof(u.rxuart)))
                        return -EFAULT;
		return 0;	

	case BAYCOMUSB_TRANSMITUART:
		if (!capable(CAP_NET_ADMIN))
			return -EACCES;
		if (copy_from_user(&u.txuart, ifr->ifr_data, sizeof(u.txuart)))
                        return -EFAULT;
		spin_lock_irqsave(&priv->lock, flags);
		i = priv->irqbuf[0];
		priv->irqbuf[0] &= ~0x20;
		spin_unlock_irqrestore(&priv->lock, flags);
		if (!(i & 0x20))
			return -EBUSY;
		down_read(&priv->devsem);
		i = usb_control_msg(priv->usbdev, usb_sndctrlpipe(priv->usbdev, 0), 0xd3, 0x40, u.txuart.txchar, 0, NULL, 0, 500);
		up_read(&priv->devsem);
		return i;

	case BAYCOMUSB_MODEMDISC:
		if (priv->mode == MODE_EXTERNAL)
			return -EIO;
		if (copy_from_user(&u.mdisc, ifr->ifr_data, sizeof(u.mdisc)))
                        return -EFAULT;
		if ((u.mdisc.setdirection != -1 || u.mdisc.setoutput != -1) && !capable(CAP_NET_ADMIN))
			return -EACCES;
		down_read(&priv->devsem);
		if (u.mdisc.setoutput != -1) {
			i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 
					    0xd4, 0xc0, u.mdisc.setoutput, 1, NULL, 0, 500);
			if (i < 0) {
				up_read(&priv->devsem);
				return i;
			}
		}
		if (u.mdisc.setdirection != -1) {
			i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 
					    0xd4, 0xc0, u.mdisc.setdirection, 2, NULL, 0, 500);
			if (i < 0) {
				up_read(&priv->devsem);
				return i;
			}
		}
		i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 0xd4, 0xc0, 0, 0, buf, 3, 500);
		up_read(&priv->devsem);
		if (i < 0)
			return i;
		if (i != 3)
			return -EIO;
		u.mdisc.input = buf[0];
		u.mdisc.output = buf[1];
		u.mdisc.direction = buf[2];
		if (copy_to_user(ifr->ifr_data, &u.mdisc, sizeof(u.mdisc)))
                        return -EFAULT;
		return 0;

	case BAYCOMUSB_T7FCONTROL:
		if (copy_from_user(&u.t7f, ifr->ifr_data, sizeof(u.t7f)))
                        return -EFAULT;
		if (u.t7f.setoutput != -1 && !capable(CAP_NET_ADMIN))
			return -EACCES;
		down_read(&priv->devsem);
		if (u.t7f.setoutput != -1) {
			i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 
					    0xd5, 0xc0, u.t7f.setoutput, 1, NULL, 0, 500);
			if (i < 0) {
				up_read(&priv->devsem);
				return i;
			}
		}
		i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 0xd5, 0xc0, 0, 0, buf, 2, 500);
		up_read(&priv->devsem);
		if (i < 0)
			return i;
		if (i != 2)
			return -EIO;
		u.t7f.input = buf[0];
		u.t7f.output = buf[1];
		if (copy_to_user(ifr->ifr_data, &u.t7f, sizeof(u.t7f)))
                        return -EFAULT;
		return 0;

	case BAYCOMUSB_FORCEPTT:
		if (!capable(CAP_NET_ADMIN))
			return -EACCES;
		if (copy_from_user(&u.fptt, ifr->ifr_data, sizeof(u.fptt)))
                        return -EFAULT;
		down_read(&priv->devsem);
		i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 0xd0, 0xc0, u.fptt.ptt, 1, NULL, 0, 500);
		up_read(&priv->devsem);
		if (i < 0)
			return i;
		return 0;

	case BAYCOMUSB_SETLEDS:
		if (!capable(CAP_NET_ADMIN))
			return -EACCES;
		if (copy_from_user(&u.leds, ifr->ifr_data, sizeof(u.leds)))
                        return -EFAULT;
		down_read(&priv->devsem);
		i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 0xd2, 0x40, u.leds.leds, 0, NULL, 0, 500);
		up_read(&priv->devsem);
		if (i < 0)
			return i;
		return 0;

	default:
		return -ENOIOCTLCMD;
	}
        return 0;
}

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

static int baycomusb_set_mac_address(struct net_device *dev, void *addr)
{
        struct sockaddr *sa = (struct sockaddr *)addr;

        /* addr is an AX.25 shifted ASCII mac address */
        memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); 
        return 0;                                         
}

static struct net_device_stats *baycomusb_get_stats(struct net_device *dev)
{
	struct baycomusb_priv *priv = dev->priv;

        /* 
         * Get the current statistics.  This may be called with the
         * card open or closed. 
         */
        return &priv->stats;
}

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

static int baycomusb_send_packet(struct sk_buff *skb, struct net_device *dev)
{
	struct baycomusb_priv *priv = dev->priv;
	unsigned long flags;
	unsigned int txd, newrdy, i;
	struct sk_buff *skb2;

        if (skb->data[0] != 0) {
                do_kiss_params(priv, skb->data, skb->len);
                dev_kfree_skb(skb);
                return 0;
        }
	if (skb->len < 2) {
		dev_kfree_skb(skb);
                return 0;
        }
	if (skb_headroom(skb) < 1) {
		printk(KERN_ERR "%s: tx packet headroom < 1\n", bc_drvname);
		dev_kfree_skb(skb);
                return 0;
        }
	if (!(skb2 = skb_unshare(skb, GFP_ATOMIC))) {
		printk(KERN_ERR "%s: skb unshare failed\n", bc_drvname);
		return 0;
	}
	skb = skb2;
	txd = priv->chpar.txdelay;
	skb->data[-1] = txd;
	skb->data[0] = txd >> 8;
	spin_lock_irqsave(&priv->lock, flags);
	for (i = 0; i < NUMTX; i++)
		if (!(priv->tx.flgready & (1 << i)))
			break;
	if (i < NUMTX) {
	        priv->tx.urb[i]->transfer_buffer = skb->data - 1;
		priv->tx.urb[i]->transfer_buffer_length = skb->len + 1;
		priv->tx.skb[i] = skb;
		priv->tx.flgready |= (1 << i);
	}
	newrdy = priv->tx.flgready;
	spin_unlock_irqrestore(&priv->lock, flags);
	if (newrdy == ((1 << NUMTX)-1))
		netif_stop_queue(dev);
	if (i >= NUMTX) {
		printk(KERN_DEBUG "%s: send_packet: queue overrun!\n", bc_drvname);
		netif_stop_queue(dev);
		dev_kfree_skb(skb);
		return 0;
	}
	/* check for half duplex; start channel access state machine */
	if (priv->chpar.fulldup || (priv->irqbuf[0] & 0x04))
		txsubmitready(dev);
	return 0;
}

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

static int baycomusb_netinit(struct net_device *dev)
{
        static char ax25_bcast[AX25_ADDR_LEN] = {
                'Q' << 1, 'S' << 1, 'T' << 1, ' ' << 1, ' ' << 1, ' ' << 1, '0' << 1
        };
        static char ax25_nocall[AX25_ADDR_LEN] = {
                'L' << 1, 'I' << 1, 'N' << 1, 'U' << 1, 'X' << 1, ' ' << 1, '1' << 1
        };
	struct baycomusb_priv *priv = dev->priv;
        const struct channel_params dfltchpar = { 
                20, 100, 40, 0 
        };

        /*
         * initialize the private struct
         */
	priv->chpar = dfltchpar;
	priv->chpar.txdelay = (dfltchpar.txdelay * priv->bitrate + 799) / 800;
	if (!priv->chpar.txdelay)
		priv->chpar.txdelay = 1;

        /*
         * initialize the device struct
         */
        dev->open = baycomusb_open;
        dev->stop = baycomusb_close;
        dev->do_ioctl = baycomusb_ioctl;
        dev->hard_start_xmit = baycomusb_send_packet;
        dev->get_stats = baycomusb_get_stats;

        /* Fill in the fields of the device structure */
        dev_init_buffers(dev);
        
#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
        dev->hard_header = ax25_encapsulate;
        dev->rebuild_header = ax25_rebuild_header;
#else /* CONFIG_AX25 || CONFIG_AX25_MODULE */
        dev->hard_header = NULL;
        dev->rebuild_header = NULL;
#endif /* CONFIG_AX25 || CONFIG_AX25_MODULE */
        dev->set_mac_address = baycomusb_set_mac_address;
        
        dev->type = ARPHRD_AX25;           /* AF_AX25 device */
        dev->hard_header_len = AX25_MAX_HEADER_LEN + AX25_BPQ_HEADER_LEN + 2; /* +2 to make room for TxDelay word */
        dev->mtu = AX25_DEF_PACLEN;        /* eth_mtu is the default */
        dev->addr_len = AX25_ADDR_LEN;     /* sizeof an ax.25 address */
        memcpy(dev->broadcast, ax25_bcast, AX25_ADDR_LEN);
        memcpy(dev->dev_addr, ax25_nocall, AX25_ADDR_LEN);
        dev->tx_queue_len = 16;

        /* New style flags */
        dev->flags = 0;

        return 0;
}

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

static void *baycomusb_probe(struct usb_device *usbdev, unsigned int ifnum);
static void baycomusb_disconnect(struct usb_device *usbdev, void *ptr);

static struct usb_driver baycomusb_driver = {
        "baycomusb",
        baycomusb_probe,
        baycomusb_disconnect,
        { NULL, NULL }
};

static void *baycomusb_probe(struct usb_device *usbdev, unsigned int ifnum)
{
	static unsigned int netifnumber = 0;
	struct net_device *dev;
	struct baycomusb_priv *priv;
	unsigned int bitrate = 0;
	unsigned char mode, buf[3];
	int actlen;

        if ((usbdev->descriptor.idVendor != BAYCOMUSB_VENDORID ||
	     usbdev->descriptor.idProduct != BAYCOMUSB_PRODUCTID))
                return NULL;
	/* We don't handle multiple configurations */
        if (usbdev->descriptor.bNumConfigurations != 1)
                return NULL;
	/* we do all probing for the first interface */
	if (ifnum)
		return NULL;
	/* check if already configured */
	if (usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0xc8, 0xc0, 0, 0, &mode, 1, 500) != 1) {
		printk(KERN_DEBUG "%s: uninitialized modem found at %03d:%03d\n",
		       bc_drvname, usbdev->bus->busnum, usbdev->devnum);
		/* run usermode helper to download firmware to modem */
#if 0
		run_baycomusb(usbdev, NULL);
#endif
		return NULL;
	}
	printk(KERN_DEBUG "%s: initialized modem found at %03d:%03d, mode %u\n",
	       bc_drvname, usbdev->bus->busnum, usbdev->devnum, (unsigned int)mode);
	if (mode != MODE_FSK && mode != MODE_EXTERNAL && mode != MODE_AFSK && mode != MODE_AUDIO)
		return NULL;
	if (mode != MODE_AUDIO) {
		if (usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), 0xd1, 0xc0, 0, 0, buf, 3, 500) != 3) {
			printk(KERN_DEBUG "%s: cannot retrieve bitrate from device %03d:%03d\n",
			       bc_drvname, usbdev->bus->busnum, usbdev->devnum);
			return NULL;
		}
		bitrate = (buf[2] << 16) | (buf[1] << 8) | buf[0];
	}
	printk(KERN_DEBUG "%s: modem at %03d:%03d has bitrate %u\n", 
	       bc_drvname, usbdev->bus->busnum, usbdev->devnum, bitrate);
	if (usb_set_configuration(usbdev, usbdev->config[0].bConfigurationValue) < 0) {
                printk(KERN_WARNING "%s: usb_set_configuration failed\n", bc_drvname);
                return NULL;
        }
	if (usb_set_interface(usbdev, 0, 1) < 0) {
                printk(KERN_WARNING "%s: usb_set_interface failed\n", bc_drvname);
                return NULL;
        }
	if (mode != MODE_AUDIO) {
		/* kludge: the first OUT2 packet seems to arrive corrupted, so send this special "magic" packet */
		buf[0] = 0x55;
		if (usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 2), buf, 1, &actlen, 500) || actlen != 1) {
			printk(KERN_ERR "%s: usb_bulk_msg error device %03d:%03d\n",
			       bc_drvname, usbdev->bus->busnum, usbdev->devnum);
		}
	}
	dev = kmalloc(sizeof(struct net_device), GFP_KERNEL);
	memset(dev, 0, sizeof(struct net_device));
	priv = dev->priv = kmalloc(sizeof(struct baycomusb_priv), GFP_KERNEL);
	memset(priv, 0, sizeof(struct baycomusb_priv));
	dev->init = baycomusb_netinit;
#if LINUX_VERSION_CODE < 0x20400
	dev->name = kmalloc(IFNAMSIZ, GFP_KERNEL);
#endif
	sprintf(dev->name, "bcux%d", netifnumber++);
        init_rwsem(&priv->devsem);
	spin_lock_init(&priv->lock);
	priv->usbdev = usbdev;
	priv->mode = mode;
	priv->bitrate = bitrate;

	if (register_netdev(dev)) {
		printk(KERN_WARNING "%s: cannot register net device %s\n", bc_drvname, dev->name);
		kfree(dev->priv);
		kfree(dev);
		return NULL;
	}
	
	priv->irqurb = usb_alloc_urb(0);
	priv->irqurb->dev = usbdev;
        priv->irqurb->pipe = usb_rcvintpipe(usbdev, 1);
        priv->irqurb->transfer_buffer = priv->irqbuf;
        priv->irqurb->transfer_buffer_length = sizeof(priv->irqbuf);
        priv->irqurb->complete = baycomusb_irq;
        priv->irqurb->context = dev;
        priv->irqurb->interval = 8;
        priv->irqurb->start_frame = -1;
	if (usb_submit_urb(priv->irqurb)) {
		printk(KERN_WARNING "%s: cannot start irq URB\n", bc_drvname);
		usb_free_urb(priv->irqurb);
		unregister_netdev(dev);
		kfree(dev->priv);
		kfree(dev);
		return NULL;
	}

#if 0
	/* run usermode helper to perform final configuration */
	run_baycomusb(usbdev, dev);
#endif

        MOD_INC_USE_COUNT;
        return dev;
}

static void baycomusb_disconnect(struct usb_device *usbdev, void *ptr)
{
	struct net_device *dev = ptr;
	struct baycomusb_priv *priv = dev->priv;

	down_write(&priv->devsem);
	unregister_netdev(dev);
	up_write(&priv->devsem);
	usb_unlink_urb(priv->irqurb);
	usb_free_urb(priv->irqurb);
	kfree(dev->priv);
#if LINUX_VERSION_CODE < 0x20400
	kfree(dev->name);
#endif
	kfree(dev);
        MOD_DEC_USE_COUNT;
}

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

MODULE_AUTHOR("Thomas M. Sailer, sailer@ife.ee.ethz.ch");
MODULE_DESCRIPTION("Baycom USB ham radio modem driver");

static int __init baycomusb_init(void)
{
        if (usb_register(&baycomusb_driver) < 0)
                return -1;
        printk(KERN_INFO "%s: Baycom USB driver registered.\n"
               KERN_INFO "%s: (C) 2000 by Thomas Sailer, <sailer@ife.ee.ethz.ch>\n", bc_drvname, bc_drvname);
        return 0;
}

static void __exit baycomusb_cleanup(void)
{
        usb_deregister(&baycomusb_driver);
}

module_init(baycomusb_init);
module_exit(baycomusb_cleanup);

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