1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/usb/usbus/msc/msc.c

459 lines
15 KiB
C
Raw Normal View History

/*
* Copyright (C) 2019-2021 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup usbus_msc
* @{
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
* @}
*/
#include "usb/descriptor.h"
#include "usb/usbus.h"
#include "usb/usbus/control.h"
#include "usb/msc.h"
#include "usb/usbus/msc.h"
#include "usb/usbus/msc/scsi.h"
#include "board.h"
#include <string.h>
#include <errno.h>
#define ENABLE_DEBUG 0
#include "debug.h"
#include "mtd.h"
static unsigned char _ep_out_buf[CONFIG_USBUS_EP0_SIZE];
static unsigned char _ep_in_buf[CONFIG_USBUS_EP0_SIZE];
/* Internal handler definitions */
static void _event_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_event_usb_t event);
static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_control_request_state_t state,
usb_setup_t *setup);
static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, usbus_event_transfer_t event);
static void _init(usbus_t *usbus, usbus_handler_t *handler);
static const usbus_handler_driver_t msc_driver = {
.init = _init,
.event_handler = _event_handler,
.control_handler = _control_handler,
.transfer_handler = _transfer_handler,
};
static const usbus_descr_gen_funcs_t _msc_descriptor = {
.fmt_post_descriptor = NULL,
.fmt_pre_descriptor = NULL,
.len = {
.fixed_len = 0,
},
.len_type = USBUS_DESCR_LEN_FIXED,
};
static void _write_xfer(usbus_msc_device_t *msc)
{
uint8_t lun = msc->cmd.lun;
uint16_t block_size = msc->lun_dev[lun].block_size;
uint32_t sector_count;
mtd_dev_t *mtd = msc->lun_dev[lun].mtd;
size_t len;
int ret;
/* Check if we have a block to read and transfer */
if (!msc->block_nb) {
return;
}
/* Retrieve incoming data */
usbdev_ep_get(msc->ep_out->ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t));
/* Update offset for page buffer */
msc->block_offset += len;
/* Decrement whole len */
msc->cmd.len -= len;
/* buffer is full, write it and point to new block if any */
if (msc->block_offset >= block_size) {
/* Specific case for MTD devices with sector size < 512 bytes
Since USB report 512 bytes per sector, we need to erase
several sectors at the same time */
if (mtd->page_size * mtd->pages_per_sector < 512) {
sector_count = (512 / (mtd->page_size * mtd->pages_per_sector));
} else {
sector_count = 1;
}
/* Write one or multiple sectors */
ret = mtd_write_page(mtd, msc->buffer,
msc->block * sector_count * mtd->pages_per_sector,
0, block_size);
if (ret != 0) {
/* Failure occurs during write operation, stall the operation */
DEBUG("[msc]: Write fail with error: %d\n", ret);
/* Stall IN bulk endpoint and signal error to host through
* CSW command with command failed status */
static const usbopt_enable_t enable = USBOPT_ENABLE;
usbdev_ep_set(msc->ep_in->ep, USBOPT_EP_STALL, &enable,
sizeof(usbopt_enable_t));
msc->state = GEN_CSW;
msc->cmd.status = USB_MSC_CSW_STATUS_COMMAND_FAILED;
}
msc->block_offset = 0;
msc->block++;
msc->block_nb--;
}
if (msc->cmd.len == 0) {
/* All blocks have been transferred, send CSW to host */
if (msc->state == DATA_TRANSFER_OUT) {
msc->state = GEN_CSW;
/* Data was processed, ready next transfer */
usbdev_ep_xmit(msc->ep_out->ep, msc->out_buf, CONFIG_USBUS_EP0_SIZE);
return;
}
}
if (len > 0) {
/* Directly put data incoming on the endpoint to the flashpage buffer */
usbdev_ep_xmit(msc->ep_out->ep, &msc->buffer[msc->block_offset], len);
}
}
static void _read_xfer(usbus_msc_device_t *msc)
{
uint8_t lun = msc->cmd.lun;
mtd_dev_t *mtd = msc->lun_dev[lun].mtd;
uint32_t block_size = msc->lun_dev[lun].block_size;
int ret;
/* Check if we have a block to read and transfer */
if (msc->block_nb) {
/* read buffer from mtd device */
if (msc->block_offset == 0) {
ret = mtd_read(mtd, msc->buffer, msc->block * block_size, block_size);
if (ret != 0) {
DEBUG("[msc]: Read operation failed with error:%d\n", ret);
/* Stall the current operation, and signal it to host */
static const usbopt_enable_t enable = USBOPT_ENABLE;
usbdev_ep_set(msc->ep_in->ep, USBOPT_EP_STALL, &enable,
sizeof(usbopt_enable_t));
msc->state = GEN_CSW;
msc->cmd.status = USB_MSC_CSW_STATUS_COMMAND_FAILED;
}
}
/* Data prepared, signal ready to usbus */
usbdev_ep_xmit(msc->ep_in->ep, &msc->buffer[msc->block_offset],
msc->ep_in->ep->len);
/* Update offset for page buffer */
msc->block_offset += msc->ep_in->ep->len;
/* Decrement whole len */
msc->cmd.len -= msc->ep_in->ep->len;
/* whole buffer is empty, point to new block if any */
if (msc->block_offset >= block_size) {
msc->block_offset = 0;
msc->block++;
msc->block_nb--;
}
}
else {
/* All blocks have been transferred, send CSW to host */
if (msc->state == DATA_TRANSFER_IN) {
msc->state = GEN_CSW;
}
}
}
static void _handle_rx_event(event_t *ev)
{
usbus_msc_device_t *msc = container_of(ev, usbus_msc_device_t, rx_event);
_read_xfer(msc);
}
static usbus_msc_device_t *_get_msc_handler(usbus_t *usbus)
{
for (usbus_interface_t *iface = usbus->iface; iface; iface = iface->next) {
if (iface->class == USB_CLASS_MASS_STORAGE) {
return (usbus_msc_device_t *)iface->handler;
}
}
return NULL;
}
static unsigned _get_lun(usbus_msc_device_t *msc)
{
unsigned count = 0;
/* Count only registered MTD devices as USB LUN, (using usbus_msc_add_lun)
* not every MTD devices available on board */
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
if (msc->lun_dev[i].mtd != NULL) {
count++;
}
}
return count;
}
int usbus_msc_add_lun(usbus_t *usbus, mtd_dev_t *dev)
{
uint32_t block_size;
/* Loop through all interfaces to find the MSC one */
usbus_msc_device_t *msc = _get_msc_handler(usbus);
if (!dev) {
return -ENODEV;
}
/* Check if MTD isn't already registered */
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
if (dev == msc->lun_dev[i].mtd) {
return -EBUSY;
}
}
/* Store new MTD device in first slot available
Also re alloc internal buffer if needed */
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
if (msc->lun_dev[i].mtd == NULL) {
int ret = mtd_init(dev);
if (ret != 0) {
DEBUG_PUTS("[msc]: cannot initialize MTD device");
return -ENODEV;
}
block_size = dev->page_size * dev->pages_per_sector;
/* Ensure MTD device can be exported */
if (block_size * dev->sector_count < 512) {
DEBUG("page size:%" PRIu32 " pages per sector:%" PRIu32 " \
sector_count:%" PRIu32 "\n", dev->page_size,
dev->pages_per_sector, dev->sector_count);
return -ENOMEM;
}
if (msc->buffer != NULL) {
/* If new registered MTD device need more memory than the
previous, realloc a new buffer */
if (block_size > msc->buffer_size) {
msc->buffer = realloc(msc->buffer, block_size);
}
} else {
/* No buffer allocated yet, so allocate one */
msc->buffer = malloc(block_size);
}
if (!msc->buffer) {
DEBUG_PUTS("[msc]: Failed to realloc new buffer\n");
return -ENOMEM;
}
msc->buffer_size = block_size;
msc->lun_dev[i].mtd = dev;
return 0;
}
}
/* No more slots available */
return -ENFILE;
}
int usbus_msc_remove_lun(usbus_t *usbus, mtd_dev_t *dev)
{
/* Loop through all interfaces to find the MSC one */
usbus_msc_device_t *msc = _get_msc_handler(usbus);
/* Identify the LUN to unexport */
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
if (dev == msc->lun_dev[i].mtd) {
/* Wait for any pending transaction to end */
/* Clear relevant information */
msc->lun_dev[i].mtd = NULL;
msc->lun_dev[i].block_size = 0;
msc->lun_dev[i].block_nb = 0;
return 0;
}
}
/* No more slots available */
free(msc->buffer);
msc->buffer = NULL;
return -ENODEV;
}
int usbus_msc_init(usbus_t *usbus, usbus_msc_device_t *handler)
{
assert(usbus);
assert(handler);
memset(handler, 0, sizeof(usbus_msc_device_t));
handler->usbus = usbus;
handler->handler_ctrl.driver = &msc_driver;
usbus_register_event_handler(usbus, (usbus_handler_t *)handler);
return 0;
}
static void _init(usbus_t *usbus, usbus_handler_t *handler)
{
DEBUG_PUTS("[msc]: initialization\n");
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
msc->msc_descr.next = NULL;
msc->msc_descr.funcs = &_msc_descriptor;
msc->msc_descr.arg = msc;
msc->block_offset = 0;
/* Buffer will be initialized later */
msc->buffer = NULL;
msc->buffer_size = 0;
msc->block_nb = 0;
msc->block = 0;
msc->state = WAITING;
for (int i = 0; i < USBUS_MSC_EXPORTED_NUMOF; i++) {
msc->lun_dev[i].mtd = NULL;
msc->lun_dev[i].block_size = 0;
msc->lun_dev[i].block_nb = 0;
}
/* Assign internal endpoint buffers */
msc->out_buf = _ep_out_buf;
msc->in_buf = _ep_in_buf;
/* Add event handlers */
msc->rx_event.handler = _handle_rx_event;
/* Instantiate interfaces */
memset(&msc->iface, 0, sizeof(usbus_interface_t));
/* Configure Interface 0 as control interface */
msc->iface.class = USB_CLASS_MASS_STORAGE;
msc->iface.subclass = USB_MSC_SUBCLASS_SCSI_TCS;
msc->iface.protocol = USB_MSC_PROTOCOL_BBB;
msc->iface.descr_gen = &(msc->msc_descr);
msc->iface.handler = handler;
/* Create required endpoints */
msc->ep_in = usbus_add_endpoint(usbus, &msc->iface, USB_EP_TYPE_BULK,
USB_EP_DIR_IN, CONFIG_USBUS_EP0_SIZE);
msc->ep_in->interval = 0;
msc->ep_out = usbus_add_endpoint(usbus, &msc->iface, USB_EP_TYPE_BULK,
USB_EP_DIR_OUT, CONFIG_USBUS_EP0_SIZE);
msc->ep_out->interval = 0;
/* Add interfaces to the stack */
usbus_add_interface(usbus, &msc->iface);
usbus_enable_endpoint(msc->ep_in);
usbus_enable_endpoint(msc->ep_out);
usbus_handler_set_flag(handler, USBUS_HANDLER_FLAG_RESET);
/* Prepare to receive first bytes from Host */
usbdev_ep_xmit(msc->ep_out->ep, msc->out_buf, CONFIG_USBUS_EP0_SIZE);
}
static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_control_request_state_t state,
usb_setup_t *setup)
{
(void)usbus;
(void)state;
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
uint8_t data;
switch (setup->request) {
case USB_MSC_SETUP_REQ_GML:
data = _get_lun(msc);
if (data > 0) {
data -= 1;
}
/* Return the number of MTD devices available on the board */
usbus_control_slicer_put_bytes(usbus, &data, sizeof(data));
usbus_control_slicer_ready(usbus);
break;
case USB_MSC_SETUP_REQ_BOMSR:
DEBUG_PUTS("[msc]: TODO: implement reset setup request");
break;
default:
DEBUG("[msc]: unsupported handle setup request:0x%x\n", setup->request);
return -1;
}
return 1;
}
static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, usbus_event_transfer_t event)
{
(void)usbus;
(void)event;
usbus_msc_device_t *msc = container_of(handler, usbus_msc_device_t,
handler_ctrl);
if (ep->dir == USB_EP_DIR_OUT) {
size_t len = 0;
/* Previous transfer is sent, send the next one */
if (msc->state == DATA_TRANSFER_OUT) {
_write_xfer(msc);
}
else {
/* Retrieve incoming data */
usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t));
if (len > 0) {
/* Process incoming endpoint buffer */
usbus_msc_scsi_process_cmd(usbus, handler, ep, len);
}
/* Data was processed, ready next transfer */
if (msc->state == DATA_TRANSFER_OUT) {
/* If the next pkt is supposed to be a WRITE10 transfer,
directly redirect content to appropriate buffer */
usbdev_ep_xmit(msc->ep_out->ep, &msc->buffer[msc->block_offset], 64);
}
else {
usbdev_ep_xmit(ep, msc->out_buf, CONFIG_USBUS_EP0_SIZE);
}
}
}
else {
/* Data Transfer pending */
if (msc->state == DATA_TRANSFER_IN) {
_read_xfer(msc);
}
/* CBW answer was sent, prepare CSW */
else if (msc->state == WAIT_FOR_TRANSFER) {
msc->state = GEN_CSW;
}
}
if (msc->cmd.tag && msc->cmd.len == 0 && msc->state == GEN_CSW) {
DEBUG_PUTS("[msc]: Generate CSW");
scsi_gen_csw(handler, &(msc->cmd));
/* Command has been handled, so clear tag and state machine */
msc->cmd.tag = 0;
msc->state = WAITING;
}
}
static void _event_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_event_usb_t event)
{
(void)usbus;
(void)handler;
switch (event) {
case USBUS_EVENT_USB_RESET:
DEBUG_PUTS("[msc]: host reset event");
break;
default:
DEBUG("[msc]: Unhandled event :0x%x\n", event);
break;
}
}