Add mass storage

This commit is contained in:
Willy-JL
2023-06-14 00:55:44 +01:00
parent 93bb30810b
commit eb1e81621c
15 changed files with 1259 additions and 0 deletions
@@ -0,0 +1,222 @@
#include "mass_storage_scsi.h"
#include <furi/log.h>
#define TAG "MassStorageSCSI"
#define SCSI_TEST_UNIT_READY (0x00)
#define SCSI_REQUEST_SENSE (0x03)
#define SCSI_INQUIRY (0x12)
#define SCSI_READ_CAPACITY_6 (0x25)
#define SCSI_MODE_SENSE_6 (0x1A)
#define SCSI_READ_10 (0x28)
#define SCSI_PREVENT_MEDIUM_REMOVAL (0x1E)
#define SCSI_START_STOP_UNIT (0x1B)
#define SCSI_WRITE_10 (0x2A)
bool scsi_cmd_start(SCSISession* scsi, uint8_t* cmd, uint8_t len) {
if(!len) {
scsi->sk = SCSI_SK_ILLEGAL_REQUEST;
scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
return false;
}
// FURI_LOG_I(TAG, "START %02x", cmd[0]);
scsi->cmd = cmd;
scsi->cmd_len = len;
scsi->rx_done = false;
scsi->tx_done = false;
switch(cmd[0]) {
case SCSI_WRITE_10: {
if(len < 10) return false;
scsi->write_10.lba = cmd[2] << 24 | cmd[3] << 16 | cmd[4] << 8 | cmd[5];
scsi->write_10.count = cmd[7] << 8 | cmd[8];
FURI_LOG_I(TAG, "SCSI_WRITE_10 %08lx %04x", scsi->write_10.lba, scsi->write_10.count);
return true;
}; break;
case SCSI_READ_10: {
if(len < 10) return false;
scsi->read_10.lba = cmd[2] << 24 | cmd[3] << 16 | cmd[4] << 8 | cmd[5];
scsi->read_10.count = cmd[7] << 8 | cmd[8];
FURI_LOG_I(TAG, "SCSI_READ_10 %08lx %04x", scsi->read_10.lba, scsi->read_10.count);
return true;
}; break;
}
return true;
}
bool scsi_cmd_rx_data(SCSISession* scsi, uint8_t* data, uint32_t len) {
// FURI_LOG_I(TAG, "RX %02x len %d", scsi->cmd[0], len);
if(scsi->rx_done) return false;
switch(scsi->cmd[0]) {
case SCSI_WRITE_10: {
uint32_t block_size = SCSI_BLOCK_SIZE;
uint16_t blocks = len / block_size;
bool result =
scsi->fn.write(scsi->fn.ctx, scsi->write_10.lba, blocks, data, blocks * block_size);
scsi->write_10.lba += blocks;
scsi->write_10.count -= blocks;
if(!scsi->write_10.count) {
scsi->rx_done = true;
}
return result;
}; break;
default: {
FURI_LOG_W(TAG, "unexpected scsi rx data cmd=%02x", scsi->cmd[0]);
scsi->sk = SCSI_SK_ILLEGAL_REQUEST;
scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
return false;
}; break;
}
}
bool scsi_cmd_tx_data(SCSISession* scsi, uint8_t* data, uint32_t* len, uint32_t cap) {
// FURI_LOG_I(TAG, "TX %02x cap %d", scsi->cmd[0], cap);
if(scsi->tx_done) return false;
switch(scsi->cmd[0]) {
case SCSI_REQUEST_SENSE: {
FURI_LOG_I(TAG, "SCSI_REQUEST_SENSE");
if(cap < 18) return false;
memset(data, 0, cap);
data[0] = 0x70; // fixed format sense data
data[1] = 0; // obsolete
data[2] = scsi->sk; // sense key
data[3] = 0; // information
data[4] = 0; // information
data[5] = 0; // information
data[6] = 0; // information
data[7] = 10; // additional sense length (len-8)
data[8] = 0; // command specific information
data[9] = 0; // command specific information
data[10] = 0; // command specific information
data[11] = 0; // command specific information
data[12] = scsi->asc; // additional sense code
data[13] = 0; // additional sense code qualifier
data[14] = 0; // field replaceable unit code
data[15] = 0; // sense key specific information
data[16] = 0; // sense key specific information
data[17] = 0; // sense key specific information
*len = 18;
scsi->sk = 0;
scsi->asc = 0;
scsi->tx_done = true;
return true;
}; break;
case SCSI_INQUIRY: {
FURI_LOG_I(TAG, "SCSI_INQUIRY");
if(scsi->cmd_len < 5) return false;
if(cap < 36) return false;
bool evpd = scsi->cmd[1] & 1;
uint8_t page_code = scsi->cmd[2];
// uint16_t alloc_len = scsi->cmd[3] << 8 | scsi->cmd[4];
if(evpd) return false;
if(page_code) return false;
data[0] = 0x00; // device type: direct access block device
data[1] = 0x80; // removable: true
data[2] = 0x04; // version
data[3] = 0x02; // response data format
data[4] = 31; // additional length (len - 5)
data[5] = 0; // flags
data[6] = 0; // flags
data[7] = 0; // flags
memcpy(data + 8, "Flipper ", 8); // vendor id
memcpy(data + 16, "Mass Storage ", 16); // product id
memcpy(data + 32, "0001", 4); // product revision level
*len = 36;
scsi->tx_done = true;
return true;
}; break;
case SCSI_READ_CAPACITY_6: {
FURI_LOG_I(TAG, "SCSI_READ_CAPACITY_6");
if(cap < 8) return false;
uint32_t n_blocks = scsi->fn.num_blocks(scsi->fn.ctx);
uint32_t block_size = SCSI_BLOCK_SIZE;
data[0] = (n_blocks - 1) >> 24;
data[1] = (n_blocks - 1) >> 16;
data[2] = (n_blocks - 1) >> 8;
data[3] = (n_blocks - 1);
data[4] = block_size >> 24;
data[5] = block_size >> 16;
data[6] = block_size >> 8;
data[7] = block_size;
*len = 8;
scsi->tx_done = true;
return true;
}; break;
case SCSI_MODE_SENSE_6: {
FURI_LOG_I(TAG, "SCSI_MODE_SENSE_6 %ld", cap);
if(cap < 4) return false;
data[0] = 3; // mode data length (len - 1)
data[1] = 0; // medium type
data[2] = 0; // device-specific parameter
data[3] = 0; // block descriptor length
*len = 4;
scsi->tx_done = true;
return true;
}; break;
case SCSI_READ_10: {
uint32_t block_size = SCSI_BLOCK_SIZE;
bool result =
scsi->fn.read(scsi->fn.ctx, scsi->read_10.lba, scsi->read_10.count, data, len, cap);
*len -= *len % block_size;
uint16_t blocks = *len / block_size;
scsi->read_10.lba += blocks;
scsi->read_10.count -= blocks;
if(!scsi->read_10.count) {
scsi->tx_done = true;
}
return result;
}; break;
default: {
FURI_LOG_W(TAG, "unexpected scsi tx data cmd=%02x", scsi->cmd[0]);
scsi->sk = SCSI_SK_ILLEGAL_REQUEST;
scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
return false;
}; break;
}
}
bool scsi_cmd_end(SCSISession* scsi) {
// FURI_LOG_I(TAG, "END %02x", scsi->cmd[0]);
uint8_t* cmd = scsi->cmd;
uint8_t len = scsi->cmd_len;
scsi->cmd = NULL;
scsi->cmd_len = 0;
switch(cmd[0]) {
case SCSI_WRITE_10:
return scsi->rx_done;
case SCSI_REQUEST_SENSE:
case SCSI_INQUIRY:
case SCSI_READ_CAPACITY_6:
case SCSI_MODE_SENSE_6:
case SCSI_READ_10:
return scsi->tx_done;
case SCSI_TEST_UNIT_READY: {
FURI_LOG_I(TAG, "SCSI_TEST_UNIT_READY");
return true;
}; break;
case SCSI_PREVENT_MEDIUM_REMOVAL: {
if(len < 6) return false;
bool prevent = cmd[5];
FURI_LOG_I(TAG, "SCSI_PREVENT_MEDIUM_REMOVAL prevent=%d", prevent);
return !prevent;
}; break;
case SCSI_START_STOP_UNIT: {
if(len < 6) return false;
bool eject = (cmd[4] & 2) != 0;
bool start = (cmd[4] & 1) != 0;
FURI_LOG_I(TAG, "SCSI_START_STOP_UNIT eject=%d start=%d", eject, start);
if(eject) {
scsi->fn.eject(scsi->fn.ctx);
}
return true;
}; break;
default: {
FURI_LOG_W(TAG, "unexpected scsi cmd=%02x", cmd[0]);
scsi->sk = SCSI_SK_ILLEGAL_REQUEST;
scsi->asc = SCSI_ASC_INVALID_COMMAND_OPERATION_CODE;
return false;
}; break;
}
}
@@ -0,0 +1,56 @@
#pragma once
#include <furi.h>
#define SCSI_BLOCK_SIZE (0x200)
#define SCSI_SK_ILLEGAL_REQUEST (5)
#define SCSI_ASC_INVALID_COMMAND_OPERATION_CODE (0x20)
#define SCSI_ASC_LBA_OOB (0x21)
#define SCSI_ASC_INVALID_FIELD_IN_CDB (0x24)
typedef struct {
void* ctx;
bool (*read)(
void* ctx,
uint32_t lba,
uint16_t count,
uint8_t* out,
uint32_t* out_len,
uint32_t out_cap);
bool (*write)(void* ctx, uint32_t lba, uint16_t count, uint8_t* buf, uint32_t len);
uint32_t (*num_blocks)(void* ctx);
void (*eject)(void* ctx);
} SCSIDeviceFunc;
typedef struct {
SCSIDeviceFunc fn;
uint8_t* cmd;
uint8_t cmd_len;
bool rx_done;
bool tx_done;
uint8_t sk; // sense key
uint8_t asc; // additional sense code
// command-specific data
// valid from cmd_start to cmd_end
union {
struct {
uint16_t count;
uint32_t lba;
} read_10; // SCSI_READ_10
struct {
uint16_t count;
uint32_t lba;
} write_10; // SCSI_WRITE_10
};
} SCSISession;
bool scsi_cmd_start(SCSISession* scsi, uint8_t* cmd, uint8_t len);
bool scsi_cmd_rx_data(SCSISession* scsi, uint8_t* data, uint32_t len);
bool scsi_cmd_tx_data(SCSISession* scsi, uint8_t* data, uint32_t* len, uint32_t cap);
bool scsi_cmd_end(SCSISession* scsi);
@@ -0,0 +1,468 @@
#include "mass_storage_usb.h"
#include <furi_hal.h>
#define TAG "MassStorageUsb"
#define USB_MSC_RX_EP (0x01)
#define USB_MSC_TX_EP (0x82)
#define USB_MSC_RX_EP_SIZE (64)
#define USB_MSC_TX_EP_SIZE (64)
#define USB_MSC_BOT_GET_MAX_LUN (0xFE)
#define USB_MSC_BOT_RESET (0xFF)
#define CBW_SIG (0x43425355)
#define CBW_FLAGS_DEVICE_TO_HOST (0x80)
#define CSW_SIG (0x53425355)
#define CSW_STATUS_OK (0)
#define CSW_STATUS_NOK (1)
#define CSW_STATUS_PHASE_ERROR (2)
// must be SCSI_BLOCK_SIZE aligned
// larger than 0x10000 exceeds size_t, storage_file_* ops fail
#define USB_MSC_BUF_MAX (0x10000 - SCSI_BLOCK_SIZE)
static usbd_respond usb_ep_config(usbd_device* dev, uint8_t cfg);
static usbd_respond usb_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback);
typedef enum {
EventExit = 1 << 0,
EventReset = 1 << 1,
EventRxTx = 1 << 2,
EventAll = EventExit | EventReset | EventRxTx,
} MassStorageEvent;
typedef struct {
uint32_t sig;
uint32_t tag;
uint32_t len;
uint8_t flags;
uint8_t lun;
uint8_t cmd_len;
uint8_t cmd[16];
} __attribute__((packed)) CBW;
typedef struct {
uint32_t sig;
uint32_t tag;
uint32_t residue;
uint8_t status;
} __attribute__((packed)) CSW;
struct MassStorageUsb {
FuriHalUsbInterface usb;
FuriHalUsbInterface* usb_prev;
FuriThread* thread;
usbd_device* dev;
SCSIDeviceFunc fn;
};
static int32_t mass_thread_worker(void* context) {
MassStorageUsb* mass = context;
usbd_device* dev = mass->dev;
SCSISession scsi = {
.fn = mass->fn,
};
CBW cbw = {0};
CSW csw = {0};
uint8_t* buf = NULL;
uint32_t buf_len = 0, buf_cap = 0, buf_sent = 0;
enum {
StateReadCBW,
StateReadData,
StateWriteData,
StateBuildCSW,
StateWriteCSW,
} state = StateReadCBW;
while(true) {
uint32_t flags = osThreadFlagsWait(EventAll, osFlagsWaitAny, osWaitForever);
if(flags & EventExit) {
FURI_LOG_I(TAG, "exit");
free(buf);
return 0;
}
if(flags & EventReset) {
FURI_LOG_I(TAG, "reset");
scsi.sk = 0;
scsi.asc = 0;
memset(&cbw, 0, sizeof(cbw));
memset(&csw, 0, sizeof(csw));
free(buf);
buf = NULL;
buf_len = buf_cap = buf_sent = 0;
state = StateReadCBW;
}
if(flags & EventRxTx) do {
switch(state) {
case StateReadCBW: {
// FURI_LOG_I(TAG, "StateReadCBW");
int32_t len = usbd_ep_read(dev, USB_MSC_RX_EP, &cbw, sizeof(cbw));
if(len <= 0) {
// FURI_LOG_I(TAG, "cbw not ready");
break;
}
if(len != sizeof(cbw) || cbw.sig != CBW_SIG) {
FURI_LOG_W(TAG, "bad cbw sig=%08lx", cbw.sig);
usbd_ep_stall(dev, USB_MSC_TX_EP);
usbd_ep_stall(dev, USB_MSC_RX_EP);
continue;
}
if(!scsi_cmd_start(&scsi, cbw.cmd, cbw.cmd_len)) {
FURI_LOG_W(TAG, "bad cmd");
usbd_ep_stall(dev, USB_MSC_RX_EP);
csw.sig = CSW_SIG;
csw.tag = cbw.tag;
csw.status = CSW_STATUS_NOK;
state = StateWriteCSW;
continue;
}
if(cbw.flags & CBW_FLAGS_DEVICE_TO_HOST) {
buf_len = 0;
buf_sent = 0;
state = StateWriteData;
} else {
buf_len = 0;
state = StateReadData;
}
continue;
}; break;
case StateReadData: {
// FURI_LOG_I(TAG, "StateReadData %d/%d", buf_len, cbw.len);
if(!cbw.len) {
state = StateBuildCSW;
continue;
}
uint32_t buf_clamp = MIN(cbw.len, USB_MSC_BUF_MAX);
if(buf_clamp > buf_cap) {
// FURI_LOG_I(TAG, "growing buf %d -> %d", buf_cap, buf_clamp);
free(buf);
buf_cap = buf_clamp;
buf = malloc(buf_cap);
}
if(buf_len < buf_clamp) {
int32_t len =
usbd_ep_read(dev, USB_MSC_RX_EP, buf + buf_len, buf_clamp - buf_len);
if(len < 0) {
// FURI_LOG_I(TAG, "rx not ready %d", len);
break;
}
// FURI_LOG_I(TAG, "clamp %ld len %d", buf_clamp, len);
buf_len += len;
}
if(buf_len == buf_clamp) {
if(!scsi_cmd_rx_data(&scsi, buf, buf_len)) {
FURI_LOG_W(TAG, "short rx");
usbd_ep_stall(dev, USB_MSC_RX_EP);
csw.sig = CSW_SIG;
csw.tag = cbw.tag;
csw.status = CSW_STATUS_NOK;
csw.residue = cbw.len;
state = StateWriteCSW;
continue;
}
cbw.len -= buf_len;
buf_len = 0;
}
continue;
}; break;
case StateWriteData: {
// FURI_LOG_I(TAG, "StateWriteData %d", cbw.len);
if(!cbw.len) {
state = StateBuildCSW;
continue;
}
uint32_t buf_clamp = MIN(cbw.len, USB_MSC_BUF_MAX);
if(buf_clamp > buf_cap) {
// FURI_LOG_I(TAG, "growing buf %d -> %d", buf_cap, buf_clamp);
free(buf);
buf_cap = buf_clamp;
buf = malloc(buf_cap);
}
if(!buf_len && !scsi_cmd_tx_data(&scsi, buf, &buf_len, buf_clamp)) {
FURI_LOG_W(TAG, "short tx");
// usbd_ep_stall(dev, USB_MSC_TX_EP);
state = StateBuildCSW;
continue;
}
int32_t len = usbd_ep_write(
dev,
USB_MSC_TX_EP,
buf + buf_sent,
MIN(USB_MSC_TX_EP_SIZE, buf_len - buf_sent));
if(len < 0) {
// FURI_LOG_I(TAG, "tx not ready %d", len);
break;
}
buf_sent += len;
if(buf_sent == buf_len) {
cbw.len -= buf_len;
buf_len = 0;
buf_sent = 0;
}
continue;
}; break;
case StateBuildCSW: {
// FURI_LOG_I(TAG, "StateBuildCSW");
csw.sig = CSW_SIG;
csw.tag = cbw.tag;
if(scsi_cmd_end(&scsi)) {
csw.status = CSW_STATUS_OK;
} else {
csw.status = CSW_STATUS_NOK;
}
csw.residue = cbw.len;
state = StateWriteCSW;
continue;
}; break;
case StateWriteCSW: {
// FURI_LOG_I(TAG, "StateWriteCSW");
if(csw.status) {
FURI_LOG_W(
TAG,
"csw sig=%08lx tag=%08lx residue=%08lx status=%02x",
csw.sig,
csw.tag,
csw.residue,
csw.status);
}
int32_t len = usbd_ep_write(dev, USB_MSC_TX_EP, &csw, sizeof(csw));
if(len < 0) {
// FURI_LOG_I(TAG, "csw not ready");
break;
}
if(len != sizeof(csw)) {
FURI_LOG_W(TAG, "bad csw write %d", len);
usbd_ep_stall(dev, USB_MSC_TX_EP);
break;
}
memset(&cbw, 0, sizeof(cbw));
memset(&csw, 0, sizeof(csw));
state = StateReadCBW;
continue;
}; break;
}
break;
} while(true);
}
}
// needed in usb_deinit, usb_suspend, usb_rxtx_ep_callback, usb_control,
// where if_ctx isn't passed
static MassStorageUsb* mass_cur = NULL;
static void usb_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) {
MassStorageUsb* mass = ctx;
mass_cur = mass;
mass->dev = dev;
usbd_reg_config(dev, usb_ep_config);
usbd_reg_control(dev, usb_control);
usbd_connect(dev, true);
mass->thread = furi_thread_alloc();
furi_thread_set_name(mass->thread, "MassStorageUsb");
furi_thread_set_stack_size(mass->thread, 1024);
furi_thread_set_context(mass->thread, ctx);
furi_thread_set_callback(mass->thread, mass_thread_worker);
furi_thread_start(mass->thread);
}
static void usb_deinit(usbd_device* dev) {
usbd_reg_config(dev, NULL);
usbd_reg_control(dev, NULL);
MassStorageUsb* mass = mass_cur;
if(!mass || mass->dev != dev) {
FURI_LOG_E(TAG, "deinit mass_cur leak");
return;
}
mass_cur = NULL;
furi_assert(mass->thread);
osThreadFlagsSet(furi_thread_get_thread_id(mass->thread), EventExit);
furi_thread_join(mass->thread);
furi_thread_free(mass->thread);
mass->thread = NULL;
free(mass->usb.str_prod_descr);
mass->usb.str_prod_descr = NULL;
free(mass->usb.str_serial_descr);
mass->usb.str_serial_descr = NULL;
free(mass);
}
static void usb_wakeup(usbd_device* dev) {
}
static void usb_suspend(usbd_device* dev) {
MassStorageUsb* mass = mass_cur;
if(!mass || mass->dev != dev) return;
osThreadFlagsSet(furi_thread_get_thread_id(mass->thread), EventReset);
}
static void usb_rxtx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) {
MassStorageUsb* mass = mass_cur;
if(!mass || mass->dev != dev) return;
osThreadFlagsSet(furi_thread_get_thread_id(mass->thread), EventRxTx);
}
static usbd_respond usb_ep_config(usbd_device* dev, uint8_t cfg) {
switch(cfg) {
case 0: // deconfig
usbd_ep_deconfig(dev, USB_MSC_RX_EP);
usbd_ep_deconfig(dev, USB_MSC_TX_EP);
usbd_reg_endpoint(dev, USB_MSC_RX_EP, NULL);
usbd_reg_endpoint(dev, USB_MSC_TX_EP, NULL);
return usbd_ack;
case 1: // config
usbd_ep_config(
dev, USB_MSC_RX_EP, USB_EPTYPE_BULK /* | USB_EPTYPE_DBLBUF*/, USB_MSC_RX_EP_SIZE);
usbd_ep_config(
dev, USB_MSC_TX_EP, USB_EPTYPE_BULK /* | USB_EPTYPE_DBLBUF*/, USB_MSC_TX_EP_SIZE);
usbd_reg_endpoint(dev, USB_MSC_RX_EP, usb_rxtx_ep_callback);
usbd_reg_endpoint(dev, USB_MSC_TX_EP, usb_rxtx_ep_callback);
return usbd_ack;
}
return usbd_fail;
}
static usbd_respond usb_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_callback* callback) {
if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) !=
(USB_REQ_INTERFACE | USB_REQ_CLASS)) {
return usbd_fail;
}
switch(req->bRequest) {
case USB_MSC_BOT_GET_MAX_LUN: {
static uint8_t max_lun = 0;
dev->status.data_ptr = &max_lun;
dev->status.data_count = 1;
return usbd_ack;
}; break;
case USB_MSC_BOT_RESET: {
MassStorageUsb* mass = mass_cur;
if(!mass || mass->dev != dev) return usbd_fail;
osThreadFlagsSet(furi_thread_get_thread_id(mass->thread), EventReset);
return usbd_ack;
}; break;
}
return usbd_fail;
}
static const struct usb_string_descriptor dev_manuf_desc = USB_STRING_DESC("Flipper Devices Inc.");
struct MassStorageDescriptor {
struct usb_config_descriptor config;
struct usb_interface_descriptor intf;
struct usb_endpoint_descriptor ep_rx;
struct usb_endpoint_descriptor ep_tx;
} __attribute__((packed));
static const struct usb_device_descriptor usb_mass_dev_descr = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = USB_DTYPE_DEVICE,
.bcdUSB = VERSION_BCD(2, 0, 0),
.bDeviceClass = USB_CLASS_PER_INTERFACE,
.bDeviceSubClass = USB_SUBCLASS_NONE,
.bDeviceProtocol = USB_PROTO_NONE,
.bMaxPacketSize0 = 8, // USB_EP0_SIZE
.idVendor = 0x0483,
.idProduct = 0x5720,
.bcdDevice = VERSION_BCD(1, 0, 0),
.iManufacturer = 1, // UsbDevManuf
.iProduct = 2, // UsbDevProduct
.iSerialNumber = 3, // UsbDevSerial
.bNumConfigurations = 1,
};
static const struct MassStorageDescriptor usb_mass_cfg_descr = {
.config =
{
.bLength = sizeof(struct usb_config_descriptor),
.bDescriptorType = USB_DTYPE_CONFIGURATION,
.wTotalLength = sizeof(struct MassStorageDescriptor),
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = NO_DESCRIPTOR,
.bmAttributes = USB_CFG_ATTR_RESERVED | USB_CFG_ATTR_SELFPOWERED,
.bMaxPower = USB_CFG_POWER_MA(100),
},
.intf =
{
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DTYPE_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_MASS_STORAGE,
.bInterfaceSubClass = 0x06, // scsi transparent
.bInterfaceProtocol = 0x50, // bulk only
.iInterface = NO_DESCRIPTOR,
},
.ep_rx =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = USB_MSC_RX_EP,
.bmAttributes = USB_EPTYPE_BULK,
.wMaxPacketSize = USB_MSC_RX_EP_SIZE,
.bInterval = 0,
},
.ep_tx =
{
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DTYPE_ENDPOINT,
.bEndpointAddress = USB_MSC_TX_EP,
.bmAttributes = USB_EPTYPE_BULK,
.wMaxPacketSize = USB_MSC_TX_EP_SIZE,
.bInterval = 0,
},
};
MassStorageUsb* mass_storage_usb_start(const char* filename, SCSIDeviceFunc fn) {
MassStorageUsb* mass = malloc(sizeof(MassStorageUsb));
mass->usb_prev = furi_hal_usb_get_config();
mass->usb.init = usb_init;
mass->usb.deinit = usb_deinit;
mass->usb.wakeup = usb_wakeup;
mass->usb.suspend = usb_suspend;
mass->usb.dev_descr = (struct usb_device_descriptor*)&usb_mass_dev_descr;
mass->usb.str_manuf_descr = (void*)&dev_manuf_desc;
mass->usb.str_prod_descr = NULL;
mass->usb.str_serial_descr = NULL;
mass->usb.cfg_descr = (void*)&usb_mass_cfg_descr;
const char* name = furi_hal_version_get_device_name_ptr();
if(!name) name = "Flipper Zero";
size_t len = strlen(name);
struct usb_string_descriptor* str_prod_descr = malloc(len * 2 + 2);
str_prod_descr->bLength = len * 2 + 2;
str_prod_descr->bDescriptorType = USB_DTYPE_STRING;
for(uint8_t i = 0; i < len; i++) str_prod_descr->wString[i] = name[i];
mass->usb.str_prod_descr = str_prod_descr;
len = strlen(filename);
struct usb_string_descriptor* str_serial_descr = malloc(len * 2 + 2);
str_serial_descr->bLength = len * 2 + 2;
str_serial_descr->bDescriptorType = USB_DTYPE_STRING;
for(uint8_t i = 0; i < len; i++) str_serial_descr->wString[i] = filename[i];
mass->usb.str_serial_descr = str_serial_descr;
mass->fn = fn;
if(!furi_hal_usb_set_config(&mass->usb, mass)) {
FURI_LOG_E(TAG, "USB locked, cannot start Mass Storage");
free(mass->usb.str_prod_descr);
free(mass->usb.str_serial_descr);
free(mass);
return NULL;
}
return mass;
}
void mass_storage_usb_stop(MassStorageUsb* mass) {
furi_hal_usb_set_config(mass->usb_prev, NULL);
// freed by usb_deinit asynchronously from usb thread
}
@@ -0,0 +1,9 @@
#pragma once
#include <storage/storage.h>
#include "mass_storage_scsi.h"
typedef struct MassStorageUsb MassStorageUsb;
MassStorageUsb* mass_storage_usb_start(const char* filename, SCSIDeviceFunc fn);
void mass_storage_usb_stop(MassStorageUsb* mass);
+124
View File
@@ -0,0 +1,124 @@
#include "mass_storage_app_i.h"
#include <furi.h>
#include <storage/storage.h>
#include <lib/toolbox/path.h>
static bool mass_storage_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
MassStorageApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool mass_storage_app_back_event_callback(void* context) {
furi_assert(context);
MassStorageApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void mass_storage_app_tick_event_callback(void* context) {
furi_assert(context);
MassStorageApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
static bool mass_storage_check_assets(Storage* fs_api) {
File* dir = storage_file_alloc(fs_api);
bool ret = false;
if(storage_dir_open(dir, MASS_STORAGE_APP_PATH_FOLDER)) {
ret = true;
}
storage_dir_close(dir);
storage_file_free(dir);
return ret;
}
MassStorageApp* mass_storage_app_alloc(char* arg) {
MassStorageApp* app = malloc(sizeof(MassStorageApp));
memset(app, 0, sizeof(MassStorageApp));
if(arg != NULL) {
string_t filename;
string_init_set(filename, arg);
if(string_start_with_str_p(filename, MASS_STORAGE_APP_PATH_FOLDER)) {
string_right(filename, strlen(MASS_STORAGE_APP_PATH_FOLDER) + 1);
}
strncpy(app->file_name, string_get_cstr(filename), MASS_STORAGE_FILE_NAME_LEN);
string_clear(filename);
}
app->gui = furi_record_open("gui");
app->fs_api = furi_record_open("storage");
app->notifications = furi_record_open("notification");
app->dialogs = furi_record_open("dialogs");
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher);
app->scene_manager = scene_manager_alloc(&mass_storage_scene_handlers, app);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, mass_storage_app_tick_event_callback, 500);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, mass_storage_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, mass_storage_app_back_event_callback);
// Custom Widget
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher, MassStorageAppViewError, widget_get_view(app->widget));
app->mass_storage_view = mass_storage_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
MassStorageAppViewWork,
mass_storage_get_view(app->mass_storage_view));
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
if(*app->file_name != '\0') {
scene_manager_next_scene(app->scene_manager, MassStorageSceneWork);
} else if(mass_storage_check_assets(app->fs_api)) {
scene_manager_next_scene(app->scene_manager, MassStorageSceneFileSelect);
} else {
scene_manager_next_scene(app->scene_manager, MassStorageSceneError);
}
return app;
}
void mass_storage_app_free(MassStorageApp* app) {
furi_assert(app);
// Views
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewFileSelect);
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewWork);
mass_storage_free(app->mass_storage_view);
// Custom Widget
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewError);
widget_free(app->widget);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Close records
furi_record_close("gui");
furi_record_close("storage");
furi_record_close("notification");
furi_record_close("dialogs");
free(app);
}
int32_t mass_storage_app(void* p) {
MassStorageApp* mass_storage_app = mass_storage_app_alloc((char*)p);
view_dispatcher_run(mass_storage_app->view_dispatcher);
mass_storage_app_free(mass_storage_app);
return 0;
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct MassStorageApp MassStorageApp;
#ifdef __cplusplus
}
#endif
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "mass_storage_app.h"
#include "scenes/mass_storage_scene.h"
#include "helpers/mass_storage_usb.h"
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <dialogs/dialogs.h>
#include <notification/notification_messages.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/widget.h>
#include <storage/storage.h>
#include "views/mass_storage_view.h"
#define MASS_STORAGE_APP_PATH_FOLDER "/any/mass_storage"
// #define MASS_STORAGE_APP_EXTENSION ".iso"
#define MASS_STORAGE_FILE_NAME_LEN 40
struct MassStorageApp {
Gui* gui;
Storage* fs_api;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notifications;
DialogsApp* dialogs;
Widget* widget;
char file_name[MASS_STORAGE_FILE_NAME_LEN + 1];
File* file;
MassStorage* mass_storage_view;
osMutexId_t usb_mutex;
MassStorageUsb* usb;
};
typedef enum {
MassStorageAppViewError,
MassStorageAppViewFileSelect,
MassStorageAppViewWork,
} MassStorageAppView;
@@ -0,0 +1,30 @@
#include "mass_storage_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const mass_storage_scene_on_enter_handlers[])(void*) = {
#include "mass_storage_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const mass_storage_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "mass_storage_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const mass_storage_scene_on_exit_handlers[])(void* context) = {
#include "mass_storage_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers mass_storage_scene_handlers = {
.on_enter_handlers = mass_storage_scene_on_enter_handlers,
.on_event_handlers = mass_storage_scene_on_event_handlers,
.on_exit_handlers = mass_storage_scene_on_exit_handlers,
.scene_num = MassStorageSceneNum,
};
@@ -0,0 +1,29 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) MassStorageScene##id,
typedef enum {
#include "mass_storage_scene_config.h"
MassStorageSceneNum,
} MassStorageScene;
#undef ADD_SCENE
extern const SceneManagerHandlers mass_storage_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "mass_storage_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "mass_storage_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "mass_storage_scene_config.h"
#undef ADD_SCENE
@@ -0,0 +1,3 @@
ADD_SCENE(mass_storage, file_select, FileSelect)
ADD_SCENE(mass_storage, work, Work)
ADD_SCENE(mass_storage, error, Error)
@@ -0,0 +1,53 @@
#include "../mass_storage_app_i.h"
typedef enum {
SubghzCustomEventErrorBack,
} MassStorageCustomEvent;
static void
mass_storage_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
furi_assert(context);
MassStorageApp* app = context;
if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
view_dispatcher_send_custom_event(app->view_dispatcher, SubghzCustomEventErrorBack);
}
}
void mass_storage_scene_error_on_enter(void* context) {
MassStorageApp* app = context;
widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
widget_add_string_multiline_element(
app->widget,
81,
4,
AlignCenter,
AlignTop,
FontSecondary,
"No SD card or\napp data found.\nThis app requires a\nmass_storage/\ndirectory.");
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Back", mass_storage_scene_error_event_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewError);
}
bool mass_storage_scene_error_on_event(void* context, SceneManagerEvent event) {
MassStorageApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubghzCustomEventErrorBack) {
view_dispatcher_stop(app->view_dispatcher);
consumed = true;
}
}
return consumed;
}
void mass_storage_scene_error_on_exit(void* context) {
MassStorageApp* app = context;
widget_reset(app->widget);
}
@@ -0,0 +1,36 @@
#include "../mass_storage_app_i.h"
#include "furi_hal_power.h"
static bool mass_storage_file_select(MassStorageApp* mass_storage) {
furi_assert(mass_storage);
// Input events and views are managed by file_select
bool res = dialog_file_select_show(
mass_storage->dialogs,
MASS_STORAGE_APP_PATH_FOLDER,
"*",
mass_storage->file_name,
sizeof(mass_storage->file_name),
NULL);
return res;
}
void mass_storage_scene_file_select_on_enter(void* context) {
MassStorageApp* mass_storage = context;
if(mass_storage_file_select(mass_storage)) {
scene_manager_next_scene(mass_storage->scene_manager, MassStorageSceneWork);
} else {
scene_manager_previous_scene(mass_storage->scene_manager);
view_dispatcher_stop(mass_storage->view_dispatcher);
}
}
bool mass_storage_scene_file_select_on_event(void* context, SceneManagerEvent event) {
// MassStorageApp* mass_storage = context;
return false;
}
void mass_storage_scene_file_select_on_exit(void* context) {
// MassStorageApp* mass_storage = context;
}
@@ -0,0 +1,105 @@
#include "../mass_storage_app_i.h"
#include "../views/mass_storage_view.h"
#include "../helpers/mass_storage_usb.h"
#define TAG "MassStorageSceneWork"
static bool file_read(
void* ctx,
uint32_t lba,
uint16_t count,
uint8_t* out,
uint32_t* out_len,
uint32_t out_cap) {
MassStorageApp* app = ctx;
// FURI_LOG_I(TAG, "file_read lba=%08lx count=%04x out_cap=%04x", lba, count, out_cap);
if(!storage_file_seek(app->file, lba * SCSI_BLOCK_SIZE, true)) {
FURI_LOG_W(TAG, "seek failed");
return false;
}
uint16_t clamp = MIN(out_cap, count * SCSI_BLOCK_SIZE);
*out_len = storage_file_read(app->file, out, clamp);
// FURI_LOG_I(TAG, "%d/%d", *out_len, count * SCSI_BLOCK_SIZE);
return *out_len == clamp;
}
static bool file_write(void* ctx, uint32_t lba, uint16_t count, uint8_t* buf, uint32_t len) {
MassStorageApp* app = ctx;
// FURI_LOG_I(TAG, "file_write lba=%08lx count=%04x len=%04x", lba, count, len);
if(len != count * SCSI_BLOCK_SIZE) {
FURI_LOG_W(TAG, "bad write params count=%d len=%d", count, len);
return false;
}
if(!storage_file_seek(app->file, lba * SCSI_BLOCK_SIZE, true)) {
FURI_LOG_W(TAG, "seek failed");
return false;
}
return storage_file_write(app->file, buf, len) == len;
}
static uint32_t file_num_blocks(void* ctx) {
MassStorageApp* app = ctx;
return storage_file_size(app->file) / SCSI_BLOCK_SIZE;
}
static void file_eject(void* ctx) {
MassStorageApp* app = ctx;
FURI_LOG_I(TAG, "EJECT");
furi_check(osMutexAcquire(app->usb_mutex, osWaitForever) == osOK);
mass_storage_usb_stop(app->usb);
app->usb = NULL;
furi_check(osMutexRelease(app->usb_mutex) == osOK);
}
bool mass_storage_scene_work_on_event(void* context, SceneManagerEvent event) {
MassStorageApp* app = context;
if(event.type == SceneManagerEventTypeTick) {
bool ejected;
furi_check(osMutexAcquire(app->usb_mutex, osWaitForever) == osOK);
ejected = app->usb == NULL;
furi_check(osMutexRelease(app->usb_mutex) == osOK);
if(ejected) scene_manager_previous_scene(app->scene_manager);
}
return false;
}
void mass_storage_scene_work_on_enter(void* context) {
MassStorageApp* app = context;
app->usb_mutex = osMutexNew(NULL);
string_t file_name;
string_init(file_name);
mass_storage_set_file_name(app->mass_storage_view, app->file_name);
string_printf(file_name, "%s/%s", MASS_STORAGE_APP_PATH_FOLDER, app->file_name);
app->file = storage_file_alloc(app->fs_api);
furi_assert(storage_file_open(
app->file, string_get_cstr(file_name), FSAM_READ | FSAM_WRITE, FSOM_OPEN_EXISTING));
string_clear(file_name);
SCSIDeviceFunc fn = {
.ctx = app,
.read = file_read,
.write = file_write,
.num_blocks = file_num_blocks,
.eject = file_eject,
};
app->usb = mass_storage_usb_start(app->file_name, fn);
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewWork);
}
void mass_storage_scene_work_on_exit(void* context) {
MassStorageApp* app = context;
furi_assert(osMutexDelete(app->usb_mutex) == osOK);
if(app->usb) {
mass_storage_usb_stop(app->usb);
app->usb = NULL;
}
if(app->file) {
storage_file_free(app->file);
app->file = NULL;
}
}
@@ -0,0 +1,56 @@
#include "mass_storage_view.h"
#include <gui/elements.h>
struct MassStorage {
View* view;
};
typedef struct {
char* file_name;
} MassStorageModel;
static void mass_storage_draw_callback(Canvas* canvas, void* _model) {
MassStorageModel* model = _model;
string_t disp_str;
string_init_set_str(disp_str, model->file_name);
elements_string_fit_width(canvas, disp_str, 128 - 2);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 2, 8, string_get_cstr(disp_str));
string_reset(disp_str);
canvas_draw_icon(canvas, 40, 20, &I_UsbTree_48x22);
string_clear(disp_str);
}
MassStorage* mass_storage_alloc() {
MassStorage* mass_storage = malloc(sizeof(MassStorage));
mass_storage->view = view_alloc();
view_allocate_model(mass_storage->view, ViewModelTypeLocking, sizeof(MassStorageModel));
view_set_context(mass_storage->view, mass_storage);
view_set_draw_callback(mass_storage->view, mass_storage_draw_callback);
return mass_storage;
}
void mass_storage_free(MassStorage* mass_storage) {
furi_assert(mass_storage);
view_free(mass_storage->view);
free(mass_storage);
}
View* mass_storage_get_view(MassStorage* mass_storage) {
furi_assert(mass_storage);
return mass_storage->view;
}
void mass_storage_set_file_name(MassStorage* mass_storage, char* name) {
furi_assert(name);
with_view_model(
mass_storage->view, (MassStorageModel * model) {
model->file_name = name;
return true;
});
}
@@ -0,0 +1,13 @@
#pragma once
#include <gui/view.h>
typedef struct MassStorage MassStorage;
MassStorage* mass_storage_alloc();
void mass_storage_free(MassStorage* mass_storage);
View* mass_storage_get_view(MassStorage* mass_storage);
void mass_storage_set_file_name(MassStorage* mass_storage, char* name);