Brad Hards wrote: > > Find enclosed a highly experimental patch for the Communication Device Class > Ethernet model. This aspect of CDC is used by some USB cable modems > (Ericsson, Broadcom, etc). It might be worth putting in the AC patches, but > is definately too unstable to go to Linux just yet. Err "Linus". The man's name is still "Linus". > If anyone uses this patch / driver, please let me know. I am keen to get any > feedback on the driver. > > Brad. > > P.S. Sorry about the comments in the code. It was my alter ego who did the > damage :) Sorry about the lack of comments in the (not attached) patch. Let me try that again.
diff -X dontdiff -Naur linux-2.4.2ac28-clean/Documentation/Configure.help linux/Documentation/Configure.help --- linux-2.4.2ac28-clean/Documentation/Configure.help Fri Mar 30 09:18:28 2001 +++ linux/Documentation/Configure.help Fri Mar 30 10:11:59 2001 @@ -10924,6 +10924,19 @@ The module will be called ov511.o. If you want to compile it as a module, say M here and read Documentation/modules.txt. +USB Communication Class Ethernet driver +CONFIG_USB_CDCETHER + This driver supports devices conforming to the Communication + Device Class Ethernet Control Model. This is used in some + cable modems (Ericsson, Broadcom and others). For more + details on the specification, get the Communication + Device Class specification from http://www.usb.org + + This code is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called CDCether.o. If you want to compile it as a + module, say M here and read Documentation/modules.txt. + USB ADMtek Pegasus-based ethernet device support CONFIG_USB_PEGASUS Say Y if you want to use your USB ethernet device. Supported diff -X dontdiff -Naur linux-2.4.2ac28-clean/MAINTAINERS linux/MAINTAINERS --- linux-2.4.2ac28-clean/MAINTAINERS Fri Mar 30 09:18:30 2001 +++ linux/MAINTAINERS Fri Mar 30 10:11:59 2001 @@ -1353,6 +1353,13 @@ S: Maintained W: http://www.kroah.com/linux-usb/ +USB CDC ETHERNET DRIVER +P: Brad Hards +M: [EMAIL PROTECTED] +L: [EMAIL PROTECTED] +L: [EMAIL PROTECTED] +S: Maintained + USB HID/HIDBP/INPUT DRIVERS P: Vojtech Pavlik M: [EMAIL PROTECTED] diff -X dontdiff -Naur linux-2.4.2ac28-clean/drivers/usb/CDCEther.c linux/drivers/usb/CDCEther.c --- linux-2.4.2ac28-clean/drivers/usb/CDCEther.c Thu Jan 1 10:00:00 1970 +++ linux/drivers/usb/CDCEther.c Fri Mar 30 10:32:52 2001 @@ -0,0 +1,1269 @@ +// Portions of this file taken from +// Petko Manolov - Petkan ([EMAIL PROTECTED]) +// from his driver pegasus.c + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/usb.h> +#include <linux/module.h> +#include "CDCEther.h" + +static const char *version = __FILE__ ": v0.98.2 28 March 2001 Brad Hards and +another"; + +// We will attempt to probe anything that is in the +// communication device class... +// We will sort through them later. +static struct usb_device_id CDCEther_ids[] = { + { USB_DEVICE_INFO(2, 0, 0) }, + { } +}; + +////////////////////////////////////////////////////////////////////////////// +// Callback routines from USB device ///////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static void read_bulk_callback( struct urb *urb ) +{ + ether_dev_t *ether_dev = urb->context; + struct net_device *net; + int count = urb->actual_length, res; + struct sk_buff *skb; + + // Sanity check + if ( !ether_dev || !(ether_dev->flags & CDC_ETHER_RUNNING) ) { + dbg("BULK IN callback but driver is not active!"); + return; + } + + net = ether_dev->net; + if ( !netif_device_present(net) ) { + // Somebody killed our network interface... + return; + } + + if ( ether_dev->flags & CDC_ETHER_RX_BUSY ) { + // Are we already trying to receive a frame??? + ether_dev->stats.rx_errors++; + dbg("ether_dev Rx busy"); + return; + } + + // We are busy, leave us alone! + ether_dev->flags |= CDC_ETHER_RX_BUSY; + + switch ( urb->status ) { + case USB_ST_NOERROR: + break; + case USB_ST_NORESPONSE: + dbg( "no repsonse in BULK IN" ); + ether_dev->flags &= ~CDC_ETHER_RX_BUSY; + break; + default: + dbg( "%s: RX status %d", net->name, urb->status ); + goto goon; + } + + // Check to make sure we got some data... + if ( !count ) { + // We got no data!!! + goto goon; + } + + // Tell the kernel we want some memory + if ( !(skb = dev_alloc_skb(count)) ) { + // We got no receive buffer. + goto goon; + } + + // Here's where it came from + skb->dev = net; + + // Now we copy it over + eth_copy_and_sum(skb, ether_dev->rx_buff, count, 0); + + // Not sure + skb_put(skb, count); + // Not sure here either + skb->protocol = eth_type_trans(skb, net); + + // Ship it off to the kernel + netif_rx(skb); + + // update out statistics + ether_dev->stats.rx_packets++; + ether_dev->stats.rx_bytes += count; + +goon: + // Prep the USB to wait for another frame + FILL_BULK_URB( ðer_dev->rx_urb, ether_dev->usb, + usb_rcvbulkpipe(ether_dev->usb, ether_dev->data_ep_in), + ether_dev->rx_buff, ether_dev->wMaxSegmentSize, + read_bulk_callback, ether_dev ); + + // Give this to the USB subsystem so it can tell us + // when more data arrives. + if ( (res = usb_submit_urb(ðer_dev->rx_urb)) ) { + warn( __FUNCTION__ " failed submint rx_urb %d", res); + } + + // We are no longer busy, show us the frames!!! + ether_dev->flags &= ~CDC_ETHER_RX_BUSY; +} + +static void write_bulk_callback( struct urb *urb ) +{ + ether_dev_t *ether_dev = urb->context; + + // Sanity check + if ( !ether_dev || !(ether_dev->flags & CDC_ETHER_RUNNING) ) { + // We are insane!!! + err( "write_bulk_callback: device not running" ); + return; + } + + // Do we still have a valid kernel network device? + if ( !netif_device_present(ether_dev->net) ) { + // Someone killed our network interface. + err( "write_bulk_callback: net device not present" ); + return; + } + + // Hmm... What on Earth could have happened??? + if ( urb->status ) { + info("%s: TX status %d", ether_dev->net->name, urb->status); + } + + // Update the network interface and tell it we are + // ready for another frame + ether_dev->net->trans_start = jiffies; + netif_wake_queue( ether_dev->net ); +} + +//static void intr_callback( struct urb *urb ) +//{ +// ether_dev_t *ether_dev = urb->context; +// struct net_device *net; +// __u8 *d; +// +// if ( !ether_dev ) +// return; +// +// switch ( urb->status ) { +// case USB_ST_NOERROR: +// break; +// case USB_ST_URB_KILLED: +// return; +// default: +// info("intr status %d", urb->status); +// } +// +// d = urb->transfer_buffer; +// net = ether_dev->net; +// if ( d[0] & 0xfc ) { +// ether_dev->stats.tx_errors++; +// if ( d[0] & TX_UNDERRUN ) +// ether_dev->stats.tx_fifo_errors++; +// if ( d[0] & (EXCESSIVE_COL | JABBER_TIMEOUT) ) +// ether_dev->stats.tx_aborted_errors++; +// if ( d[0] & LATE_COL ) +// ether_dev->stats.tx_window_errors++; +// if ( d[0] & (NO_CARRIER | LOSS_CARRIER) ) +// ether_dev->stats.tx_carrier_errors++; +// } +//} + +////////////////////////////////////////////////////////////////////////////// +// Routines for turning net traffic on and off on the USB side /////////////// +////////////////////////////////////////////////////////////////////////////// + +static inline int enable_net_traffic( ether_dev_t *ether_dev ) +{ + struct usb_device *usb = ether_dev->usb; + + // Here would be the time to set the data interface to the configuration where + // it has two endpoints that use a protocol we can understand. + + if (usb_set_interface( usb, + ether_dev->data_bInterfaceNumber, + ether_dev->data_bAlternateSetting_with_traffic ) ) { + err("usb_set_interface() failed" ); + err("Attempted to set interface %d", ether_dev->data_bInterfaceNumber); + err("To alternate setting %d", +ether_dev->data_bAlternateSetting_with_traffic); + return -1; + } + return 0; +} + +static inline void disable_net_traffic( ether_dev_t *ether_dev ) +{ + // The thing to do is to set the data interface to the alternate setting that +has + // no endpoints. This is what the spec suggests. + + if (ether_dev->data_interface_altset_num_without_traffic >= 0 ) { + if (usb_set_interface( ether_dev->usb, + ether_dev->data_bInterfaceNumber, + +ether_dev->data_bAlternateSetting_without_traffic ) ) { + err("usb_set_interface() failed"); + } + } else { + // Some devices just may not support this... + warn("No way to disable net traffic"); + } +} + +////////////////////////////////////////////////////////////////////////////// +// Callback routines for kernel Ethernet Device ////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static void CDCEther_tx_timeout( struct net_device *net ) +{ + ether_dev_t *ether_dev = net->priv; + + // Sanity check + if ( !ether_dev ) { + // Seems to be a case of insanity here + return; + } + + // Tell syslog we are hosed. + warn("%s: Tx timed out.", net->name); + + // Tear the waiting frame off the list + ether_dev->tx_urb.transfer_flags |= USB_ASYNC_UNLINK; + usb_unlink_urb( ðer_dev->tx_urb ); + + // Update statistics + ether_dev->stats.tx_errors++; +} + +static int CDCEther_start_xmit( struct sk_buff *skb, struct net_device *net ) +{ + ether_dev_t *ether_dev = net->priv; + int count; + int res; + + // If we are told to transmit an ethernet frame that fits EXACTLY + // into an integer number of USB packets, we force it to send one + // more byte so the device will get a runt USB packet signalling the + // end of the ethernet frame + if ( (skb->len) ^ (ether_dev->data_ep_out_size) ) { + // It was not an exact multiple + // no need to add anything extra + count = skb->len; + } else { + // Add one to make it NOT an exact multiple + count = skb->len + 1; + } + + // Tell the kernel, "No more frames 'til we are done + // with this one.' + netif_stop_queue( net ); + + // Copy it from kernel memory to OUR memory + memcpy(ether_dev->tx_buff, skb->data, skb->len); + + // Fill in the URB for shipping it out. + FILL_BULK_URB( ðer_dev->tx_urb, ether_dev->usb, + usb_sndbulkpipe(ether_dev->usb, ether_dev->data_ep_out), + ether_dev->tx_buff, ether_dev->wMaxSegmentSize, + write_bulk_callback, ether_dev ); + + // Tell the URB how much it will be transporting today + ether_dev->tx_urb.transfer_buffer_length = count; + + // Send the URB on its merry way. + if ((res = usb_submit_urb(ðer_dev->tx_urb))) { + // Hmm... It didn't go. Tell someone... + warn("failed tx_urb %d", res); + // update some stats... + ether_dev->stats.tx_errors++; + // and tell the kernel to give us another. + // Maybe we'll get it right next time. + netif_start_queue( net ); + } else { + // Okay, it went out. + // Update statistics + ether_dev->stats.tx_packets++; + ether_dev->stats.tx_bytes += skb->len; + // And tell the kernel when the last transmit occurred. + net->trans_start = jiffies; + } + + // We are done with the kernel's memory + dev_kfree_skb(skb); + + // We are done here. + return 0; +} + +static struct net_device_stats *CDCEther_netdev_stats( struct net_device *net ) +{ + // Easy enough! + return &((ether_dev_t *)net->priv)->stats; +} + +static int CDCEther_open(struct net_device *net) +{ + ether_dev_t *ether_dev = (ether_dev_t *)net->priv; + int res; + + // We are finally getting used! + MOD_INC_USE_COUNT; + + // Turn on the USB and let the packets flow!!! + if ( (res = enable_net_traffic( ether_dev )) ) { + err( __FUNCTION__ "can't enable_net_traffic() - %d", res ); + MOD_DEC_USE_COUNT; + return -EIO; + } + + // Prep a receive URB + FILL_BULK_URB( ðer_dev->rx_urb, ether_dev->usb, + usb_rcvbulkpipe(ether_dev->usb, ether_dev->data_ep_in), + ether_dev->rx_buff, ether_dev->wMaxSegmentSize, + read_bulk_callback, ether_dev ); + + // Put it out there so the device can send us stuff + if ( (res = usb_submit_urb(ðer_dev->rx_urb)) ) + { + // Hmm... Okay... + warn( __FUNCTION__ " failed rx_urb %d", res ); + } + + // Tell the kernel we are ready to start receiving from it + netif_start_queue( net ); + + // We are up and running. + ether_dev->flags |= CDC_ETHER_RUNNING; + + // Let's get ready to move frames!!! + return 0; +} + +static int CDCEther_close( struct net_device *net ) +{ + ether_dev_t *ether_dev = net->priv; + + // We are no longer running. + ether_dev->flags &= ~CDC_ETHER_RUNNING; + + // Tell the kernel to stop sending us stuff + netif_stop_queue( net ); + + // If we are not already unplugged, turn off USB + // traffic + if ( !(ether_dev->flags & CDC_ETHER_UNPLUG) ) { + disable_net_traffic( ether_dev ); + } + + // We don't need the URBs anymore. + usb_unlink_urb( ðer_dev->rx_urb ); + usb_unlink_urb( ðer_dev->tx_urb ); + usb_unlink_urb( ðer_dev->intr_urb ); + + // We are not being used now. + MOD_DEC_USE_COUNT; + + // That's it. I'm done. + return 0; +} + +static int CDCEther_ioctl( struct net_device *net, struct ifreq *rq, int cmd ) +{ + //__u16 *data = (__u16 *)&rq->ifr_data; + //ether_dev_t *ether_dev = net->priv; + + // No support here yet. + // Do we need support??? + switch(cmd) { + case SIOCDEVPRIVATE: + return -EOPNOTSUPP; + case SIOCDEVPRIVATE+1: + return -EOPNOTSUPP; + case SIOCDEVPRIVATE+2: + //return 0; + return -EOPNOTSUPP; + default: + return -EOPNOTSUPP; + } +} + +static void CDCEther_set_multicast( struct net_device *net ) +{ + ether_dev_t *ether_dev = net->priv; + + // Tell the kernel to stop sending us frames while we get this + // all set up. + netif_stop_queue(net); + + // Do what we are told. + if (net->flags & IFF_PROMISC) { + // TODO - Turn on promiscuous mode + info( "%s: Promiscuous mode enabled", net->name); + } else if (net->flags & IFF_ALLMULTI){ + // TODO - Here we need to tell the device to block ALL multicast +traffic. + info("%s: set allmulti", net->name); + } else if (net->mc_count > ether_dev->wNumberMCFilters) { + // TODO - Here we need to set multicast filters, but + // There are more than our limit... Hmm... + info("%s: set too many MC filters", net->name); + } else { + // TODO - Here we are supposed to set SOME of the multicast filters. + // I must learn how to do this... + //info("%s: set Rx mode", net->name); + } + + // Tell the kernel to start giving frames to us again. + netif_wake_queue(net); +} + +////////////////////////////////////////////////////////////////////////////// +// Routines used to parse out the Functional Descriptors ///////////////////// +////////////////////////////////////////////////////////////////////////////// + +static int parse_header_functional_descriptor( int *bFunctionLength, + int bDescriptorType, + int bDescriptorSubtype, + unsigned char *data, + ether_dev_t *ether_dev, + int *requirements ) +{ + // Check to make sure we haven't seen one of these already. + if ( (~*requirements) & REQ_HDR_FUNC_DESCR ) { + err( "Multiple Header Functional Descriptors found." ); + return -1; + } + + // Is it the right size??? + if (*bFunctionLength != 5) { + info( "Invalid length in Header Functional Descriptor" ); + // This is a hack to get around a particular device (NO NAMES) + // It has this function length set to the length of the + // whole class-specific descriptor + *bFunctionLength = 5; + } + + // Nothing extremely useful here. + // We'll keep it for posterity + ether_dev->bcdCDC = data[0] + (data[1] << 8); + + // We've seen one of these + *requirements &= ~REQ_HDR_FUNC_DESCR; + + // It's all good. + return 0; +} + +static int parse_union_functional_descriptor( int *bFunctionLength, + int bDescriptorType, + int bDescriptorSubtype, + unsigned char *data, + ether_dev_t *ether_dev, + int *requirements ) +{ + // Check to make sure we haven't seen one of these already. + if ( (~*requirements) & REQ_UNION_FUNC_DESCR ) { + err( "Multiple Union Functional Descriptors found." ); + return -1; + } + + // Is it the right size? + if (*bFunctionLength != 5) { + // It is NOT the size we expected. + err( "Unsupported length in Union Functional Descriptor" ); + return -1; + } + + // Sanity check of sorts + if (ether_dev->comm_interface != data[0]) { + // This tells us that we are chasing the wrong comm + // interface or we are crazy or something else wierd. + err( "Union Functional Descriptor tells us to use a different +Communication Interface" ); + return -1; + } + + // We'll need this in a few microseconds! + ether_dev->data_interface = data[1]; + + // We've seen one of these now. + *requirements &= ~REQ_UNION_FUNC_DESCR; + + // Done + return 0; +} + +static int parse_ethernet_functional_descriptor( int *bFunctionLength, + int bDescriptorType, + int bDescriptorSubtype, + unsigned char *data, + ether_dev_t *ether_dev, + int *requirements ) +{ + // Check to make sure we haven't seen one of these already. + if ( (~*requirements) & REQ_ETH_FUNC_DESCR ) { + err( "Multiple Ethernet Functional Descriptors found." ); + return -1; + } + + // Is it the right size? + if (*bFunctionLength != 13) { + err( "Invalid length in Ethernet Networking Functional Descriptor" ); + return -1; + } + + // Lots of goodies from this one. They are all important. + ether_dev->iMACAddress = data[0]; + ether_dev->bmEthernetStatistics = data[1] + (data[2] << 8) + (data[3] << 16) + +(data[4] << 24); + ether_dev->wMaxSegmentSize = data[5] + (data[6] << 8); + ether_dev->wNumberMCFilters = (data[7] + (data[8] << 8)) & 0x00007FFF; + ether_dev->bNumberPowerFilters = data[9]; + + // We've seen one of these now. + *requirements &= ~REQ_ETH_FUNC_DESCR; + + // That's all she wrote. + return 0; +} + +static int parse_protocol_unit_functional_descriptor( int *bFunctionLength, + int bDescriptorType, + int bDescriptorSubtype, + unsigned char *data, + ether_dev_t *ether_dev, + int *requirements ) +{ + // There should only be one type if we are sane + if (bDescriptorType != CS_INTERFACE) { + info( "Invalid bDescriptorType found." ); + return -1; + } + + // The Subtype tells the tale. + switch (bDescriptorSubtype){ + case 0x00: // Header Functional Descriptor + return parse_header_functional_descriptor( bFunctionLength, + bDescriptorType, + bDescriptorSubtype, + data, + ether_dev, + requirements ); + break; + case 0x06: // Union Functional Descriptor + return parse_union_functional_descriptor( bFunctionLength, + bDescriptorType, + bDescriptorSubtype, + data, + ether_dev, + requirements ); + break; + case 0x0F: // Ethernet Networking Functional Descriptor + return parse_ethernet_functional_descriptor( bFunctionLength, + bDescriptorType, + +bDescriptorSubtype, + data, + ether_dev, + requirements ); + break; + default: // We don't support this at this time... + // However that doesn't necessarily indicate an error. + return 0; + } + // How did we get here??? + return -1; +} + +static int parse_ethernet_class_information( unsigned char *data, int length, +ether_dev_t *ether_dev ) +{ + int loc = 0; + int rc; + int bFunctionLength; + int bDescriptorType; + int bDescriptorSubtype; + int requirements = REQUIREMENTS_TOTAL; + + // As long as there is something here, we will try to parse it + while (loc < length) { + // Length + bFunctionLength = data[loc]; + loc++; + + // Type + bDescriptorType = data[loc]; + loc++; + + // Subtype + bDescriptorSubtype = data[loc]; + loc++; + + // ship this off to be processed elsewhere. + rc = parse_protocol_unit_functional_descriptor( &bFunctionLength, + bDescriptorType, + bDescriptorSubtype, + &data[loc], + ether_dev, + &requirements ); + // Did it process okay? + if (rc) { + // Something was hosed somewhere. + // No need to continue; + return -1; + } + // We have already taken three bytes. + loc += (bFunctionLength - 3); + } + // Check to see if we got everything we need. + if (requirements) { + // We missed some of the requirements... + err( "Not all required functional descriptors present 0x%08X", +requirements ); + return -1; + } + // We got everything. + return 0; +} + +////////////////////////////////////////////////////////////////////////////// +// Routine to check for the existence of the Functional Descriptors ////////// +////////////////////////////////////////////////////////////////////////////// + +static int find_and_parse_ethernet_class_information( struct usb_device *device, +ether_dev_t *ether_dev ) +{ + struct usb_config_descriptor *conf = NULL; + struct usb_interface *comm_intf_group = NULL; + struct usb_interface_descriptor *comm_intf = NULL; + int rc = -1; + // The assumption here is that find_ethernet_comm_interface + // and find_valid_configuration + // have already filled in the information about where to find + // the a valid commication interface. + + conf = &( device->config[ether_dev->configuration_num] ); + comm_intf_group = &( conf->interface[ether_dev->comm_interface] ); + comm_intf = &( +comm_intf_group->altsetting[ether_dev->comm_interface_altset_num] ); + // Let's check and see if it has the extra information we need... + + if (comm_intf->extralen > 0) { + // This is where the information is SUPPOSED to be. + rc = parse_ethernet_class_information( comm_intf->extra, +comm_intf->extralen, ether_dev ); + } else if (conf->extralen > 0) { + // This is a hack. The spec says it should be at the interface + // location checked above. However I have seen it here also. + // This is the same device that requires the functional descriptor +hack above + warn( "Ethernet information found at device configuration. This is +broken." ); + rc = parse_ethernet_class_information( comm_intf->extra, +comm_intf->extralen, ether_dev ); + } else { + // I don't know where else to look. + warn( "No ethernet information found." ); + rc = -1; + } + return rc; +} + +////////////////////////////////////////////////////////////////////////////// +// Routines to verify the data interface ///////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static int get_data_interface_endpoints( struct usb_device *device, ether_dev_t +*ether_dev ) +{ + struct usb_config_descriptor *conf = NULL; + struct usb_interface *data_intf_group = NULL; + struct usb_interface_descriptor *data_intf = NULL; + + // Walk through and get to the data interface we are checking. + conf = &( device->config[ether_dev->configuration_num] ); + data_intf_group = &( conf->interface[ether_dev->data_interface] ); + data_intf = &( +data_intf_group->altsetting[ether_dev->data_interface_altset_num_with_traffic] ); + + // Start out assuming we won't find anything we can use + ether_dev->data_ep_in = 0; + ether_dev->data_ep_out = 0; + + // If these are not BULK endpoints, we don't want them + if ( data_intf->endpoint[0].bmAttributes != 0x02 ) { + return -1; + } if ( data_intf->endpoint[1].bmAttributes != 0x02 ) { + return -1; + } + + // Check the first endpoint to see if it is IN or OUT + if ( data_intf->endpoint[0].bEndpointAddress & 0x80 ) { + // This endpoint is IN + ether_dev->data_ep_in = data_intf->endpoint[0].bEndpointAddress & 0x7F; + } else { + // This endpoint is OUT + ether_dev->data_ep_out = data_intf->endpoint[0].bEndpointAddress & +0x7F; + ether_dev->data_ep_out_size = data_intf->endpoint[0].wMaxPacketSize; + } + + // Check the second endpoint to see if it is IN or OUT + if ( data_intf->endpoint[1].bEndpointAddress & 0x80 ) { + // This endpoint is IN + ether_dev->data_ep_in = data_intf->endpoint[1].bEndpointAddress & 0x7F; + } else { + // This endpoint is OUT + ether_dev->data_ep_out = data_intf->endpoint[1].bEndpointAddress & +0x7F; + ether_dev->data_ep_out_size = data_intf->endpoint[1].wMaxPacketSize; + } + + // Now make sure we got both an IN and an OUT + if (ether_dev->data_ep_in && ether_dev->data_ep_out) { + // We did get both, we are in good shape... + info( "detected BULK OUT packets of size %d", +ether_dev->data_ep_out_size ); + return 0; + } + return -1; +} + +static int verify_ethernet_data_interface( struct usb_device *device, ether_dev_t +*ether_dev ) +{ + struct usb_config_descriptor *conf = NULL; + struct usb_interface *data_intf_group = NULL; + struct usb_interface_descriptor *data_intf = NULL; + int rc = -1; + int status; + int altset_num; + + // The assumption here is that parse_ethernet_class_information() + // and find_valid_configuration() + // have already filled in the information about where to find + // a data interface + conf = &( device->config[ether_dev->configuration_num] ); + data_intf_group = &( conf->interface[ether_dev->data_interface] ); + + // start out assuming we won't find what we are looking for. + ether_dev->data_interface_altset_num_with_traffic = -1; + ether_dev->data_bAlternateSetting_with_traffic = -1; + ether_dev->data_interface_altset_num_without_traffic = -1; + ether_dev->data_bAlternateSetting_without_traffic = -1; + + // Walk through every possible setting for this interface until + // we find what makes us happy. + for ( altset_num = 0; altset_num < data_intf_group->num_altsetting; +altset_num++ ) { + data_intf = &( data_intf_group->altsetting[altset_num] ); + + // Is this a data interface we like? + if ( ( data_intf->bInterfaceClass == 0x0A ) + && ( data_intf->bInterfaceSubClass == 0x00 ) + && ( data_intf->bInterfaceProtocol == 0x00 ) ) { + if ( data_intf->bNumEndpoints == 2 ) { + // We are required to have one of these. + // An interface with 2 endpoints to send Ethernet +traffic back and forth + // It actually may be possible that the device might +only + // communicate in a vendor specific manner. + // That would not be very nice. + // We can add that one later. + ether_dev->data_bInterfaceNumber = +data_intf->bInterfaceNumber; + ether_dev->data_interface_altset_num_with_traffic = +altset_num; + ether_dev->data_bAlternateSetting_with_traffic = +data_intf->bAlternateSetting; + status = get_data_interface_endpoints( device, +ether_dev ); + if (!status) { + rc = 0; + } + } + if ( data_intf->bNumEndpoints == 0 ) { + // According to the spec we are SUPPOSED to have one +of these + // In fact the device is supposed to come up in this +state. + // However, I have seen a device that did not have +such an interface. + // So it must be just optional for our driver... + ether_dev->data_bInterfaceNumber = +data_intf->bInterfaceNumber; + ether_dev->data_interface_altset_num_without_traffic = +altset_num; + ether_dev->data_bAlternateSetting_without_traffic = +data_intf->bAlternateSetting; + } + } + } + return rc; +} + +////////////////////////////////////////////////////////////////////////////// +// Routine to find a communication interface ///////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static int find_ethernet_comm_interface( struct usb_device *device, ether_dev_t +*ether_dev ) +{ + struct usb_config_descriptor *conf = NULL; + struct usb_interface *comm_intf_group = NULL; + struct usb_interface_descriptor *comm_intf = NULL; + int intf_num; + int altset_num; + int rc; + + conf = &( device->config[ether_dev->configuration_num] ); + + // We need to check and see if any of these interfaces are something we want. + // Walk through each interface one at a time + for ( intf_num = 0; intf_num < conf->bNumInterfaces; intf_num++ ) { + comm_intf_group = &( conf->interface[intf_num] ); + // Now for each of those interfaces, check every possible + // alternate setting. + for ( altset_num = 0; altset_num < comm_intf_group->num_altsetting; +altset_num++ ) { + comm_intf = &( comm_intf_group->altsetting[altset_num] ); + + // Is this a communication class of interface of the + // ethernet subclass variety. + if ( ( comm_intf->bInterfaceClass == 0x02 ) + && ( comm_intf->bInterfaceSubClass == 0x06 ) + && ( comm_intf->bInterfaceProtocol == 0x00 ) ) { + if ( comm_intf->bNumEndpoints == 1 ) { + // Good, we found one, we will try this one + // Fill in the structure... + ether_dev->comm_interface = intf_num; + ether_dev->comm_bInterfaceNumber = +comm_intf->bInterfaceNumber; + ether_dev->comm_interface_altset_num = +altset_num; + ether_dev->comm_bAlternateSetting = +comm_intf->bAlternateSetting; + + // Look for the Ethernet Functional Descriptors + rc = +find_and_parse_ethernet_class_information( device, ether_dev ); + if (rc) { + // Nope this was no good after all. + continue; + } + + // Check that we really can talk to the data + // interface + // This includes # of endpoints, protocols, + // etc. + rc = verify_ethernet_data_interface( device, +ether_dev ); + if (rc) { + // We got something we didn't like + continue; + } + // This communication interface seems to give +us everything + // we require. We have all the ethernet info +we need. + // Let's get out of here and go home right now. + return 0; + } else { + // bNumEndPoints != 1 + // We found an interface that had the wrong +number of + // endpoints but would have otherwise been okay + } // end bNumEndpoints check. + } // end interface specifics check. + } // end for altset_num + } // end for intf_num + return -1; +} + +////////////////////////////////////////////////////////////////////////////// +// Routine to go through all configurations and find one that //////////////// +// is an Ethernet Networking Device ////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static int find_valid_configuration( struct usb_device *device, ether_dev_t +*ether_dev ) +{ + struct usb_config_descriptor *conf = NULL; + int conf_num; + int rc; + + // We will try each and every possible configuration + for ( conf_num = 0; conf_num < device->descriptor.bNumConfigurations; +conf_num++ ) { + conf = &( device->config[conf_num] ); + + // Our first requirement : 2 interfaces + if ( conf->bNumInterfaces != 2 ) { + // I currently don't know how to handle devices with any +number of interfaces + // other than 2. + continue; + } + + // This one passed our first check, fill in some + // useful data + ether_dev->configuration_num = conf_num; + ether_dev->bConfigurationValue = conf->bConfigurationValue; + + // Now run it through the ringers and see what comes + // out the other side. + rc = find_ethernet_comm_interface( device, ether_dev ); + + // Check if we found an ethernet Communcation Device + if ( !rc ) { + // We found one. + return 0; + } + } + // None of the configurations suited us. + return -1; +} + +////////////////////////////////////////////////////////////////////////////// +// Routine that checks a given configuration to see if any driver //////////// +// has claimed any of the devices interfaces ///////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static int check_for_claimed_interfaces( struct usb_config_descriptor *config ) +{ + struct usb_interface *comm_intf_group; + int intf_num; + + // Go through all the interfaces and make sure none are + // claimed by anybody else. + for ( intf_num = 0; intf_num < config->bNumInterfaces; intf_num++ ) { + comm_intf_group = &( config->interface[intf_num] ); + if ( usb_interface_claimed( comm_intf_group ) ) { + // Somebody has beat us to this guy. + // We can't change the configuration out from underneath of +whoever + // is using this device, so we will go ahead and give up. + return -1; + } + } + // We made it all the way through. + // I guess no one has claimed any of these interfaces. + return 0; +} + +////////////////////////////////////////////////////////////////////////////// +// Routines to ask for and set the kernel network interface's MAC address //// +// Used by driver's probe routine //////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static inline unsigned char hex2dec( unsigned char digit ) +{ + // Is there a standard way to do this??? + // I have written this code TOO MANY times. + if ( (digit >= '0') && (digit <= '9') ) { + return (digit - '0'); + } + if ( (digit >= 'a') && (digit <= 'f') ) { + return (digit - 'a' + 10); + } + if ( (digit >= 'A') && (digit <= 'F') ) { + return (digit - 'A' + 10); + } + return 0; +} + +static void set_ethernet_addr( ether_dev_t *ether_dev ) +{ + unsigned char mac_addr[6]; + int i; + int len; + unsigned char buffer[13]; + + // Let's assume we don't get anything... + mac_addr[0] = 0x00; + mac_addr[1] = 0x00; + mac_addr[2] = 0x00; + mac_addr[3] = 0x00; + mac_addr[4] = 0x00; + mac_addr[5] = 0x00; + + // Let's ask the device... + len = usb_string(ether_dev->usb, ether_dev->iMACAddress, buffer, 13); + + // Sanity check! + if (len != 12) { + // You gotta love failing sanity checks + err("Attempting to get MAC address returned %d bytes", len); + return; + } + + // Fill in the mac_addr + for (i = 0; i < 6; i++) { + mac_addr[i] = ( hex2dec( buffer[2 * i] ) << 4 ) + hex2dec( buffer[2 * +i + 1] ); + } + + // Now copy it over to the kernel's network driver. + memcpy( ether_dev->net->dev_addr, mac_addr, sizeof(mac_addr) ); +} + +////////////////////////////////////////////////////////////////////////////// +// Routine to print to syslog information about the driver /////////////////// +// Used by driver's probe routine //////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +void log_device_info(ether_dev_t *ether_dev) +{ + int len; + int string_num; + unsigned char manu[256]; + unsigned char prod[256]; + unsigned char sern[256]; + unsigned char *mac_addr; + + // Default empty strings in case we don't find a real one + manu[0] = 0x00; + prod[0] = 0x00; + sern[0] = 0x00; + + // Try to get the device Manufacturer + string_num = ether_dev->usb->descriptor.iManufacturer; + if (string_num) { + // Put it into its buffer + len = usb_string(ether_dev->usb, string_num, manu, 255); + // Just to be safe + manu[len] = 0x00; + } + + // Try to get the device Product Name + string_num = ether_dev->usb->descriptor.iProduct; + if (string_num) { + // Put it into its buffer + len = usb_string(ether_dev->usb, string_num, prod, 255); + // Just to be safe + prod[len] = 0x00; + } + + // Try to get the device Serial Number + string_num = ether_dev->usb->descriptor.iSerialNumber; + if (string_num) { + // Put it into its buffer + len = usb_string(ether_dev->usb, string_num, sern, 255); + // Just to be safe + sern[len] = 0x00; + } + + // This makes it easier for us to print + mac_addr = ether_dev->net->dev_addr; + + // Now send everything we found to the syslog + info( "%s: %s %s %s %02X:%02X:%02X:%02X:%02X:%02X", + ether_dev->net->name, manu, prod, sern, mac_addr[0], + mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], + mac_addr[5] ); +} + +/* Forward declaration */ +static struct usb_driver CDCEther_driver ; + +////////////////////////////////////////////////////////////////////////////// +// Module's probe routine //////////////////////////////////////////////////// +// claims interfaces if they are for an Ethernet CDC ///////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static void * CDCEther_probe( struct usb_device *usb, unsigned int ifnum, + const struct usb_device_id *id) +{ + struct net_device *net; + ether_dev_t *ether_dev; + int rc; + + // First we should check the active configuration to see if + // any other driver has claimed any of the interfaces. + if ( check_for_claimed_interfaces( usb->actconfig ) ) { + // Someone has already put there grubby paws on this device. + // We don't want it now... + return NULL; + } + + // We might be finding a device we can use. + // We all go ahead and allocate our storage space. + // We need to because we have to start filling in the data that + // we are going to need later. + if(!(ether_dev = kmalloc(sizeof(ether_dev_t), GFP_KERNEL))) { + err("out of memory allocating device structure"); + return NULL; + } + + // Zero everything out. + memset(ether_dev, 0, sizeof(ether_dev_t)); + + // Let's see if we can find a configuration we can use. + rc = find_valid_configuration( usb, ether_dev ); + if (rc) { + // Nope we couldn't find one we liked. + // This device was not meant for us to control. + kfree( ether_dev ); + return NULL; + } + + // Now that we FOUND a configuration. let's try to make the + // device go into it. + if ( usb_set_configuration( usb, ether_dev->bConfigurationValue ) ) { + err("usb_set_configuration() failed"); + kfree( ether_dev ); + return NULL; + } + + // Now set the communication interface up as required. + if (usb_set_interface(usb, ether_dev->comm_bInterfaceNumber, +ether_dev->comm_bAlternateSetting)) { + err("usb_set_interface() failed"); + kfree( ether_dev ); + return NULL; + } + + // Only turn traffic on right now if we must... + if (ether_dev->data_interface_altset_num_without_traffic >= 0) { + // We found an alternate setting for the data + // interface that allows us to turn off traffic. + // We should use it. + if (usb_set_interface( usb, + ether_dev->data_bInterfaceNumber, + +ether_dev->data_bAlternateSetting_without_traffic)) { + err("usb_set_interface() failed"); + kfree( ether_dev ); + return NULL; + } + } else { + // We didn't find an alternate setting for the data + // interface that would let us turn off traffic. + // Oh well, let's go ahead and do what we must... + if (usb_set_interface( usb, + ether_dev->data_bInterfaceNumber, + +ether_dev->data_bAlternateSetting_with_traffic)) { + err("usb_set_interface() failed"); + kfree( ether_dev ); + return NULL; + } + } + + // Now we need to get a kernel Ethernet interface. + net = init_etherdev( NULL, 0 ); + if ( !net ) { + // Hmm... The kernel is not sharing today... + // Fine, we didn't want it anyway... + err( "Unable to initialize ethernet device" ); + kfree( ether_dev ); + return NULL; + } + + // Now that we have an ethernet device, let's set it up + // (And I don't mean "set [it] up the bomb".) + net->priv = ether_dev; + net->open = CDCEther_open; + net->stop = CDCEther_close; + net->watchdog_timeo = CDC_ETHER_TX_TIMEOUT; + net->tx_timeout = CDCEther_tx_timeout; // TX timeout function + net->do_ioctl = CDCEther_ioctl; + net->hard_start_xmit = CDCEther_start_xmit; + net->set_multicast_list = CDCEther_set_multicast; + net->get_stats = CDCEther_netdev_stats; + net->mtu = ether_dev->wMaxSegmentSize - 14; + + // We'll keep track of this information for later... + ether_dev->usb = usb; + ether_dev->net = net; + + // and don't forget the MAC address. + set_ethernet_addr( ether_dev ); + + // Send a message to syslog about what we are handling + log_device_info( ether_dev ); + + // I claim this interface to be a CDC Ethernet Networking device + usb_driver_claim_interface( &CDCEther_driver, + +&(usb->config[ether_dev->configuration_num].interface[ether_dev->comm_interface]), + ether_dev ); + // I claim this interface to be a CDC Ethernet Networking device + usb_driver_claim_interface( &CDCEther_driver, + +&(usb->config[ether_dev->configuration_num].interface[ether_dev->data_interface]), + ether_dev ); + + // Does this REALLY do anything??? + usb_inc_dev_use( usb ); + + // TODO - last minute HACK + ether_dev->comm_ep_in = 5; + + // Okay, we are finally done... + return NULL; +} + + +////////////////////////////////////////////////////////////////////////////// +// Module's disconnect routine /////////////////////////////////////////////// +// Called when the driver is unloaded or the device is unplugged ///////////// +// (Whichever happens first assuming the driver suceeded at its probe) /////// +////////////////////////////////////////////////////////////////////////////// + +static void CDCEther_disconnect( struct usb_device *usb, void *ptr ) +{ + ether_dev_t *ether_dev = ptr; + + // Sanity check!!! + if ( !ether_dev || !ether_dev->usb ) { + // We failed. We are insane!!! + warn("unregistering non-existant device"); + return; + } + + // Make sure we fail the sanity check if we try this again. + ether_dev->usb = NULL; + + // It is possible that this function is called before + // the "close" function. + // This tells the close function we are already disconnected + ether_dev->flags |= CDC_ETHER_UNPLUG; + + // We don't need the network device any more + unregister_netdev( ether_dev->net ); + + // For sanity checks + ether_dev->net = NULL; + + // I ask again, does this do anything??? + usb_dec_dev_use( usb ); + + // We are done with this interface + usb_driver_release_interface( &CDCEther_driver, + +&(usb->config[ether_dev->configuration_num].interface[ether_dev->comm_interface]) ); + + // We are done with this interface too + usb_driver_release_interface( &CDCEther_driver, + +&(usb->config[ether_dev->configuration_num].interface[ether_dev->data_interface]) ); + + // No more tied up kernel memory + kfree( ether_dev ); + + // This does no good, but it looks nice! + ether_dev = NULL; +} + +////////////////////////////////////////////////////////////////////////////// +// Driver info /////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +static struct usb_driver CDCEther_driver = { + name: "CDCEther", + probe: CDCEther_probe, + disconnect: CDCEther_disconnect, + id_table: CDCEther_ids, +}; + +////////////////////////////////////////////////////////////////////////////// +// init and exit routines called when driver is installed and uninstalled //// +////////////////////////////////////////////////////////////////////////////// + +int __init CDCEther_init(void) +{ + info( "%s", version ); + return usb_register( &CDCEther_driver ); +} + +void __exit CDCEther_exit(void) +{ + usb_deregister( &CDCEther_driver ); +} + +////////////////////////////////////////////////////////////////////////////// +// Module info /////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// + +module_init( CDCEther_init ); +module_exit( CDCEther_exit ); + +MODULE_AUTHOR("Brad Hards and another"); +MODULE_DESCRIPTION("USB CDC Ethernet driver"); + +MODULE_DEVICE_TABLE (usb, CDCEther_ids); + +////////////////////////////////////////////////////////////////////////////// +// End of file /////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// diff -X dontdiff -Naur linux-2.4.2ac28-clean/drivers/usb/CDCEther.h linux/drivers/usb/CDCEther.h --- linux-2.4.2ac28-clean/drivers/usb/CDCEther.h Thu Jan 1 10:00:00 1970 +++ linux/drivers/usb/CDCEther.h Fri Mar 30 10:11:59 2001 @@ -0,0 +1,88 @@ +// Portions of this file taken from +// Petko Manolov - Petkan ([EMAIL PROTECTED]) +// from his driver pegasus.h + +/* + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#define CS_INTERFACE 0x24 + +#define CDC_ETHER_MAX_MTU 1536 + +#define CDC_ETHER_PRESENT 0x00000001 +#define CDC_ETHER_RUNNING 0x00000002 +#define CDC_ETHER_TX_BUSY 0x00000004 +#define CDC_ETHER_RX_BUSY 0x00000008 +#define CDC_ETHER_UNPLUG 0x00000040 + +#define CDC_ETHER_TX_TIMEOUT (HZ*10) + +#define TX_UNDERRUN 0x80 +#define EXCESSIVE_COL 0x40 +#define LATE_COL 0x20 +#define NO_CARRIER 0x10 +#define LOSS_CARRIER 0x08 +#define JABBER_TIMEOUT 0x04 + +#define CDC_ETHER_REQT_READ 0xc0 +#define CDC_ETHER_REQT_WRITE 0x40 +#define CDC_ETHER_REQ_GET_REGS 0xf0 +#define CDC_ETHER_REQ_SET_REGS 0xf1 +#define CDC_ETHER_REQ_SET_REG PIPERIDER_REQ_SET_REGS +#define ALIGN(x) x __attribute__((aligned(L1_CACHE_BYTES))) + +typedef struct _ether_dev_t { + struct usb_device *usb; + struct net_device *net; + struct net_device_stats stats; + unsigned flags; + int configuration_num; + int bConfigurationValue; + int comm_interface; + int comm_bInterfaceNumber; + int comm_interface_altset_num; + int comm_bAlternateSetting; + int comm_ep_in; + int data_interface; + int data_bInterfaceNumber; + int data_interface_altset_num_with_traffic; + int data_bAlternateSetting_with_traffic; + int data_interface_altset_num_without_traffic; + int data_bAlternateSetting_without_traffic; + int data_ep_in; + int data_ep_out; + int data_ep_out_size; + __u16 bcdCDC; + __u8 iMACAddress; + __u32 bmEthernetStatistics; + __u16 wMaxSegmentSize; + __u16 wNumberMCFilters; + __u8 bNumberPowerFilters; + int intr_interval; + struct urb rx_urb, tx_urb, intr_urb; + unsigned char ALIGN(rx_buff[CDC_ETHER_MAX_MTU]); + unsigned char ALIGN(tx_buff[CDC_ETHER_MAX_MTU]); + unsigned char ALIGN(intr_buff[8]); +} ether_dev_t; + +#define REQ_HDR_FUNC_DESCR 0x0001 +#define REQ_UNION_FUNC_DESCR 0x0002 +#define REQ_ETH_FUNC_DESCR 0x0004 +#define REQUIREMENTS_TOTAL 0x0007 + + + diff -X dontdiff -Naur linux-2.4.2ac28-clean/drivers/usb/Config.in linux/drivers/usb/Config.in --- linux-2.4.2ac28-clean/drivers/usb/Config.in Fri Mar 30 09:19:32 2001 +++ linux/drivers/usb/Config.in Fri Mar 30 10:11:59 2001 @@ -65,6 +65,7 @@ dep_tristate ' PLUSB Prolific USB-Network driver (EXPERIMENTAL)' CONFIG_USB_PLUSB $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL dep_tristate ' USB ADMtek Pegasus-based ethernet device support (EXPERIMENTAL)' CONFIG_USB_PEGASUS $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL dep_tristate ' USB KLSI KL5USB101-based ethernet device support (EXPERIMENTAL)' CONFIG_USB_KAWETH $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL + dep_tristate ' USB Communication Class Ethernet driver (EXPERIMENTAL)' +CONFIG_USB_CDCETHER $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL dep_tristate ' USB-to-USB Networking (NetChip, Prolific, ...) (EXPERIMENTAL)' CONFIG_USB_USBNET $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL comment 'USB port drivers' diff -X dontdiff -Naur linux-2.4.2ac28-clean/drivers/usb/Makefile linux/drivers/usb/Makefile --- linux-2.4.2ac28-clean/drivers/usb/Makefile Fri Mar 30 09:19:32 2001 +++ linux/drivers/usb/Makefile Fri Mar 30 10:11:59 2001 @@ -60,6 +60,7 @@ # network drivers +obj-$(CONFIG_USB_CDCETHER) += CDCEther.o obj-$(CONFIG_USB_KAWETH) += kaweth.o obj-$(CONFIG_USB_PEGASUS) += pegasus.o obj-$(CONFIG_USB_PLUSB) += plusb.o
