[U-Boot] [PATCH v3 4/5] usb/gadget: add the fastboot gadget

Rob Herring
2014-04-10T19:18:06Z

From: Sebastian Siewior <bigeasy@linutronix.de>

This patch contains an implementation of the fastboot protocol on the
device side and a little of documentation.
The gadget expects the new-style gadget framework.
The gadget implements the getvar, reboot, download and reboot commands.
What is missing is the flash handling i.e. writting the image to media.

v3 (Rob Herring):
This is based on http://patchwork.ozlabs.org/patch/126798/ with the
following changes:
- Rebase to current mainline and updates for current gadget API
- Use SPDX identifiers for licenses
- Traced the history and added missing copyright to cmd_fastboot.c
- Use load_addr/load_size for transfer buffer
- Allow vendor strings to be optional
- Set vendor/product ID from config defines
- Allow Ctrl-C to exit fastboot mode

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Rob Herring <robh@kernel.org>
---
common/Makefile | 2 +
common/cmd_fastboot.c | 36 +++
doc/README.android-fastboot | 86 ++++++
doc/README.android-fastboot-protocol | 170 +++++++++++
drivers/usb/gadget/Makefile | 1 +
drivers/usb/gadget/f_fastboot.c | 535 +++++++++++++++++++++++++++++++++++
drivers/usb/gadget/g_fastboot.h | 15 +
drivers/usb/gadget/u_fastboot.c | 260 +++++++++++++++++
include/usb/fastboot.h | 36 +++
9 files changed, 1141 insertions(+)
create mode 100644 common/cmd_fastboot.c
create mode 100644 doc/README.android-fastboot
create mode 100644 doc/README.android-fastboot-protocol
create mode 100644 drivers/usb/gadget/f_fastboot.c
create mode 100644 drivers/usb/gadget/g_fastboot.h
create mode 100644 drivers/usb/gadget/u_fastboot.c
create mode 100644 include/usb/fastboot.h

diff --git a/common/Makefile b/common/Makefile
index da208f3..fe1d8b9 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -167,6 +167,8 @@ obj-y += cmd_usb.o
obj-y += usb.o usb_hub.o
obj-$(CONFIG_USB_STORAGE) += usb_storage.o
endif
+obj-$(CONFIG_CMD_FASTBOOT) += cmd_fastboot.o
+
obj-$(CONFIG_CMD_USB_MASS_STORAGE) += cmd_usb_mass_storage.o
obj-$(CONFIG_CMD_THOR_DOWNLOAD) += cmd_thordown.o
obj-$(CONFIG_CMD_XIMG) += cmd_ximg.o
diff --git a/common/cmd_fastboot.c b/common/cmd_fastboot.c
new file mode 100644
index 0000000..fc8d9e0
--- /dev/null
+++ b/common/cmd_fastboot.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008 - 2009 Windriver, <www.windriver.com>
+ * Author: Tom Rix <Tom.Rix@windriver.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+#include <common.h>
+#include <command.h>
+#include <usb/fastboot.h>
+
+static int do_fastboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
+{
+ int ret = 1;
+
+ if (!fastboot_init()) {
+ printf("Fastboot entered...\n");
+
+ ret = 0;
+
+ while (1) {
+ if (fastboot_poll())
+ break;
+ if (ctrlc())
+ break;
+ }
+ }
+
+ fastboot_shutdown();
+ return ret;
+}
+
+U_BOOT_CMD(
+ fastboot, 1, 1, do_fastboot,
+ "fastboot - enter USB Fastboot protocol",
+ ""
+);
diff --git a/doc/README.android-fastboot b/doc/README.android-fastboot
new file mode 100644
index 0000000..4b2a9aa
--- /dev/null
+++ b/doc/README.android-fastboot
@@ -0,0 +1,86 @@
+Android Fastboot
+~~~~~~~~~~~~~~~~
+
+Overview
+========
+The protocol that is used over USB is described in
+README.android-fastboot-protocol in same folder.
+
+The current implementation does not yet support the flash and erase
+commands.
+
+Client installation
+===================
+The counterpart to this gadget is the fastboot client which can
+be found in Android's platform/system/core repository in the fastboot
+folder. It runs on Windows, Linux and even OSx. Linux user are lucky since
+they only need libusb.
+Windows users need to bring some time until they have Android SDK (currently
+http://dl.google.com/android/installer_r12-windows.exe) installed. You
+need to install ADB package which contains the required glue libraries for
+accessing USB. Also you need "Google USB driver package" and "SDK platform
+tools". Once installed the usb driver is placed in your SDK folder under
+extras\google\usb_driver. The android_winusb.inf needs a line like
+
+ %SingleBootLoaderInterface% = USB_Install, USB\VID_0451&PID_D022
+
+either in the [Google.NTx86] section for 32bit Windows or [Google.NTamd64]
+for 64bit Windows. VID and PID should match whatever the fastboot is
+advertising.
+
+Board specific
+==============
+The gadget calls at probe time the function fastboot_board_init() which
+should be provided by the board to setup its specific configuration.
+It is possible here to overwrite specific strings like Vendor or Serial
+number. Strings which are not specified here will return a default value.
+This init function must also provide a memory area for the
+"transfer_buffer" and its size. This buffer should be large enough to hold
+whatever the download commands is willing to send or it will fail. This
+can be a kernel image for booting which could be around two MiB or a flash
+partition which could be slightly larger :)
+
+In Action
+=========
+Enter into fastboot by executing the fastboot command in u-boot and you
+should see:
+|Fastboot entered...
+
+The gadget terminates once the is unplugged. On the client side you can
+fetch the product name for instance:
+|>fastboot getvar product
+|product: Default Product
+|finished. total time: 0.016s
+
+or initiate a reboot:
+|>fastboot reboot
+
+and once the client comes back, the board should reset.
+
+You can also specify a kernel image to boot. You have to either specify
+the an image in Android format _or_ pass a binary kernel and let the
+fastboot client wrap the Android suite around it. On OMAP for instance you
+take zImage kernel and pass it to the fastboot client:
+
+|>fastboot -b 0x80000000 -c "console=ttyO2 earlyprintk root=/dev/ram0
+| mem=128M" boot zImage
+|creating boot image...
+|creating boot image - 1847296 bytes
+|downloading 'boot.img'...
+|OKAY [ 2.766s]
+|booting...
+|OKAY [ -0.000s]
+|finished. total time: 2.766s
+
+and on the gadget side you should see:
+|Starting download of 1847296 bytes
+|........................................................
+|downloading of 1847296 bytes finished
+|Booting kernel..
+|## Booting Android Image at 0x81000000 ...
+|Kernel load addr 0x80008000 size 1801 KiB
+|Kernel command line: console=ttyO2 earlyprintk root=/dev/ram0 mem=128M
+| Loading Kernel Image ... OK
+|OK
+|
+|Starting kernel ...
diff --git a/doc/README.android-fastboot-protocol b/doc/README.android-fastboot-protocol
new file mode 100644
index 0000000..e9e7166
--- /dev/null
+++ b/doc/README.android-fastboot-protocol
@@ -0,0 +1,170 @@
+FastBoot Version 0.4
+----------------------
+
+The fastboot protocol is a mechanism for communicating with bootloaders
+over USB. It is designed to be very straightforward to implement, to
+allow it to be used across a wide range of devices and from hosts running
+Linux, Windows, or OSX.
+
+
+Basic Requirements
+------------------
+
+* Two bulk endpoints (in, out) are required
+* Max packet size must be 64 bytes for full-speed and 512 bytes for
+ high-speed USB
+* The protocol is entirely host-driven and synchronous (unlike the
+ multi-channel, bi-directional, asynchronous ADB protocol)
+
+
+Transport and Framing
+---------------------
+
+1. Host sends a command, which is an ascii string in a single
+ packet no greater than 64 bytes.
+
+2. Client response with a single packet no greater than 64 bytes.
+ The first four bytes of the response are "OKAY", "FAIL", "DATA",
+ or "INFO". Additional bytes may contain an (ascii) informative
+ message.
+
+ a. INFO -> the remaining 60 bytes are an informative message
+ (providing progress or diagnostic messages). They should
+ be displayed and then step #2 repeats
+
+ b. FAIL -> the requested command failed. The remaining 60 bytes
+ of the response (if present) provide a textual failure message
+ to present to the user. Stop.
+
+ c. OKAY -> the requested command completed successfully. Go to #5
+
+ d. DATA -> the requested command is ready for the data phase.
+ A DATA response packet will be 12 bytes long, in the form of
+ DATA00000000 where the 8 digit hexidecimal number represents
+ the total data size to transfer.
+
+3. Data phase. Depending on the command, the host or client will
+ send the indicated amount of data. Short packets are always
+ acceptable and zero-length packets are ignored. This phase continues
+ until the client has sent or received the number of bytes indicated
+ in the "DATA" response above.
+
+4. Client responds with a single packet no greater than 64 bytes.
+ The first four bytes of the response are "OKAY", "FAIL", or "INFO".
+ Similar to #2:
+
+ a. INFO -> display the remaining 60 bytes and return to #4
+
+ b. FAIL -> display the remaining 60 bytes (if present) as a failure
+ reason and consider the command failed. Stop.
+
+ c. OKAY -> success. Go to #5
+
+5. Success. Stop.
+
+
+Example Session
+---------------
+
+Host: "getvar:version" request version variable
+
+Client: "OKAY0.4" return version "0.4"
+
+Host: "getvar:nonexistant" request some undefined variable
+
+Client: "OKAY" return value ""
+
+Host: "download:00001234" request to send 0x1234 bytes of data
+
+Client: "DATA00001234" ready to accept data
+
+Host: < 0x1234 bytes > send data
+
+Client: "OKAY" success
+
+Host: "flash:bootloader" request to flash the data to the bootloader
+
+Client: "INFOerasing flash" indicate status / progress
+ "INFOwriting flash"
+ "OKAY" indicate success
+
+Host: "powerdown" send a command
+
+Client: "FAILunknown command" indicate failure
+
+
+Command Reference
+-----------------
+
+* Command parameters are indicated by printf-style escape sequences.
+
+* Commands are ascii strings and sent without the quotes (which are
+ for illustration only here) and without a trailing 0 byte.
+
+* Commands that begin with a lowercase letter are reserved for this
+ specification. OEM-specific commands should not begin with a
+ lowercase letter, to prevent incompatibilities with future specs.
+
+ "getvar:%s" Read a config/version variable from the bootloader.
+ The variable contents will be returned after the
+ OKAY response.
+
+ "download:%08x" Write data to memory which will be later used
+ by "boot", "ramdisk", "flash", etc. The client
+ will reply with "DATA%08x" if it has enough
+ space in RAM or "FAIL" if not. The size of
+ the download is remembered.
+
+ "verify:%08x" Send a digital signature to verify the downloaded
+ data. Required if the bootloader is "secure"
+ otherwise "flash" and "boot" will be ignored.
+
+ "flash:%s" Write the previously downloaded image to the
+ named partition (if possible).
+
+ "erase:%s" Erase the indicated partition (clear to 0xFFs)
+
+ "boot" The previously downloaded data is a boot.img
+ and should be booted according to the normal
+ procedure for a boot.img
+
+ "continue" Continue booting as normal (if possible)
+
+ "reboot" Reboot the device.
+
+ "reboot-bootloader" Reboot back into the bootloader.
+ Useful for upgrade processes that require upgrading
+ the bootloader and then upgrading other partitions
+ using the new bootloader.
+
+ "powerdown" Power off the device.
+
+
+
+Client Variables
+----------------
+
+The "getvar:%s" command is used to read client variables which
+represent various information about the device and the software
+on it.
+
+The various currently defined names are:
+
+ version Version of FastBoot protocol supported.
+ It should be "0.3" for this document.
+
+ version-bootloader Version string for the Bootloader.
+
+ version-baseband Version string of the Baseband Software
+
+ product Name of the product
+
+ serialno Product serial number
+
+ secure If the value is "yes", this is a secure
+ bootloader requiring a signature before
+ it will install or boot images.
+
+Names starting with a lowercase character are reserved by this
+specification. OEM-specific names should not start with lowercase
+characters.
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 804a2bd..b14f60e 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_THOR_FUNCTION) += f_thor.o
obj-$(CONFIG_USBDOWNLOAD_GADGET) += g_dnl.o
obj-$(CONFIG_DFU_FUNCTION) += f_dfu.o
obj-$(CONFIG_USB_GADGET_MASS_STORAGE) += f_mass_storage.o
+obj-$(CONFIG_CMD_FASTBOOT) += f_fastboot.o u_fastboot.o
endif
ifdef CONFIG_USB_ETHER
obj-y += ether.o
diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c
new file mode 100644
index 0000000..f74a52e
--- /dev/null
+++ b/drivers/usb/gadget/f_fastboot.c
@@ -0,0 +1,535 @@
+/*
+ * (C) Copyright 2008 - 2009
+ * Windriver, <www.windriver.com>
+ * Tom Rix <Tom.Rix@windriver.com>
+ *
+ * Copyright 2011 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+#include <common.h>
+#include <errno.h>
+#include <usb/fastboot.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/compiler.h>
+
+#include "g_fastboot.h"
+
+#define FASTBOOT_INTERFACE_CLASS 0xff
+#define FASTBOOT_INTERFACE_SUB_CLASS 0x42
+#define FASTBOOT_INTERFACE_PROTOCOL 0x03
+
+#define CONFIGURATION_NORMAL 1
+#define BULK_ENDPOINT 1
+#define RX_ENDPOINT_MAXIMUM_PACKET_SIZE_2_0 (0x0200)
+#define RX_ENDPOINT_MAXIMUM_PACKET_SIZE_1_1 (0x0040)
+#define TX_ENDPOINT_MAXIMUM_PACKET_SIZE (0x0040)
+
+static struct usb_string def_usb_fb_strings[] = {
+ { FB_STR_PRODUCT_IDX, "Default Product" },
+ { FB_STR_SERIAL_IDX, "1234567890" },
+ { FB_STR_CONFIG_IDX, "Android Fastboot" },
+ { FB_STR_INTERFACE_IDX, "Android Fastboot" },
+ { FB_STR_MANUFACTURER_IDX, "Default Manufacturer" },
+ { FB_STR_PROC_REV_IDX, "Default 1.0" },
+ { FB_STR_PROC_TYPE_IDX, "Emulator" },
+ { }
+};
+
+static struct usb_gadget_strings def_fb_strings = {
+ .language = 0x0409, /* en-us */
+ .strings = def_usb_fb_strings,
+};
+
+static struct usb_gadget_strings *vendor_fb_strings;
+
+static unsigned int gadget_is_connected;
+
+static u8 ep0_buffer[512];
+static u8 ep_out_buffer[EP_BUFFER_SIZE];
+static u8 ep_in_buffer[EP_BUFFER_SIZE];
+static int current_config;
+
+/* e1 */
+static struct usb_endpoint_descriptor fs_ep_in = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN, /* IN */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = TX_ENDPOINT_MAXIMUM_PACKET_SIZE,
+ .bInterval = 0x00,
+};
+
+/* e2 */
+static struct usb_endpoint_descriptor fs_ep_out = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT, /* OUT */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = RX_ENDPOINT_MAXIMUM_PACKET_SIZE_1_1,
+ .bInterval = 0x00,
+};
+
+static struct usb_endpoint_descriptor hs_ep_out = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT, /* OUT */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = RX_ENDPOINT_MAXIMUM_PACKET_SIZE_2_0,
+ .bInterval = 0x00,
+};
+
+const char *fb_find_usb_string(unsigned int id)
+{
+ struct usb_string *s = NULL;
+
+ if (vendor_fb_strings) {
+ for (s = vendor_fb_strings->strings; s && s->s; s++) {
+ if (s->id == id)
+ break;
+ }
+ }
+ if (!s || !s->s) {
+ for (s = def_fb_strings.strings; s && s->s; s++) {
+ if (s->id == id)
+ break;
+ }
+ }
+ if (!s)
+ return NULL;
+ return s->s;
+}
+
+static struct usb_request *ep0_req;
+
+struct usb_ep *ep_in;
+struct usb_request *req_in;
+
+struct usb_ep *ep_out;
+struct usb_request *req_out;
+
+static void fastboot_ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ int status = req->status;
+
+ if (!status)
+ return;
+ printf("ep0 status %d\n", status);
+}
+
+static int fastboot_bind(struct usb_gadget *gadget)
+{
+ ep0_req = usb_ep_alloc_request(gadget->ep0, 0);
+ if (!ep0_req)
+ goto err;
+ ep0_req->buf = ep0_buffer;
+ ep0_req->complete = fastboot_ep0_complete;
+
+ ep_in = usb_ep_autoconfig(gadget, &fs_ep_in);
+ if (!ep_in)
+ goto err;
+ ep_in->driver_data = ep_in;
+
+ ep_out = usb_ep_autoconfig(gadget, &fs_ep_out);
+ if (!ep_out)
+ goto err;
+ ep_out->driver_data = ep_out;
+
+ hs_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress;
+
+ usb_gadget_connect(gadget);
+ gadget_is_connected = 1;
+
+ return 0;
+err:
+ return -1;
+}
+
+static void fastboot_unbind(struct usb_gadget *gadget)
+{
+ usb_ep_free_request(gadget->ep0, ep0_req);
+ ep_in->driver_data = NULL;
+ ep_out->driver_data = NULL;
+
+ gadget_is_connected = 0;
+ usb_gadget_disconnect(gadget);
+}
+
+#define DEVICE_BCD 0x0100
+
+struct usb_device_descriptor fb_descriptor = {
+ .bLength = sizeof(fb_descriptor),
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = 0x200,
+ .bMaxPacketSize0 = 0x40,
+ .idVendor = cpu_to_le16(CONFIG_USB_FASTBOOT_VENDOR_ID),
+ .idProduct = cpu_to_le16(CONFIG_USB_FASTBOOT_PRODUCT_ID),
+ .bcdDevice = DEVICE_BCD,
+ .iManufacturer = FB_STR_MANUFACTURER_IDX,
+ .iProduct = FB_STR_PRODUCT_IDX,
+ .iSerialNumber = FB_STR_SERIAL_IDX,
+ .bNumConfigurations = 1,
+};
+
+#define TOT_CFG_DESC_LEN (USB_DT_CONFIG_SIZE + USB_DT_INTERFACE_SIZE + \
+ USB_DT_ENDPOINT_SIZE + USB_DT_ENDPOINT_SIZE)
+
+static struct usb_config_descriptor config_desc = {
+ .bLength = USB_DT_CONFIG_SIZE,
+ .bDescriptorType = USB_DT_CONFIG,
+ .wTotalLength = cpu_to_le16(TOT_CFG_DESC_LEN),
+ .bNumInterfaces = 1,
+ .bConfigurationValue = CONFIGURATION_NORMAL,
+ .iConfiguration = FB_STR_CONFIG_IDX,
+ .bmAttributes = 0xc0,
+ .bMaxPower = 0x32,
+};
+
+static struct usb_interface_descriptor interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0x00,
+ .bAlternateSetting = 0x00,
+ .bNumEndpoints = 0x02,
+ .bInterfaceClass = FASTBOOT_INTERFACE_CLASS,
+ .bInterfaceSubClass = FASTBOOT_INTERFACE_SUB_CLASS,
+ .bInterfaceProtocol = FASTBOOT_INTERFACE_PROTOCOL,
+ .iInterface = FB_STR_INTERFACE_IDX,
+};
+
+static struct usb_qualifier_descriptor qual_desc = {
+ .bLength = sizeof(qual_desc),
+ .bDescriptorType = USB_DT_DEVICE_QUALIFIER,
+ .bcdUSB = 0x200,
+ .bMaxPacketSize0 = 0x40,
+ .bNumConfigurations = 1,
+};
+
+static int fastboot_setup_get_descr(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *ctrl)
+{
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+ u16 val;
+ int ret;
+ u32 bytes_remaining;
+ u32 bytes_total;
+ u32 this_inc;
+
+ val = w_value >> 8;
+
+ switch (val) {
+ case USB_DT_DEVICE:
+ memcpy(ep0_buffer, &fb_descriptor, sizeof(fb_descriptor));
+ ep0_req->length = min(w_length, sizeof(fb_descriptor));
+ ret = usb_ep_queue(gadget->ep0, ep0_req, 0);
+ break;
+ case USB_DT_CONFIG:
+ bytes_remaining = min(w_length, sizeof(ep0_buffer));
+ bytes_total = 0;
+
+ /* config */
+ this_inc = min(bytes_remaining, USB_DT_CONFIG_SIZE);
+ bytes_remaining -= this_inc;
+ memcpy(ep0_buffer + bytes_total, &config_desc, this_inc);
+ bytes_total += this_inc;
+
+ /* interface */
+ this_inc = min(bytes_remaining, USB_DT_INTERFACE_SIZE);
+ bytes_remaining -= this_inc;
+ memcpy(ep0_buffer + bytes_total, &interface_desc, this_inc);
+ bytes_total += this_inc;
+
+ /* ep in */
+ this_inc = min(bytes_remaining, USB_DT_ENDPOINT_SIZE);
+ bytes_remaining -= this_inc;
+ memcpy(ep0_buffer + bytes_total, &fs_ep_in, this_inc);
+ bytes_total += this_inc;
+
+ /* ep out */
+ this_inc = min(bytes_remaining, USB_DT_ENDPOINT_SIZE);
+
+ if (gadget->speed == USB_SPEED_HIGH)
+ memcpy(ep0_buffer + bytes_total, &hs_ep_out, this_inc);
+ else
+ memcpy(ep0_buffer + bytes_total, &fs_ep_out, this_inc);
+ bytes_total += this_inc;
+
+ ep0_req->length = bytes_total;
+ ret = usb_ep_queue(gadget->ep0, ep0_req, 0);
+ break;
+ case USB_DT_STRING:
+ ret = usb_gadget_get_string(vendor_fb_strings,
+ w_value & 0xff, ep0_buffer);
+ if (ret < 0)
+ ret = usb_gadget_get_string(&def_fb_strings,
+ w_value & 0xff,
+ ep0_buffer);
+ if (ret < 0)
+ break;
+
+ ep0_req->length = ret;
+ ret = usb_ep_queue(gadget->ep0, ep0_req, 0);
+ break;
+ case USB_DT_DEVICE_QUALIFIER:
+ memcpy(ep0_buffer, &qual_desc, sizeof(qual_desc));
+ ep0_req->length = min(w_length, sizeof(qual_desc));
+ ret = usb_ep_queue(gadget->ep0, ep0_req, 0);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int fastboot_setup_get_conf(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *ctrl)
+{
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ if (w_length == 0)
+ return -1;
+
+ ep0_buffer[0] = current_config;
+ ep0_req->length = 1;
+ return usb_ep_queue(gadget->ep0, ep0_req, 0);
+}
+
+static void fastboot_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ int status = req->status;
+
+ if (status)
+ printf("status: %d ep_in trans: %d\n", status, req->actual);
+}
+
+static int fastboot_disable_ep(struct usb_gadget *gadget)
+{
+ if (req_out) {
+ usb_ep_free_request(ep_out, req_out);
+ req_out = NULL;
+ }
+ if (req_in) {
+ usb_ep_free_request(ep_in, req_in);
+ req_in = NULL;
+ }
+ usb_ep_disable(ep_out);
+ usb_ep_disable(ep_in);
+
+ return 0;
+}
+
+static int fastboot_enable_ep(struct usb_gadget *gadget)
+{
+ int ret;
+
+ /* make sure we don't enable the ep twice */
+ if (gadget->speed == USB_SPEED_HIGH)
+ ret = usb_ep_enable(ep_out, &hs_ep_out);
+ else
+ ret = usb_ep_enable(ep_out, &fs_ep_out);
+ if (ret) {
+ printf("failed to enable out ep\n");
+ goto err;
+ }
+
+ req_out = usb_ep_alloc_request(ep_out, 0);
+ if (!req_out) {
+ printf("failed to alloc out req\n");
+ goto err;
+ }
+
+ ret = usb_ep_enable(ep_in, &fs_ep_in);
+ if (ret) {
+ printf("failed to enable in ep\n");
+ goto err;
+ }
+ req_in = usb_ep_alloc_request(ep_in, 0);
+ if (!req_in) {
+ printf("failed alloc req in\n");
+ goto err;
+ }
+
+ req_out->complete = rx_handler_command;
+ req_out->buf = ep_out_buffer;
+ req_out->length = sizeof(ep_out_buffer);
+
+ req_in->buf = ep_in_buffer;
+ req_in->length = sizeof(ep_in_buffer);
+
+ ret = usb_ep_queue(ep_out, req_out, 0);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ fastboot_disable_ep(gadget);
+ return -1;
+}
+
+static int fastboot_set_interface(struct usb_gadget *gadget, u32 enable)
+{
+ if (enable && req_out)
+ return 0;
+ if (!enable && !req_out)
+ return 0;
+
+ if (enable)
+ return fastboot_enable_ep(gadget);
+ else
+ return fastboot_disable_ep(gadget);
+}
+
+static int fastboot_setup_out_req(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *req)
+{
+ switch (req->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ switch (req->bRequest) {
+ case USB_REQ_SET_CONFIGURATION:
+
+ ep0_req->length = 0;
+ if (req->wValue == CONFIGURATION_NORMAL) {
+ current_config = CONFIGURATION_NORMAL;
+ fastboot_set_interface(gadget, 1);
+ return usb_ep_queue(gadget->ep0,
+ ep0_req, 0);
+ }
+ if (req->wValue == 0) {
+ current_config = 0;
+ fastboot_set_interface(gadget, 0);
+ return usb_ep_queue(gadget->ep0,
+ ep0_req, 0);
+ }
+ return -1;
+ break;
+ default:
+ return -1;
+ };
+
+ case USB_RECIP_INTERFACE:
+ switch (req->bRequest) {
+ case USB_REQ_SET_INTERFACE:
+
+ ep0_req->length = 0;
+ if (!fastboot_set_interface(gadget, 1))
+ return usb_ep_queue(gadget->ep0,
+ ep0_req, 0);
+ return -1;
+ break;
+ default:
+ return -1;
+ }
+
+ case USB_RECIP_ENDPOINT:
+ switch (req->bRequest) {
+ case USB_REQ_CLEAR_FEATURE:
+
+ return usb_ep_queue(gadget->ep0, ep0_req, 0);
+ break;
+ default:
+ return -1;
+ }
+ }
+ return -1;
+}
+
+static int fastboot_setup(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *req)
+{
+ if ((req->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD)
+ return -1;
+
+ if ((req->bRequestType & USB_DIR_IN) == 0)
+ /* host-to-device */
+ return fastboot_setup_out_req(gadget, req);
+
+ /* device-to-host */
+ if ((req->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) {
+ switch (req->bRequest) {
+ case USB_REQ_GET_DESCRIPTOR:
+ return fastboot_setup_get_descr(gadget, req);
+ break;
+
+ case USB_REQ_GET_CONFIGURATION:
+ return fastboot_setup_get_conf(gadget, req);
+ break;
+ default:
+ return -1;
+ }
+ }
+ return -1;
+}
+
+static void fastboot_disconnect(struct usb_gadget *gadget)
+{
+ fastboot_disable_ep(gadget);
+ gadget_is_connected = 0;
+}
+
+struct usb_gadget_driver fast_gadget = {
+ .speed = USB_SPEED_HIGH,
+ .bind = fastboot_bind,
+ .unbind = fastboot_unbind,
+ .setup = fastboot_setup,
+ .disconnect = fastboot_disconnect,
+};
+
+static int udc_is_probbed;
+
+int __weak fastboot_board_init(struct usb_gadget_strings **str)
+{
+ return 0;
+}
+
+int fastboot_init(void)
+{
+ int ret;
+
+ ret = fastboot_board_init(&vendor_fb_strings);
+ if (ret)
+ return ret;
+
+ ret = usb_gadget_register_driver(&fast_gadget);
+ if (ret) {
+ printf("Add gadget failed\n");
+ return ret;
+ }
+
+ udc_is_probbed = 1;
+ return 0;
+}
+
+int fastboot_poll(void)
+{
+ usb_gadget_handle_interrupts();
+
+ if (gadget_is_connected)
+ return 0;
+ else
+ return 1;
+}
+
+void fastboot_shutdown(void)
+{
+ if (!udc_is_probbed)
+ return;
+ udc_is_probbed = 0;
+ usb_gadget_unregister_driver(&fast_gadget);
+}
+
+int fastboot_tx_write(const char *buffer, unsigned int buffer_size)
+{
+ int ret;
+
+ if (req_in->complete == NULL)
+ req_in->complete = fastboot_complete_in;
+
+ memcpy(req_in->buf, buffer, buffer_size);
+ req_in->length = buffer_size;
+ ret = usb_ep_queue(ep_in, req_in, 0);
+ if (ret)
+ printf("Error %d on queue\n", ret);
+ return 0;
+}
diff --git a/drivers/usb/gadget/g_fastboot.h b/drivers/usb/gadget/g_fastboot.h
new file mode 100644
index 0000000..733eb38
--- /dev/null
+++ b/drivers/usb/gadget/g_fastboot.h
@@ -0,0 +1,15 @@
+#ifndef _G_FASTBOOT_H_
+#define _G_FASTBOOT_H_
+
+#define EP_BUFFER_SIZE 4096
+
+extern struct usb_ep *ep_in;
+extern struct usb_request *req_in;
+extern struct usb_ep *ep_out;
+extern struct usb_request *req_out;
+
+void rx_handler_command(struct usb_ep *ep, struct usb_request *req);
+int fastboot_tx_write(const char *buffer, unsigned int buffer_size);
+const char *fb_find_usb_string(unsigned int id);
+
+#endif
diff --git a/drivers/usb/gadget/u_fastboot.c b/drivers/usb/gadget/u_fastboot.c
new file mode 100644
index 0000000..76116c0
--- /dev/null
+++ b/drivers/usb/gadget/u_fastboot.c
@@ -0,0 +1,260 @@
+/*
+ * (C) Copyright 2008 - 2009
+ * Windriver, <www.windriver.com>
+ * Tom Rix <Tom.Rix@windriver.com>
+ *
+ * Copyright 2011 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+/*
+ * Part of the rx_handler were copied from the Android project.
+ * Specifically rx command parsing in the usb_rx_cmd_complete
+ * function of the file bootable/bootloader/legacy/usbloader/usbloader.c
+ *
+ * Repository: https://android.googlesource.com/platform/bootable/bootloader/legacy
+ * Files: usbloader/usbloader.c
+ * Commit: 4205b865141ff2e255fe1d3bd16de18e217ef06a
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+#include <common.h>
+#include <usb/fastboot.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include "g_fastboot.h"
+
+#define FASTBOOT_VERSION "0.4"
+
+/* The 64 defined bytes plus \0 */
+#define RESPONSE_LEN (64 + 1)
+
+static unsigned int download_size;
+static unsigned int download_bytes;
+
+static int fastboot_tx_write_str(const char *buffer)
+{
+ return fastboot_tx_write(buffer, strlen(buffer));
+}
+
+static void compl_do_reset(struct usb_ep *ep, struct usb_request *req)
+{
+ do_reset(NULL, 0, 0, NULL);
+}
+
+static void cb_reboot(struct usb_ep *ep, struct usb_request *req)
+{
+ req_in->complete = compl_do_reset;
+ fastboot_tx_write_str("OKAY");
+}
+
+static int strcmp_l1(const char *s1, const char *s2)
+{
+ return strncmp(s1, s2, strlen(s1));
+}
+
+static void cb_getvar(struct usb_ep *ep, struct usb_request *req)
+{
+ char *cmd = req->buf;
+ char response[RESPONSE_LEN];
+ const char *s;
+
+ strcpy(response, "OKAY");
+ strsep(&cmd, ":");
+ if (!cmd) {
+ fastboot_tx_write_str("FAILmissing var");
+ return;
+ }
+
+ if (!strcmp_l1("version", cmd)) {
+ strncat(response, FASTBOOT_VERSION, sizeof(response));
+ } else if (!strcmp_l1("downloadsize", cmd)) {
+ char str_num[12];
+
+ sprintf(str_num, "%08lx", load_size);
+ strncat(response, str_num, sizeof(response));
+ } else if (!strcmp_l1("product", cmd)) {
+ s = fb_find_usb_string(FB_STR_PRODUCT_IDX);
+ if (s)
+ strncat(response, s, sizeof(response));
+ else
+ strcpy(response, "FAILValue not set");
+ } else if (!strcmp_l1("serialno", cmd)) {
+ s = fb_find_usb_string(FB_STR_SERIAL_IDX);
+ if (s)
+ strncat(response, s, sizeof(response));
+ else
+ strcpy(response, "FAILValue not set");
+ } else if (!strcmp_l1("cpurev", cmd)) {
+ s = fb_find_usb_string(FB_STR_PROC_REV_IDX);
+ if (s)
+ strncat(response, s, sizeof(response));
+ else
+ strcpy(response, "FAILValue not set");
+ } else if (!strcmp_l1("secure", cmd)) {
+ s = fb_find_usb_string(FB_STR_PROC_TYPE_IDX);
+ if (s)
+ strncat(response, s, sizeof(response));
+ else
+ strcpy(response, "FAILValue not set");
+ } else {
+ strcpy(response, "FAILVariable not implemented");
+ }
+ fastboot_tx_write_str(response);
+}
+
+static unsigned int rx_bytes_expected(void)
+{
+ int rx_remain = download_size - download_bytes;
+ if (rx_remain < 0)
+ return 0;
+ if (rx_remain > EP_BUFFER_SIZE)
+ return EP_BUFFER_SIZE;
+ return rx_remain;
+}
+
+#define BYTES_PER_DOT 32768
+static void rx_handler_dl_image(struct usb_ep *ep, struct usb_request *req)
+{
+ char response[RESPONSE_LEN];
+ unsigned int transfer_size = download_size - download_bytes;
+ const unsigned char *buffer = req->buf;
+ unsigned int buffer_size = req->actual;
+
+ if (req->status != 0) {
+ printf("Bad status: %d\n", req->status);
+ return;
+ }
+
+ if (buffer_size < transfer_size)
+ transfer_size = buffer_size;
+
+ memcpy((void *)load_addr + download_bytes, buffer, transfer_size);
+
+ download_bytes += transfer_size;
+
+ /* Check if transfer is done */
+ if (download_bytes >= download_size) {
+ /*
+ * Reset global transfer variable, keep download_bytes because
+ * it will be used in the next possible flashing command
+ */
+ download_size = 0;
+ req->complete = rx_handler_command;
+ req->length = EP_BUFFER_SIZE;
+
+ sprintf(response, "OKAY");
+ fastboot_tx_write_str(response);
+
+ printf("\ndownloading of %d bytes finished\n", download_bytes);
+ } else {
+ req->length = rx_bytes_expected();
+ }
+
+ if (download_bytes && !(download_bytes % BYTES_PER_DOT)) {
+ printf(".");
+ if (!(download_bytes % (74 * BYTES_PER_DOT)))
+ printf("\n");
+ }
+ req->actual = 0;
+ usb_ep_queue(ep, req, 0);
+}
+
+static void cb_download(struct usb_ep *ep, struct usb_request *req)
+{
+ char *cmd = req->buf;
+ char response[RESPONSE_LEN];
+
+ strsep(&cmd, ":");
+ download_size = simple_strtoul(cmd, NULL, 16);
+ download_bytes = 0;
+
+ printf("Starting download of %d bytes\n", download_size);
+
+ if (0 == download_size) {
+ sprintf(response, "FAILdata invalid size");
+ } else if (download_size > load_size) {
+ download_size = 0;
+ sprintf(response, "FAILdata too large");
+ } else {
+ sprintf(response, "DATA%08x", download_size);
+ req->complete = rx_handler_dl_image;
+ req->length = rx_bytes_expected();
+ }
+ fastboot_tx_write_str(response);
+}
+
+static char boot_addr_start[32];
+static char *bootm_args[] = { "bootm", boot_addr_start, NULL };
+
+static void do_bootm_on_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ req->complete = NULL;
+ fastboot_shutdown();
+ printf("Booting kernel..\n");
+
+ do_bootm(NULL, 0, 2, bootm_args);
+
+ /* This only happens if image is somehow faulty so we start over */
+ do_reset(NULL, 0, 0, NULL);
+}
+
+static void cb_boot(struct usb_ep *ep, struct usb_request *req)
+{
+ sprintf(boot_addr_start, "0x%lx", load_addr);
+
+ req_in->complete = do_bootm_on_complete;
+ fastboot_tx_write_str("OKAY");
+ return;
+}
+
+struct cmd_dispatch_info {
+ char *cmd;
+ void (*cb)(struct usb_ep *ep, struct usb_request *req);
+};
+
+static struct cmd_dispatch_info cmd_dispatch_info[] = {
+ {
+ .cmd = "reboot",
+ .cb = cb_reboot,
+ }, {
+ .cmd = "getvar:",
+ .cb = cb_getvar,
+ }, {
+ .cmd = "download:",
+ .cb = cb_download,
+ }, {
+ .cmd = "boot",
+ .cb = cb_boot,
+ },
+};
+
+void rx_handler_command(struct usb_ep *ep, struct usb_request *req)
+{
+ char response[RESPONSE_LEN];
+ char *cmdbuf = req->buf;
+ void (*func_cb)(struct usb_ep *ep, struct usb_request *req) = NULL;
+ int i;
+
+ sprintf(response, "FAIL");
+
+ for (i = 0; i < ARRAY_SIZE(cmd_dispatch_info); i++) {
+ if (!strcmp_l1(cmd_dispatch_info[i].cmd, cmdbuf)) {
+ func_cb = cmd_dispatch_info[i].cb;
+ break;
+ }
+ }
+
+ if (!func_cb)
+ fastboot_tx_write_str("FAILunknown command");
+ else
+ func_cb(ep, req);
+
+ if (req->status == 0) {
+ *cmdbuf = '\0';
+ req->actual = 0;
+ usb_ep_queue(ep, req, 0);
+ }
+}
diff --git a/include/usb/fastboot.h b/include/usb/fastboot.h
new file mode 100644
index 0000000..b348886
--- /dev/null
+++ b/include/usb/fastboot.h
@@ -0,0 +1,36 @@
+/*
+ * (C) Copyright 2008 - 2009
+ * Windriver, <www.windriver.com>
+ * Tom Rix <Tom.Rix@windriver.com>
+ *
+ * Copyright 2011 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#ifndef FASTBOOT_H
+#define FASTBOOT_H
+
+#include <common.h>
+#include <command.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#define FB_STR_PRODUCT_IDX 1
+#define FB_STR_SERIAL_IDX 2
+#define FB_STR_CONFIG_IDX 3
+#define FB_STR_INTERFACE_IDX 4
+#define FB_STR_MANUFACTURER_IDX 5
+#define FB_STR_PROC_REV_IDX 6
+#define FB_STR_PROC_TYPE_IDX 7
+
+#ifdef CONFIG_CMD_FASTBOOT
+
+int fastboot_init(void);
+void fastboot_shutdown(void);
+int fastboot_poll(void);
+
+int fastboot_board_init(struct usb_gadget_strings **str);
+
+#endif
+#endif
--
1.9.1