david/ipxe
Archived
1
0

[ehci] Support arbitrarily large transfers

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2015-09-13 11:48:14 +01:00
parent 6424a38323
commit 8f418ee477

View File

@ -1220,6 +1220,30 @@ static int ehci_endpoint_message ( struct usb_endpoint *ep,
return 0; 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 * Enqueue stream transfer
* *
@ -1232,29 +1256,40 @@ static int ehci_endpoint_stream ( struct usb_endpoint *ep,
struct io_buffer *iobuf, int zlp ) { struct io_buffer *iobuf, int zlp ) {
struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep ); struct ehci_endpoint *endpoint = usb_endpoint_get_hostdata ( ep );
struct ehci_device *ehci = endpoint->ehci; struct ehci_device *ehci = endpoint->ehci;
unsigned int input = ( ep->address & USB_DIR_IN ); void *data = iobuf->data;
struct ehci_transfer xfers[2];
struct ehci_transfer *xfer = xfers;
size_t len = iob_len ( iobuf ); size_t len = iob_len ( iobuf );
unsigned int count = ehci_endpoint_count ( len, zlp );
unsigned int input = ( ep->address & USB_DIR_IN );
unsigned int flags = ( input ? EHCI_FL_PID_IN : EHCI_FL_PID_OUT );
struct ehci_transfer xfers[count];
struct ehci_transfer *xfer = xfers;
size_t xfer_len;
unsigned int i;
int rc; int rc;
/* Create transfer */ /* Create transfers */
xfer->data = iobuf->data; for ( i = 0 ; i < count ; i++ ) {
xfer->len = len;
xfer->flags = ( EHCI_FL_IOC | /* Calculate transfer length */
( input ? EHCI_FL_PID_IN : EHCI_FL_PID_OUT ) ); xfer_len = EHCI_MTU;
xfer++; if ( xfer_len > len )
if ( zlp ) { xfer_len = len;
xfer->data = NULL;
xfer->len = 0; /* Create transfer */
assert ( ! input ); xfer->data = data;
xfer->flags = ( EHCI_FL_IOC | EHCI_FL_PID_OUT ); xfer->len = xfer_len;
xfer->flags = flags;
/* Move to next transfer */
data += xfer_len;
len -= xfer_len;
xfer++; xfer++;
} }
xfer[-1].flags |= EHCI_FL_IOC;
/* Enqueue transfer */ /* Enqueue transfer */
if ( ( rc = ehci_enqueue ( ehci, &endpoint->ring, iobuf, xfers, if ( ( rc = ehci_enqueue ( ehci, &endpoint->ring, iobuf, xfers,
( xfer - xfers ) ) ) != 0 ) count ) ) != 0 )
return rc; return rc;
return 0; return 0;