mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-11 15:37:23 +00:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
425 lines
10 KiB
C
425 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2015 Google, Inc
|
|
* Written by Simon Glass <sjg@chromium.org>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <os.h>
|
|
#include <scsi.h>
|
|
#include <usb.h>
|
|
|
|
/*
|
|
* 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 *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);
|
|
|
|
plat->pathname = dev_read_string(dev, "sandbox,filepath");
|
|
|
|
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, 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),
|
|
};
|