patch-2.4.0-test11 linux/drivers/usb/plusb.c
Next file: linux/drivers/usb/plusb.h
Previous file: linux/drivers/usb/pegasus.h
Back to the patch index
Back to the overall index
- Lines: 1048
- Date:
Wed Nov 8 17:03:09 2000
- Orig file:
v2.4.0-test10/linux/drivers/usb/plusb.c
- Orig date:
Tue Oct 31 12:42:27 2000
diff -u --recursive --new-file v2.4.0-test10/linux/drivers/usb/plusb.c linux/drivers/usb/plusb.c
@@ -1,9 +1,11 @@
/*****************************************************************************/
/*
- * plusb.c -- prolific pl-2302 driver.
+ * plusb.c -- prolific pl-2301/pl-2302 driver.
*
* Copyright (C) 2000 Deti Fliegl (deti@fliegl.de)
+ * Copyright (C) 2000 Pavel Machek (pavel@suse.cz)
+ * Copyright (C) 2000 Eric Z. Ayers (eric@compgen.com)
*
* 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
@@ -20,9 +22,100 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*
+ * This driver creates a network interface (plusb0, plusb1, ...) that will
+ * send messages over a USB host-host cable based on the Prolific ASIC.
+ * It works a lot like plip or PP over an RS-232C null modem cable.
+ *
+ * Expect speeds of around 330Kbytes/second over a UHCI host controller.
+ * OHCI should be faster. Increase the MTU for faster transfers of large
+ * files. (16384 is a good size)
*
* $Id: plusb.c,v 1.18 2000/02/14 10:38:58 fliegl Exp $
*
+ * Changelog:
+ *
+ * v0.1 deti
+ * Original Version of driver.
+ * v0.2 15 Sep 2000 pavel
+ * Patches to decrease latency by rescheduling the bottom half of
+ * interrupt code.
+ * v0.3 10 Oct 2000 eric
+ * Patches to work in v2.2 backport (v2.4 changes the way net_dev.name
+ * is allocated)
+ * v0.4 19 Oct 2000 eric
+ * Some more performance fixes. Lock re-submitting urbs.
+ * Lower the number of sk_buff's to queue.
+ * v0.5 25 Oct 2000 eric
+ * Removed use of usb_bulk_msg() all together. This caused
+ * the driver to block in an interrupt context.
+ * Consolidate read urb submission into read_urb_submit().
+ * Performance is the same as v0.4.
+ * v0.5.1 27 Oct 2000 eric
+ * Extra debugging messages to help diagnose problem with uchi.o stack.
+ * v0.5.2 27 Oct 2000 eric
+ * Set the 'start' flag for the network device in plusb_net_start()
+ * and plusb_net_stop() (doesn't help)
+ * v0.5.3 27 Oct 2000 pavel
+ * Commented out handlers when -EPIPE is received,
+ * (remove calls to usb_clear_halt()) Since the callback is in
+ * an interrupt context, it doesn't help, it just panics
+ * the kernel. (what do we do?)
+ * Under high load, dev_alloc_skb() fails, the read URB must
+ * be re-submitted.
+ * Added plusb_change_mtu() and increased the size of _BULK_DATA_LEN
+ * v0.5.4 31 Oct 2000 eric
+ * Fix race between plusb_net_xmit() and plusb_bulk_write_complete()
+ * v0.5.5 1 Nov 2000 eric
+ * Remove dev->start field, otherwise, it won't compile in 2.4
+ * Use dev_kfree_skb_any(). (important in 2.4 kernel)
+ * v0.5.6 2 Nov 2000 pavel,eric
+ * Add calls to netif_stop_queue() and netif_start_queue()
+ * Drop packets that come in while the free list is empty.
+ * (This version is being submitted after the release of 2.4-test10)
+ * v0.5.7 6 Nov 2000
+ * Fix to not re-submit the urb on error to help when cables
+ * are yanked (not tested)
+ *
+ *
+ * KNOWN PROBLEMS: (Any suggestions greatfully accepted!)
+ *
+ * 2 Nov 2000
+ * - The shutdown for this may not be entirely clean. Sometimes, the
+ * kernel will Oops when the cable is unplugged, or
+ * if the plusb module is removed.
+ * - If you ifdown a device and then ifup it again, the link will not
+ * always work. You have to 'rmmod plusb ; modprobe plusb' on
+ * both machines to get it to work again. Something must be wrong with
+ * plusb_net_open() and plusb_net_start() ? Maybe
+ * the 'suspend' and 'resume' entry points need to be
+ * implemented?
+ * - Needs to handle -EPIPE correctly in bulk complete handlers.
+ * (replace usb_clear_halt() function with async urbs?)
+ * - I think this code relies too much on one spinlock and does
+ * too much in the interrupt handler. The net1080 code is
+ * much more elegant, and should work for this chip. Its
+ * only drawback is that it is going to be tough to backport
+ * it to v2.2.
+ * - Occasionally the device will hang under the 'uhci.o'
+ * driver. The workaround is to ifdown the device and
+ * remove the modules, then re-insert them. You may have
+ * better luck with the 'usb-uhci.o' driver.
+ * - After using ifconfig down ; ifconfig up, sometimes packets
+ * continue to be received, but there is a framing problem.
+ *
+ * FUTURE DIRECTIONS:
+ *
+ * - Fix the known problems.
+ * - There isn't much functional difference between the net1080
+ * driver and this one. It would be neat if the same driver
+ * could handle both types of chips. Or if both drivers
+ * could handle both types of chips - this one is easier to
+ * backport to the 2.2 kernel.
+ * - Get rid of plusb_add_buf_tail and the single spinlock.
+ * Use a separate spinlock for the 2 lists, and use atomic
+ * operators for writeurb_submitted and readurb_submitted members.
+ *
+ *
*/
/*****************************************************************************/
@@ -40,23 +133,102 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
-//#define DEBUG
+//#define DEBUG 1
#include <linux/usb.h>
-#include "plusb.h"
+/* Definitions formerly in plusb.h relocated. No need to export them -EZA */
+
+#define _PLUSB_INTPIPE 0x1
+#define _PLUSB_BULKOUTPIPE 0x2
+#define _PLUSB_BULKINPIPE 0x3
+
+#define _SKB_NUM 32
+
+/* increase size of BULK_DATA_LEN so we can use bigger MTU's*/
+#define _BULK_DATA_LEN 32768
+
+
+typedef struct
+{
+ int connected; /* indicates if this structure is active */
+ struct usb_device *usbdev;
+ /* keep track of USB structure */
+ int status; /* Prolific status byte returned from interrupt */
+ int in_bh; /* flag to indicate that we are in the bulk handler */
+ int opened; /* flag to indicate that network dev is open */
+
+ spinlock_t lock; /* Lock for the buffer list. re-used for
+ locking around submitting the readurb member.
+ */
+ urb_t *inturb; /* Read buffer for the interrupt callback */
+ unsigned char * interrupt_in_buffer;
+ /* holds data for the inturb*/
+ urb_t *readurb; /* Read buffer for the bulk data callback */
+ unsigned char * bulk_in_buffer;
+ /* kmalloc'ed data for the readurb */
+ int readurb_submitted;
+ /* Flag to indicate that readurb already sent */
+ urb_t *writeurb; /* Write buffer for the bulk data callback */
+ int writeurb_submitted;
+ /* Flag to indicate that writeurb already sent */
+
+ struct list_head tx_skb_list;
+ /* sk_buff's read from net device */
+ struct list_head free_skb_list;
+ /* free sk_buff list */
+ struct net_device net_dev;
+ /* handle to linux network device */
+ struct net_device_stats net_stats;
+ /* stats to return for ifconfig output */
+} plusb_t,*pplusb_t;
+
+/*
+ * skb_list - queue of packets from the network driver to be delivered to USB
+ */
+typedef struct
+{
+ struct list_head skb_list;
+ struct sk_buff *skb;
+ int state;
+ plusb_t *s;
+} skb_list_t,*pskb_list_t;
+
/* --------------------------------------------------------------------- */
#define NRPLUSB 4
+/*
+ * Interrupt endpoint status byte, from Prolific PL-2301 docs
+ * Check the 'download' link at www.prolifictech.com
+ */
+#define _PL_INT_RES1 0x80 /* reserved */
+#define _PL_INT_RES2 0x40 /* reserved */
+#define _PL_INT_RXD _PL_INT_RES2 /* Read data ready - Not documented by Prolific, but seems to work! */
+#define _PL_INT_TX_RDY 0x20 /* OK to transmit data */
+#define _PL_INT_RESET_O 0x10 /* reset output pipe */
+#define _PL_INT_RESET_I 0x08 /* reset input pipe */
+#define _PL_INT_TX_C 0x04 /* transmission complete */
+#define _PL_INT_TX_REQ 0x02 /* transmission received */
+#define _PL_INT_PEER_E 0x01 /* peer exists */
+
/*-------------------------------------------------------------------*/
static plusb_t plusb[NRPLUSB];
+static void plusb_write_bulk_complete(urb_t *purb);
+static void plusb_read_bulk_complete(urb_t *purb);
+static void plusb_int_complete(urb_t *purb);
+
/* --------------------------------------------------------------------- */
+
+/*
+ * plusb_add_buf_tail - Take the head of the src list and append it to
+ * the tail of the dest list
+ */
static int plusb_add_buf_tail (plusb_t *s, struct list_head *dst, struct list_head *src)
{
- unsigned long flags;
+ unsigned long flags = 0;
struct list_head *tmp;
int ret = 0;
@@ -76,126 +248,227 @@
}
/*-------------------------------------------------------------------*/
-static int plusb_my_bulk(plusb_t *s, int pipe, void *data, int size, int *actual_length)
+/*
+ * dequeue_next_skb - submit the first thing on the tx_skb_list to the
+ * USB stack. This function should be called each time we get a new
+ * message to send to the other host, or each time a message is sucessfully
+ * sent.
+ */
+static void dequeue_next_skb(char * func, plusb_t * s)
{
- int ret;
+ skb_list_t * skb_list;
+ unsigned long flags = 0;
- dbg("plusb_my_bulk: len:%d",size);
+ if (!s->connected)
+ return;
+
+ spin_lock_irqsave (&s->lock, flags);
+
+ if (!list_empty (&s->tx_skb_list) && !s->writeurb_submitted) {
+ int submit_ret;
+ skb_list = list_entry (s->tx_skb_list.next, skb_list_t, skb_list);
- ret=usb_bulk_msg(s->usbdev, pipe, data, size, actual_length, 500);
- if(ret<0) {
- err("plusb: usb_bulk_msg failed(%d)",ret);
+ if (skb_list->skb) {
+ s->writeurb_submitted = 1;
+
+ /* Use the buffer inside the sk_buff directly. why copy? */
+ FILL_BULK_URB_TO(s->writeurb, s->usbdev,
+ usb_sndbulkpipe(s->usbdev, _PLUSB_BULKOUTPIPE),
+ skb_list->skb->data, skb_list->skb->len,
+ plusb_write_bulk_complete, skb_list, 500);
+
+ dbg ("%s: %s: submitting urb. skb_list %p", s->net_dev.name, func, skb_list);
+
+ submit_ret = usb_submit_urb(s->writeurb);
+ if (submit_ret) {
+ s->writeurb_submitted = 0;
+ printk (KERN_CRIT "%s: %s: can't submit writeurb: %d\n",
+ s->net_dev.name, func, submit_ret);
+ }
+ } /* end if the skb value has been filled in */
}
- if( ret == -EPIPE ) {
- warn("CLEAR_FEATURE request to remove STALL condition.");
- if(usb_clear_halt(s->usbdev, usb_pipeendpoint(pipe)))
- err("request failed");
- }
-
- dbg("plusb_my_bulk: finished act: %d", *actual_length);
- return ret;
+ spin_unlock_irqrestore (&s->lock, flags);
}
-/* --------------------------------------------------------------------- */
-
-static void plusb_bh(void *context)
+/*
+ * submit_read_urb - re-submit the read URB to the stack
+ */
+void submit_read_urb(char * func, plusb_t * s)
{
- plusb_t *s=context;
- struct net_device_stats *stats=&s->net_stats;
- int ret=0;
- int actual_length;
- skb_list_t *skb_list;
- struct sk_buff *skb;
-
- dbg("plusb_bh: i:%d",in_interrupt());
-
- while(!list_empty(&s->tx_skb_list)) {
-
- if(!(s->status&_PLUSB_TXOK))
- break;
-
- skb_list = list_entry (s->tx_skb_list.next, skb_list_t, skb_list);
- if(!skb_list->state) {
- dbg("plusb_bh: not yet ready");
- schedule();
- continue;
- }
-
- skb=skb_list->skb;
- ret=plusb_my_bulk(s, usb_sndbulkpipe (s->usbdev, _PLUSB_BULKOUTPIPE),
- skb->data, skb->len, &actual_length);
-
- if(ret || skb->len != actual_length ||!(skb->len%64)) {
- plusb_my_bulk(s, usb_sndbulkpipe (s->usbdev, _PLUSB_BULKOUTPIPE),
- NULL, 0, &actual_length);
- }
+ unsigned long flags=0;
- if(!ret) {
- stats->tx_packets++;
- stats->tx_bytes+=skb->len;
- }
- else {
- stats->tx_errors++;
- stats->tx_aborted_errors++;
+ if (!s->connected)
+ return;
+
+ spin_lock_irqsave (&s->lock, flags);
+
+ if (!s->readurb_submitted) {
+ int ret;
+ s->readurb_submitted=1;
+ s->readurb->dev=s->usbdev;
+ ret = usb_submit_urb(s->readurb);
+ if (ret) {
+ printk (KERN_CRIT "%s: %s: error %d submitting read URB\n",
+ s->net_dev.name, func, ret);
+ s->readurb_submitted=0;
}
-
- dbg("plusb_bh: dev_kfree_skb");
-
- dev_kfree_skb(skb);
- skb_list->state=0;
- plusb_add_buf_tail (s, &s->free_skb_list, &s->tx_skb_list);
}
- dbg("plusb_bh: finished");
- s->in_bh=0;
+ spin_unlock_irqrestore (&s->lock, flags);
+
}
-
/* --------------------------------------------------------------------- */
+/*
+ * plusb_net_xmit - callback from the network device driver for outgoing data
+ *
+ * Data has arrived to the network device from the local machine and needs
+ * to be sent over the USB cable. This is in an interrupt, so we don't
+ * want to spend too much time in this function.
+ *
+ */
static int plusb_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
plusb_t *s=dev->priv;
skb_list_t *skb_list;
- int ret=NET_XMIT_SUCCESS;
+ unsigned int flags;
dbg("plusb_net_xmit: len:%d i:%d",skb->len,in_interrupt());
- if(!s->connected || list_empty(&s->free_skb_list)) {
- ret=NET_XMIT_CN;
- goto lab;
- }
+ if(!s->connected || !s->opened) {
+ /*
+ NOTE: If we get to this point, you'll return the error
+ kernel: virtual device plusb0 asks to queue packet
+
+ Other things we could do:
+ 1) just drop this packet
+ 2) drop other packets in the queue
+ */
+ return 1;
+ }
+
+ spin_lock_irqsave (&s->lock, flags);
- plusb_add_buf_tail (s, &s->tx_skb_list, &s->free_skb_list);
+ if (list_empty(&s->free_skb_list)
+ || plusb_add_buf_tail (s, &s->tx_skb_list, &s->free_skb_list)) {
+ /* The buffers on this side are full. DROP the packet
+ I think that this shouldn't happen with the correct
+ use of the netif_XXX functions -EZA
+ */
+ dbg ("plusb: Free list is empty.");
+ kfree_skb(skb);
+ s->net_stats.tx_dropped++;
+ spin_unlock_irqrestore (&s->lock, flags);
+ return 0;
+ }
+
skb_list = list_entry (s->tx_skb_list.prev, skb_list_t, skb_list);
skb_list->skb=skb;
skb_list->state=1;
+ skb_list->s=s;
+
+ if (list_empty(&s->free_skb_list)) {
+ /* apply "backpressure". Tell the net layer to stop sending
+ the driver packets.
+ */
+ netif_stop_queue(dev);
+ }
+
+ spin_unlock_irqrestore (&s->lock, flags);
+
+ /* If there is no write urb outstanding, pull the first thing
+ off of the list and submit it to the USB stack
+ */
+ dequeue_next_skb("plusb_net_xmit", s);
+
+ return 0;
+}
-lab:
- if(s->in_bh)
- return ret;
+/* --------------------------------------------------------------------- */
- dbg("plusb_net_xmit: queue_task");
+/*
+ * plusb_write_bulk_complete () - callback after the data has been
+ * sent to the USB device, or a timeout occured.
+ */
+static void plusb_write_bulk_complete(urb_t *purb)
+{
+ skb_list_t * skb_list=purb->context;
+ plusb_t *s=skb_list->s;
- s->in_bh=1;
- queue_task(&s->bh, &tq_scheduler);
+ dbg ("%s: plusb_write_bulk_complete: status:%d skb_list:%p\n",
+ s->net_dev.name, purb->status, skb_list);
- dbg("plusb_net_xmit: finished");
- return ret;
+ skb_list->state=0;
-}
+ if( purb->status == -EPIPE ) {
+
+ printk(KERN_CRIT "%s: plusb_write_bulk_complete: got -EPIPE and don't know what to do!\n",
+ s->net_dev.name);
+ }
+
+ if(!purb->status) {
+ s->net_stats.tx_packets++;
+ s->net_stats.tx_bytes+=skb_list->skb->len;
+ }
+ else {
+ err ("%s: plusb_write_bulk_complete: returned ERROR status:%d\n",
+ s->net_dev.name, purb->status);
-/* --------------------------------------------------------------------- */
+ s->net_stats.tx_errors++;
+ s->net_stats.tx_aborted_errors++;
+ }
+
+ dbg("plusb_bh: dev_kfree_skb");
+
+
+#if (LINUX_VERSION_CODE < 0x020300)
+ dev_kfree_skb(skb_list->skb);
+#else
+ /* NOTE: In 2.4 it's a problem to call dev_kfree_skb() in a hard IRQ:
+ Oct 28 23:42:14 bug kernel: Warning: kfree_skb on hard IRQ c023329a
+ */
+ dev_kfree_skb_any(skb_list->skb);
+#endif
+
+ skb_list->skb = NULL;
+ if (plusb_add_buf_tail (s, &s->free_skb_list, &s->tx_skb_list)) {
+ err ("plusb: tx list empty. This shouldn't happen.");
+ }
+
+ purb->status = 0;
+ s->writeurb_submitted = 0;
+
+ netif_wake_queue((&s->net_dev));
+
+ dequeue_next_skb("plusb_write_bulk_complete", s);
-static void plusb_bulk_complete(urb_t *purb)
+
+}
+
+/*
+ * plusb_read_bulk_complete - Callback for data arriving from the USB device
+ *
+ * This gets called back when a full 'urb' is received from the remote system.
+ * This urb was allocated by this driver and is kept in the member: s->readurb
+ *
+ */
+static void plusb_read_bulk_complete(urb_t *purb)
{
+
plusb_t *s=purb->context;
- dbg("plusb_bulk_complete: status:%d length:%d",purb->status,purb->actual_length);
+ dbg("plusb_read_bulk_complete: status:%d length:%d", purb->status,purb->actual_length);
+
if(!s->connected)
return;
- if( !purb->status) {
+ if( purb->status == -EPIPE ) {
+
+ printk(KERN_CRIT "%s: plusb_read_bulk_complete: got -EPIPE and I don't know what to do!\n",
+ s->net_dev.name);
+
+ } else if (!purb->status) {
struct sk_buff *skb;
unsigned char *dst;
int len=purb->transfer_buffer_length;
@@ -204,31 +477,69 @@
skb=dev_alloc_skb(len);
if(!skb) {
- err("plusb_bulk_complete: dev_alloc_skb(%d)=NULL, dropping frame",len);
+ printk (KERN_CRIT "%s: plusb_read_bulk_complete: dev_alloc_skb(%d)=NULL, dropping frame\n", s->net_dev.name, len);
stats->rx_dropped++;
- return;
+ } else {
+ dst=(char *)skb_put(skb, len);
+ memcpy( dst, purb->transfer_buffer, len);
+
+ skb->dev=&s->net_dev;
+ skb->protocol=eth_type_trans(skb, skb->dev);
+ stats->rx_packets++;
+ stats->rx_bytes+=len;
+ netif_rx(skb);
}
+
+ }
+
+ s->readurb_submitted = 0;
+
+ if (purb->status) {
+ /* Give the system a chance to "catch its breath". Shortcut
+ re-submitting the read URB> It will be re-submitted if
+ another interrupt comes back. The problem scenario is that
+ the plub is pulled and the read returns an error.
+ You don't want to resumbit in this case.
+ */
+ err ("%s: plusb_read_bulk_complete: returned status %d\n",
+ s->net_dev.name, purb->status);
+ return;
+ }
- dst=(char *)skb_put(skb, len);
- memcpy( dst, purb->transfer_buffer, len);
- skb->dev=&s->net_dev;
- skb->protocol=eth_type_trans(skb, skb->dev);
- stats->rx_packets++;
- stats->rx_bytes+=len;
- netif_rx(skb);
- }
- else
- purb->status=0;
+ purb->status=0;
+
+ /* Keep it coming! resubmit the URB for reading.. Make sure
+ we aren't in contention with the interrupt callback.
+ */
+ submit_read_urb("plusb_read_bulk_complete", s);
}
/* --------------------------------------------------------------------- */
-
+/*
+ * plusb_int_complete - USB driver callback for interrupt msg from the device
+ *
+ * Interrupts are scheduled to go off on a periodic basis (see FILL_INT_URB)
+ * For the prolific device, this is basically just returning a register
+ * filled with bits. See the macro definitions for _PL_INT_XXX above.
+ * Most of these bits are for implementing a machine-machine protocol
+ * and can be set with a special message (described as the "Quicklink"
+ * feature in the prolific documentation.)
+ *
+ * I don't think we need any of that to work as a network device. If a
+ * message is lost, big deal - that's what UNIX networking expects from
+ * the physical layer.
+ *
+ */
static void plusb_int_complete(urb_t *purb)
{
plusb_t *s=purb->context;
s->status=((unsigned char*)purb->transfer_buffer)[0]&255;
+
#if 0
+ /* This isn't right because 0x20 is TX_RDY and
+ sometimes will not be set
+ */
if((s->status&0x3f)!=0x20) {
warn("invalid device status %02X", s->status);
return;
@@ -237,67 +548,95 @@
if(!s->connected)
return;
- if(s->status&_PLUSB_RXD) {
- int ret;
-
- if(s->bulkurb->status) {
- err("plusb_int_complete: URB still in use");
- return;
- }
-
- s->bulkurb->dev = s->usbdev;
- ret=usb_submit_urb(s->bulkurb);
- if(ret && ret!=-EBUSY) {
- err("plusb_int_complete: usb_submit_urb failed");
- }
- }
-
- if(purb->status || s->status!=160)
- dbg("status: %p %d buf: %02X", purb->dev, purb->status, s->status);
+ /* Don't turn this on unless you want to see the log flooded. */
+#if 0
+ printk("plusb_int_complete: PEER_E:%d TX_REQ:%d TX_C:%d RESET_IN:%d RESET_O: %d TX_RDY:%d RES1:%d RES2:%d\n",
+ s->status & _PL_INT_PEER_E ? 1 : 0,
+ s->status & _PL_INT_TX_REQ ? 1 : 0,
+ s->status & _PL_INT_TX_C ? 1 : 0,
+ s->status & _PL_INT_RESET_I ? 1 : 0,
+ s->status & _PL_INT_RESET_O ? 1 : 0,
+ s->status & _PL_INT_TX_RDY ? 1 : 0,
+ s->status & _PL_INT_RES1 ? 1 : 0,
+ s->status & _PL_INT_RES2 ? 1 : 0);
+#endif
+
+#if 1
+ /* At first glance, this logic appears to not really be needed, but
+ it can help recover from intermittent problems where the
+ usb_submit_urb() fails in the read callback. -EZA
+ */
+
+ /* Try to submit the read URB again. Make sure
+ we aren't in contention with the bulk read callback
+ */
+ submit_read_urb ("plusb_int_complete", s);
+
+ /* While we are at it, why not check to see if the
+ write urb should be re-submitted?
+ */
+ dequeue_next_skb("plusb_int_complete", s);
+
+#endif
+
}
/* --------------------------------------------------------------------- */
-
+/*
+ * plusb_free_all - deallocate all memory kept for an instance of the device.
+ */
static void plusb_free_all(plusb_t *s)
{
struct list_head *skb;
skb_list_t *skb_list;
dbg("plusb_free_all");
+
+ /* set a flag to tell all callbacks to cease and desist */
+ s->connected = 0;
+
+ /* If the interrupt handler is about to fire, let it finish up */
run_task_queue(&tq_immediate);
if(s->inturb) {
dbg("unlink inturb");
usb_unlink_urb(s->inturb);
- }
-
- if(s->inturb && s->inturb->transfer_buffer) {
- dbg("kfree inturb->transfer_buffer");
- kfree(s->inturb->transfer_buffer);
- s->inturb->transfer_buffer=NULL;
- }
-
- if(s->inturb) {
dbg("free_urb inturb");
usb_free_urb(s->inturb);
s->inturb=NULL;
}
+
+ if(s->interrupt_in_buffer) {
+ dbg("kfree s->interrupt_in_buffer");
+ kfree(s->interrupt_in_buffer);
+ s->interrupt_in_buffer=NULL;
+ }
+
+ if(s->readurb) {
+ dbg("unlink readurb");
+ usb_unlink_urb(s->readurb);
+ dbg("free_urb readurb:");
+ usb_free_urb(s->readurb);
+ s->readurb=NULL;
+ }
- if(s->bulkurb) {
- dbg("unlink bulkurb");
- usb_unlink_urb(s->bulkurb);
+ if(s->bulk_in_buffer) {
+ dbg("kfree s->bulk_in_buffer");
+ kfree(s->bulk_in_buffer);
+ s->bulk_in_buffer=NULL;
}
- if(s->bulkurb && s->bulkurb->transfer_buffer) {
- dbg("kfree bulkurb->transfer_buffer");
- kfree(s->bulkurb->transfer_buffer);
- s->bulkurb->transfer_buffer=NULL;
- }
- if(s->bulkurb) {
- dbg("free_urb bulkurb");
- usb_free_urb(s->bulkurb);
- s->bulkurb=NULL;
+ s->readurb_submitted = 0;
+
+ if(s->writeurb) {
+ dbg("unlink writeurb");
+ usb_unlink_urb(s->writeurb);
+ dbg("free_urb writeurb:");
+ usb_free_urb(s->writeurb);
+ s->writeurb=NULL;
}
+
+ s->writeurb_submitted = 0;
while(!list_empty(&s->free_skb_list)) {
skb=s->free_skb_list.next;
@@ -310,20 +649,34 @@
skb=s->tx_skb_list.next;
list_del(skb);
skb_list = list_entry (skb, skb_list_t, skb_list);
- kfree(skb_list);
+ if (skb_list->skb) {
+ dbg ("Freeing SKB in queue");
+#if (LINUX_VERSION_CODE < 0x020300)
+ dev_kfree_skb(skb_list->skb);
+#else
+ dev_kfree_skb_any(skb_list->skb);
+#endif
+ skb_list->skb = NULL;
+ }
+ kfree(skb_list);
}
+
+ s->in_bh=0;
+
dbg("plusb_free_all: finished");
}
/*-------------------------------------------------------------------*/
-
+/*
+ * plusb_alloc - allocate memory associated with one instance of the device
+ */
static int plusb_alloc(plusb_t *s)
{
int i;
skb_list_t *skb;
dbg("plusb_alloc");
-
+
for(i=0 ; i < _SKB_NUM ; i++) {
skb=kmalloc(sizeof(skb_list_t), GFP_KERNEL);
if(!skb) {
@@ -341,47 +694,63 @@
goto reject;
}
- dbg("bulkurb allocation:");
- s->bulkurb=usb_alloc_urb(0);
- if(!s->bulkurb) {
+ dbg("bulk read urb allocation:");
+ s->readurb=usb_alloc_urb(0);
+ if(!s->readurb) {
err("alloc_urb failed");
goto reject;
}
- dbg("bulkurb/inturb init:");
- s->inturb->dev=s->usbdev;
- s->inturb->pipe=usb_rcvintpipe (s->usbdev, _PLUSB_INTPIPE);
- s->inturb->transfer_buffer=kmalloc(64, GFP_KERNEL);
- if(!s->inturb->transfer_buffer) {
- err("kmalloc failed");
+ dbg("bulk write urb allocation:");
+ s->writeurb=usb_alloc_urb(0);
+ if(!s->writeurb) {
+ err("alloc_urb for writeurb failed");
goto reject;
}
- s->inturb->transfer_buffer_length=1;
- s->inturb->complete=plusb_int_complete;
- s->inturb->context=s;
- s->inturb->interval=10;
+ dbg("readurb/inturb init:");
+ s->interrupt_in_buffer=kmalloc(64, GFP_KERNEL);
+ if(!s->interrupt_in_buffer) {
+ err("kmalloc failed");
+ goto reject;
+ }
+
+ /* The original value of '10' makes this interrupt fire off a LOT.
+ It was set so low because the callback determined when to
+ sumbit the buld read URB. I've lowered it to 100 - the driver
+ doesn't depend on that logic anymore. -EZA
+ */
+ FILL_INT_URB(s->inturb, s->usbdev,
+ usb_rcvintpipe (s->usbdev, _PLUSB_INTPIPE),
+ s->interrupt_in_buffer, 1,
+ plusb_int_complete, s, HZ);
dbg("inturb submission:");
if(usb_submit_urb(s->inturb)<0) {
err("usb_submit_urb failed");
goto reject;
}
-
- dbg("bulkurb init:");
- s->bulkurb->dev=s->usbdev;
- s->bulkurb->pipe=usb_rcvbulkpipe (s->usbdev, _PLUSB_BULKINPIPE);
- s->bulkurb->transfer_buffer=kmalloc(_BULK_DATA_LEN, GFP_KERNEL);
- if(!s->bulkurb->transfer_buffer) {
- err("kmalloc failed");
- goto reject;
- }
-
- s->bulkurb->transfer_buffer_length=_BULK_DATA_LEN;
- s->bulkurb->complete=plusb_bulk_complete;
- s->bulkurb->context=s;
- dbg("plusb_alloc: finished");
+ dbg("readurb init:");
+ s->bulk_in_buffer = kmalloc(_BULK_DATA_LEN, GFP_KERNEL);
+ if (!s->bulk_in_buffer) {
+ err("kmalloc %d bytes for bulk in buffer failed", _BULK_DATA_LEN);
+ }
+
+ FILL_BULK_URB(s->readurb, s->usbdev,
+ usb_rcvbulkpipe(s->usbdev, _PLUSB_BULKINPIPE),
+ s->bulk_in_buffer, _BULK_DATA_LEN,
+ plusb_read_bulk_complete, s);
+
+ /* The write urb will be initialized inside the network
+ interrupt.
+ */
+
+ /* get the bulk read going */
+ submit_read_urb("plusb_alloc", s);
+
+ dbg ("plusb_alloc: finished. readurb=%p writeurb=%p inturb=%p",
+ s->readurb, s->writeurb, s->inturb);
return 0;
@@ -404,8 +773,11 @@
return -ENOMEM;
s->opened=1;
- MOD_INC_USE_COUNT;
+ MOD_INC_USE_COUNT;
+
+ netif_start_queue(dev);
+
dbg("plusb_net_open: success");
return 0;
@@ -417,11 +789,14 @@
static int plusb_net_stop(struct net_device *dev)
{
plusb_t *s=dev->priv;
+
+ netif_stop_queue(dev);
dbg("plusb_net_stop");
- plusb_free_all(s);
s->opened=0;
+ plusb_free_all(s);
+
MOD_DEC_USE_COUNT;
dbg("plusb_net_stop:finished");
return 0;
@@ -459,7 +834,6 @@
plusb_t *s = ptr;
dbg("plusb_disconnect");
- s->connected = 0;
plusb_free_all(s);
@@ -467,6 +841,11 @@
dbg("unregistering netdev: %s",s->net_dev.name);
unregister_netdev(&s->net_dev);
s->net_dev.name[0] = '\0';
+#if (LINUX_VERSION_CODE < 0x020300)
+ dbg("plusb_disconnect: About to free name");
+ kfree (s->net_dev.name);
+ s->net_dev.name = NULL;
+#endif
}
dbg("plusb_disconnect: finished");
@@ -475,6 +854,22 @@
/* --------------------------------------------------------------------- */
+static int plusb_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if ((new_mtu < 68) || (new_mtu > _BULK_DATA_LEN))
+ return -EINVAL;
+
+ printk("plusb: changing mtu to %d\n", new_mtu);
+ dev->mtu = new_mtu;
+
+ /* NOTE: Could we change the size of the READ URB here dynamically
+ to save kernel memory?
+ */
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
int plusb_net_init(struct net_device *dev)
{
dbg("plusb_net_init");
@@ -484,7 +879,14 @@
dev->hard_start_xmit=plusb_net_xmit;
dev->get_stats = plusb_net_get_stats;
ether_setup(dev);
- dev->tx_queue_len = 0;
+ dev->change_mtu = plusb_change_mtu;
+ /* Setting the default MTU to 16K gives good performance for
+ me, and keeps the ping latency low too. Setting it up
+ to 32K made performance go down. -EZA
+ Pavel says it would be best not to do this...
+ */
+ /*dev->mtu=16384; */
+ dev->tx_queue_len = 0;
dev->flags = IFF_POINTOPOINT|IFF_NOARP;
@@ -524,6 +926,42 @@
return NULL;
}
+#if (LINUX_VERSION_CODE < 0x020300)
+ {
+ int i;
+
+ /* For Kernel version 2.2, the driver is responsible for
+ allocating this memory. For version 2.4, the rules
+ have apparently changed, but there is a nifty function
+ 'init_netdev' that might make this easier... It's in
+ ../net/net_init.c - but can we get there from here? (no)
+ -EZA
+ */
+
+ /* Find the device number... we seem to have lost it... -EZA */
+ for (i=0; i<NRPLUSB; i++) {
+ if (&plusb[i] == s)
+ break;
+ }
+
+ if(!s->net_dev.name) {
+ s->net_dev.name = kmalloc(strlen("plusbXXXX"), GFP_KERNEL);
+ sprintf (s->net_dev.name, "plusb%d", i);
+ s->net_dev.init=plusb_net_init;
+ s->net_dev.priv=s;
+
+ printk ("plusb_probe: Registering Device\n");
+ if(!register_netdev(&s->net_dev))
+ info("registered: %s", s->net_dev.name);
+ else {
+ err("register_netdev failed");
+ s->net_dev.name[0] = '\0';
+ }
+ dbg ("plusb_probe: Connected!");
+ }
+ }
+#else
+ /* Kernel version 2.3+ works a little bit differently than 2.2 */
if(!s->net_dev.name[0]) {
strcpy(s->net_dev.name, "plusb%d");
s->net_dev.init=plusb_net_init;
@@ -535,7 +973,8 @@
s->net_dev.name[0] = '\0';
}
}
-
+#endif
+
s->connected = 1;
if(s->opened) {
@@ -567,8 +1006,6 @@
for (u = 0; u < NRPLUSB; u++) {
plusb_t *s = &plusb[u];
memset (s, 0, sizeof (plusb_t));
- s->bh.routine = (void (*)(void *))plusb_bh;
- s->bh.data = s;
INIT_LIST_HEAD (&s->tx_skb_list);
INIT_LIST_HEAD (&s->free_skb_list);
spin_lock_init (&s->lock);
@@ -591,10 +1028,21 @@
dbg("plusb_cleanup");
for (u = 0; u < NRPLUSB; u++) {
plusb_t *s = &plusb[u];
+#if (LINUX_VERSION_CODE < 0x020300)
+ if(s->net_dev.name) {
+ dbg("unregistering netdev: %s",s->net_dev.name);
+ unregister_netdev(&s->net_dev);
+ s->net_dev.name[0] = '\0';
+ kfree (s->net_dev.name);
+ s->net_dev.name = NULL;
+ }
+#else
if(s->net_dev.name[0]) {
dbg("unregistering netdev: %s",s->net_dev.name);
unregister_netdev(&s->net_dev);
+ s->net_dev.name[0] = '\0';
}
+#endif
}
usb_deregister (&plusb_driver);
dbg("plusb_cleanup: finished");
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)