/* * (C) Copyright 2015 Google, Inc * Written by Simon Glass * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; /* * This driver emulates a flash stick using the UFI command specification and * the BBB (bulk/bulk/bulk) protocol. It supports only a single logical unit * number (LUN 0). */ enum { SANDBOX_FLASH_EP_OUT = 1, /* endpoints */ SANDBOX_FLASH_EP_IN = 2, SANDBOX_FLASH_BLOCK_LEN = 512, }; enum cmd_phase { PHASE_START, PHASE_DATA, PHASE_STATUS, }; enum { STRINGID_MANUFACTURER = 1, STRINGID_PRODUCT, STRINGID_SERIAL, STRINGID_COUNT, }; /** * struct sandbox_flash_priv - private state for this driver * * @error: true if there is an error condition * @alloc_len: Allocation length from the last incoming command * @transfer_len: Transfer length from CBW header * @read_len: Number of blocks of data left in the current read command * @tag: Tag value from last command * @fd: File descriptor of backing file * @file_size: Size of file in bytes * @status_buff: Data buffer for outgoing status * @buff_used: Number of bytes ready to transfer back to host * @buff: Data buffer for outgoing data */ struct sandbox_flash_priv { bool error; int alloc_len; int transfer_len; int read_len; enum cmd_phase phase; u32 tag; int fd; loff_t file_size; struct umass_bbb_csw status; int buff_used; u8 buff[512]; }; struct sandbox_flash_plat { const char *pathname; struct usb_string flash_strings[STRINGID_COUNT]; }; struct scsi_inquiry_resp { u8 type; u8 flags; u8 version; u8 data_format; u8 additional_len; u8 spare[3]; char vendor[8]; char product[16]; char revision[4]; }; struct scsi_read_capacity_resp { u32 last_block_addr; u32 block_len; }; struct __packed scsi_read10_req { u8 cmd; u8 lun_flags; u32 lba; u8 spare; u16 transfer_len; u8 spare2[3]; }; static struct usb_device_descriptor flash_device_desc = { .bLength = sizeof(flash_device_desc), .bDescriptorType = USB_DT_DEVICE, .bcdUSB = __constant_cpu_to_le16(0x0200), .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .idVendor = __constant_cpu_to_le16(0x1234), .idProduct = __constant_cpu_to_le16(0x5678), .iManufacturer = STRINGID_MANUFACTURER, .iProduct = STRINGID_PRODUCT, .iSerialNumber = STRINGID_SERIAL, .bNumConfigurations = 1, }; static struct usb_config_descriptor flash_config0 = { .bLength = sizeof(flash_config0), .bDescriptorType = USB_DT_CONFIG, /* wTotalLength is set up by usb-emul-uclass */ .bNumInterfaces = 1, .bConfigurationValue = 0, .iConfiguration = 0, .bmAttributes = 1 << 7, .bMaxPower = 50, }; static struct usb_interface_descriptor flash_interface0 = { .bLength = sizeof(flash_interface0), .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 2, .bInterfaceClass = USB_CLASS_MASS_STORAGE, .bInterfaceSubClass = US_SC_UFI, .bInterfaceProtocol = US_PR_BULK, .iInterface = 0, }; static struct usb_endpoint_descriptor flash_endpoint0_out = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = SANDBOX_FLASH_EP_OUT, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = __constant_cpu_to_le16(1024), .bInterval = 0, }; static struct usb_endpoint_descriptor flash_endpoint1_in = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = SANDBOX_FLASH_EP_IN | USB_ENDPOINT_DIR_MASK, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = __constant_cpu_to_le16(1024), .bInterval = 0, }; static void *flash_desc_list[] = { &flash_device_desc, &flash_config0, &flash_interface0, &flash_endpoint0_out, &flash_endpoint1_in, NULL, }; static int sandbox_flash_control(struct udevice *dev, struct usb_device *udev, unsigned long pipe, void *buff, int len, struct devrequest *setup) { struct sandbox_flash_priv *priv = dev_get_priv(dev); if (pipe == usb_rcvctrlpipe(udev, 0)) { switch (setup->request) { case US_BBB_RESET: priv->error = false; return 0; case US_BBB_GET_MAX_LUN: *(char *)buff = '\0'; return 1; default: debug("request=%x\n", setup->request); break; } } debug("pipe=%lx\n", pipe); return -EIO; } static void setup_fail_response(struct sandbox_flash_priv *priv) { struct umass_bbb_csw *csw = &priv->status; csw->dCSWSignature = CSWSIGNATURE; csw->dCSWTag = priv->tag; csw->dCSWDataResidue = 0; csw->bCSWStatus = CSWSTATUS_FAILED; priv->buff_used = 0; } /** * setup_response() - set up a response to send back to the host * * @priv: Sandbox flash private data * @resp: Response to send, or NULL if none * @size: Size of response */ static void setup_response(struct sandbox_flash_priv *priv, void *resp, int size) { struct umass_bbb_csw *csw = &priv->status; csw->dCSWSignature = CSWSIGNATURE; csw->dCSWTag = priv->tag; csw->dCSWDataResidue = 0; csw->bCSWStatus = CSWSTATUS_GOOD; assert(!resp || resp == priv->buff); priv->buff_used = size; } static void handle_read(struct sandbox_flash_priv *priv, ulong lba, ulong transfer_len) { debug("%s: lba=%lx, transfer_len=%lx\n", __func__, lba, transfer_len); if (priv->fd != -1) { os_lseek(priv->fd, lba * SANDBOX_FLASH_BLOCK_LEN, OS_SEEK_SET); priv->read_len = transfer_len; setup_response(priv, priv->buff, transfer_len * SANDBOX_FLASH_BLOCK_LEN); } else { setup_fail_response(priv); } } static int handle_ufi_command(struct sandbox_flash_plat *plat, struct sandbox_flash_priv *priv, const void *buff, int len) { const struct SCSI_cmd_block *req = buff; switch (*req->cmd) { case SCSI_INQUIRY: { struct scsi_inquiry_resp *resp = (void *)priv->buff; priv->alloc_len = req->cmd[4]; memset(resp, '\0', sizeof(*resp)); resp->data_format = 1; resp->additional_len = 0x1f; strncpy(resp->vendor, plat->flash_strings[STRINGID_MANUFACTURER - 1].s, sizeof(resp->vendor)); strncpy(resp->product, plat->flash_strings[STRINGID_PRODUCT - 1].s, sizeof(resp->product)); strncpy(resp->revision, "1.0", sizeof(resp->revision)); setup_response(priv, resp, sizeof(*resp)); break; } case SCSI_TST_U_RDY: setup_response(priv, NULL, 0); break; case SCSI_RD_CAPAC: { struct scsi_read_capacity_resp *resp = (void *)priv->buff; uint blocks; if (priv->file_size) blocks = priv->file_size / SANDBOX_FLASH_BLOCK_LEN - 1; else blocks = 0; resp->last_block_addr = cpu_to_be32(blocks); resp->block_len = cpu_to_be32(SANDBOX_FLASH_BLOCK_LEN); setup_response(priv, resp, sizeof(*resp)); break; } case SCSI_READ10: { struct scsi_read10_req *req = (void *)buff; handle_read(priv, be32_to_cpu(req->lba), be16_to_cpu(req->transfer_len)); break; } default: debug("Command not supported: %x\n", req->cmd[0]); return -EPROTONOSUPPORT; } priv->phase = priv->transfer_len ? PHASE_DATA : PHASE_STATUS; return 0; } static int sandbox_flash_bulk(struct udevice *dev, struct usb_device *udev, unsigned long pipe, void *buff, int len) { struct sandbox_flash_plat *plat = dev_get_platdata(dev); struct sandbox_flash_priv *priv = dev_get_priv(dev); int ep = usb_pipeendpoint(pipe); struct umass_bbb_cbw *cbw = buff; debug("%s: dev=%s, pipe=%lx, ep=%x, len=%x, phase=%d\n", __func__, dev->name, pipe, ep, len, priv->phase); switch (ep) { case SANDBOX_FLASH_EP_OUT: switch (priv->phase) { case PHASE_START: priv->alloc_len = 0; priv->read_len = 0; if (priv->error || len != UMASS_BBB_CBW_SIZE || cbw->dCBWSignature != CBWSIGNATURE) goto err; if ((cbw->bCBWFlags & CBWFLAGS_SBZ) || cbw->bCBWLUN != 0) goto err; if (cbw->bCDBLength < 1 || cbw->bCDBLength >= 0x10) goto err; priv->transfer_len = cbw->dCBWDataTransferLength; priv->tag = cbw->dCBWTag; return handle_ufi_command(plat, priv, cbw->CBWCDB, cbw->bCDBLength); case PHASE_DATA: debug("data out\n"); break; default: break; } case SANDBOX_FLASH_EP_IN: switch (priv->phase) { case PHASE_DATA: debug("data in, len=%x, alloc_len=%x, priv->read_len=%x\n", len, priv->alloc_len, priv->read_len); if (priv->read_len) { ulong bytes_read; bytes_read = os_read(priv->fd, buff, len); if (bytes_read != len) return -EIO; priv->read_len -= len / SANDBOX_FLASH_BLOCK_LEN; if (!priv->read_len) priv->phase = PHASE_STATUS; } else { if (priv->alloc_len && len > priv->alloc_len) len = priv->alloc_len; memcpy(buff, priv->buff, len); priv->phase = PHASE_STATUS; } return len; case PHASE_STATUS: debug("status in, len=%x\n", len); if (len > sizeof(priv->status)) len = sizeof(priv->status); memcpy(buff, &priv->status, len); priv->phase = PHASE_START; return len; default: break; } } err: priv->error = true; debug("%s: Detected transfer error\n", __func__); return 0; } static int sandbox_flash_ofdata_to_platdata(struct udevice *dev) { struct sandbox_flash_plat *plat = dev_get_platdata(dev); const void *blob = gd->fdt_blob; plat->pathname = fdt_getprop(blob, dev->of_offset, "sandbox,filepath", NULL); return 0; } static int sandbox_flash_bind(struct udevice *dev) { struct sandbox_flash_plat *plat = dev_get_platdata(dev); struct usb_string *fs; fs = plat->flash_strings; fs[0].id = STRINGID_MANUFACTURER; fs[0].s = "sandbox"; fs[1].id = STRINGID_PRODUCT; fs[1].s = "flash"; fs[2].id = STRINGID_SERIAL; fs[2].s = dev->name; return usb_emul_setup_device(dev, PACKET_SIZE_64, plat->flash_strings, flash_desc_list); } static int sandbox_flash_probe(struct udevice *dev) { struct sandbox_flash_plat *plat = dev_get_platdata(dev); struct sandbox_flash_priv *priv = dev_get_priv(dev); priv->fd = os_open(plat->pathname, OS_O_RDONLY); if (priv->fd != -1) return os_get_filesize(plat->pathname, &priv->file_size); return 0; } static const struct dm_usb_ops sandbox_usb_flash_ops = { .control = sandbox_flash_control, .bulk = sandbox_flash_bulk, }; static const struct udevice_id sandbox_usb_flash_ids[] = { { .compatible = "sandbox,usb-flash" }, { } }; U_BOOT_DRIVER(usb_sandbox_flash) = { .name = "usb_sandbox_flash", .id = UCLASS_USB_EMUL, .of_match = sandbox_usb_flash_ids, .bind = sandbox_flash_bind, .probe = sandbox_flash_probe, .ofdata_to_platdata = sandbox_flash_ofdata_to_platdata, .ops = &sandbox_usb_flash_ops, .priv_auto_alloc_size = sizeof(struct sandbox_flash_priv), .platdata_auto_alloc_size = sizeof(struct sandbox_flash_plat), };