patch-2.3.46 linux/drivers/usb/plusb.c
Next file: linux/drivers/usb/plusb.h
Previous file: linux/drivers/usb/keybdev.c
Back to the patch index
Back to the overall index
- Lines: 633
- Date:
Tue Feb 15 22:39:01 2000
- Orig file:
v2.3.45/linux/drivers/usb/plusb.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.3.45/linux/drivers/usb/plusb.c linux/drivers/usb/plusb.c
@@ -0,0 +1,632 @@
+/*****************************************************************************/
+
+/*
+ * plusb.c -- prolific pl-2302 driver.
+ *
+ * Copyright (C) 2000 Deti Fliegl (deti@fliegl.de)
+ *
+ * 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.
+ *
+ *
+ *
+ * $Id: plusb.c,v 1.18 2000/02/14 10:38:58 fliegl Exp $
+ *
+ */
+
+/*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/socket.h>
+#include <linux/miscdevice.h>
+#include <linux/list.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+
+//#define DEBUG
+
+#include "usb.h"
+#include "plusb.h"
+
+/* --------------------------------------------------------------------- */
+
+#define NRPLUSB 4
+
+/*-------------------------------------------------------------------*/
+
+static plusb_t plusb[NRPLUSB];
+
+/* --------------------------------------------------------------------- */
+static int plusb_add_buf_tail (plusb_t *s, struct list_head *dst, struct list_head *src)
+{
+ unsigned long flags;
+ struct list_head *tmp;
+ int ret = 0;
+
+ spin_lock_irqsave (&s->lock, flags);
+
+ if (list_empty (src)) {
+ // no elements in source buffer
+ ret = -1;
+ goto err;
+ }
+ tmp = src->next;
+ list_del (tmp);
+ list_add_tail (tmp, dst);
+
+ err: spin_unlock_irqrestore (&s->lock, flags);
+ return ret;
+}
+/*-------------------------------------------------------------------*/
+
+static int plusb_my_bulk(plusb_t *s, int pipe, void *data, int size, int *actual_length)
+{
+ int ret;
+
+ dbg("plusb_my_bulk: len:%d",size);
+
+ ret=usb_bulk_msg(s->usbdev, pipe, data, size, actual_length, 500);
+ if(ret<0) {
+ err("plusb: usb_bulk_msg failed(%d)",ret);
+ }
+
+ 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;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void plusb_bh(void *context)
+{
+ 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);
+ }
+
+ if(!ret) {
+ stats->tx_packets++;
+ stats->tx_bytes+=skb->len;
+ }
+ else {
+ stats->tx_errors++;
+ stats->tx_aborted_errors++;
+ }
+
+ 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;
+}
+
+/* --------------------------------------------------------------------- */
+
+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;
+
+ 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;
+ }
+
+ plusb_add_buf_tail (s, &s->tx_skb_list, &s->free_skb_list);
+ skb_list = list_entry (s->tx_skb_list.prev, skb_list_t, skb_list);
+ skb_list->skb=skb;
+ skb_list->state=1;
+
+lab:
+ if(s->in_bh)
+ return ret;
+
+ dbg("plusb_net_xmit: queue_task");
+
+ s->in_bh=1;
+ queue_task(&s->bh, &tq_scheduler);
+
+ dbg("plusb_net_xmit: finished");
+ return ret;
+
+}
+
+/* --------------------------------------------------------------------- */
+
+static void plusb_bulk_complete(urb_t *purb)
+{
+ plusb_t *s=purb->context;
+
+ dbg("plusb_bulk_complete: status:%d length:%d",purb->status,purb->actual_length);
+ if(!s->connected)
+ return;
+
+ if( !purb->status) {
+ struct sk_buff *skb;
+ unsigned char *dst;
+ int len=purb->transfer_buffer_length;
+ struct net_device_stats *stats=&s->net_stats;
+
+ skb=dev_alloc_skb(len);
+
+ if(!skb) {
+ err("plusb_bulk_complete: dev_alloc_skb(%d)=NULL, dropping frame",len);
+ stats->rx_dropped++;
+ 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;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void plusb_int_complete(urb_t *purb)
+{
+ plusb_t *s=purb->context;
+ s->status=((unsigned char*)purb->transfer_buffer)[0]&255;
+#if 0
+ if((s->status&0x3f)!=0x20) {
+ warn("invalid device status %02X", s->status);
+ return;
+ }
+#endif
+ if(!s->connected)
+ return;
+
+ if(s->status&_PLUSB_RXD) {
+ int ret;
+
+ if(s->bulkurb->status) {
+ err("plusb_int_complete: URB still in use");
+ return;
+ }
+
+ 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);
+}
+
+/* --------------------------------------------------------------------- */
+
+static void plusb_free_all(plusb_t *s)
+{
+ struct list_head *skb;
+ skb_list_t *skb_list;
+
+ dbg("plusb_free_all");
+ 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->bulkurb) {
+ dbg("unlink bulkurb");
+ usb_unlink_urb(s->bulkurb);
+ }
+
+ 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;
+ }
+
+ while(!list_empty(&s->free_skb_list)) {
+ skb=s->free_skb_list.next;
+ list_del(skb);
+ skb_list = list_entry (skb, skb_list_t, skb_list);
+ kfree(skb_list);
+ }
+
+ while(!list_empty(&s->tx_skb_list)) {
+ skb=s->tx_skb_list.next;
+ list_del(skb);
+ skb_list = list_entry (skb, skb_list_t, skb_list);
+ kfree(skb_list);
+ }
+ dbg("plusb_free_all: finished");
+}
+
+/*-------------------------------------------------------------------*/
+
+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) {
+ err("kmalloc for skb_list failed");
+ goto reject;
+ }
+ memset(skb, 0, sizeof(skb_list_t));
+ list_add(&skb->skb_list, &s->free_skb_list);
+ }
+
+ dbg("inturb allocation:");
+ s->inturb=usb_alloc_urb(0);
+ if(!s->inturb) {
+ err("alloc_urb failed");
+ goto reject;
+ }
+
+ dbg("bulkurb allocation:");
+ s->bulkurb=usb_alloc_urb(0);
+ if(!s->bulkurb) {
+ 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");
+ goto reject;
+ }
+
+ s->inturb->transfer_buffer_length=1;
+ s->inturb->complete=plusb_int_complete;
+ s->inturb->context=s;
+ s->inturb->interval=10;
+
+ 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");
+
+ return 0;
+
+ reject:
+ dbg("plusb_alloc: failed");
+
+ plusb_free_all(s);
+ return -ENOMEM;
+}
+
+/*-------------------------------------------------------------------*/
+
+static int plusb_net_open(struct net_device *dev)
+{
+ plusb_t *s=dev->priv;
+
+ dbg("plusb_net_open");
+
+ if(plusb_alloc(s))
+ return -ENOMEM;
+
+ s->opened=1;
+ MOD_INC_USE_COUNT;
+
+ dbg("plusb_net_open: success");
+
+ return 0;
+
+}
+
+/* --------------------------------------------------------------------- */
+
+static int plusb_net_stop(struct net_device *dev)
+{
+ plusb_t *s=dev->priv;
+
+ dbg("plusb_net_stop");
+
+ plusb_free_all(s);
+ s->opened=0;
+ MOD_DEC_USE_COUNT;
+ dbg("plusb_net_stop:finished");
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static struct net_device_stats *plusb_net_get_stats(struct net_device *dev)
+{
+ plusb_t *s=dev->priv;
+
+ dbg("net_device_stats");
+
+ return &s->net_stats;
+}
+
+/* --------------------------------------------------------------------- */
+
+static plusb_t *plusb_find_struct (void)
+{
+ int u;
+
+ for (u = 0; u < NRPLUSB; u++) {
+ plusb_t *s = &plusb[u];
+ if (!s->connected)
+ return s;
+ }
+ return NULL;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void plusb_disconnect (struct usb_device *usbdev, void *ptr)
+{
+ plusb_t *s = ptr;
+
+ dbg("plusb_disconnect");
+ s->connected = 0;
+
+ plusb_free_all(s);
+
+ if(!s->opened && s->net_dev.name) {
+ dbg("unregistering netdev: %s",s->net_dev.name);
+ unregister_netdev(&s->net_dev);
+ kfree(s->net_dev.name);
+ s->net_dev.name=NULL;
+ }
+
+ dbg("plusb_disconnect: finished");
+ MOD_DEC_USE_COUNT;
+}
+
+/* --------------------------------------------------------------------- */
+
+int plusb_net_init(struct net_device *dev)
+{
+ dbg("plusb_net_init");
+
+ dev->open=plusb_net_open;
+ dev->stop=plusb_net_stop;
+ dev->hard_start_xmit=plusb_net_xmit;
+ dev->get_stats = plusb_net_get_stats;
+ ether_setup(dev);
+ dev->tx_queue_len = 0;
+ dev->flags = IFF_POINTOPOINT|IFF_NOARP;
+
+
+ dbg("plusb_net_init: finished");
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+static void *plusb_probe (struct usb_device *usbdev, unsigned int ifnum)
+{
+ plusb_t *s;
+
+ dbg("plusb: probe: vendor id 0x%x, device id 0x%x ifnum:%d",
+ usbdev->descriptor.idVendor, usbdev->descriptor.idProduct, ifnum);
+
+ if (usbdev->descriptor.idVendor != 0x067b || usbdev->descriptor.idProduct != 0x1)
+ return NULL;
+
+ /* We don't handle multiple configurations */
+ if (usbdev->descriptor.bNumConfigurations != 1)
+ return NULL;
+
+ s = plusb_find_struct ();
+ if (!s)
+ return NULL;
+
+ s->usbdev = usbdev;
+
+ if (usb_set_configuration (s->usbdev, usbdev->config[0].bConfigurationValue) < 0) {
+ err("set_configuration failed");
+ return NULL;
+ }
+
+ if (usb_set_interface (s->usbdev, 0, 0) < 0) {
+ err("set_interface failed");
+ return NULL;
+ }
+
+ if(!s->net_dev.name) {
+ s->net_dev.name=kmalloc(16, GFP_KERNEL);
+
+ if(!s->net_dev.name || dev_alloc_name(&s->net_dev,"plusb%d")<0) {
+ err("alloc name failed\n");
+ return NULL;
+ }
+
+ s->net_dev.init=plusb_net_init;
+ s->net_dev.priv=s;
+ if(!register_netdev(&s->net_dev))
+ info("registered: %s", s->net_dev.name);
+ else {
+ err("register_netdev failed");
+ kfree(s->net_dev.name);
+ s->net_dev.name=NULL;
+ }
+ }
+
+ s->connected = 1;
+
+ if(s->opened) {
+ dbg("net device already allocated, restarting USB transfers");
+ plusb_alloc(s);
+ }
+
+ info("bound to interface: %d dev: %p", ifnum, usbdev);
+ MOD_INC_USE_COUNT;
+ return s;
+}
+/* --------------------------------------------------------------------- */
+
+static struct usb_driver plusb_driver =
+{
+ name: "plusb",
+ probe: plusb_probe,
+ disconnect: plusb_disconnect,
+};
+
+/* --------------------------------------------------------------------- */
+
+int __init plusb_init (void)
+{
+ unsigned u;
+ dbg("plusb_init");
+
+ /* initialize struct */
+ 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);
+ }
+
+ /* register misc device */
+ usb_register (&plusb_driver);
+
+ dbg("plusb_init: driver registered");
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+void __exit plusb_cleanup (void)
+{
+ unsigned u;
+
+ dbg("plusb_cleanup");
+ for (u = 0; u < NRPLUSB; u++) {
+ plusb_t *s = &plusb[u];
+ if(s->net_dev.name) {
+ dbg("unregistering netdev: %s",s->net_dev.name);
+ unregister_netdev(&s->net_dev);
+ kfree(s->net_dev.name);
+ s->net_dev.name=NULL;
+ }
+ }
+ usb_deregister (&plusb_driver);
+ dbg("plusb_cleanup: finished");
+}
+
+/* --------------------------------------------------------------------- */
+
+#ifdef MODULE
+MODULE_AUTHOR ("Deti Fliegl, deti@fliegl.de");
+MODULE_DESCRIPTION ("PL-2302 USB Interface Driver for Linux (c)2000");
+
+/* --------------------------------------------------------------------- */
+int __init init_module (void)
+{
+ return plusb_init ();
+}
+/* --------------------------------------------------------------------- */
+void __exit cleanup_module (void)
+{
+ plusb_cleanup ();
+}
+
+#endif
+
+/* --------------------------------------------------------------------- */
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)