#include "mass_storage_usb.h" #include #define TAG "MassStorageUsb" #define USB_MSC_RX_EP (0x01) #define USB_MSC_TX_EP (0x82) #define USB_MSC_RX_EP_SIZE (64UL) #define USB_MSC_TX_EP_SIZE (64UL) #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 (0x10000UL - 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 = furi_thread_flags_wait(EventAll, FuriFlagWaitAny, FuriWaitForever); if(flags & EventExit) { FURI_LOG_D(TAG, "exit"); break; } if(flags & EventReset) { FURI_LOG_D(TAG, "reset"); scsi.sk = 0; scsi.asc = 0; memset(&cbw, 0, sizeof(cbw)); memset(&csw, 0, sizeof(csw)); if(buf) { free(buf); buf = NULL; } buf_len = buf_cap = buf_sent = 0; state = StateReadCBW; } if(flags & EventRxTx) do { switch(state) { case StateReadCBW: { FURI_LOG_T(TAG, "StateReadCBW"); int32_t len = usbd_ep_read(dev, USB_MSC_RX_EP, &cbw, sizeof(cbw)); if(len <= 0) { FURI_LOG_T(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_T(TAG, "StateReadData %lu/%lu", 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_T(TAG, "growing buf %lu -> %lu", buf_cap, buf_clamp); if(buf) { 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_T(TAG, "rx not ready %ld", len); break; } FURI_LOG_T(TAG, "clamp %lu len %ld", 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_T(TAG, "StateWriteData %lu", 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_T(TAG, "growing buf %lu -> %lu", buf_cap, buf_clamp); if(buf) { 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_T(TAG, "tx not ready %ld", 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_T(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_T(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_T(TAG, "csw not ready"); break; } if(len != sizeof(csw)) { FURI_LOG_W(TAG, "bad csw write %ld", 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); } if(buf) { free(buf); } return 0; } // 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) { UNUSED(intf); 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); furi_thread_flags_set(furi_thread_get_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) { UNUSED(dev); } static void usb_suspend(usbd_device* dev) { MassStorageUsb* mass = mass_cur; if(!mass || mass->dev != dev) return; furi_thread_flags_set(furi_thread_get_id(mass->thread), EventReset); } static void usb_rxtx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(ep); UNUSED(event); MassStorageUsb* mass = mass_cur; if(!mass || mass->dev != dev) return; furi_thread_flags_set(furi_thread_get_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) { UNUSED(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; furi_thread_flags_set(furi_thread_get_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); }