]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[ehci] Support arbitrarily large transfers
authorMichael Brown <mcb30@ipxe.org>
Sun, 13 Sep 2015 10:48:14 +0000 (11:48 +0100)
committerMichael Brown <mcb30@ipxe.org>
Sun, 13 Sep 2015 11:54:30 +0000 (12:54 +0100)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/drivers/usb/ehci.c

index c2de53a47b9fb79c59bbbe6d7895faab9c49644a..f90d6a91affbc961876bc918c8f15b7c8e758b12 100644 (file)
@@ -1220,6 +1220,30 @@ static int ehci_endpoint_message ( struct usb_endpoint *ep,
        return 0;
 }
 
+/**
+ * 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
  *
@@ -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;