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 (...<controller>->bulk/control(...)) checks to
if (...<controller>->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.

Signed-off-by: Julius Werner <jwerner@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/48308
Reviewed-by: Gabe Black <gabeblack@chromium.org>
Reviewed-by: Stefan Reinauer <reinauer@google.com>
Tested-by: Gabe Black <gabeblack@chromium.org>
Commit-Queue: Gabe Black <gabeblack@chromium.org>

Updated the patch to support XHCI as well.

Change-Id: Ic2ea2810c5edb992cbe185bc9711d2f8f557cae6
(cherry picked from commit e39e2d84762a3804653d950a228ed2269c651458)
Signed-off-by: Isaac Christensen <isaac.christensen@se-eng.com>
Reviewed-on: http://review.coreboot.org/6390
Tested-by: build bot (Jenkins)
Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
Reviewed-by: Patrick Georgi <patrick@georgi-clan.de>
diff --git a/payloads/libpayload/drivers/usb/ehci.c b/payloads/libpayload/drivers/usb/ehci.c
index b983701..0764592 100644
--- a/payloads/libpayload/drivers/usb/ehci.c
+++ b/payloads/libpayload/drivers/usb/ehci.c
@@ -270,6 +270,7 @@
 
 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 @@
 		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 @@
 	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_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 @@
 	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 @@
 		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 @@
 
 	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 @@
 	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 @@
 
 	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 0683de3..723b0db 100644
--- a/payloads/libpayload/drivers/usb/ohci.c
+++ b/payloads/libpayload/drivers/usb/ohci.c
@@ -44,7 +44,7 @@
 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
@@ -321,13 +321,13 @@
 		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
@@ -355,6 +355,7 @@
 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()
@@ -392,21 +393,21 @@
 		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;
 		}
 	}
@@ -454,7 +455,7 @@
 	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);
@@ -464,7 +465,10 @@
 	/* 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 */
@@ -472,6 +476,7 @@
 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;
@@ -501,28 +506,28 @@
                         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. */
@@ -558,7 +563,7 @@
 	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);
@@ -570,12 +575,12 @@
 	/* free memory */
 	ohci_free_ed(head);
 
-	if (failure) {
-		/* try cleanup */
+	if (result >= 0)
+		result = dalen - result;
+	else
 		clear_stall(ep);
-	}
 
-	return failure;
+	return result;
 }
 
 
@@ -780,9 +785,11 @@
 	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). */
@@ -790,7 +797,7 @@
 
 	/* 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;
@@ -807,7 +814,11 @@
 
 		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: {
@@ -864,5 +875,7 @@
 	}
 	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 b81b911..3eff612 100644
--- a/payloads/libpayload/drivers/usb/uhci.c
+++ b/payloads/libpayload/drivers/usb/uhci.c
@@ -386,7 +386,7 @@
 	} else {
 		usb_debug ("control packet, req %x\n", req);
 		td_dump (td);
-		result = 1;
+		result = -1;
 	}
 	free (tds);
 	return result;
@@ -468,7 +468,7 @@
 		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 6b54dbb..d9727f0 100644
--- a/payloads/libpayload/drivers/usb/usb.c
+++ b/payloads/libpayload/drivers/usb/usb.c
@@ -157,7 +157,7 @@
 	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 @@
 	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 @@
 	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
@@ -270,7 +270,7 @@
 	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) {
 		return -1;
 	}
 	mdelay (50);
diff --git a/payloads/libpayload/drivers/usb/usbmsc.c b/payloads/libpayload/drivers/usb/usbmsc.c
index f1de483..2d28fbd 100644
--- a/payloads/libpayload/drivers/usb/usbmsc.c
+++ b/payloads/libpayload/drivers/usb/usbmsc.c
@@ -184,7 +184,7 @@
 	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 @@
 	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 @@
 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 @@
 	}
 	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 d31a8e2..184a370 100644
--- a/payloads/libpayload/drivers/usb/xhci.c
+++ b/payloads/libpayload/drivers/usb/xhci.c
@@ -588,14 +588,14 @@
 	const size_t off = (size_t)data & 0xffff;
 	if ((off + dalen) > ((TRANSFER_RING_SIZE - 4) << 16)) {
 		xhci_debug("Unsupported transfer size\n");
-		return 1;
+		return -1;
 	}
 
 	/* Reset endpoint if it's halted */
 	const unsigned ep_state = EC_GET(STATE, di->devctx.ep0);
 	if (ep_state == 2 || ep_state == 4) {
 		if (xhci_reset_endpoint(dev, NULL, 0))
-			return 1;
+			return -1;
 	}
 
 	/* Fill and enqueue setup TRB */
@@ -631,11 +631,12 @@
 	xhci->dbreg[dev->address] = 1;
 
 	/* Wait for transfer events */
-	int i;
+	int i, transferred = 0;
 	const int n_stages = 2 + !!dalen;
 	for (i = 0; i < n_stages; ++i) {
 		const int ret = xhci_wait_for_transfer(xhci, dev->address, 1);
-		if (ret != CC_SUCCESS) {
+		transferred += ret;
+		if (ret < 0) {
 			if (ret == TIMEOUT) {
 				xhci_debug("Stopping ID %d EP 1\n",
 					   dev->address);
@@ -651,11 +652,11 @@
 				   tr->ring, setup, status,
 				   ep_state, EC_GET(STATE, di->devctx.ep0),
 				   xhci->opreg->usbsts);
-			return 1;
+			return ret;
 		}
 	}
 
-	return 0;
+	return transferred;
 }
 
 /* finalize == 1: if data is of packet aligned size, add a zero length packet */
@@ -675,14 +676,14 @@
 	const size_t off = (size_t)data & 0xffff;
 	if ((off + size) > ((TRANSFER_RING_SIZE - 2) << 16)) {
 		xhci_debug("Unsupported transfer size\n");
-		return 1;
+		return -1;
 	}
 
 	/* Reset endpoint if it's halted */
 	const unsigned ep_state = EC_GET(STATE, di->devctx.eps[ep_id]);
 	if (ep_state == 2 || ep_state == 4) {
 		if (xhci_reset_endpoint(ep->dev, ep, 0))
-			return 1;
+			return -1;
 	}
 
 	/* Enqueue transfer and ring doorbell */
@@ -693,12 +694,12 @@
 
 	/* Wait for transfer event */
 	const int ret = xhci_wait_for_transfer(xhci, ep->dev->address, ep_id);
-	if (ret != CC_SUCCESS) {
+	if (ret < 0) {
 		if (ret == TIMEOUT) {
 			xhci_debug("Stopping ID %d EP %d\n",
 				   ep->dev->address, ep_id);
 			xhci_cmd_stop_endpoint(xhci, ep->dev->address, ep_id);
-		} else if (ret == CC_STALL_ERROR) {
+		} else if (ret == -CC_STALL_ERROR) {
 			xhci_reset_endpoint(ep->dev, ep, 1);
 		}
 		xhci_debug("Bulk transfer failed: %d\n"
@@ -707,10 +708,10 @@
 			   ret, ep_state,
 			   EC_GET(STATE, di->devctx.eps[ep_id]),
 			   xhci->opreg->usbsts);
-		return 1;
+		return ret;
 	}
 
-	return 0;
+	return ret;
 }
 
 static trb_t *
diff --git a/payloads/libpayload/drivers/usb/xhci_events.c b/payloads/libpayload/drivers/usb/xhci_events.c
index b04ecda..ab90c59 100644
--- a/payloads/libpayload/drivers/usb/xhci_events.c
+++ b/payloads/libpayload/drivers/usb/xhci_events.c
@@ -308,7 +308,7 @@
 	return cc;
 }
 
-/* returns cc of transfer for given slot/endpoint pair */
+/* returns amount of bytes transferred on success, negative CC on error */
 int
 xhci_wait_for_transfer(xhci_t *const xhci, const int slot_id, const int ep_id)
 {
@@ -319,7 +319,9 @@
 	while (xhci_wait_for_event_type(xhci, TRB_EV_TRANSFER, &timeout_us)) {
 		if (TRB_GET(ID, xhci->er.cur) == slot_id &&
 				TRB_GET(EP, xhci->er.cur) == ep_id) {
-			cc = TRB_GET(CC, xhci->er.cur);
+			cc = -TRB_GET(CC, xhci->er.cur);
+			if (cc == -CC_SUCCESS || cc == -CC_SHORT_PACKET)
+				cc = TRB_GET(EVTL, xhci->er.cur);
 			xhci_advance_event_ring(xhci);
 			break;
 		}