summaryrefslogtreecommitdiffstats
path: root/src/drivers/usb
diff options
context:
space:
mode:
authorMichael Brown2015-09-13 12:48:14 +0200
committerMichael Brown2015-09-13 13:54:30 +0200
commit8f418ee477e17fdfe40870f94c03ddd088cc7a2b (patch)
tree7935dcdb789d00b1a456ba22da2712497cf7f0c6 /src/drivers/usb
parent[ehci] Do not treat zero-length NULL pointers as unreachable (diff)
downloadipxe-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.c63
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;