mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-19 11:18:28 +00:00
c4876320db
We would like the serial number to come from the device tree node name of the emulated device. This avoids them all having the same name. Adjust the code to support this. Signed-off-by: Simon Glass <sjg@chromium.org>
431 lines
10 KiB
C
431 lines
10 KiB
C
/*
|
|
* (C) Copyright 2015 Google, Inc
|
|
* Written by Simon Glass <sjg@chromium.org>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <os.h>
|
|
#include <scsi.h>
|
|
#include <usb.h>
|
|
|
|
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),
|
|
};
|