aboutsummaryrefslogtreecommitdiffstats
path: root/src/drivers
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2015-02-10 13:45:57 +0000
committerMichael Brown <mcb30@ipxe.org>2015-02-10 13:49:27 +0000
commit58c3e7f74771dd4748983cc47ed53241ac1864f1 (patch)
treee5baedb5b58a1abadf5ea3811fa66e87d14d40d7 /src/drivers
parent17aceb34da3bad166cab8579a3616806fff8e05b (diff)
downloadipxe-58c3e7f74771dd4748983cc47ed53241ac1864f1.tar.gz
[usb] Allow usb_stream() to enforce a terminating short packet
Some USB endpoints require that a short packet be used to terminate transfers, since they have no other way to determine message boundaries. If the message length happens to be an exact multiple of the USB packet size, then this requires the use of an additional zero-length packet. Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/bus/usb.c6
-rw-r--r--src/drivers/net/ncm.c4
-rw-r--r--src/drivers/usb/usbhub.c2
-rw-r--r--src/drivers/usb/xhci.c22
4 files changed, 23 insertions, 11 deletions
diff --git a/src/drivers/bus/usb.c b/src/drivers/bus/usb.c
index 5a338a5e..8900324a 100644
--- a/src/drivers/bus/usb.c
+++ b/src/drivers/bus/usb.c
@@ -451,9 +451,11 @@ int usb_message ( struct usb_endpoint *ep, unsigned int request,
*
* @v ep USB endpoint
* @v iobuf I/O buffer
+ * @v terminate Terminate using a short packet
* @ret rc Return status code
*/
-int usb_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf ) {
+int usb_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf,
+ int terminate ) {
struct usb_device *usb = ep->usb;
struct usb_port *port = usb->port;
int rc;
@@ -467,7 +469,7 @@ int usb_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf ) {
return rc;
/* Enqueue stream transfer */
- if ( ( rc = ep->host->stream ( ep, iobuf ) ) != 0 ) {
+ if ( ( rc = ep->host->stream ( ep, iobuf, terminate ) ) != 0 ) {
DBGC ( usb, "USB %s %s could not enqueue stream transfer: %s\n",
usb->name, usb_endpoint_name ( ep->address ),
strerror ( rc ) );
diff --git a/src/drivers/net/ncm.c b/src/drivers/net/ncm.c
index 0fc3ab79..e05559bb 100644
--- a/src/drivers/net/ncm.c
+++ b/src/drivers/net/ncm.c
@@ -145,7 +145,7 @@ static int ncm_rx_refill ( struct ncm_device *ncm, struct ncm_rx_ring *ring ) {
iob_put ( iobuf, ( ring->mtu - iob_len ( iobuf ) ) );
/* Enqueue I/O buffer */
- if ( ( rc = usb_stream ( &ring->ep, iobuf ) ) != 0 ) {
+ 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 */
@@ -548,7 +548,7 @@ 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 )
+ if ( ( rc = usb_stream ( &ncm->out.ep, iobuf, 0 ) ) != 0 )
return rc;
/* Increment sequence number */
diff --git a/src/drivers/usb/usbhub.c b/src/drivers/usb/usbhub.c
index ecac460a..9c88531b 100644
--- a/src/drivers/usb/usbhub.c
+++ b/src/drivers/usb/usbhub.c
@@ -52,7 +52,7 @@ static void hub_refill ( struct usb_hub_device *hubdev ) {
iob_put ( iobuf, ( mtu - iob_len ( iobuf ) ) );
/* Enqueue I/O buffer */
- if ( ( rc = usb_stream ( &hubdev->intr, iobuf ) ) != 0 ) {
+ if ( ( rc = usb_stream ( &hubdev->intr, iobuf, 0 ) ) != 0 ) {
DBGC ( hubdev, "HUB %s could not enqueue interrupt: "
"%s\n", hubdev->name, strerror ( rc ) );
/* Leave in available list and wait for next refill */
diff --git a/src/drivers/usb/xhci.c b/src/drivers/usb/xhci.c
index 49901275..bf8cf1c8 100644
--- a/src/drivers/usb/xhci.c
+++ b/src/drivers/usb/xhci.c
@@ -2457,27 +2457,37 @@ static int xhci_endpoint_message ( struct usb_endpoint *ep,
*
* @v ep USB endpoint
* @v iobuf I/O buffer
+ * @v terminate Terminate using a short packet
* @ret rc Return status code
*/
static int xhci_endpoint_stream ( struct usb_endpoint *ep,
- struct io_buffer *iobuf ) {
+ struct io_buffer *iobuf, int terminate ) {
struct xhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep );
- union xhci_trb trb;
+ union xhci_trb trbs[ 1 /* Normal */ + 1 /* Possible zero-length */ ];
+ union xhci_trb *trb = trbs;
struct xhci_trb_normal *normal;
+ size_t len = iob_len ( iobuf );
int rc;
/* Profile stream transfers */
profile_start ( &xhci_stream_profiler );
/* Construct normal TRBs */
- normal = &trb.normal;
+ memset ( &trbs, 0, sizeof ( trbs ) );
+ normal = &(trb++)->normal;
normal->data = cpu_to_le64 ( virt_to_phys ( iobuf->data ) );
- normal->len = cpu_to_le32 ( iob_len ( iobuf ) );
- normal->flags = XHCI_TRB_IOC;
+ normal->len = cpu_to_le32 ( len );
normal->type = XHCI_TRB_NORMAL;
+ if ( terminate && ( ( len & ( ep->mtu - 1 ) ) == 0 ) ) {
+ normal->flags = XHCI_TRB_CH;
+ normal = &(trb++)->normal;
+ normal->type = XHCI_TRB_NORMAL;
+ }
+ normal->flags = XHCI_TRB_IOC;
/* Enqueue TRBs */
- if ( ( rc = xhci_enqueue ( &endpoint->ring, iobuf, &trb ) ) != 0 )
+ if ( ( rc = xhci_enqueue_multi ( &endpoint->ring, iobuf, trbs,
+ ( trb - trbs ) ) ) != 0 )
return rc;
/* Ring the doorbell */