From 4a7d69169788a65d3d4bb4d3a00497bb1a45d51d Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sun, 13 Sep 2015 12:31:18 +0100 Subject: [PATCH] [xhci] Support arbitrarily large transfers Signed-off-by: Michael Brown --- src/drivers/usb/xhci.c | 56 +++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/src/drivers/usb/xhci.c b/src/drivers/usb/xhci.c index f0f8eb1d..110b4328 100644 --- a/src/drivers/usb/xhci.c +++ b/src/drivers/usb/xhci.c @@ -2541,6 +2541,26 @@ static int xhci_endpoint_message ( struct usb_endpoint *ep, return 0; } +/** + * Calculate number of TRBs + * + * @v len Length of data + * @v zlp Append a zero-length packet + * @ret count Number of transfer descriptors + */ +static unsigned int xhci_endpoint_count ( size_t len, int zlp ) { + unsigned int count; + + /* Split into 64kB TRBs */ + count = ( ( len + XHCI_MTU - 1 ) / XHCI_MTU ); + + /* Append a zero-length TRB if applicable */ + if ( zlp || ( count == 0 ) ) + count++; + + return count; +} + /** * Enqueue stream transfer * @@ -2552,10 +2572,14 @@ static int xhci_endpoint_message ( struct usb_endpoint *ep, static int xhci_endpoint_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf, int zlp ) { struct xhci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); - union xhci_trb trbs[ 1 /* Normal */ + 1 /* Possible zero-length */ ]; + void *data = iobuf->data; + size_t len = iob_len ( iobuf ); + unsigned int count = xhci_endpoint_count ( len, zlp ); + union xhci_trb trbs[count]; union xhci_trb *trb = trbs; struct xhci_trb_normal *normal; - size_t len = iob_len ( iobuf ); + unsigned int i; + size_t trb_len; int rc; /* Profile stream transfers */ @@ -2563,20 +2587,30 @@ static int xhci_endpoint_stream ( struct usb_endpoint *ep, /* Construct normal TRBs */ memset ( &trbs, 0, sizeof ( trbs ) ); - normal = &(trb++)->normal; - normal->data = cpu_to_le64 ( virt_to_phys ( iobuf->data ) ); - normal->len = cpu_to_le32 ( len ); - normal->type = XHCI_TRB_NORMAL; - if ( zlp ) { - normal->flags = XHCI_TRB_CH; - normal = &(trb++)->normal; + for ( i = 0 ; i < count ; i ++ ) { + + /* Calculate TRB length */ + trb_len = XHCI_MTU; + if ( trb_len > len ) + trb_len = len; + + /* Construct normal TRB */ + normal = &trb->normal; + normal->data = cpu_to_le64 ( virt_to_phys ( data ) ); + normal->len = cpu_to_le32 ( trb_len ); normal->type = XHCI_TRB_NORMAL; + normal->flags = XHCI_TRB_CH; + + /* Move to next TRB */ + data += trb_len; + len -= trb_len; + trb++; } - normal->flags = XHCI_TRB_IOC; + trb[-1].normal.flags = XHCI_TRB_IOC; /* Enqueue TRBs */ if ( ( rc = xhci_enqueue_multi ( &endpoint->ring, iobuf, trbs, - ( trb - trbs ) ) ) != 0 ) + count ) ) != 0 ) return rc; /* Ring the doorbell */