diff --git a/src/drivers/net/ncm.c b/src/drivers/net/ncm.c index e05559bb..02b4cc36 100644 --- a/src/drivers/net/ncm.c +++ b/src/drivers/net/ncm.c @@ -35,10 +35,6 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ -/** Ring refill profiler */ -static struct profiler ncm_refill_profiler __profiler = - { .name = "ncm.refill" }; - /** Interrupt completion profiler */ static struct profiler ncm_intr_profiler __profiler = { .name = "ncm.intr" }; @@ -55,144 +51,6 @@ static struct profiler ncm_in_datagram_profiler __profiler = static struct profiler ncm_out_profiler __profiler = { .name = "ncm.out" }; -/****************************************************************************** - * - * Ring management - * - ****************************************************************************** - */ - -/** - * Transcribe receive ring name (for debugging) - * - * @v ncm CDC-NCM device - * @v ring Receive ring - * @ret name Receive ring name - */ -static inline const char * ncm_rx_name ( struct ncm_device *ncm, - struct ncm_rx_ring *ring ) { - if ( ring == &ncm->intr ) { - return "interrupt"; - } else if ( ring == &ncm->in ) { - return "bulk IN"; - } else { - return "UNKNOWN"; - } -} - -/** - * Allocate receive ring buffers - * - * @v ncm CDC-NCM device - * @v ring Receive ring - * @v mtu I/O buffer size - * @v count Number of I/O buffers - * @ret rc Return status code - */ -static int ncm_rx_alloc ( struct ncm_device *ncm, struct ncm_rx_ring *ring, - size_t mtu, unsigned int count ) { - struct io_buffer *iobuf; - struct io_buffer *tmp; - unsigned int i; - int rc; - - /* Initialise ring */ - ring->mtu = mtu; - INIT_LIST_HEAD ( &ring->list ); - - /* Allocate I/O buffers */ - for ( i = 0 ; i < count ; i++ ) { - iobuf = alloc_iob ( mtu ); - if ( ! iobuf ) { - DBGC ( ncm, "NCM %p could not allocate %dx %zd-byte " - "buffers for %s\n", ncm, count, mtu, - ncm_rx_name ( ncm, ring ) ); - rc = -ENOMEM; - goto err_alloc; - } - list_add ( &iobuf->list, &ring->list ); - } - - return 0; - - err_alloc: - list_for_each_entry_safe ( iobuf, tmp, &ring->list, list ) { - list_del ( &iobuf->list ); - free_iob ( iobuf ); - } - return rc; -} - -/** - * Refill receive ring - * - * @v ncm CDC-NCM device - * @v ring Receive ring - * @ret rc Return status code - */ -static int ncm_rx_refill ( struct ncm_device *ncm, struct ncm_rx_ring *ring ) { - struct io_buffer *iobuf; - int rc; - - /* Enqueue any recycled I/O buffers */ - while ( ( iobuf = list_first_entry ( &ring->list, struct io_buffer, - list ) ) ) { - - /* Profile refill */ - profile_start ( &ncm_refill_profiler ); - - /* Reset size */ - iob_put ( iobuf, ( ring->mtu - iob_len ( iobuf ) ) ); - - /* Enqueue I/O buffer */ - if ( ( rc = usb_stream ( &ring->ep, iobuf, 0 ) ) != 0 ) { - DBGC ( ncm, "NCM %p could not enqueue %s: %s\n", ncm, - ncm_rx_name ( ncm, ring ), strerror ( rc ) ); - /* Leave in recycled list and wait for next refill */ - return rc; - } - - /* Remove from recycled list */ - list_del ( &iobuf->list ); - profile_stop ( &ncm_refill_profiler ); - } - - return 0; -} - -/** - * Recycle receive buffer - * - * @v ncm CDC-NCM device - * @v ring Receive ring - * @v iobuf I/O buffer - */ -static inline void ncm_rx_recycle ( struct ncm_device *ncm __unused, - struct ncm_rx_ring *ring, - struct io_buffer *iobuf ) { - - /* Add to recycled list */ - list_add_tail ( &iobuf->list, &ring->list ); -} - -/** - * Free receive ring - * - * @v ncm CDC-NCM device - * @v ring Receive ring - */ -static void ncm_rx_free ( struct ncm_device *ncm __unused, - struct ncm_rx_ring *ring ) { - struct io_buffer *iobuf; - struct io_buffer *tmp; - - /* Free I/O buffers */ - list_for_each_entry_safe ( iobuf, tmp, &ring->list, list ) { - list_del ( &iobuf->list ); - free_iob ( iobuf ); - } -} - /****************************************************************************** * * CDC-NCM communications interface @@ -209,7 +67,7 @@ static void ncm_rx_free ( struct ncm_device *ncm __unused, */ static void ncm_intr_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, int rc ) { - struct ncm_device *ncm = container_of ( ep, struct ncm_device, intr.ep); + struct ncm_device *ncm = container_of ( ep, struct ncm_device, intr ); struct net_device *netdev = ncm->netdev; struct usb_setup_packet *message; size_t len = iob_len ( iobuf ); @@ -219,21 +77,22 @@ static void ncm_intr_complete ( struct usb_endpoint *ep, /* Ignore packets cancelled when the endpoint closes */ if ( ! ep->open ) - goto done; + goto ignore; /* Ignore packets with errors */ if ( rc != 0 ) { DBGC ( ncm, "NCM %p interrupt failed: %s\n", ncm, strerror ( rc ) ); DBGC_HDA ( ncm, 0, iobuf->data, iob_len ( iobuf ) ); - goto done; + goto error; } /* Extract message header */ if ( len < sizeof ( *message ) ) { DBGC ( ncm, "NCM %p underlength interrupt:\n", ncm ); DBGC_HDA ( ncm, 0, iobuf->data, iob_len ( iobuf ) ); - goto done; + rc = -EINVAL; + goto error; } message = iobuf->data; @@ -257,13 +116,20 @@ static void ncm_intr_complete ( struct usb_endpoint *ep, default: DBGC ( ncm, "NCM %p unrecognised interrupt:\n", ncm ); DBGC_HDA ( ncm, 0, iobuf->data, iob_len ( iobuf ) ); - break; + goto error; } - done: - /* Recycle buffer */ - ncm_rx_recycle ( ncm, &ncm->intr, iobuf ); + /* Free I/O buffer */ + free_iob ( iobuf ); profile_stop ( &ncm_intr_profiler ); + + return; + + error: + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); + ignore: + free_iob ( iobuf ); + return; } /** Interrupt endpoint operations */ @@ -280,27 +146,20 @@ static struct usb_endpoint_driver_operations ncm_intr_operations = { static int ncm_comms_open ( struct ncm_device *ncm ) { int rc; - /* Allocate I/O buffers */ - if ( ( rc = ncm_rx_alloc ( ncm, &ncm->intr, ncm->intr.ep.mtu, - NCM_INTR_COUNT ) ) != 0 ) { - DBGC ( ncm, "NCM %p could not allocate RX buffers: %s\n", - ncm, strerror ( rc ) ); - goto err_alloc; - } - /* Open interrupt endpoint */ - if ( ( rc = usb_endpoint_open ( &ncm->intr.ep ) ) != 0 ) { + if ( ( rc = usb_endpoint_open ( &ncm->intr ) ) != 0 ) { DBGC ( ncm, "NCM %p could not open interrupt: %s\n", ncm, strerror ( rc ) ); goto err_open; } + /* Refill interrupt endpoint */ + usb_refill ( &ncm->intr ); + return 0; - usb_endpoint_close ( &ncm->intr.ep ); + usb_endpoint_close ( &ncm->intr ); err_open: - ncm_rx_free ( ncm, &ncm->intr ); - err_alloc: return rc; } @@ -312,10 +171,7 @@ static int ncm_comms_open ( struct ncm_device *ncm ) { static void ncm_comms_close ( struct ncm_device *ncm ) { /* Close interrupt endpoint */ - usb_endpoint_close ( &ncm->intr.ep ); - - /* Free I/O buffers */ - ncm_rx_free ( ncm, &ncm->intr ); + usb_endpoint_close ( &ncm->intr ); } /****************************************************************************** @@ -326,12 +182,12 @@ static void ncm_comms_close ( struct ncm_device *ncm ) { */ /** - * Allocate bulk IN receive ring buffers + * Prefill bulk IN endpoint * * @v ncm CDC-NCM device * @ret rc Return status code */ -static int ncm_in_alloc ( struct ncm_device *ncm ) { +static int ncm_in_prefill ( struct ncm_device *ncm ) { size_t mtu; unsigned int count; int rc; @@ -358,15 +214,19 @@ static int ncm_in_alloc ( struct ncm_device *ncm ) { count = NCM_IN_MIN_COUNT; if ( ( count * mtu ) > NCM_IN_MAX_SIZE ) continue; - if ( ( rc = ncm_rx_alloc ( ncm, &ncm->in, mtu, count ) ) != 0 ) + usb_refill_init ( &ncm->in, mtu, count ); + if ( ( rc = usb_prefill ( &ncm->in ) ) != 0 ) { + DBGC ( ncm, "NCM %p could not prefill %dx %zd-byte " + "buffers for bulk IN\n", ncm, count, mtu ); continue; + } DBGC ( ncm, "NCM %p using %dx %zd-byte buffers for bulk IN\n", ncm, count, mtu ); return 0; } - DBGC ( ncm, "NCM %p could not allocate bulk IN buffers\n", ncm ); + DBGC ( ncm, "NCM %p could not prefill bulk IN endpoint\n", ncm ); return -ENOMEM; } @@ -379,7 +239,7 @@ static int ncm_in_alloc ( struct ncm_device *ncm ) { */ static void ncm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, int rc ) { - struct ncm_device *ncm = container_of ( ep, struct ncm_device, in.ep ); + struct ncm_device *ncm = container_of ( ep, struct ncm_device, in ); struct net_device *netdev = ncm->netdev; struct ncm_transfer_header *nth; struct ncm_datagram_pointer *ndp; @@ -404,13 +264,14 @@ static void ncm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, if ( rc != 0 ) { DBGC ( ncm, "NCM %p bulk IN failed: %s\n", ncm, strerror ( rc ) ); - goto drop; + goto error; } /* Locate transfer header */ len = iob_len ( iobuf ); if ( sizeof ( *nth ) > len ) { DBGC ( ncm, "NCM %p packet too short for NTH:\n", ncm ); + rc = -EINVAL; goto error; } nth = iobuf->data; @@ -419,16 +280,19 @@ static void ncm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, ndp_offset = le16_to_cpu ( nth->offset ); if ( ( ndp_offset + sizeof ( *ndp ) ) > len ) { DBGC ( ncm, "NCM %p packet too short for NDP:\n", ncm ); + rc = -EINVAL; goto error; } ndp = ( iobuf->data + ndp_offset ); ndp_len = le16_to_cpu ( ndp->header_len ); if ( ndp_len < offsetof ( typeof ( *ndp ), desc ) ) { DBGC ( ncm, "NCM %p NDP header length too short:\n", ncm ); + rc = -EINVAL; goto error; } if ( ( ndp_offset + ndp_len ) > len ) { DBGC ( ncm, "NCM %p packet too short for NDP:\n", ncm ); + rc = -EINVAL; goto error; } @@ -445,10 +309,12 @@ static void ncm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, pkt_len = le16_to_cpu ( desc->len ); if ( pkt_len < ETH_HLEN ) { DBGC ( ncm, "NCM %p underlength datagram:\n", ncm ); + rc = -EINVAL; goto error; } if ( ( pkt_offset + pkt_len ) > len ) { DBGC ( ncm, "NCM %p datagram exceeds packet:\n", ncm ); + rc = -EINVAL; goto error; } @@ -468,8 +334,7 @@ static void ncm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, * received packet and reuse the same I/O buffer for * transmission. */ - headroom = ( sizeof ( struct ncm_ntb_header ) + - ncm->out.padding ); + headroom = ( sizeof ( struct ncm_ntb_header ) + ncm->padding ); pkt = alloc_iob ( headroom + pkt_len ); if ( ! pkt ) { /* Record error and continue */ @@ -490,19 +355,17 @@ static void ncm_in_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, } /* Recycle I/O buffer */ - ncm_rx_recycle ( ncm, &ncm->in, iobuf ); + usb_recycle ( &ncm->in, iobuf ); profile_stop ( &ncm_in_profiler ); return; error: - rc = -EIO; - drop: /* Record error against network device */ DBGC_HDA ( ncm, 0, iobuf->data, iob_len ( iobuf ) ); netdev_rx_err ( netdev, NULL, rc ); ignore: - ncm_rx_recycle ( ncm, &ncm->in, iobuf ); + usb_recycle ( &ncm->in, iobuf ); } /** Bulk IN endpoint operations */ @@ -521,7 +384,7 @@ static int ncm_out_transmit ( struct ncm_device *ncm, struct io_buffer *iobuf ) { struct ncm_ntb_header *header; size_t len = iob_len ( iobuf ); - size_t header_len = ( sizeof ( *header ) + ncm->out.padding ); + size_t header_len = ( sizeof ( *header ) + ncm->padding ); int rc; /* Profile transmissions */ @@ -535,7 +398,7 @@ static int ncm_out_transmit ( struct ncm_device *ncm, /* Populate header */ header->nth.magic = cpu_to_le32 ( NCM_TRANSFER_HEADER_MAGIC ); header->nth.header_len = cpu_to_le16 ( sizeof ( header->nth ) ); - header->nth.sequence = cpu_to_le16 ( ncm->out.sequence ); + header->nth.sequence = cpu_to_le16 ( ncm->sequence ); header->nth.len = cpu_to_le16 ( iob_len ( iobuf ) ); header->nth.offset = cpu_to_le16 ( offsetof ( typeof ( *header ), ndp ) ); @@ -548,11 +411,11 @@ static int ncm_out_transmit ( struct ncm_device *ncm, memset ( &header->desc[1], 0, sizeof ( header->desc[1] ) ); /* Enqueue I/O buffer */ - if ( ( rc = usb_stream ( &ncm->out.ep, iobuf, 0 ) ) != 0 ) + if ( ( rc = usb_stream ( &ncm->out, iobuf, 0 ) ) != 0 ) return rc; /* Increment sequence number */ - ncm->out.sequence++; + ncm->sequence++; profile_stop ( &ncm_out_profiler ); return 0; @@ -567,7 +430,7 @@ static int ncm_out_transmit ( struct ncm_device *ncm, */ static void ncm_out_complete ( struct usb_endpoint *ep, struct io_buffer *iobuf, int rc ) { - struct ncm_device *ncm = container_of ( ep, struct ncm_device, out.ep ); + struct ncm_device *ncm = container_of ( ep, struct ncm_device, out ); struct net_device *netdev = ncm->netdev; /* Report TX completion */ @@ -590,13 +453,13 @@ static int ncm_data_open ( struct ncm_device *ncm ) { struct ncm_set_ntb_input_size size; int rc; - /* Allocate I/O buffers */ - if ( ( rc = ncm_in_alloc ( ncm ) ) != 0 ) - goto err_alloc; + /* Prefill I/O buffers */ + if ( ( rc = ncm_in_prefill ( ncm ) ) != 0 ) + goto err_prefill; /* Set maximum input size */ memset ( &size, 0, sizeof ( size ) ); - size.mtu = cpu_to_le32 ( ncm->in.mtu ); + size.mtu = cpu_to_le32 ( ncm->in.len ); if ( ( rc = usb_control ( usb, NCM_SET_NTB_INPUT_SIZE, 0, ncm->comms, &size, sizeof ( size ) ) ) != 0 ) { DBGC ( ncm, "NCM %p could not set input size to %zd: %s\n", @@ -613,33 +476,38 @@ static int ncm_data_open ( struct ncm_device *ncm ) { } /* Open bulk IN endpoint */ - if ( ( rc = usb_endpoint_open ( &ncm->in.ep ) ) != 0 ) { + if ( ( rc = usb_endpoint_open ( &ncm->in ) ) != 0 ) { DBGC ( ncm, "NCM %p could not open bulk IN: %s\n", ncm, strerror ( rc ) ); goto err_open_in; } /* Open bulk OUT endpoint */ - if ( ( rc = usb_endpoint_open ( &ncm->out.ep ) ) != 0 ) { + if ( ( rc = usb_endpoint_open ( &ncm->out ) ) != 0 ) { DBGC ( ncm, "NCM %p could not open bulk OUT: %s\n", ncm, strerror ( rc ) ); goto err_open_out; } - /* Reset transmit sequence number */ - ncm->out.sequence = 0; + /* Refill bulk IN endpoint */ + if ( ( rc = usb_refill ( &ncm->in ) ) != 0 ) { + DBGC ( ncm, "NCM %p could not refill bulk IN: %s\n", + ncm, strerror ( rc ) ); + goto err_refill; + } return 0; - usb_endpoint_close ( &ncm->out.ep ); + err_refill: + usb_endpoint_close ( &ncm->out ); err_open_out: - usb_endpoint_close ( &ncm->in.ep ); + usb_endpoint_close ( &ncm->in ); err_open_in: usb_set_interface ( usb, ncm->data, 0 ); err_set_interface: err_set_ntb_input_size: - ncm_rx_free ( ncm, &ncm->in ); - err_alloc: + usb_flush ( &ncm->in ); + err_prefill: return rc; } @@ -652,14 +520,11 @@ static void ncm_data_close ( struct ncm_device *ncm ) { struct usb_device *usb = ncm->usb; /* Close endpoints */ - usb_endpoint_close ( &ncm->out.ep ); - usb_endpoint_close ( &ncm->in.ep ); + usb_endpoint_close ( &ncm->out ); + usb_endpoint_close ( &ncm->in ); /* Reset data interface */ usb_set_interface ( usb, ncm->data, 0 ); - - /* Free I/O buffers */ - ncm_rx_free ( ncm, &ncm->in ); } /****************************************************************************** @@ -680,30 +545,20 @@ static int ncm_open ( struct net_device *netdev ) { int rc; /* Reset sequence number */ - ncm->out.sequence = 0; + ncm->sequence = 0; /* Open communications interface */ if ( ( rc = ncm_comms_open ( ncm ) ) != 0 ) goto err_comms_open; - /* Refill interrupt ring */ - if ( ( rc = ncm_rx_refill ( ncm, &ncm->intr ) ) != 0 ) - goto err_intr_refill; - /* Open data interface */ if ( ( rc = ncm_data_open ( ncm ) ) != 0 ) goto err_data_open; - /* Refill bulk IN ring */ - if ( ( rc = ncm_rx_refill ( ncm, &ncm->in ) ) != 0 ) - goto err_in_refill; - return 0; - err_in_refill: ncm_data_close ( ncm ); err_data_open: - err_intr_refill: ncm_comms_close ( ncm ); err_comms_open: return rc; @@ -755,12 +610,12 @@ static void ncm_poll ( struct net_device *netdev ) { /* Poll USB bus */ usb_poll ( ncm->bus ); - /* Refill interrupt ring */ - if ( ( rc = ncm_rx_refill ( ncm, &ncm->intr ) ) != 0 ) + /* Refill interrupt endpoint */ + if ( ( rc = usb_refill ( &ncm->intr ) ) != 0 ) netdev_rx_err ( netdev, NULL, rc ); - /* Refill bulk IN ring */ - if ( ( rc = ncm_rx_refill ( ncm, &ncm->in ) ) != 0 ) + /* Refill bulk IN endpoint */ + if ( ( rc = usb_refill ( &ncm->in ) ) != 0 ) netdev_rx_err ( netdev, NULL, rc ); } @@ -810,9 +665,10 @@ static int ncm_probe ( struct usb_function *func, ncm->usb = usb; ncm->bus = usb->port->hub->bus; ncm->netdev = netdev; - usb_endpoint_init ( &ncm->intr.ep, usb, &ncm_intr_operations ); - usb_endpoint_init ( &ncm->in.ep, usb, &ncm_in_operations ); - usb_endpoint_init ( &ncm->out.ep, usb, &ncm_out_operations ); + usb_endpoint_init ( &ncm->intr, usb, &ncm_intr_operations ); + usb_endpoint_init ( &ncm->in, usb, &ncm_in_operations ); + usb_endpoint_init ( &ncm->out, usb, &ncm_out_operations ); + usb_refill_init ( &ncm->intr, 0, NCM_INTR_COUNT ); DBGC ( ncm, "NCM %p on %s\n", ncm, func->name ); /* Identify interfaces */ @@ -843,7 +699,7 @@ static int ncm_probe ( struct usb_function *func, } /* Describe interrupt endpoint */ - if ( ( rc = usb_endpoint_described ( &ncm->intr.ep, config, comms, + if ( ( rc = usb_endpoint_described ( &ncm->intr, config, comms, USB_INTERRUPT, 0 ) ) != 0 ) { DBGC ( ncm, "NCM %p could not describe interrupt endpoint: " "%s\n", ncm, strerror ( rc ) ); @@ -851,7 +707,7 @@ static int ncm_probe ( struct usb_function *func, } /* Describe bulk IN endpoint */ - if ( ( rc = usb_endpoint_described ( &ncm->in.ep, config, data, + if ( ( rc = usb_endpoint_described ( &ncm->in, config, data, USB_BULK_IN, 0 ) ) != 0 ) { DBGC ( ncm, "NCM %p could not describe bulk IN endpoint: " "%s\n", ncm, strerror ( rc ) ); @@ -859,7 +715,7 @@ static int ncm_probe ( struct usb_function *func, } /* Describe bulk OUT endpoint */ - if ( ( rc = usb_endpoint_described ( &ncm->out.ep, config, data, + if ( ( rc = usb_endpoint_described ( &ncm->out, config, data, USB_BULK_OUT, 0 ) ) != 0 ) { DBGC ( ncm, "NCM %p could not describe bulk OUT endpoint: " "%s\n", ncm, strerror ( rc ) ); @@ -894,12 +750,12 @@ static int ncm_probe ( struct usb_function *func, DBGC2 ( ncm, "NCM %p maximum IN size is %zd bytes\n", ncm, ncm->mtu ); /* Calculate transmit padding */ - ncm->out.padding = ( ( le16_to_cpu ( params.out.remainder ) - - sizeof ( struct ncm_ntb_header ) - ETH_HLEN ) & - ( le16_to_cpu ( params.out.divisor ) - 1 ) ); + ncm->padding = ( ( le16_to_cpu ( params.out.remainder ) - + sizeof ( struct ncm_ntb_header ) - ETH_HLEN ) & + ( le16_to_cpu ( params.out.divisor ) - 1 ) ); DBGC2 ( ncm, "NCM %p using %zd-byte transmit padding\n", - ncm, ncm->out.padding ); - assert ( ( ( sizeof ( struct ncm_ntb_header ) + ncm->out.padding + + ncm, ncm->padding ); + assert ( ( ( sizeof ( struct ncm_ntb_header ) + ncm->padding + ETH_HLEN ) % le16_to_cpu ( params.out.divisor ) ) == le16_to_cpu ( params.out.remainder ) ); diff --git a/src/drivers/net/ncm.h b/src/drivers/net/ncm.h index b76902b9..a551f0d2 100644 --- a/src/drivers/net/ncm.h +++ b/src/drivers/net/ncm.h @@ -139,26 +139,6 @@ struct ncm_ntb_header { struct ncm_datagram_descriptor desc[2]; } __attribute__ (( packed )); -/** A CDC-NCM receive ring */ -struct ncm_rx_ring { - /** USB endpoint */ - struct usb_endpoint ep; - /** I/O buffer size */ - size_t mtu; - /** Recycled buffer list */ - struct list_head list; -}; - -/** A CDC-NCM transmit ring */ -struct ncm_tx_ring { - /** USB endpoint */ - struct usb_endpoint ep; - /** Transmitted packet sequence number */ - uint16_t sequence; - /** Alignment padding required on transmitted packets */ - size_t padding; -}; - /** A CDC-NCM network device */ struct ncm_device { /** USB device */ @@ -175,13 +155,17 @@ struct ncm_device { /** Maximum supported NTB input size */ size_t mtu; + /** Transmitted packet sequence number */ + uint16_t sequence; + /** Alignment padding required on transmitted packets */ + size_t padding; - /** Interrupt ring */ - struct ncm_rx_ring intr; - /** Bulk IN ring */ - struct ncm_rx_ring in; - /** Bulk OUT ring */ - struct ncm_tx_ring out; + /** Interrupt endpoint */ + struct usb_endpoint intr; + /** Bulk IN endpoint */ + struct usb_endpoint in; + /** Bulk OUT endpoint */ + struct usb_endpoint out; }; /** Bulk IN ring minimum buffer count