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

index f0f8eb1d98f1bb4a74742e8d50806dd10585a4ed..110b432876b14ac7ff96adae21afe3a219827c13 100644 (file)
@@ -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 */