From e39e2d84762a3804653d950a228ed2269c651458 Mon Sep 17 00:00:00 2001 From: Julius Werner Date: Thu, 21 Feb 2013 13:41:40 -0800 Subject: [PATCH] libpayload: Make USB transfer functions return amount of bytes The USB bulk and control transfer functions in libpayload currently always return 0 for success and 1 for all errors. This is sufficient for current use cases (essentially just mass storage), but other classes (like certain Ethernet adapters) need to be able to tell if a transfer reached the intended amount of bytes, or if it fell short. This patch slightly changes that USB API to return -1 on errors, and the amount of transferred bytes on successes. All drivers in the current libpayload mainline are modified to conform to the new error detection model. Any third party users of this API will need to adapt their if (...->bulk/control(...)) checks to if (...->bulk/control(...) < 0) as well. The host controller drivers for OHCI and EHCI correctly implement the new behavior. UHCI and the XHCI stub just comply with the new API by returning 0 or -1, but do not actually count the returned bytes. BUG=chrome-os-partner:16957 TEST=None BRANCH=None CQ-DEPEND=CL:59674 Change-Id: Ic2ea2810c5edb992cbe185bc9711d2f8f557cae6 Signed-off-by: Julius Werner Reviewed-on: https://gerrit.chromium.org/gerrit/48308 Reviewed-by: Gabe Black Reviewed-by: Stefan Reinauer Tested-by: Gabe Black Commit-Queue: Gabe Black --- payloads/libpayload/drivers/usb/ehci.c | 29 ++++++---- payloads/libpayload/drivers/usb/ohci.c | 71 ++++++++++++++---------- payloads/libpayload/drivers/usb/uhci.c | 4 +- payloads/libpayload/drivers/usb/usb.c | 8 +-- payloads/libpayload/drivers/usb/usbmsc.c | 15 +++-- payloads/libpayload/drivers/usb/xhci.c | 4 +- 6 files changed, 76 insertions(+), 55 deletions(-) diff --git a/payloads/libpayload/drivers/usb/ehci.c b/payloads/libpayload/drivers/usb/ehci.c index 6e9f03aff0..35292e5706 100644 --- a/payloads/libpayload/drivers/usb/ehci.c +++ b/payloads/libpayload/drivers/usb/ehci.c @@ -270,6 +270,7 @@ static void free_qh_and_tds(ehci_qh_t *qh, qtd_t *cur) static int wait_for_tds(qtd_t *head) { + /* returns the amount of bytes *not* transmitted, or -1 for error */ int result = 0; qtd_t *cur = head; while (1) { @@ -291,16 +292,18 @@ static int wait_for_tds(qtd_t *head) if (timeout < 0) { usb_debug("Error: ehci: queue transfer " "processing timed out.\n"); - return 1; + return -1; } if (cur->token & QTD_HALTED) { usb_debug("ERROR with packet\n"); dump_td(virt_to_phys(cur)); usb_debug("-----------------\n"); - return 1; + return -1; } + result += (cur->token & QTD_TOTAL_LEN_MASK) + >> QTD_TOTAL_LEN_SHIFT; if (cur->next_qtd & 1) { - return 0; + break; } if (0) dump_td(virt_to_phys(cur)); /* helps debugging the TD chain */ @@ -338,13 +341,13 @@ static int ehci_process_async_schedule( int result; /* make sure async schedule is disabled */ - if (ehci_set_async_schedule(ehcic, 0)) return 1; + if (ehci_set_async_schedule(ehcic, 0)) return -1; /* hook up QH */ ehcic->operation->asynclistaddr = virt_to_phys(qhead); /* start async schedule */ - if (ehci_set_async_schedule(ehcic, 1)) return 1; + if (ehci_set_async_schedule(ehcic, 1)) return -1; /* wait for result */ result = wait_for_tds(head); @@ -358,6 +361,7 @@ static int ehci_process_async_schedule( static int ehci_bulk (endpoint_t *ep, int size, u8 *data, int finalize) { int result = 0; + int remaining = size; int endp = ep->endpoint & 0xf; int pid = (ep->direction==IN)?EHCI_IN:EHCI_OUT; @@ -365,7 +369,7 @@ static int ehci_bulk (endpoint_t *ep, int size, u8 *data, int finalize) if (ep->dev->speed < 2) { /* we need a split transaction */ if (closest_usb2_hub(ep->dev, &hubaddr, &hubport)) - return 1; + return -1; } qtd_t *head = memalign(64, sizeof(qtd_t)); @@ -375,12 +379,12 @@ static int ehci_bulk (endpoint_t *ep, int size, u8 *data, int finalize) cur->token = QTD_ACTIVE | (pid << QTD_PID_SHIFT) | (0 << QTD_CERR_SHIFT); - u32 chunk = fill_td(cur, data, size); - size -= chunk; + u32 chunk = fill_td(cur, data, remaining); + remaining -= chunk; data += chunk; cur->alt_next_qtd = QTD_TERMINATE; - if (size == 0) { + if (remaining == 0) { cur->next_qtd = virt_to_phys(0) | QTD_TERMINATE; break; } else { @@ -411,10 +415,13 @@ static int ehci_bulk (endpoint_t *ep, int size, u8 *data, int finalize) result = ehci_process_async_schedule( EHCI_INST(ep->dev->controller), qh, head); + if (result >= 0) + result = size - result; ep->toggle = (cur->token & QTD_TOGGLE_MASK) >> QTD_TOGGLE_SHIFT; free_qh_and_tds(qh, head); + return result; } @@ -432,7 +439,7 @@ static int ehci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq if (dev->speed < 2) { /* we need a split transaction */ if (closest_usb2_hub(dev, &hubaddr, &hubport)) - return 1; + return -1; non_hs_ctrl_ep = 1; } @@ -499,6 +506,8 @@ static int ehci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq result = ehci_process_async_schedule( EHCI_INST(dev->controller), qh, head); + if (result >= 0) + result = dalen - result; free_qh_and_tds(qh, head); return result; diff --git a/payloads/libpayload/drivers/usb/ohci.c b/payloads/libpayload/drivers/usb/ohci.c index 51ea8fb433..8a09a953f4 100644 --- a/payloads/libpayload/drivers/usb/ohci.c +++ b/payloads/libpayload/drivers/usb/ohci.c @@ -44,7 +44,7 @@ static int ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq static void* ohci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming); static void ohci_destroy_intr_queue (endpoint_t *ep, void *queue); static u8* ohci_poll_intr_queue (void *queue); -static void ohci_process_done_queue(ohci_t *ohci, int spew_debug); +static int ohci_process_done_queue(ohci_t *ohci, int spew_debug); #ifdef USB_DEBUG static void @@ -319,13 +319,13 @@ wait_for_ed(usbdev_t *dev, ed_t *head, int pages) usb_debug("Error: ohci: endpoint " "descriptor processing timed out.\n"); /* Clear the done queue. */ - ohci_process_done_queue(OHCI_INST(dev->controller), 1); + int result = ohci_process_done_queue(OHCI_INST(dev->controller), 1); if (head->head_pointer & 1) { usb_debug("HALTED!\n"); - return 1; + return -1; } - return 0; + return result; } static void @@ -353,6 +353,7 @@ static int ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen, unsigned char *data) { + int remaining = dalen; td_t *cur; // pages are specified as 4K in OHCI, so don't use getpagesize() @@ -390,21 +391,21 @@ ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen cur->current_buffer_pointer = virt_to_phys(data); pages--; int consumed = (4096 - ((unsigned long)data % 4096)); - if (consumed >= dalen) { + if (consumed >= remaining) { // end of data is within same page - cur->buffer_end = virt_to_phys(data + dalen - 1); - dalen = 0; + cur->buffer_end = virt_to_phys(data + remaining - 1); + remaining = 0; /* assert(pages == 0); */ } else { - dalen -= consumed; + remaining -= consumed; data += consumed; pages--; - int second_page_size = dalen; - if (dalen > 4096) { + int second_page_size = remaining; + if (remaining > 4096) { second_page_size = 4096; } cur->buffer_end = virt_to_phys(data + second_page_size - 1); - dalen -= second_page_size; + remaining -= second_page_size; data += second_page_size; } } @@ -452,7 +453,7 @@ ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen OHCI_INST(dev->controller)->opreg->HcControl |= ControlListEnable; OHCI_INST(dev->controller)->opreg->HcCommandStatus = ControlListFilled; - int failure = wait_for_ed(dev, head, + int result = wait_for_ed(dev, head, (dalen==0)?0:(last_page - first_page + 1)); /* Wait some frames before and one after disabling list access. */ mdelay(4); @@ -462,7 +463,10 @@ ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen /* free memory */ ohci_free_ed(head); - return failure; + if (result >= 0) + result = dalen - result; + + return result; } /* finalize == 1: if data is of packet aligned size, add a zero length packet */ @@ -470,6 +474,7 @@ static int ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) { int i; + int remaining = dalen; usb_debug("bulk: %x bytes from %x, finalize: %x, maxpacketsize: %x\n", dalen, data, finalize, ep->maxpacketsize); td_t *cur, *next; @@ -499,28 +504,28 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) TD_CC_NOACCESS; cur->current_buffer_pointer = virt_to_phys(data); pages--; - if (dalen == 0) { + if (remaining == 0) { /* magic TD for empty packet transfer */ cur->current_buffer_pointer = 0; cur->buffer_end = 0; /* assert((pages == 0) && finalize); */ } int consumed = (4096 - ((unsigned long)data % 4096)); - if (consumed >= dalen) { + if (consumed >= remaining) { // end of data is within same page - cur->buffer_end = virt_to_phys(data + dalen - 1); - dalen = 0; + cur->buffer_end = virt_to_phys(data + remaining - 1); + remaining = 0; /* assert(pages == finalize); */ } else { - dalen -= consumed; + remaining -= consumed; data += consumed; pages--; - int second_page_size = dalen; - if (dalen > 4096) { + int second_page_size = remaining; + if (remaining > 4096) { second_page_size = 4096; } cur->buffer_end = virt_to_phys(data + second_page_size - 1); - dalen -= second_page_size; + remaining -= second_page_size; data += second_page_size; } /* One more TD. */ @@ -556,7 +561,7 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) OHCI_INST(ep->dev->controller)->opreg->HcControl |= BulkListEnable; OHCI_INST(ep->dev->controller)->opreg->HcCommandStatus = BulkListFilled; - int failure = wait_for_ed(ep->dev, head, + int result = wait_for_ed(ep->dev, head, (dalen==0)?0:(last_page - first_page + 1)); /* Wait some frames before and one after disabling list access. */ mdelay(4); @@ -568,12 +573,12 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) /* free memory */ ohci_free_ed(head); - if (failure) { - /* try cleanup */ + if (result >= 0) + result = dalen - result; + else clear_stall(ep); - } - return failure; + return result; } @@ -778,9 +783,11 @@ ohci_poll_intr_queue(void *const q_) return data; } -static void +static int ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) { + /* returns the amount of bytes *not* transmitted for short packets */ + int result = 0; int i, j; /* Temporary queue of interrupt queue TDs (to reverse order). */ @@ -788,7 +795,7 @@ ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) /* Check if done head has been written. */ if (!(ohci->opreg->HcInterruptStatus & WritebackDoneHead)) - return; + return 0; /* Fetch current done head. Lsb is only interesting for hw interrupts. */ u32 phys_done_queue = ohci->hcca->HccaDoneHead & ~1; @@ -805,7 +812,11 @@ ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) switch (done_td->config & TD_QUEUETYPE_MASK) { case TD_QUEUETYPE_ASYNC: - /* Free processed async TDs. */ + /* Free processed async TDs and count short transfer. */ + if (done_td->current_buffer_pointer) + result += (done_td->buffer_end & 0xfff) - + (done_td->current_buffer_pointer + & 0xfff) + 1; free((void *)done_td); break; case TD_QUEUETYPE_INTR: { @@ -862,5 +873,7 @@ ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) } if (spew_debug) usb_debug("processed %d done tds, %d intr tds thereof.\n", i, j); + + return result; } diff --git a/payloads/libpayload/drivers/usb/uhci.c b/payloads/libpayload/drivers/usb/uhci.c index df9bdca03c..94d855205b 100644 --- a/payloads/libpayload/drivers/usb/uhci.c +++ b/payloads/libpayload/drivers/usb/uhci.c @@ -383,7 +383,7 @@ uhci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen } else { usb_debug ("control packet, req %x\n", req); td_dump (td); - result = 1; + result = -1; } free (tds); return result; @@ -465,7 +465,7 @@ uhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize) usb_debug("Stalled. Trying to clean up.\n"); clear_stall (ep); free (tds); - return 1; + return -1; } ep->toggle = toggle; free (tds); diff --git a/payloads/libpayload/drivers/usb/usb.c b/payloads/libpayload/drivers/usb/usb.c index 23561c40aa..83d03735a8 100644 --- a/payloads/libpayload/drivers/usb/usb.c +++ b/payloads/libpayload/drivers/usb/usb.c @@ -157,7 +157,7 @@ get_descriptor (usbdev_t *dev, unsigned char bmRequestType, int descType, dr.wValue = (descType << 8) | descIdx; dr.wIndex = langID; dr.wLength = 8; - if (dev->controller->control (dev, IN, sizeof (dr), &dr, 8, buf)) { + if (dev->controller->control (dev, IN, sizeof (dr), &dr, 8, buf) < 0) { usb_debug ("getting descriptor size (type %x) failed\n", descType); } @@ -181,7 +181,7 @@ get_descriptor (usbdev_t *dev, unsigned char bmRequestType, int descType, memset (result, 0, size); dr.wLength = size; if (dev->controller-> - control (dev, IN, sizeof (dr), &dr, size, result)) { + control (dev, IN, sizeof (dr), &dr, size, result) < 0) { usb_debug ("getting descriptor (type %x, size %x) failed\n", descType, size); } @@ -213,7 +213,7 @@ clear_feature (usbdev_t *dev, int endp, int feature, int rtype) dr.wValue = feature; dr.wIndex = endp; dr.wLength = 0; - return dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); + return dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0) < 0; } int @@ -272,7 +272,7 @@ set_address (hci_t *controller, int speed, int hubport, int hubaddr) dev->endpoints[0].toggle = 0; dev->endpoints[0].direction = SETUP; mdelay (50); - if (dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0)) { + if (dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0) < 0) { usb_debug ("set_address failed\n"); return -1; } diff --git a/payloads/libpayload/drivers/usb/usbmsc.c b/payloads/libpayload/drivers/usb/usbmsc.c index f1de483170..2d28fbd12f 100644 --- a/payloads/libpayload/drivers/usb/usbmsc.c +++ b/payloads/libpayload/drivers/usb/usbmsc.c @@ -184,7 +184,7 @@ reset_transport (usbdev_t *dev) dr.wLength = 0; /* if any of these fails, detach device, as we are lost */ - if (dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0) || + if (dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0) < 0 || clear_stall (MSC_INST (dev)->bulk_in) || clear_stall (MSC_INST (dev)->bulk_out)) { usb_debug ("Detaching unresponsive device.\n"); @@ -211,9 +211,8 @@ get_max_luns (usbdev_t *dev) dr.wValue = 0; dr.wIndex = 0; dr.wLength = 1; - if (dev->controller->control (dev, IN, sizeof (dr), &dr, 1, &luns)) { + if (dev->controller->control (dev, IN, sizeof (dr), &dr, 1, &luns) < 0) luns = 0; // assume only 1 lun if req fails - } return luns; } @@ -239,10 +238,10 @@ wrap_cbw (cbw_t *cbw, int datalen, cbw_direction dir, const u8 *cmd, static int get_csw (endpoint_t *ep, csw_t *csw) { - if (ep->dev->controller->bulk (ep, sizeof (csw_t), (u8 *) csw, 1)) { + if (ep->dev->controller->bulk (ep, sizeof (csw_t), (u8 *) csw, 1) < 0) { clear_stall (ep); if (ep->dev->controller->bulk - (ep, sizeof (csw_t), (u8 *) csw, 1)) { + (ep, sizeof (csw_t), (u8 *) csw, 1) < 0) { return reset_transport (ep->dev); } } @@ -265,17 +264,17 @@ execute_command (usbdev_t *dev, cbw_direction dir, const u8 *cb, int cblen, } wrap_cbw (&cbw, buflen, dir, cb, cblen); if (dev->controller-> - bulk (MSC_INST (dev)->bulk_out, sizeof (cbw), (u8 *) &cbw, 0)) { + bulk (MSC_INST (dev)->bulk_out, sizeof (cbw), (u8 *) &cbw, 0) < 0) { return reset_transport (dev); } if (buflen > 0) { if (dir == cbw_direction_data_in) { if (dev->controller-> - bulk (MSC_INST (dev)->bulk_in, buflen, buf, 0)) + bulk (MSC_INST (dev)->bulk_in, buflen, buf, 0) < 0) clear_stall (MSC_INST (dev)->bulk_in); } else { if (dev->controller-> - bulk (MSC_INST (dev)->bulk_out, buflen, buf, 0)) + bulk (MSC_INST (dev)->bulk_out, buflen, buf, 0) < 0) clear_stall (MSC_INST (dev)->bulk_out); } } diff --git a/payloads/libpayload/drivers/usb/xhci.c b/payloads/libpayload/drivers/usb/xhci.c index 341b5969c2..512cf5d106 100644 --- a/payloads/libpayload/drivers/usb/xhci.c +++ b/payloads/libpayload/drivers/usb/xhci.c @@ -242,7 +242,7 @@ static int xhci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen, unsigned char *data) { - return 1; + return -1; } /* finalize == 1: if data is of packet aligned size, add a zero length packet */ @@ -252,7 +252,7 @@ xhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize) int maxpsize = ep->maxpacketsize; if (maxpsize == 0) fatal("MaxPacketSize == 0!!!"); - return 1; + return -1; } /* create and hook-up an intr queue into device schedule */