This patch adds USB capabilities to libpayload. It requires some
memalign implementation (eg. the one I sent yesterday).
Features:
 - UHCI controller driver
 - UHCI root hub driver
 - USB MSC (Mass Storage Class) driver
 - skeleton of a USB HID driver
   (requires better interrupt transfer handling, which is TODO)
 - skeleton of a USB hub driver
   (needs several blank spots filled in, eg. power management.
    Again: TODO)

OHCI and EHCI are not supported, though OHCI support should be rather
easy as the stack provides reasonable abstractions (or so I hope). EHCI
will probably be more complicated.

Isochronous transfers (eg. webcams, audio stuff, ...) are not supported.
They can be, but I doubt we'll have a reason for that in the boot
environment.

The MSC driver was tested against a couple of USB flash drives, and
should be reasonably tolerant by now. But I probably underestimate
the amount of bugs present in USB flash drives, so feedback is welcome.

Signed-off-by: Patrick Georgi <patrick.georgi@coresystems.de>
Acked-by: Jordan Crouse <jordan.crouse@amd.com>


git-svn-id: svn://svn.coreboot.org/coreboot/trunk@3560 2b7e53f0-3cfb-0310-b3e9-8179ed1497e1
diff --git a/payloads/libpayload/drivers/usb/usbmsc.c b/payloads/libpayload/drivers/usb/usbmsc.c
new file mode 100644
index 0000000..cbef585
--- /dev/null
+++ b/payloads/libpayload/drivers/usb/usbmsc.c
@@ -0,0 +1,397 @@
+/*
+ * This file is part of the libpayload project.
+ *
+ * Copyright (C) 2008 coresystems GmbH
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <arch/endian.h>
+#include "usb.h"
+#include "usbmsc.h"
+#include "usbdisk.h"
+
+enum {
+	msc_subclass_rbc = 0x1,
+	msc_subclass_mmc2 = 0x2,
+	msc_subclass_qic157 = 0x3,
+	msc_subclass_ufi = 0x4,
+	msc_subclass_sff8070i = 0x5,
+	msc_subclass_scsitrans = 0x6
+};
+static const char *msc_subclass_strings[7] = {
+	"(none)",
+	"RBC",
+	"MMC-2",
+	"QIC-157",
+	"UFI",
+	"SFF-8070i",
+	"SCSI transparent"
+};
+enum {
+	msc_proto_cbi_wcomp = 0x0,
+	msc_proto_cbi_wocomp = 0x1,
+	msc_proto_bulk_only = 0x50
+};
+static const char *msc_protocol_strings[0x51] = {
+	"Control/Bulk/Interrupt protocol (with command completion interrupt)",
+	"Control/Bulk/Interrupt protocol (with no command completion interrupt)",
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	"Bulk-Only Transport"
+};
+
+
+static void
+usb_msc_destroy (usbdev_t *dev)
+{
+	usbdisk_remove (dev);
+	free (dev->data);
+	dev->data = 0;
+}
+
+static void
+usb_msc_poll (usbdev_t *dev)
+{
+}
+
+const int DEV_RESET = 0xff;
+const int GET_MAX_LUN = 0xfe;
+
+const unsigned int cbw_signature = 0x43425355;
+const unsigned int csw_signature = 0x53425355;
+
+typedef struct {
+	unsigned int dCBWSignature;
+	unsigned int dCBWTag;
+	unsigned int dCBWDataTransferLength;
+	unsigned char bmCBWFlags;
+	unsigned long bCBWLUN:4;
+	unsigned long:4;
+	unsigned long bCBWCBLength:5;
+	unsigned long:3;
+	unsigned char CBWCB[31 - 15];
+} __attribute__ ((packed))
+     cbw_t;
+
+     typedef struct {
+	     unsigned int dCSWSignature;
+	     unsigned int dCSWTag;
+	     unsigned int dCSWDataResidue;
+	     unsigned char bCSWStatus;
+     } __attribute__ ((packed))
+     csw_t;
+
+     static void
+       reset_transport (usbdev_t *dev)
+{
+	dev_req_t dr;
+	memset (&dr, 0, sizeof (dr));
+	dr.bmRequestType = 0;
+	dr.data_dir = host_to_device;
+#ifndef QEMU
+	dr.req_type = class_type;
+	dr.req_recp = iface_recp;
+#endif
+	dr.bRequest = DEV_RESET;
+	dr.wValue = 0;
+	dr.wIndex = 0;
+	dr.wLength = 0;
+	dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0);
+	clear_stall (MSC_INST (dev)->bulk_in);
+	clear_stall (MSC_INST (dev)->bulk_out);
+}
+
+/* device may stall this command, so beware! */
+static int
+get_max_luns (usbdev_t *dev)
+{
+	unsigned char luns = 75;
+	dev_req_t dr;
+	dr.bmRequestType = 0;
+	dr.data_dir = device_to_host;
+#ifndef QEMU
+	dr.req_type = class_type;
+	dr.req_recp = iface_recp;
+#endif
+	dr.bRequest = GET_MAX_LUN;
+	dr.wValue = 0;
+	dr.wIndex = 0;
+	dr.wLength = 1;
+	if (dev->controller->control (dev, IN, sizeof (dr), &dr, 1, &luns)) {
+		luns = 0;	// assume only 1 lun if req fails
+	}
+	return luns;
+}
+
+int tag;
+int lun = 0;
+
+static void
+wrap_cbw (cbw_t *cbw, int datalen, cbw_direction dir, const u8 *cmd,
+	  int cmdlen)
+{
+	memset (cbw, 0, sizeof (cbw_t));
+
+	cbw->dCBWSignature = cbw_signature;
+	cbw->dCBWTag = tag++;
+	cbw->bCBWLUN = lun;	// static value per device
+
+	cbw->dCBWDataTransferLength = datalen;
+	cbw->bmCBWFlags = dir;
+	memcpy (cbw->CBWCB, cmd, sizeof (cbw->CBWCB));
+	cbw->bCBWCBLength = cmdlen;
+}
+
+static void
+get_csw (endpoint_t *ep, csw_t *csw)
+{
+	ep->dev->controller->bulk (ep, sizeof (csw_t), (u8 *) csw, 1);
+}
+
+static int
+execute_command (usbdev_t *dev, cbw_direction dir, const u8 *cb, int cblen,
+		 u8 *buf, int buflen)
+{
+	cbw_t cbw;
+	csw_t csw;
+
+	int always_succeed = 0;
+	if ((cb[0] == 0x1b) && (cb[4] == 1)) {	//start command, always succeed
+		always_succeed = 1;
+	}
+	wrap_cbw (&cbw, buflen, dir, cb, cblen);
+	if (dev->controller->
+	    bulk (MSC_INST (dev)->bulk_out, sizeof (cbw), (u8 *) &cbw, 0)) {
+		clear_stall (MSC_INST (dev)->bulk_out);
+		return 1;
+	}
+	mdelay (10);
+	if (dir == cbw_direction_data_in) {
+		if (dev->controller->
+		    bulk (MSC_INST (dev)->bulk_in, buflen, buf, 0)) {
+			clear_stall (MSC_INST (dev)->bulk_in);
+			return 1;
+		}
+	} else {
+		if (dev->controller->
+		    bulk (MSC_INST (dev)->bulk_out, buflen, buf, 0)) {
+			clear_stall (MSC_INST (dev)->bulk_out);
+			return 1;
+		}
+	}
+	get_csw (MSC_INST (dev)->bulk_in, &csw);
+	if (always_succeed == 1) {
+		// return success, regardless of message
+		return 0;
+	}
+	if (csw.bCSWStatus == 2) {
+		// phase error, reset transport
+		reset_transport (dev);
+		return 1;
+	}
+	if (csw.bCSWStatus == 0) {
+		// no error, exit
+		return 0;
+	}
+	// error "check condition" or reserved error
+	return 1;
+}
+
+typedef struct {
+	unsigned char command;	//0
+	unsigned char res1;	//1
+	unsigned int block;	//2-5
+	unsigned char res2;	//6
+	unsigned short numblocks;	//7-8
+	unsigned char res3;	//9 - the block is 10 bytes long
+} __attribute__ ((packed)) cmdblock_t;
+
+typedef struct {
+	unsigned char command;	//0
+	unsigned char res1;	//1
+	unsigned char res2;	//2
+	unsigned char res3;	//3
+	unsigned char lun;	//4
+	unsigned char res4;	//5
+} __attribute__ ((packed)) cmdblock6_t;
+
+
+/**
+ * Reads or writes a number of sequential blocks on a USB storage device.
+ * As it uses the READ(10) SCSI-2 command, it's limited to storage devices
+ * of at most 2TB. It assumes sectors of 512 bytes.
+ *
+ * @param dev device to access
+ * @param start first sector to access
+ * @param n number of sectors to access
+ * @param dir direction of access: cbw_direction_data_in == read, cbw_direction_data_out == write
+ * @param buf buffer to read into or write from. Must be at least n*512 bytes
+ * @return 0 on success, 1 on failure
+ */
+int
+readwrite_blocks (usbdev_t *dev, int start, int n, cbw_direction dir, u8 *buf)
+{
+	cmdblock_t cb;
+	memset (&cb, 0, sizeof (cb));
+	if (dir == cbw_direction_data_in) {
+		// read
+		cb.command = 0x28;
+	} else {
+		// write
+		cb.command = 0x2a;
+	}
+	cb.block = ntohl (start);
+	cb.numblocks = ntohw (n);
+	return execute_command (dev, dir, (u8 *) &cb, sizeof (cb), buf,
+				n * 512);
+}
+
+static int
+test_unit_ready (usbdev_t *dev)
+{
+	cmdblock6_t cb;
+	memset (&cb, 0, sizeof (cb));	// full initialization for T-U-R
+	return execute_command (dev, cbw_direction_data_out, (u8 *) &cb,
+				sizeof (cb), 0, 0);
+}
+
+static int
+spin_up (usbdev_t *dev)
+{
+	cmdblock6_t cb;
+	memset (&cb, 0, sizeof (cb));
+	cb.command = 0x1b;
+	cb.lun = 1;
+	return execute_command (dev, cbw_direction_data_out, (u8 *) &cb,
+				sizeof (cb), 0, 0);
+}
+
+static void
+read_capacity (usbdev_t *dev)
+{
+	cmdblock_t cb;
+	memset (&cb, 0, sizeof (cb));
+	cb.command = 0x25;	// read capacity
+	u8 buf[8];
+	int count = 0;
+	while ((count++ < 20)
+	       &&
+	       (execute_command
+		(dev, cbw_direction_data_in, (u8 *) &cb, sizeof (cb), buf,
+		 8) == 1));
+	if (count >= 20) {
+		// still not successful, assume 2tb in 512byte sectors, which is just the same garbage as any other number, but probably reasonable.
+		printf ("assuming 2TB in 512byte sectors as READ CAPACITY didn't answer.\n");
+		MSC_INST (dev)->numblocks = 0xffffffff;
+		MSC_INST (dev)->blocksize = 512;
+	} else {
+		MSC_INST (dev)->numblocks = ntohl (*(u32 *) buf) + 1;
+		MSC_INST (dev)->blocksize = ntohl (*(u32 *) (buf + 4));
+	}
+	printf ("  has %d blocks sized %db\n", MSC_INST (dev)->numblocks,
+		MSC_INST (dev)->blocksize);
+}
+
+void
+usb_msc_init (usbdev_t *dev)
+{
+	int i, timeout;
+
+	dev->destroy = usb_msc_destroy;
+	dev->poll = usb_msc_poll;
+
+	configuration_descriptor_t *cd =
+		(configuration_descriptor_t *) dev->configuration;
+	interface_descriptor_t *interface =
+		(interface_descriptor_t *) (((char *) cd) + cd->bLength);
+
+	printf ("  it uses %s command set\n",
+		msc_subclass_strings[interface->bInterfaceSubClass]);
+	printf ("  it uses %s protocol\n",
+		msc_protocol_strings[interface->bInterfaceProtocol]);
+
+	if ((interface->bInterfaceProtocol != 0x50)
+	    || (interface->bInterfaceSubClass != 6)) {
+		/* Other protocols, such as ATAPI don't seem to be very popular. looks like ATAPI would be really easy to add, if necessary. */
+		printf ("  Only SCSI over Bulk is supported.\n");
+		return;
+	}
+
+	dev->data = malloc (sizeof (usbmsc_inst_t));
+	MSC_INST (dev)->bulk_in = 0;
+	MSC_INST (dev)->bulk_out = 0;
+
+	for (i = 1; i <= dev->num_endp; i++) {
+		if (dev->endpoints[i].endpoint == 0)
+			continue;
+		if (dev->endpoints[i].type != BULK)
+			continue;
+		if ((dev->endpoints[i].direction == IN)
+		    && (MSC_INST (dev)->bulk_in == 0))
+			MSC_INST (dev)->bulk_in = &dev->endpoints[i];
+		if ((dev->endpoints[i].direction == OUT)
+		    && (MSC_INST (dev)->bulk_out == 0))
+			MSC_INST (dev)->bulk_out = &dev->endpoints[i];
+	}
+
+	if (MSC_INST (dev)->bulk_in == 0)
+		fatal ("couldn't find bulk-in endpoint");
+	if (MSC_INST (dev)->bulk_out == 0)
+		fatal ("couldn't find bulk-out endpoint");
+	printf ("  using endpoint %x as in, %x as out\n",
+		MSC_INST (dev)->bulk_in->endpoint,
+		MSC_INST (dev)->bulk_out->endpoint);
+
+	printf ("  has %d luns\n", get_max_luns (dev) + 1);
+
+	printf ("  Waiting for device to become ready... ");
+	timeout = 10;
+	while (test_unit_ready (dev) && --timeout) {
+		mdelay (100);
+		printf (".");
+	}
+	if (test_unit_ready (dev)) {
+		printf ("timeout. Device not ready. Still trying...\n");
+	} else {
+		printf ("ok.\n");
+	}
+
+	printf ("  spin up");
+	for (i = 0; i < 30; i++) {
+		printf (".");
+		if (!spin_up (dev)) {
+			printf (" OK.");
+			break;
+		}
+		mdelay (100);
+	}
+	printf ("\n");
+
+	read_capacity (dev);
+	usbdisk_create (dev);
+}