]> git.ipfire.org Git - people/ms/u-boot.git/commitdiff
usb: rockchip: add the rockusb gadget
authorEddie Cai <eddie.cai.linux@gmail.com>
Fri, 15 Dec 2017 00:17:10 +0000 (08:17 +0800)
committerMarek Vasut <marex@denx.de>
Wed, 10 Jan 2018 10:11:05 +0000 (11:11 +0100)
this patch implement rockusb protocol on the device side. this is based on
USB download gadget infrastructure. the rockusb function implements the rd,
wl, rid commands. it can work with rkdeveloptool

Signed-off-by: Eddie Cai <eddie.cai.linux@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
MAINTAINERS
arch/arm/include/asm/arch-rockchip/f_rockusb.h [new file with mode: 0644]
drivers/usb/gadget/Kconfig
drivers/usb/gadget/Makefile
drivers/usb/gadget/f_rockusb.c [new file with mode: 0644]

index e950267494a3c608b7f541dfe0b9f622295dcaf3..baf2beb00c0672035446ba475130e445fe048ffb 100644 (file)
@@ -488,6 +488,11 @@ S: Maintained
 T:     git git://git.denx.de/u-boot-usb.git topic-xhci
 F:     drivers/usb/host/xhci*
 
+ROCKUSB
+M:     Eddie Cai <eddie.cai.linux@gmail.com>
+S:     Maintained
+F:     drivers/usb/gadget/f_rockusb.c
+
 VIDEO
 M:     Anatolij Gustschin <agust@denx.de>
 S:     Maintained
diff --git a/arch/arm/include/asm/arch-rockchip/f_rockusb.h b/arch/arm/include/asm/arch-rockchip/f_rockusb.h
new file mode 100644 (file)
index 0000000..0e99f1b
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * (C) Copyright 2017
+ *
+ * Eddie Cai <eddie.cai.linux@gmail.com>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#ifndef _F_ROCKUSB_H_
+#define _F_ROCKUSB_H_
+#include <blk.h>
+
+#define ROCKUSB_VERSION                "0.1"
+
+#define ROCKUSB_INTERFACE_CLASS        0xff
+#define ROCKUSB_INTERFACE_SUB_CLASS    0x06
+#define ROCKUSB_INTERFACE_PROTOCOL     0x05
+
+#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
+
+#define EP_BUFFER_SIZE                 4096
+/*
+ * EP_BUFFER_SIZE must always be an integral multiple of maxpacket size
+ * (64 or 512 or 1024), else we break on certain controllers like DWC3
+ * that expect bulk OUT requests to be divisible by maxpacket size.
+ */
+
+#define RKUSB_BUF_SIZE         EP_BUFFER_SIZE * 2
+
+#define RKUSB_STATUS_IDLE                      0
+#define RKUSB_STATUS_CMD                       1
+#define RKUSB_STATUS_RXDATA                    2
+#define RKUSB_STATUS_TXDATA                    3
+#define RKUSB_STATUS_CSW                       4
+#define RKUSB_STATUS_RXDATA_PREPARE            5
+#define RKUSB_STATUS_TXDATA_PREPARE            6
+
+enum rkusb_command {
+K_FW_TEST_UNIT_READY   = 0x00,
+K_FW_READ_FLASH_ID = 0x01,
+K_FW_SET_DEVICE_ID = 0x02,
+K_FW_TEST_BAD_BLOCK = 0x03,
+K_FW_READ_10 = 0x04,
+K_FW_WRITE_10 = 0x05,
+K_FW_ERASE_10 = 0x06,
+K_FW_WRITE_SPARE = 0x07,
+K_FW_READ_SPARE = 0x08,
+
+K_FW_ERASE_10_FORCE = 0x0b,
+K_FW_GET_VERSION = 0x0c,
+
+K_FW_LBA_READ_10 = 0x14,
+K_FW_LBA_WRITE_10 = 0x15,
+K_FW_ERASE_SYS_DISK = 0x16,
+K_FW_SDRAM_READ_10 = 0x17,
+K_FW_SDRAM_WRITE_10 = 0x18,
+K_FW_SDRAM_EXECUTE = 0x19,
+K_FW_READ_FLASH_INFO = 0x1A,
+K_FW_GET_CHIP_VER = 0x1B,
+K_FW_LOW_FORMAT = 0x1C,
+K_FW_SET_RESET_FLAG = 0x1E,
+K_FW_SPI_READ_10 = 0x21,
+K_FW_SPI_WRITE_10 = 0x22,
+
+K_FW_SESSION = 0X30,
+K_FW_RESET = 0xff,
+};
+
+#define CBW_DIRECTION_OUT              0x00
+#define CBW_DIRECTION_IN               0x80
+
+struct cmd_dispatch_info {
+       enum rkusb_command cmd;
+       /* call back function to handle rockusb command */
+       void (*cb)(struct usb_ep *ep, struct usb_request *req);
+};
+
+/* Bulk-only data structures */
+
+/* Command Block Wrapper */
+struct fsg_bulk_cb_wrap {
+       __le32  signature;              /* Contains 'USBC' */
+       u32     tag;                    /* Unique per command id */
+       __le32  data_transfer_length;   /* Size of the data */
+       u8      flags;                  /* Direction in bit 7 */
+       u8      lun;                    /* lun (normally 0) */
+       u8      length;                 /* Of the CDB, <= MAX_COMMAND_SIZE */
+       u8      CDB[16];                /* Command Data Block */
+};
+
+#define USB_BULK_CB_WRAP_LEN    31
+#define USB_BULK_CB_SIG         0x43425355      /* Spells out USBC */
+#define USB_BULK_IN_FLAG        0x80
+
+/* Command status Wrapper */
+struct bulk_cs_wrap {
+       __le32  signature;              /* Should = 'USBS' */
+       u32     tag;                    /* Same as original command */
+       __le32  residue;                /* Amount not transferred */
+       u8      status;                 /* See below */
+};
+
+#define USB_BULK_CS_WRAP_LEN    13
+#define USB_BULK_CS_SIG         0x53425355      /* Spells out 'USBS' */
+#define USB_STATUS_PASS         0
+#define USB_STATUS_FAIL         1
+#define USB_STATUS_PHASE_ERROR  2
+
+#define CSW_GOOD                0x00
+#define CSW_FAIL                0x01
+
+struct f_rockusb {
+       struct usb_function usb_function;
+       struct usb_ep *in_ep, *out_ep;
+       struct usb_request *in_req, *out_req;
+       char *dev_type;
+       unsigned int dev_index;
+       unsigned int tag;
+       unsigned int lba;
+       unsigned int dl_size;
+       unsigned int dl_bytes;
+       struct blk_desc *desc;
+       int reboot_flag;
+       void *buf;
+       void *buf_head;
+};
+
+/* init rockusb device, tell rockusb which device you want to read/write*/
+void rockusb_dev_init(char *dev_type, int dev_index);
+#endif /* _F_ROCKUSB_H_ */
+
index 102a63b8eeb9e7547dbe7a89b43105c00bf2c278..c387f5e497929162f64e18ca1e77521c7ca7b7ee 100644 (file)
@@ -134,6 +134,14 @@ config USB_FUNCTION_SDP
          allows to download images into memory and execute (jump to) them
          using the same protocol as implemented by the i.MX family's boot ROM.
 
+config USB_FUNCTION_ROCKUSB
+        bool "Enable USB rockusb gadget"
+        help
+          Rockusb protocol is widely used by Rockchip SoC based devices. It can
+          read/write info, image to/from devices. This enables the USB part of
+          the rockusb gadget.for more detail about Rockusb protocol, please see
+          doc/README.rockusb
+
 endif # USB_GADGET_DOWNLOAD
 
 config USB_ETHER
index 7258099c1cf5f930831a6cf7a4473804b98ff120..ee8bc994c54d62b19ef9faacc6f4a0614e47644f 100644 (file)
@@ -30,6 +30,7 @@ obj-$(CONFIG_USB_FUNCTION_DFU) += f_dfu.o
 obj-$(CONFIG_USB_FUNCTION_MASS_STORAGE) += f_mass_storage.o
 obj-$(CONFIG_USB_FUNCTION_FASTBOOT) += f_fastboot.o
 obj-$(CONFIG_USB_FUNCTION_SDP) += f_sdp.o
+obj-$(CONFIG_USB_FUNCTION_ROCKUSB) += f_rockusb.o
 endif
 endif
 ifdef CONFIG_USB_ETHER
diff --git a/drivers/usb/gadget/f_rockusb.c b/drivers/usb/gadget/f_rockusb.c
new file mode 100644 (file)
index 0000000..d5a10f1
--- /dev/null
@@ -0,0 +1,718 @@
+/*
+ * (C) Copyright 2017
+ *
+ * Eddie Cai <eddie.cai.linux@gmail.com>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+#include <config.h>
+#include <common.h>
+#include <errno.h>
+#include <malloc.h>
+#include <memalign.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
+#include <linux/compiler.h>
+#include <version.h>
+#include <g_dnl.h>
+#include <asm/arch/f_rockusb.h>
+
+static inline struct f_rockusb *func_to_rockusb(struct usb_function *f)
+{
+       return container_of(f, struct f_rockusb, usb_function);
+}
+
+static struct usb_endpoint_descriptor fs_ep_in = {
+       .bLength            = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType    = USB_DT_ENDPOINT,
+       .bEndpointAddress   = USB_DIR_IN,
+       .bmAttributes       = USB_ENDPOINT_XFER_BULK,
+       .wMaxPacketSize     = cpu_to_le16(64),
+};
+
+static struct usb_endpoint_descriptor fs_ep_out = {
+       .bLength                = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType        = USB_DT_ENDPOINT,
+       .bEndpointAddress       = USB_DIR_OUT,
+       .bmAttributes           = USB_ENDPOINT_XFER_BULK,
+       .wMaxPacketSize         = cpu_to_le16(64),
+};
+
+static struct usb_endpoint_descriptor hs_ep_in = {
+       .bLength                = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType        = USB_DT_ENDPOINT,
+       .bEndpointAddress       = USB_DIR_IN,
+       .bmAttributes           = USB_ENDPOINT_XFER_BULK,
+       .wMaxPacketSize         = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_ep_out = {
+       .bLength                = USB_DT_ENDPOINT_SIZE,
+       .bDescriptorType        = USB_DT_ENDPOINT,
+       .bEndpointAddress       = USB_DIR_OUT,
+       .bmAttributes           = USB_ENDPOINT_XFER_BULK,
+       .wMaxPacketSize         = cpu_to_le16(512),
+};
+
+static struct usb_interface_descriptor interface_desc = {
+       .bLength                = USB_DT_INTERFACE_SIZE,
+       .bDescriptorType        = USB_DT_INTERFACE,
+       .bInterfaceNumber       = 0x00,
+       .bAlternateSetting      = 0x00,
+       .bNumEndpoints          = 0x02,
+       .bInterfaceClass        = ROCKUSB_INTERFACE_CLASS,
+       .bInterfaceSubClass     = ROCKUSB_INTERFACE_SUB_CLASS,
+       .bInterfaceProtocol     = ROCKUSB_INTERFACE_PROTOCOL,
+};
+
+static struct usb_descriptor_header *rkusb_fs_function[] = {
+       (struct usb_descriptor_header *)&interface_desc,
+       (struct usb_descriptor_header *)&fs_ep_in,
+       (struct usb_descriptor_header *)&fs_ep_out,
+};
+
+static struct usb_descriptor_header *rkusb_hs_function[] = {
+       (struct usb_descriptor_header *)&interface_desc,
+       (struct usb_descriptor_header *)&hs_ep_in,
+       (struct usb_descriptor_header *)&hs_ep_out,
+       NULL,
+};
+
+static const char rkusb_name[] = "Rockchip Rockusb";
+
+static struct usb_string rkusb_string_defs[] = {
+       [0].s = rkusb_name,
+       {  }                    /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_rkusb = {
+       .language       = 0x0409,       /* en-us */
+       .strings        = rkusb_string_defs,
+};
+
+static struct usb_gadget_strings *rkusb_strings[] = {
+       &stringtab_rkusb,
+       NULL,
+};
+
+static struct f_rockusb *rockusb_func;
+static void rx_handler_command(struct usb_ep *ep, struct usb_request *req);
+static int rockusb_tx_write_csw(u32 tag, int residue, u8 status, int size);
+
+struct f_rockusb *get_rkusb(void)
+{
+       struct f_rockusb *f_rkusb = rockusb_func;
+
+       if (!f_rkusb) {
+               f_rkusb = memalign(CONFIG_SYS_CACHELINE_SIZE, sizeof(*f_rkusb));
+               if (!f_rkusb)
+                       return 0;
+
+               rockusb_func = f_rkusb;
+               memset(f_rkusb, 0, sizeof(*f_rkusb));
+       }
+
+       if (!f_rkusb->buf_head) {
+               f_rkusb->buf_head = memalign(CONFIG_SYS_CACHELINE_SIZE,
+                                            RKUSB_BUF_SIZE);
+               if (!f_rkusb->buf_head)
+                       return 0;
+
+               f_rkusb->buf = f_rkusb->buf_head;
+               memset(f_rkusb->buf_head, 0, RKUSB_BUF_SIZE);
+       }
+       return f_rkusb;
+}
+
+static struct usb_endpoint_descriptor *rkusb_ep_desc(
+struct usb_gadget *g,
+struct usb_endpoint_descriptor *fs,
+struct usb_endpoint_descriptor *hs)
+{
+       if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+               return hs;
+       return fs;
+}
+
+static void rockusb_complete(struct usb_ep *ep, struct usb_request *req)
+{
+       int status = req->status;
+
+       if (!status)
+               return;
+       debug("status: %d ep '%s' trans: %d\n", status, ep->name, req->actual);
+}
+
+/* config the rockusb device*/
+static int rockusb_bind(struct usb_configuration *c, struct usb_function *f)
+{
+       int id;
+       struct usb_gadget *gadget = c->cdev->gadget;
+       struct f_rockusb *f_rkusb = func_to_rockusb(f);
+       const char *s;
+
+       id = usb_interface_id(c, f);
+       if (id < 0)
+               return id;
+       interface_desc.bInterfaceNumber = id;
+
+       id = usb_string_id(c->cdev);
+       if (id < 0)
+               return id;
+
+       rkusb_string_defs[0].id = id;
+       interface_desc.iInterface = id;
+
+       f_rkusb->in_ep = usb_ep_autoconfig(gadget, &fs_ep_in);
+       if (!f_rkusb->in_ep)
+               return -ENODEV;
+       f_rkusb->in_ep->driver_data = c->cdev;
+
+       f_rkusb->out_ep = usb_ep_autoconfig(gadget, &fs_ep_out);
+       if (!f_rkusb->out_ep)
+               return -ENODEV;
+       f_rkusb->out_ep->driver_data = c->cdev;
+
+       f->descriptors = rkusb_fs_function;
+
+       if (gadget_is_dualspeed(gadget)) {
+               hs_ep_in.bEndpointAddress = fs_ep_in.bEndpointAddress;
+               hs_ep_out.bEndpointAddress = fs_ep_out.bEndpointAddress;
+               f->hs_descriptors = rkusb_hs_function;
+       }
+
+       s = env_get("serial#");
+       if (s)
+               g_dnl_set_serialnumber((char *)s);
+
+       return 0;
+}
+
+static void rockusb_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+       /* clear the configuration*/
+       memset(rockusb_func, 0, sizeof(*rockusb_func));
+}
+
+static void rockusb_disable(struct usb_function *f)
+{
+       struct f_rockusb *f_rkusb = func_to_rockusb(f);
+
+       usb_ep_disable(f_rkusb->out_ep);
+       usb_ep_disable(f_rkusb->in_ep);
+
+       if (f_rkusb->out_req) {
+               free(f_rkusb->out_req->buf);
+               usb_ep_free_request(f_rkusb->out_ep, f_rkusb->out_req);
+               f_rkusb->out_req = NULL;
+       }
+       if (f_rkusb->in_req) {
+               free(f_rkusb->in_req->buf);
+               usb_ep_free_request(f_rkusb->in_ep, f_rkusb->in_req);
+               f_rkusb->in_req = NULL;
+       }
+       if (f_rkusb->buf_head) {
+               free(f_rkusb->buf_head);
+               f_rkusb->buf_head = NULL;
+               f_rkusb->buf = NULL;
+       }
+}
+
+static struct usb_request *rockusb_start_ep(struct usb_ep *ep)
+{
+       struct usb_request *req;
+
+       req = usb_ep_alloc_request(ep, 0);
+       if (!req)
+               return NULL;
+
+       req->length = EP_BUFFER_SIZE;
+       req->buf = memalign(CONFIG_SYS_CACHELINE_SIZE, EP_BUFFER_SIZE);
+       if (!req->buf) {
+               usb_ep_free_request(ep, req);
+               return NULL;
+       }
+       memset(req->buf, 0, req->length);
+
+       return req;
+}
+
+static int rockusb_set_alt(struct usb_function *f, unsigned int interface,
+                          unsigned int alt)
+{
+       int ret;
+       struct usb_composite_dev *cdev = f->config->cdev;
+       struct usb_gadget *gadget = cdev->gadget;
+       struct f_rockusb *f_rkusb = func_to_rockusb(f);
+       const struct usb_endpoint_descriptor *d;
+
+       debug("%s: func: %s intf: %d alt: %d\n",
+             __func__, f->name, interface, alt);
+
+       d = rkusb_ep_desc(gadget, &fs_ep_out, &hs_ep_out);
+       ret = usb_ep_enable(f_rkusb->out_ep, d);
+       if (ret) {
+               printf("failed to enable out ep\n");
+               return ret;
+       }
+
+       f_rkusb->out_req = rockusb_start_ep(f_rkusb->out_ep);
+       if (!f_rkusb->out_req) {
+               printf("failed to alloc out req\n");
+               ret = -EINVAL;
+               goto err;
+       }
+       f_rkusb->out_req->complete = rx_handler_command;
+
+       d = rkusb_ep_desc(gadget, &fs_ep_in, &hs_ep_in);
+       ret = usb_ep_enable(f_rkusb->in_ep, d);
+       if (ret) {
+               printf("failed to enable in ep\n");
+               goto err;
+       }
+
+       f_rkusb->in_req = rockusb_start_ep(f_rkusb->in_ep);
+       if (!f_rkusb->in_req) {
+               printf("failed alloc req in\n");
+               ret = -EINVAL;
+               goto err;
+       }
+       f_rkusb->in_req->complete = rockusb_complete;
+
+       ret = usb_ep_queue(f_rkusb->out_ep, f_rkusb->out_req, 0);
+       if (ret)
+               goto err;
+
+       return 0;
+err:
+       rockusb_disable(f);
+       return ret;
+}
+
+static int rockusb_add(struct usb_configuration *c)
+{
+       struct f_rockusb *f_rkusb = get_rkusb();
+       int status;
+
+       debug("%s: cdev: 0x%p\n", __func__, c->cdev);
+
+       f_rkusb->usb_function.name = "f_rockusb";
+       f_rkusb->usb_function.bind = rockusb_bind;
+       f_rkusb->usb_function.unbind = rockusb_unbind;
+       f_rkusb->usb_function.set_alt = rockusb_set_alt;
+       f_rkusb->usb_function.disable = rockusb_disable;
+       f_rkusb->usb_function.strings = rkusb_strings;
+
+       status = usb_add_function(c, &f_rkusb->usb_function);
+       if (status) {
+               free(f_rkusb);
+               rockusb_func = f_rkusb;
+       }
+       return status;
+}
+
+void rockusb_dev_init(char *dev_type, int dev_index)
+{
+       struct f_rockusb *f_rkusb = get_rkusb();
+
+       f_rkusb->dev_type = dev_type;
+       f_rkusb->dev_index = dev_index;
+}
+
+DECLARE_GADGET_BIND_CALLBACK(usb_dnl_rockusb, rockusb_add);
+
+static int rockusb_tx_write(const char *buffer, unsigned int buffer_size)
+{
+       struct usb_request *in_req = rockusb_func->in_req;
+       int ret;
+
+       memcpy(in_req->buf, buffer, buffer_size);
+       in_req->length = buffer_size;
+       usb_ep_dequeue(rockusb_func->in_ep, in_req);
+       ret = usb_ep_queue(rockusb_func->in_ep, in_req, 0);
+       if (ret)
+               printf("Error %d on queue\n", ret);
+       return 0;
+}
+
+static int rockusb_tx_write_str(const char *buffer)
+{
+       return rockusb_tx_write(buffer, strlen(buffer));
+}
+
+#ifdef DEBUG
+static void printcbw(char *buf)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw,
+                                sizeof(struct fsg_bulk_cb_wrap));
+
+       memcpy((char *)cbw, buf, USB_BULK_CB_WRAP_LEN);
+
+       debug("cbw: signature:%x\n", cbw->signature);
+       debug("cbw: tag=%x\n", cbw->tag);
+       debug("cbw: data_transfer_length=%d\n", cbw->data_transfer_length);
+       debug("cbw: flags=%x\n", cbw->flags);
+       debug("cbw: lun=%d\n", cbw->lun);
+       debug("cbw: length=%d\n", cbw->length);
+       debug("cbw: ucOperCode=%x\n", cbw->CDB[0]);
+       debug("cbw: ucReserved=%x\n", cbw->CDB[1]);
+       debug("cbw: dwAddress:%x %x %x %x\n", cbw->CDB[5], cbw->CDB[4],
+             cbw->CDB[3], cbw->CDB[2]);
+       debug("cbw: ucReserved2=%x\n", cbw->CDB[6]);
+       debug("cbw: uslength:%x %x\n", cbw->CDB[8], cbw->CDB[7]);
+}
+
+static void printcsw(char *buf)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct bulk_cs_wrap, csw,
+                                sizeof(struct bulk_cs_wrap));
+       memcpy((char *)csw, buf, USB_BULK_CS_WRAP_LEN);
+       debug("csw: signature:%x\n", csw->signature);
+       debug("csw: tag:%x\n", csw->tag);
+       debug("csw: residue:%x\n", csw->residue);
+       debug("csw: status:%x\n", csw->status);
+}
+#endif
+
+static int rockusb_tx_write_csw(u32 tag, int residue, u8 status, int size)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct bulk_cs_wrap, csw,
+                                sizeof(struct bulk_cs_wrap));
+       csw->signature = cpu_to_le32(USB_BULK_CS_SIG);
+       csw->tag = tag;
+       csw->residue = cpu_to_be32(residue);
+       csw->status = status;
+#ifdef DEBUG
+       printcsw((char *)&csw);
+#endif
+       return rockusb_tx_write((char *)csw, size);
+}
+
+static unsigned int rx_bytes_expected(struct usb_ep *ep)
+{
+       struct f_rockusb *f_rkusb = get_rkusb();
+       int rx_remain = f_rkusb->dl_size - f_rkusb->dl_bytes;
+       unsigned int rem;
+       unsigned int maxpacket = ep->maxpacket;
+
+       if (rx_remain <= 0)
+               return 0;
+       else if (rx_remain > EP_BUFFER_SIZE)
+               return EP_BUFFER_SIZE;
+
+       rem = rx_remain % maxpacket;
+       if (rem > 0)
+               rx_remain = rx_remain + (maxpacket - rem);
+
+       return rx_remain;
+}
+
+/* usb_request complete call back to handle down load image */
+static void rx_handler_dl_image(struct usb_ep *ep, struct usb_request *req)
+{
+       struct f_rockusb *f_rkusb = get_rkusb();
+       unsigned int transfer_size = 0;
+       const unsigned char *buffer = req->buf;
+       unsigned int buffer_size = req->actual;
+
+       transfer_size = f_rkusb->dl_size - f_rkusb->dl_bytes;
+       if (!f_rkusb->desc) {
+               char *type = f_rkusb->dev_type;
+               int index = f_rkusb->dev_index;
+
+               f_rkusb->desc = blk_get_dev(type, index);
+               if (!f_rkusb->desc ||
+                   f_rkusb->desc->type == DEV_TYPE_UNKNOWN) {
+                       puts("invalid mmc device\n");
+                       rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL,
+                                            USB_BULK_CS_WRAP_LEN);
+                       return;
+               }
+       }
+
+       if (req->status != 0) {
+               printf("Bad status: %d\n", req->status);
+               rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL,
+                                    USB_BULK_CS_WRAP_LEN);
+               return;
+       }
+
+       if (buffer_size < transfer_size)
+               transfer_size = buffer_size;
+
+       memcpy((void *)f_rkusb->buf, buffer, transfer_size);
+       f_rkusb->dl_bytes += transfer_size;
+       int blks = 0, blkcnt = transfer_size  / 512;
+
+       debug("dl %x bytes, %x blks, write lba %x, dl_size:%x, dl_bytes:%x, ",
+             transfer_size, blkcnt, f_rkusb->lba, f_rkusb->dl_size,
+             f_rkusb->dl_bytes);
+       blks = blk_dwrite(f_rkusb->desc, f_rkusb->lba, blkcnt, f_rkusb->buf);
+       if (blks != blkcnt) {
+               printf("failed writing to device %s: %d\n", f_rkusb->dev_type,
+                      f_rkusb->dev_index);
+               rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_FAIL,
+                                    USB_BULK_CS_WRAP_LEN);
+               return;
+       }
+       f_rkusb->lba += blkcnt;
+
+       /* Check if transfer is done */
+       if (f_rkusb->dl_bytes >= f_rkusb->dl_size) {
+               req->complete = rx_handler_command;
+               req->length = EP_BUFFER_SIZE;
+               f_rkusb->buf = f_rkusb->buf_head;
+               printf("transfer 0x%x bytes done\n", f_rkusb->dl_size);
+               f_rkusb->dl_size = 0;
+               rockusb_tx_write_csw(f_rkusb->tag, 0, CSW_GOOD,
+                                    USB_BULK_CS_WRAP_LEN);
+       } else {
+               req->length = rx_bytes_expected(ep);
+               if (f_rkusb->buf == f_rkusb->buf_head)
+                       f_rkusb->buf = f_rkusb->buf_head + EP_BUFFER_SIZE;
+               else
+                       f_rkusb->buf = f_rkusb->buf_head;
+
+               debug("remain %x bytes, %x sectors\n", req->length,
+                     req->length / 512);
+       }
+
+       req->actual = 0;
+       usb_ep_queue(ep, req, 0);
+}
+
+static void cb_test_unit_ready(struct usb_ep *ep, struct usb_request *req)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw,
+                                sizeof(struct fsg_bulk_cb_wrap));
+
+       memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN);
+
+       rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length,
+                            CSW_GOOD, USB_BULK_CS_WRAP_LEN);
+}
+
+static void cb_read_storage_id(struct usb_ep *ep, struct usb_request *req)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw,
+                                sizeof(struct fsg_bulk_cb_wrap));
+       char emmc_id[] = "EMMC ";
+
+       printf("read storage id\n");
+       memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN);
+       rockusb_tx_write_str(emmc_id);
+       rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length, CSW_GOOD,
+                            USB_BULK_CS_WRAP_LEN);
+}
+
+static void cb_write_lba(struct usb_ep *ep, struct usb_request *req)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw,
+                                sizeof(struct fsg_bulk_cb_wrap));
+       struct f_rockusb *f_rkusb = get_rkusb();
+       int sector_count;
+
+       memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN);
+       sector_count = (int)get_unaligned_be16(&cbw->CDB[7]);
+       f_rkusb->lba = get_unaligned_be32(&cbw->CDB[2]);
+       f_rkusb->dl_size = sector_count * 512;
+       f_rkusb->dl_bytes = 0;
+       f_rkusb->tag = cbw->tag;
+       debug("require write %x bytes, %x sectors to lba %x\n",
+             f_rkusb->dl_size, sector_count, f_rkusb->lba);
+
+       if (f_rkusb->dl_size == 0)  {
+               rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length,
+                                    CSW_FAIL, USB_BULK_CS_WRAP_LEN);
+       } else {
+               req->complete = rx_handler_dl_image;
+               req->length = rx_bytes_expected(ep);
+       }
+}
+
+void __weak rkusb_set_reboot_flag(int flag)
+{
+       struct f_rockusb *f_rkusb = get_rkusb();
+
+       printf("rockkusb set reboot flag: %d\n", f_rkusb->reboot_flag);
+}
+
+static void compl_do_reset(struct usb_ep *ep, struct usb_request *req)
+{
+       struct f_rockusb *f_rkusb = get_rkusb();
+
+       rkusb_set_reboot_flag(f_rkusb->reboot_flag);
+       do_reset(NULL, 0, 0, NULL);
+}
+
+static void cb_reboot(struct usb_ep *ep, struct usb_request *req)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw,
+                                sizeof(struct fsg_bulk_cb_wrap));
+       struct f_rockusb *f_rkusb = get_rkusb();
+
+       f_rkusb->reboot_flag = 0;
+       memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN);
+       f_rkusb->reboot_flag = cbw->CDB[1];
+       rockusb_func->in_req->complete = compl_do_reset;
+       rockusb_tx_write_csw(cbw->tag, cbw->data_transfer_length, CSW_GOOD,
+                            USB_BULK_CS_WRAP_LEN);
+}
+
+static void cb_not_support(struct usb_ep *ep, struct usb_request *req)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw,
+                                sizeof(struct fsg_bulk_cb_wrap));
+
+       memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN);
+       printf("Rockusb command %x not support yet\n", cbw->CDB[0]);
+       rockusb_tx_write_csw(cbw->tag, 0, CSW_FAIL, USB_BULK_CS_WRAP_LEN);
+}
+
+static const struct cmd_dispatch_info cmd_dispatch_info[] = {
+       {
+               .cmd = K_FW_TEST_UNIT_READY,
+               .cb = cb_test_unit_ready,
+       },
+       {
+               .cmd = K_FW_READ_FLASH_ID,
+               .cb = cb_read_storage_id,
+       },
+       {
+               .cmd = K_FW_SET_DEVICE_ID,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_TEST_BAD_BLOCK,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_READ_10,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_WRITE_10,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_ERASE_10,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_WRITE_SPARE,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_READ_SPARE,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_ERASE_10_FORCE,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_GET_VERSION,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_LBA_READ_10,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_LBA_WRITE_10,
+               .cb = cb_write_lba,
+       },
+       {
+               .cmd = K_FW_ERASE_SYS_DISK,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_SDRAM_READ_10,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_SDRAM_WRITE_10,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_SDRAM_EXECUTE,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_READ_FLASH_INFO,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_GET_CHIP_VER,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_LOW_FORMAT,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_SET_RESET_FLAG,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_SPI_READ_10,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_SPI_WRITE_10,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_SESSION,
+               .cb = cb_not_support,
+       },
+       {
+               .cmd = K_FW_RESET,
+               .cb = cb_reboot,
+       },
+};
+
+static void rx_handler_command(struct usb_ep *ep, struct usb_request *req)
+{
+       void (*func_cb)(struct usb_ep *ep, struct usb_request *req) = NULL;
+
+       ALLOC_CACHE_ALIGN_BUFFER(struct fsg_bulk_cb_wrap, cbw,
+                                sizeof(struct fsg_bulk_cb_wrap));
+       char *cmdbuf = req->buf;
+       int i;
+
+       if (req->status || req->length == 0)
+               return;
+
+       memcpy((char *)cbw, req->buf, USB_BULK_CB_WRAP_LEN);
+#ifdef DEBUG
+       printcbw(req->buf);
+#endif
+
+       for (i = 0; i < ARRAY_SIZE(cmd_dispatch_info); i++) {
+               if (cmd_dispatch_info[i].cmd == cbw->CDB[0]) {
+                       func_cb = cmd_dispatch_info[i].cb;
+                       break;
+               }
+       }
+
+       if (!func_cb) {
+               printf("unknown command: %s\n", (char *)req->buf);
+               rockusb_tx_write_str("FAILunknown command");
+       } else {
+               if (req->actual < req->length) {
+                       u8 *buf = (u8 *)req->buf;
+
+                       buf[req->actual] = 0;
+                       func_cb(ep, req);
+               } else {
+                       puts("buffer overflow\n");
+                       rockusb_tx_write_str("FAILbuffer overflow");
+               }
+       }
+
+       *cmdbuf = '\0';
+       req->actual = 0;
+       usb_ep_queue(ep, req, 0);
+}