diff options
author | Michael Brown | 2015-09-13 12:48:14 +0200 |
---|---|---|
committer | Michael Brown | 2015-09-13 13:54:30 +0200 |
commit | 8f418ee477e17fdfe40870f94c03ddd088cc7a2b (patch) | |
tree | 7935dcdb789d00b1a456ba22da2712497cf7f0c6 /src/drivers/usb | |
parent | [ehci] Do not treat zero-length NULL pointers as unreachable (diff) | |
download | ipxe-8f418ee477e17fdfe40870f94c03ddd088cc7a2b.tar.gz ipxe-8f418ee477e17fdfe40870f94c03ddd088cc7a2b.tar.xz ipxe-8f418ee477e17fdfe40870f94c03ddd088cc7a2b.zip |
[ehci] Support arbitrarily large transfers
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/usb')
-rw-r--r-- | src/drivers/usb/ehci.c | 63 |
1 files changed, 49 insertions, 14 deletions
diff --git a/src/drivers/usb/ehci.c b/src/drivers/usb/ehci.c index c2de53a4..f90d6a91 100644 --- a/src/drivers/usb/ehci.c +++ b/src/drivers/usb/ehci.c @@ -1221,6 +1221,30 @@ static int ehci_endpoint_message ( struct usb_endpoint *ep, } /** + * Calculate number of transfer descriptors + * + * @v len Length of data + * @v zlp Append a zero-length packet + * @ret count Number of transfer descriptors + */ +static unsigned int ehci_endpoint_count ( size_t len, int zlp ) { + unsigned int count; + + /* Split into 16kB transfers. A single transfer can handle up + * to 20kB if it happens to be page-aligned, or up to 16kB + * with arbitrary alignment. We simplify the code by assuming + * that we can fit only 16kB into each transfer. + */ + count = ( ( len + EHCI_MTU - 1 ) / EHCI_MTU ); + + /* Append a zero-length transfer if applicable */ + if ( zlp || ( count == 0 ) ) + count++; + + return count; +} + +/** * Enqueue stream transfer * * @v ep USB endpoint @@ -1232,29 +1256,40 @@ static int ehci_endpoint_stream ( struct usb_endpoint *ep, struct io_buffer *iobuf, int zlp ) { struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); struct ehci_device *ehci = endpoint->ehci; + void *data = iobuf->data; + size_t len = iob_len ( iobuf ); + unsigned int count = ehci_endpoint_count ( len, zlp ); unsigned int input = ( ep->address & USB_DIR_IN ); - struct ehci_transfer xfers[2]; + unsigned int flags = ( input ? EHCI_FL_PID_IN : EHCI_FL_PID_OUT ); + struct ehci_transfer xfers[count]; struct ehci_transfer *xfer = xfers; - size_t len = iob_len ( iobuf ); + size_t xfer_len; + unsigned int i; int rc; - /* Create transfer */ - xfer->data = iobuf->data; - xfer->len = len; - xfer->flags = ( EHCI_FL_IOC | - ( input ? EHCI_FL_PID_IN : EHCI_FL_PID_OUT ) ); - xfer++; - if ( zlp ) { - xfer->data = NULL; - xfer->len = 0; - assert ( ! input ); - xfer->flags = ( EHCI_FL_IOC | EHCI_FL_PID_OUT ); + /* Create transfers */ + for ( i = 0 ; i < count ; i++ ) { + + /* Calculate transfer length */ + xfer_len = EHCI_MTU; + if ( xfer_len > len ) + xfer_len = len; + + /* Create transfer */ + xfer->data = data; + xfer->len = xfer_len; + xfer->flags = flags; + + /* Move to next transfer */ + data += xfer_len; + len -= xfer_len; xfer++; } + xfer[-1].flags |= EHCI_FL_IOC; /* Enqueue transfer */ if ( ( rc = ehci_enqueue ( ehci, &endpoint->ring, iobuf, xfers, - ( xfer - xfers ) ) ) != 0 ) + count ) ) != 0 ) return rc; return 0; |