]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[ecm] Add support for CDC-ECM USB Ethernet devices
authorMichael Brown <mcb30@ipxe.org>
Mon, 9 Feb 2015 16:36:06 +0000 (16:36 +0000)
committerMichael Brown <mcb30@ipxe.org>
Tue, 10 Feb 2015 13:49:27 +0000 (13:49 +0000)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/drivers/net/ecm.c
src/drivers/net/ecm.h

index 8a7fe23c75627ea1652e889b9c9051d1b0fca57c..2e1e0e8331b1e491e458fce294305ed0b75d23c0 100644 (file)
@@ -21,8 +21,11 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 #include <stdint.h>
 #include <errno.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
 #include <ipxe/if_ether.h>
 #include <ipxe/base16.h>
+#include <ipxe/profile.h>
 #include <ipxe/usb.h>
 #include "ecm.h"
 
@@ -32,6 +35,29 @@ FILE_LICENCE ( GPL2_OR_LATER );
  *
  */
 
+/** Refill profiler */
+static struct profiler ecm_refill_profiler __profiler =
+       { .name = "ecm.refill" };
+
+/** Interrupt completion profiler */
+static struct profiler ecm_intr_profiler __profiler =
+       { .name = "ecm.intr" };
+
+/** Bulk IN completion profiler */
+static struct profiler ecm_in_profiler __profiler =
+       { .name = "ecm.in" };
+
+/** Bulk OUT profiler */
+static struct profiler ecm_out_profiler __profiler =
+       { .name = "ecm.out" };
+
+/******************************************************************************
+ *
+ * Ethernet functional descriptor
+ *
+ ******************************************************************************
+ */
+
 /**
  * Locate Ethernet functional descriptor
  *
@@ -87,3 +113,634 @@ int ecm_fetch_mac ( struct usb_device *usb,
 
        return 0;
 }
+
+/******************************************************************************
+ *
+ * Ring management
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Transcribe receive ring name (for debugging)
+ *
+ * @v ecm              CDC-ECM device
+ * @v ring             Receive ring
+ * @ret name           Receive ring name
+ */
+static inline const char * ecm_rx_name ( struct ecm_device *ecm,
+                                        struct ecm_rx_ring *ring ) {
+       if ( ring == &ecm->intr ) {
+               return "interrupt";
+       } else if ( ring == &ecm->in ) {
+               return "bulk IN";
+       } else {
+               return "UNKNOWN";
+       }
+}
+
+/**
+ * Refill receive ring
+ *
+ * @v ecm              CDC-ECM device
+ * @v ring             Receive ring
+ */
+static void ecm_rx_refill ( struct ecm_device *ecm, struct ecm_rx_ring *ring ) {
+       struct net_device *netdev = ecm->netdev;
+       struct io_buffer *iobuf;
+       int rc;
+
+       /* Refill ring */
+       while ( ring->fill < ring->max ) {
+
+               /* Profile refill */
+               profile_start ( &ecm_refill_profiler );
+
+               /* Allocate I/O buffer */
+               iobuf = alloc_iob ( ring->mtu );
+               if ( ! iobuf ) {
+                       /* Wait for next refill */
+                       break;
+               }
+               iob_put ( iobuf, ring->mtu );
+
+               /* Enqueue I/O buffer */
+               if ( ( rc = usb_stream ( &ring->ep, iobuf, 0 ) ) != 0 ) {
+                       netdev_rx_err ( netdev, iob_disown ( iobuf ), rc );
+                       break;
+               }
+
+               /* Increment fill level */
+               ring->fill++;
+               profile_stop ( &ecm_refill_profiler );
+       }
+}
+
+/******************************************************************************
+ *
+ * CDC-ECM communications interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Complete interrupt transfer
+ *
+ * @v ep               USB endpoint
+ * @v iobuf            I/O buffer
+ * @v rc               Completion status code
+ */
+static void ecm_intr_complete ( struct usb_endpoint *ep,
+                               struct io_buffer *iobuf, int rc ) {
+       struct ecm_device *ecm = container_of ( ep, struct ecm_device, intr.ep);
+       struct net_device *netdev = ecm->netdev;
+       struct usb_setup_packet *message;
+       size_t len = iob_len ( iobuf );
+
+       /* Profile completions */
+       profile_start ( &ecm_intr_profiler );
+
+       /* Decrement fill level */
+       assert ( ecm->intr.fill > 0 );
+       ecm->intr.fill--;
+
+       /* Ignore packets cancelled when the endpoint closes */
+       if ( ! ep->open )
+               goto ignore;
+
+       /* Drop packets with errors */
+       if ( rc != 0 ) {
+               DBGC ( ecm, "ECM %p interrupt failed: %s\n",
+                      ecm, strerror ( rc ) );
+               DBGC_HDA ( ecm, 0, iobuf->data, iob_len ( iobuf ) );
+               goto error;
+       }
+
+       /* Extract message header */
+       if ( len < sizeof ( *message ) ) {
+               DBGC ( ecm, "ECM %p underlength interrupt:\n", ecm );
+               DBGC_HDA ( ecm, 0, iobuf->data, iob_len ( iobuf ) );
+               goto error;
+       }
+       message = iobuf->data;
+
+       /* Parse message header */
+       switch ( message->request ) {
+
+       case cpu_to_le16 ( CDC_NETWORK_CONNECTION ) :
+               if ( message->value && ! netdev_link_ok ( netdev ) ) {
+                       DBGC ( ecm, "ECM %p link up\n", ecm );
+                       netdev_link_up ( netdev );
+               } else if ( netdev_link_ok ( netdev ) && ! message->value ) {
+                       DBGC ( ecm, "ECM %p link down\n", ecm );
+                       netdev_link_down ( netdev );
+               }
+               break;
+
+       case cpu_to_le16 ( CDC_CONNECTION_SPEED_CHANGE ) :
+               /* Ignore */
+               break;
+
+       default:
+               DBGC ( ecm, "ECM %p unrecognised interrupt:\n", ecm );
+               DBGC_HDA ( ecm, 0, iobuf->data, iob_len ( iobuf ) );
+               goto error;
+       }
+
+       /* Free I/O buffer */
+       free_iob ( iobuf );
+       profile_stop ( &ecm_intr_profiler );
+
+       return;
+
+ error:
+       netdev_rx_err ( netdev, iob_disown ( iobuf ), rc );
+ ignore:
+       free_iob ( iobuf );
+       return;
+}
+
+/** Interrupt endpoint operations */
+static struct usb_endpoint_driver_operations ecm_intr_operations = {
+       .complete = ecm_intr_complete,
+};
+
+/**
+ * Open communications interface
+ *
+ * @v ecm              CDC-ECM device
+ * @ret rc             Return status code
+ */
+static int ecm_comms_open ( struct ecm_device *ecm ) {
+       int rc;
+
+       /* Open interrupt endpoint */
+       if ( ( rc = usb_endpoint_open ( &ecm->intr.ep ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not open interrupt: %s\n",
+                      ecm, strerror ( rc ) );
+               goto err_open;
+       }
+
+       /* Refill interrupt ring */
+       ecm_rx_refill ( ecm, &ecm->intr );
+
+       return 0;
+
+       usb_endpoint_close ( &ecm->intr.ep );
+       assert ( ecm->intr.fill == 0 );
+ err_open:
+       return rc;
+}
+
+/**
+ * Close communications interface
+ *
+ * @v ecm              CDC-ECM device
+ */
+static void ecm_comms_close ( struct ecm_device *ecm ) {
+
+       /* Close interrupt endpoint */
+       usb_endpoint_close ( &ecm->intr.ep );
+       assert ( ecm->intr.fill == 0 );
+}
+
+/******************************************************************************
+ *
+ * CDC-ECM data interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Complete bulk IN transfer
+ *
+ * @v ep               USB endpoint
+ * @v iobuf            I/O buffer
+ * @v rc               Completion status code
+ */
+static void ecm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf,
+                             int rc ) {
+       struct ecm_device *ecm = container_of ( ep, struct ecm_device, in.ep );
+       struct net_device *netdev = ecm->netdev;
+
+       /* Profile receive completions */
+       profile_start ( &ecm_in_profiler );
+
+       /* Decrement fill level */
+       assert ( ecm->in.fill > 0 );
+       ecm->in.fill--;
+
+       /* Ignore packets cancelled when the endpoint closes */
+       if ( ! ep->open )
+               goto ignore;
+
+       /* Record USB errors against the network device */
+       if ( rc != 0 ) {
+               DBGC ( ecm, "ECM %p bulk IN failed: %s\n",
+                      ecm, strerror ( rc ) );
+               goto error;
+       }
+
+       /* Hand off to network stack */
+       netdev_rx ( netdev, iob_disown ( iobuf ) );
+
+       profile_stop ( &ecm_in_profiler );
+       return;
+
+ error:
+       netdev_rx_err ( netdev, iob_disown ( iobuf ), rc );
+ ignore:
+       free_iob ( iobuf );
+}
+
+/** Bulk IN endpoint operations */
+static struct usb_endpoint_driver_operations ecm_in_operations = {
+       .complete = ecm_in_complete,
+};
+
+/**
+ * Transmit packet
+ *
+ * @v ecm              CDC-ECM device
+ * @v iobuf            I/O buffer
+ * @ret rc             Return status code
+ */
+static int ecm_out_transmit ( struct ecm_device *ecm,
+                             struct io_buffer *iobuf ) {
+       int rc;
+
+       /* Profile transmissions */
+       profile_start ( &ecm_out_profiler );
+
+       /* Enqueue I/O buffer */
+       if ( ( rc = usb_stream ( &ecm->out.ep, iobuf, 1 ) ) != 0 )
+               return rc;
+
+       profile_stop ( &ecm_out_profiler );
+       return 0;
+}
+
+/**
+ * Complete bulk OUT transfer
+ *
+ * @v ep               USB endpoint
+ * @v iobuf            I/O buffer
+ * @v rc               Completion status code
+ */
+static void ecm_out_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf,
+                              int rc ) {
+       struct ecm_device *ecm = container_of ( ep, struct ecm_device, out.ep );
+       struct net_device *netdev = ecm->netdev;
+
+       /* Report TX completion */
+       netdev_tx_complete_err ( netdev, iobuf, rc );
+}
+
+/** Bulk OUT endpoint operations */
+static struct usb_endpoint_driver_operations ecm_out_operations = {
+       .complete = ecm_out_complete,
+};
+
+/**
+ * Open data interface
+ *
+ * @v ecm              CDC-ECM device
+ * @ret rc             Return status code
+ */
+static int ecm_data_open ( struct ecm_device *ecm ) {
+       struct usb_device *usb = ecm->usb;
+       int rc;
+
+       /* Select alternate setting for data interface */
+       if ( ( rc = usb_set_interface ( usb, ecm->data,
+                                       ECM_DATA_ALTERNATE ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not set alternate interface: %s\n",
+                      ecm, strerror ( rc ) );
+               goto err_set_interface;
+       }
+
+       /* Open bulk IN endpoint */
+       if ( ( rc = usb_endpoint_open ( &ecm->in.ep ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not open bulk IN: %s\n",
+                      ecm, strerror ( rc ) );
+               goto err_open_in;
+       }
+
+       /* Open bulk OUT endpoint */
+       if ( ( rc = usb_endpoint_open ( &ecm->out.ep ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not open bulk OUT: %s\n",
+                      ecm, strerror ( rc ) );
+               goto err_open_out;
+       }
+
+       /* Refill bulk IN ring */
+       ecm_rx_refill ( ecm, &ecm->in );
+
+       return 0;
+
+       usb_endpoint_close ( &ecm->out.ep );
+ err_open_out:
+       usb_endpoint_close ( &ecm->in.ep );
+       assert ( ecm->in.fill == 0 );
+ err_open_in:
+       usb_set_interface ( usb, ecm->data, 0 );
+ err_set_interface:
+       return rc;
+}
+
+/**
+ * Close data interface
+ *
+ * @v ecm              CDC-ECM device
+ */
+static void ecm_data_close ( struct ecm_device *ecm ) {
+       struct usb_device *usb = ecm->usb;
+
+       /* Close endpoints */
+       usb_endpoint_close ( &ecm->out.ep );
+       usb_endpoint_close ( &ecm->in.ep );
+       assert ( ecm->in.fill == 0 );
+
+       /* Reset data interface */
+       usb_set_interface ( usb, ecm->data, 0 );
+}
+
+/******************************************************************************
+ *
+ * Network device interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Open network device
+ *
+ * @v netdev           Network device
+ * @ret rc             Return status code
+ */
+static int ecm_open ( struct net_device *netdev ) {
+       struct ecm_device *ecm = netdev->priv;
+       struct usb_device *usb = ecm->usb;
+       unsigned int filter;
+       int rc;
+
+       /* Open communications interface */
+       if ( ( rc = ecm_comms_open ( ecm ) ) != 0 )
+               goto err_comms_open;
+
+       /* Open data interface */
+       if ( ( rc = ecm_data_open ( ecm ) ) != 0 )
+               goto err_data_open;
+
+       /* Set packet filter */
+       filter = ( ECM_PACKET_TYPE_PROMISCUOUS |
+                  ECM_PACKET_TYPE_ALL_MULTICAST |
+                  ECM_PACKET_TYPE_DIRECTED |
+                  ECM_PACKET_TYPE_BROADCAST );
+       if ( ( rc = usb_control ( usb, ECM_SET_ETHERNET_PACKET_FILTER,
+                                 filter, ecm->comms, NULL, 0 ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not set packet filter: %s\n",
+                      ecm, strerror ( rc ) );
+               goto err_set_filter;
+       }
+
+       return 0;
+
+ err_set_filter:
+       ecm_data_close ( ecm );
+ err_data_open:
+       ecm_comms_close ( ecm );
+ err_comms_open:
+       return rc;
+}
+
+/**
+ * Close network device
+ *
+ * @v netdev           Network device
+ */
+static void ecm_close ( struct net_device *netdev ) {
+       struct ecm_device *ecm = netdev->priv;
+
+       /* Close data interface */
+       ecm_data_close ( ecm );
+
+       /* Close communications interface */
+       ecm_comms_close ( ecm );
+}
+
+/**
+ * Transmit packet
+ *
+ * @v netdev           Network device
+ * @v iobuf            I/O buffer
+ * @ret rc             Return status code
+ */
+static int ecm_transmit ( struct net_device *netdev,
+                         struct io_buffer *iobuf ) {
+       struct ecm_device *ecm = netdev->priv;
+       int rc;
+
+       /* Transmit packet */
+       if ( ( rc = ecm_out_transmit ( ecm, iobuf ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
+/**
+ * Poll for completed and received packets
+ *
+ * @v netdev           Network device
+ */
+static void ecm_poll ( struct net_device *netdev ) {
+       struct ecm_device *ecm = netdev->priv;
+
+       /* Poll USB bus */
+       usb_poll ( ecm->bus );
+
+       /* Refill interrupt ring */
+       ecm_rx_refill ( ecm, &ecm->intr );
+
+       /* Refill bulk IN ring */
+       ecm_rx_refill ( ecm, &ecm->in );
+}
+
+/** CDC-ECM network device operations */
+static struct net_device_operations ecm_operations = {
+       .open           = ecm_open,
+       .close          = ecm_close,
+       .transmit       = ecm_transmit,
+       .poll           = ecm_poll,
+};
+
+/******************************************************************************
+ *
+ * USB interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Probe device
+ *
+ * @v func             USB function
+ * @v config           Configuration descriptor
+ * @ret rc             Return status code
+ */
+static int ecm_probe ( struct usb_function *func,
+                      struct usb_configuration_descriptor *config ) {
+       struct usb_device *usb = func->usb;
+       struct net_device *netdev;
+       struct ecm_device *ecm;
+       struct usb_interface_descriptor *comms;
+       struct usb_interface_descriptor *data;
+       struct ecm_ethernet_descriptor *ethernet;
+       int rc;
+
+       /* Allocate and initialise structure */
+       netdev = alloc_etherdev ( sizeof ( *ecm ) );
+       if ( ! netdev ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       netdev_init ( netdev, &ecm_operations );
+       netdev->dev = &func->dev;
+       ecm = netdev->priv;
+       memset ( ecm, 0, sizeof ( *ecm ) );
+       ecm->usb = usb;
+       ecm->bus = usb->port->hub->bus;
+       ecm->netdev = netdev;
+       usb_endpoint_init ( &ecm->intr.ep, usb, &ecm_intr_operations );
+       usb_endpoint_init ( &ecm->in.ep, usb, &ecm_in_operations );
+       usb_endpoint_init ( &ecm->out.ep, usb, &ecm_out_operations );
+       DBGC ( ecm, "ECM %p on %s\n", ecm, func->name );
+
+       /* Identify interfaces */
+       if ( func->count < ECM_INTERFACE_COUNT ) {
+               DBGC ( ecm, "ECM %p has only %d interfaces\n",
+                      ecm, func->count );
+               rc = -EINVAL;
+               goto err_count;
+       }
+       ecm->comms = func->interface[ECM_INTERFACE_COMMS];
+       ecm->data = func->interface[ECM_INTERFACE_DATA];
+
+       /* Locate communications interface descriptor */
+       comms = usb_interface_descriptor ( config, ecm->comms, 0 );
+       if ( ! comms ) {
+               DBGC ( ecm, "ECM %p has no communications interface\n", ecm );
+               rc = -EINVAL;
+               goto err_comms;
+       }
+
+       /* Locate data interface descriptor */
+       data = usb_interface_descriptor ( config, ecm->data,
+                                         ECM_DATA_ALTERNATE );
+       if ( ! data ) {
+               DBGC ( ecm, "ECM %p has no data interface\n", ecm );
+               rc = -EINVAL;
+               goto err_data;
+       }
+
+       /* Describe interrupt endpoint */
+       if ( ( rc = usb_endpoint_described ( &ecm->intr.ep, config, comms,
+                                            USB_INTERRUPT, 0 ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not describe interrupt endpoint: "
+                      "%s\n", ecm, strerror ( rc ) );
+               goto err_interrupt;
+       }
+       ecm->intr.mtu = ecm->intr.ep.mtu;
+       ecm->intr.max = ECM_INTR_MAX_FILL;
+
+       /* Describe bulk IN endpoint */
+       if ( ( rc = usb_endpoint_described ( &ecm->in.ep, config, data,
+                                            USB_BULK_IN, 0 ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not describe bulk IN endpoint: "
+                      "%s\n", ecm, strerror ( rc ) );
+               goto err_bulk_in;
+       }
+       ecm->in.mtu = ECM_IN_MTU;
+       ecm->in.max = ECM_IN_MAX_FILL;
+
+       /* Describe bulk OUT endpoint */
+       if ( ( rc = usb_endpoint_described ( &ecm->out.ep, config, data,
+                                            USB_BULK_OUT, 0 ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not describe bulk OUT endpoint: "
+                      "%s\n", ecm, strerror ( rc ) );
+               goto err_bulk_out;
+       }
+
+       /* Locate Ethernet descriptor */
+       ethernet = ecm_ethernet_descriptor ( config, comms );
+       if ( ! ethernet ) {
+               DBGC ( ecm, "ECM %p has no Ethernet descriptor\n", ecm );
+               rc = -EINVAL;
+               goto err_ethernet;
+       }
+
+       /* Fetch MAC address */
+       if ( ( rc = ecm_fetch_mac ( usb, ethernet, netdev->hw_addr ) ) != 0 ) {
+               DBGC ( ecm, "ECM %p could not fetch MAC address: %s\n",
+                      ecm, strerror ( rc ) );
+               goto err_fetch_mac;
+       }
+
+       /* Register network device */
+       if ( ( rc = register_netdev ( netdev ) ) != 0 )
+               goto err_register;
+
+       usb_func_set_drvdata ( func, ecm );
+       return 0;
+
+       unregister_netdev ( netdev );
+ err_register:
+ err_fetch_mac:
+ err_ethernet:
+ err_bulk_out:
+ err_bulk_in:
+ err_interrupt:
+ err_data:
+ err_comms:
+ err_count:
+       netdev_nullify ( netdev );
+       netdev_put ( netdev );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Remove device
+ *
+ * @v func             USB function
+ */
+static void ecm_remove ( struct usb_function *func ) {
+       struct ecm_device *ecm = usb_func_get_drvdata ( func );
+       struct net_device *netdev = ecm->netdev;
+
+       unregister_netdev ( netdev );
+       netdev_nullify ( netdev );
+       netdev_put ( netdev );
+}
+
+/** CDC-ECM device IDs */
+static struct usb_device_id ecm_ids[] = {
+       {
+               .name = "cdc-ecm",
+               .vendor = USB_ANY_ID,
+               .product = USB_ANY_ID,
+               .class = {
+                       .class = USB_CLASS_CDC,
+                       .subclass = USB_SUBCLASS_CDC_ECM,
+                       .protocol = 0,
+               },
+       },
+};
+
+/** CDC-ECM driver */
+struct usb_driver ecm_driver __usb_driver = {
+       .ids = ecm_ids,
+       .id_count = ( sizeof ( ecm_ids ) / sizeof ( ecm_ids[0] ) ),
+       .probe = ecm_probe,
+       .remove = ecm_remove,
+};
index e8ec3fb6ef80a3ba41adc6eb7a6174013c1bbd67..9c754bce5a0fd75328e612c89693e467d47880b2 100644 (file)
@@ -12,13 +12,47 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/usb.h>
 #include <ipxe/cdc.h>
 
+/** CDC-ECM subclass */
+#define USB_SUBCLASS_CDC_ECM 0x06
+
+/** CDC-ECM interfaces */
+enum ecm_interfaces {
+       /** Communications interface */
+       ECM_INTERFACE_COMMS = 0,
+       /** Data interface */
+       ECM_INTERFACE_DATA,
+       ECM_INTERFACE_COUNT
+};
+
+/** Alternate setting for CDC-ECM data interface */
+#define ECM_DATA_ALTERNATE 1
+
+/** Set Ethernet packet filter */
+#define ECM_SET_ETHERNET_PACKET_FILTER                                 \
+       ( USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE |          \
+         USB_REQUEST_TYPE ( 0x43 ) )
+
+/** Ethernet packet types */
+enum ecm_ethernet_packet_filter {
+       /** Promiscuous mode */
+       ECM_PACKET_TYPE_PROMISCUOUS = 0x0001,
+       /** All multicast packets */
+       ECM_PACKET_TYPE_ALL_MULTICAST = 0x0002,
+       /** Unicast packets */
+       ECM_PACKET_TYPE_DIRECTED = 0x0004,
+       /** Broadcast packets */
+       ECM_PACKET_TYPE_BROADCAST = 0x0008,
+       /** Specified multicast packets */
+       ECM_PACKET_TYPE_MULTICAST = 0x0010,
+};
+
 /** An Ethernet Functional Descriptor */
 struct ecm_ethernet_descriptor {
        /** Descriptor header */
        struct usb_descriptor_header header;
        /** Descriptor subtype */
        uint8_t subtype;
-       /** MAC addres string */
+       /** MAC address string */
        uint8_t mac;
        /** Ethernet statistics bitmap */
        uint32_t statistics;
@@ -30,6 +64,64 @@ struct ecm_ethernet_descriptor {
        uint8_t wol;
 } __attribute__ (( packed ));
 
+/** A CDC-ECM receive ring */
+struct ecm_rx_ring {
+       /** USB endpoint */
+       struct usb_endpoint ep;
+       /** I/O buffer size */
+       size_t mtu;
+       /** Fill level */
+       unsigned int fill;
+       /** Maximum fill level */
+       unsigned int max;
+};
+
+/** A CDC-ECM transmit ring */
+struct ecm_tx_ring {
+       /** USB endpoint */
+       struct usb_endpoint ep;
+};
+
+/** A CDC-ECM network device */
+struct ecm_device {
+       /** USB device */
+       struct usb_device *usb;
+       /** USB bus */
+       struct usb_bus *bus;
+       /** Network device */
+       struct net_device *netdev;
+
+       /** Communications interface */
+       unsigned int comms;
+       /** Data interface */
+       unsigned int data;
+
+       /** Interrupt ring */
+       struct ecm_rx_ring intr;
+       /** Bulk IN ring */
+       struct ecm_rx_ring in;
+       /** Bulk OUT ring */
+       struct ecm_tx_ring out;
+};
+
+/** Interrupt maximum fill level
+ *
+ * This is a policy decision.
+ */
+#define ECM_INTR_MAX_FILL 2
+
+/** Bulk IN maximum fill level
+ *
+ * This is a policy decision.
+ */
+#define ECM_IN_MAX_FILL 8
+
+/** Bulk IN buffer size
+ *
+ * This is a policy decision.
+ */
+#define ECM_IN_MTU ( ETH_FRAME_LEN + 4 /* possible VLAN header */ )
+
 extern struct ecm_ethernet_descriptor *
 ecm_ethernet_descriptor ( struct usb_configuration_descriptor *config,
                          struct usb_interface_descriptor *interface );