Logo Search packages:      
Sourcecode: baycomusb version File versions  Download package

baycom_usb.c

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

/*
 *      baycom_usb.c  -- baycom USB radio modem driver.
 *
 *      Copyright (C) 2000-2001  Thomas Sailer (t.sailer@alumni.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[]);

#if 0
#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
#endif

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_EMPTY  0x6134
#define BAYCOMUSB_PRODUCTID_FPGALD 0x6135
#define BAYCOMUSB_PRODUCTID_MODEM  0x6136
#define BAYCOMUSB_PRODUCTID_AUDIO  0x6137

#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

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

static unsigned int txqueuelen = 1;

MODULE_PARM(txqueuelen, "i");
MODULE_PARM_DESC(txqueuelen, "transmit queue length");

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

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

#if LINUX_VERSION_CODE < 0x20400
      char ifname[IFNAMSIZ];
#endif

      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";

static int baycomusb_open(struct inode *inode, struct file *file);
static int baycomusb_release(struct inode *inode, struct file *file);
static int baycomusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
static ssize_t baycomusb_write(struct file *file, const char *buffer, size_t count, loff_t *ppos);
static ssize_t baycomusb_read(struct file *file, char *buffer, size_t count, loff_t *ppos);

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

static struct file_operations baycomusb_fops = {
        read:           baycomusb_read,
        write:          baycomusb_write,
        ioctl:          baycomusb_ioctl,
        open:           baycomusb_open,
        release:        baycomusb_release,
};

static struct usb_driver baycomusb_driver = {
        name:        "baycomusb",
        probe:       baycomusb_probe,
        disconnect:  baycomusb_disconnect,
      fops:        &baycomusb_fops,
      minor:       BAYCOMUSB_MINOR_BASE
      
};

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

#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 baycomusb_priv *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;
            priv->tx.urb[i]->dev = priv->usbdev;
            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((&priv->netdev));
            }
      }
}

static void baycomusb_irq(struct urb *urb)
{
      struct baycomusb_priv *priv = urb->context;
      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(priv);
            }
      }
#if 0
      printk(KERN_DEBUG "baycom_usb: irq: %02x %02x %02x %02x %02x %s TX: %02x/%02x \n",
             priv->irqbuf[0], priv->irqbuf[1], priv->irqbuf[2], priv->irqbuf[3], priv->irqbuf[4],
             netif_queue_stopped((&priv->netdev)) ? "XOFF" : "XON ", priv->tx.flgrun, priv->tx.flgready);
#endif
}

static void baycomusb_txpacket(struct urb *urb)
{
      struct baycomusb_priv *priv = urb->context;
      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((&priv->netdev));  /* double paren for kcomp.h define */
}

static void baycomusb_rxpacket(struct urb *urb)
{
      struct baycomusb_priv *priv = urb->context;
      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", priv->netdev.name);
                  priv->stats.rx_dropped++;
            } else {
                  cp = skb_put(skb, pktlen+1);
                  *cp++ = 0; /* KISS kludge */
                  memcpy(cp, urb->transfer_buffer, pktlen);
                  skb->dev = &priv->netdev;
                  skb->protocol = htons(ETH_P_AX25);
                  skb->mac.raw = skb->data;
                  skb->pkt_type = PACKET_HOST;
                  netif_rx(skb);
                  priv->stats.rx_packets++;
            }
      }
      urb->dev = priv->usbdev;
      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_opennet(struct net_device *dev)
{
      struct baycomusb_priv *priv = dev->priv;
      int ret = -ENOMEM;
      unsigned int i;

      printk(KERN_DEBUG "opennet: dev %p priv %p usbdev %p\n", dev, priv, priv->usbdev);

      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 = priv;
            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 = priv;
            priv->rx.urb[i]->transfer_flags = (NUMRX > 1) ? USB_QUEUE_BULK : 0;
      }
      for (i = 0; i < NUMRX; i++) {
printk(KERN_DEBUG "%s: submitting RX URB %u\n", bc_drvname, 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_closenet(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++) {
printk(KERN_DEBUG "%s: unlinking RX URB %u\n", bc_drvname, 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_ioctlnet(struct net_device *dev, struct ifreq *ifr, int cmd)
{
      struct baycomusb_priv *priv = dev->priv;

        if (cmd != SIOCDEVPRIVATE)
                return -ENOIOCTLCMD;
        return -ENOIOCTLCMD;
}

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

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(priv);
      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_opennet;
        dev->stop = baycomusb_closenet;
        dev->do_ioctl = baycomusb_ioctlnet;
        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 = txqueuelen;

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

        return 0;
}

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

static int baycomusb_open(struct inode *inode, struct file *file)
{
      struct baycomusb_priv *priv;

      if (MINOR(inode->i_rdev) != BAYCOMUSB_MINOR_BASE)
            return -ENODEV;
      if (!(priv = kmalloc(sizeof(struct baycomusb_priv), GFP_KERNEL)))
            return -ENOMEM;
      memset(priv, 0, sizeof(struct baycomusb_priv));
        init_rwsem(&priv->devsem);
      spin_lock_init(&priv->lock);
      priv->netdev.priv = priv;
#if LINUX_VERSION_CODE < 0x20400
      priv->netdev.name = priv->ifname;
#endif
      file->private_data = priv;
      MOD_INC_USE_COUNT;
      return 0;
}

static int baycomusb_release(struct inode *inode, struct file *file)
{
        struct baycomusb_priv *priv = file->private_data;

      lock_kernel();  /* lock against hub thread simultaneously calling disconnect */
      down_write(&priv->devsem);
      if (priv->usbdev) {
            if (priv->mode != MODE_AUDIO)
                  unregister_netdev(&priv->netdev);
            usb_unlink_urb(priv->irqurb);
            usb_free_urb(priv->irqurb);
            usb_driver_release_interface(&baycomusb_driver, priv->usbiface);
            priv->usbdev = NULL;
      }
      up_write(&priv->devsem);
      kfree(priv);
      unlock_kernel();
      MOD_DEC_USE_COUNT;
        return 0;
}


/*
 * the following 3 routines should only be called when holding the kernel lock;
 * to prevent the hub thread from changing the topology 
 */

static struct usb_bus *baycomusb_findbus(int busnr)
{
        struct list_head *list;
        struct usb_bus *bus;

        for (list = usb_bus_list.next; list != &usb_bus_list; list = list->next) {
                bus = list_entry(list, struct usb_bus, bus_list);
                if (bus->busnum == busnr)
                        return bus;
        }
        return NULL;
}

static struct usb_device *finddev(struct usb_device *dev, int devnr)
{
        unsigned int i;
        struct usb_device *d2;

        if (!dev)
                return NULL;
        if (dev->devnum == devnr)
                return dev;
        for (i = 0; i < dev->maxchild; i++) {
                if (!dev->children[i])
                        continue;
                if ((d2 = finddev(dev->children[i], devnr)))
                        return d2;
        }
        return NULL;
}

static struct usb_device *baycomusb_finddevice(struct usb_bus *bus, int devnr)
{
        return finddev(bus->root_hub, devnr);
}


static int start_netdev(struct baycomusb_priv *priv, struct baycomusb_startnetdev *stnetdev)
{
      struct usb_bus *bus;
      struct usb_device *dev;
      unsigned char mode, buf[3];
      unsigned int bitrate = 0;

      if (priv->usbdev)
            return -EBUSY;
      if (!(bus = baycomusb_findbus(stnetdev->busnr)))
            return -ENODEV;
      if (!(dev = baycomusb_finddevice(bus, stnetdev->devnr)))
            return -ENODEV;
        if ((dev->descriptor.idVendor != BAYCOMUSB_VENDORID ||
           (dev->descriptor.idProduct != BAYCOMUSB_PRODUCTID_MODEM &&
              dev->descriptor.idProduct != BAYCOMUSB_PRODUCTID_AUDIO)))
            return -ENODEV;
      /* We don't handle multiple configurations */
        if (dev->descriptor.bNumConfigurations != 1)
                return -ENODEV;
      /* check if interface still free */
      if (!(priv->usbiface = usb_ifnum_to_if(dev, 0)))
            return -ENODEV;
      if (usb_interface_claimed(priv->usbiface))
            return -EBUSY;
      /* check if already configured */
      if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), 0xc8, 0xc0, 0, 0, &mode, 1, 500) != 1) {
            printk(KERN_DEBUG "%s: uninitialized modem found at %03d:%03d\n", bc_drvname, dev->bus->busnum, dev->devnum);
            return -EIO;
      }
      printk(KERN_DEBUG "%s: initialized modem found at %03d:%03d, mode %u\n",
             bc_drvname, dev->bus->busnum, dev->devnum, (unsigned int)mode);
      if (mode != MODE_FSK && mode != MODE_EXTERNAL && mode != MODE_AFSK && mode != MODE_AUDIO)
            return -EIO;
      if (mode != MODE_AUDIO) {
            if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), 0xd1, 0xc0, 0, 0, buf, 3, 500) != 3) {
                  printk(KERN_DEBUG "%s: cannot retrieve bitrate from device %03d:%03d\n",
                         bc_drvname, dev->bus->busnum, dev->devnum);
                  return -EIO;
            }
            bitrate = (buf[2] << 16) | (buf[1] << 8) | buf[0];
      }
      printk(KERN_DEBUG "%s: modem at %03d:%03d has bitrate %u\n", 
             bc_drvname, dev->bus->busnum, dev->devnum, bitrate);
      if (usb_set_configuration(dev, dev->config[0].bConfigurationValue) < 0) {
                printk(KERN_WARNING "%s: usb_set_configuration failed\n", bc_drvname);
                return -EIO;
        }
      if (usb_set_interface(dev, 0, 1) < 0) {
                printk(KERN_WARNING "%s: usb_set_interface failed\n", bc_drvname);
                return -EIO;
        }
      strncpy(priv->netdev.name, stnetdev->ifname, IFNAMSIZ);
      priv->netdev.name[IFNAMSIZ-1] = 0;
      priv->mode = mode;
      priv->bitrate = bitrate;
        priv->netdev.init = baycomusb_netinit;
      priv->usbdev = dev;
      priv->irqurb = usb_alloc_urb(0);
      priv->irqurb->dev = dev;
        priv->irqurb->pipe = usb_rcvintpipe(dev, 1);
        priv->irqurb->transfer_buffer = priv->irqbuf;
        priv->irqurb->transfer_buffer_length = sizeof(priv->irqbuf);
        priv->irqurb->complete = baycomusb_irq;
        priv->irqurb->context = priv;
        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);
            return -EIO;
      }
      if (priv->mode != MODE_AUDIO) {
            if (register_netdev(&priv->netdev)) {
                  printk(KERN_WARNING "%s: cannot register net device %s\n", bc_drvname, priv->netdev.name);
                  usb_unlink_urb(priv->irqurb);
                  usb_free_urb(priv->irqurb);
                  return -EEXIST;
            }
      }
      usb_driver_claim_interface(&baycomusb_driver, priv->usbiface, priv);
      return 0;
}

static int baycomusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
        struct baycomusb_priv *priv = file->private_data;
      union {
            struct baycomusb_startnetdev stnetdev;
            struct baycomusb_status stat;
            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 r, i;

      switch (cmd) {
      case BAYCOMUSB_STARTNETDEV:
            if (copy_from_user(&u.stnetdev, (void *)arg, sizeof(u.stnetdev)))
                  return -EFAULT;
            lock_kernel();
            down_write(&priv->devsem);
            r = start_netdev(priv, &u.stnetdev);
            up_write(&priv->devsem);
            unlock_kernel();
            return r;

      case BAYCOMUSB_GETSTATUS:
            if (!priv->usbdev)
                  return -ENODEV;
            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((void *)arg, &u.stat, sizeof(u.stat)))
                        return -EFAULT;
                return 0;

      case BAYCOMUSB_RECEIVEUART:
            if (!capable(CAP_NET_ADMIN))
                  return -EACCES;
            if (!priv->usbdev)
                  return -ENODEV;
            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((void *)arg, &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, (void *)arg, 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);
            if (priv->usbdev)
                  i = usb_control_msg(priv->usbdev, usb_sndctrlpipe(priv->usbdev, 0), 0xd3, 0x40, u.txuart.txchar, 0, NULL, 0, 500);
            else
                  i = -ENODEV;
            up_read(&priv->devsem);
            return i;

      case BAYCOMUSB_MODEMDISC:
            if (priv->mode == MODE_EXTERNAL)
                  return -EIO;
            if (copy_from_user(&u.mdisc, (void *)arg, 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 (!priv->usbdev) {
                  up_read(&priv->devsem);
                  return -ENODEV;
            }
            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((void *)arg, &u.mdisc, sizeof(u.mdisc)))
                        return -EFAULT;
            return 0;

      case BAYCOMUSB_T7FCONTROL:
            if (copy_from_user(&u.t7f, (void *)arg, sizeof(u.t7f)))
                        return -EFAULT;
            if (u.t7f.setoutput != -1 && !capable(CAP_NET_ADMIN))
                  return -EACCES;
            down_read(&priv->devsem);
            if (!priv->usbdev) {
                  up_read(&priv->devsem);
                  return -ENODEV;
            }
            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((void *)arg, &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, (void *)arg, sizeof(u.fptt)))
                        return -EFAULT;
            down_read(&priv->devsem);
            if (priv->usbdev)
                  i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 0xd0, 0xc0, u.fptt.ptt, 1, NULL, 0, 500);
            else
                  i = -ENODEV;
            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, (void *)arg, sizeof(u.leds)))
                        return -EFAULT;
            down_read(&priv->devsem);
            if (priv->usbdev)
                  i = usb_control_msg(priv->usbdev, usb_rcvctrlpipe(priv->usbdev, 0), 0xd2, 0x40, u.leds.leds, 0, NULL, 0, 500);
            else
                  i = -ENODEV;
            up_read(&priv->devsem);
            if (i < 0)
                  return i;
            return 0;

      default:
            return -ENOIOCTLCMD;
      }
        return 0;
}

static ssize_t baycomusb_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
        struct baycomusb_priv *priv = file->private_data;

      return -EIO;
}

static ssize_t baycomusb_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
        struct baycomusb_priv *priv = file->private_data;

      return -EIO;
}

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

static void *baycomusb_probe(struct usb_device *usbdev, unsigned int ifnum, const struct usb_device_id *id)
{
      return NULL;
}

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

      down_write(&priv->devsem);
      if (priv->usbdev) {
            if (priv->mode != MODE_AUDIO)
                  unregister_netdev(&priv->netdev);
            usb_unlink_urb(priv->irqurb);
            usb_free_urb(priv->irqurb);
            usb_driver_release_interface(&baycomusb_driver, priv->usbiface);
            priv->usbdev = NULL;
      }
      up_write(&priv->devsem);
}

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

static struct usb_device_id baycom_usb_table[] = {
        { USB_DEVICE(BAYCOMUSB_VENDORID, BAYCOMUSB_PRODUCTID_MODEM) },
        { USB_DEVICE(BAYCOMUSB_VENDORID, BAYCOMUSB_PRODUCTID_AUDIO) },
        { }                                     /* Terminating entry */
};

MODULE_DEVICE_TABLE(usb, baycom_usb_table);

MODULE_AUTHOR("Thomas M. Sailer, t.sailer@alumni.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-2001 by Thomas Sailer, <t.sailer@alumni.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);

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

Generated by  Doxygen 1.6.0   Back to index