mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-25 03:29:58 -07:00
Move Mass Storage app to firmware repo
This commit is contained in:
16
applications/system/mass_storage/application.fam
Normal file
16
applications/system/mass_storage/application.fam
Normal file
@@ -0,0 +1,16 @@
|
||||
App(
|
||||
appid="mass_storage",
|
||||
name="Mass Storage",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="mass_storage_app",
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
fap_description="Implements a mass storage device over USB for disk images",
|
||||
fap_version="1.3",
|
||||
fap_icon="assets/floppydisk_10px.png",
|
||||
fap_icon_assets="assets",
|
||||
fap_category="USB",
|
||||
)
|
||||
BIN
applications/system/mass_storage/assets/floppydisk_10px.png
Normal file
BIN
applications/system/mass_storage/assets/floppydisk_10px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 183 B |
266
applications/system/mass_storage/helpers/mass_storage_scsi.c
Normal file
266
applications/system/mass_storage/helpers/mass_storage_scsi.c
Normal file
@@ -0,0 +1,266 @@
|
||||
#include "mass_storage_scsi.h"
|
||||
|
||||
#include <core/log.h>
|
||||
|
||||
#define TAG "MassStorageSCSI"
|
||||
|
||||
#define SCSI_TEST_UNIT_READY (0x00)
|
||||
#define SCSI_REQUEST_SENSE (0x03)
|
||||
#define SCSI_INQUIRY (0x12)
|
||||
#define SCSI_READ_FORMAT_CAPACITIES (0x23)
|
||||
#define SCSI_READ_CAPACITY_10 (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_T(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_D(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_D(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_T(TAG, "RX %02X len %lu", 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_T(TAG, "TX %02X cap %lu", scsi->cmd[0], cap);
|
||||
if(scsi->tx_done) return false;
|
||||
switch(scsi->cmd[0]) {
|
||||
case SCSI_REQUEST_SENSE: {
|
||||
FURI_LOG_D(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_D(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];
|
||||
if(evpd == 0) {
|
||||
if(page_code != 0) 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;
|
||||
} else {
|
||||
if(page_code != 0x80) {
|
||||
FURI_LOG_W(TAG, "Unsupported VPD code %02X", page_code);
|
||||
return false;
|
||||
}
|
||||
data[0] = 0x00;
|
||||
data[1] = 0x80;
|
||||
data[2] = 0x00;
|
||||
data[3] = 0x01; // Serial len
|
||||
data[4] = '0';
|
||||
*len = 5;
|
||||
scsi->tx_done = true;
|
||||
return true;
|
||||
}
|
||||
}; break;
|
||||
case SCSI_READ_FORMAT_CAPACITIES: {
|
||||
FURI_LOG_D(TAG, "SCSI_READ_FORMAT_CAPACITIES");
|
||||
if(cap < 12) {
|
||||
return false;
|
||||
}
|
||||
uint32_t n_blocks = scsi->fn.num_blocks(scsi->fn.ctx);
|
||||
uint32_t block_size = SCSI_BLOCK_SIZE;
|
||||
// Capacity List Header
|
||||
data[0] = 0;
|
||||
data[1] = 0;
|
||||
data[2] = 0;
|
||||
data[3] = 8;
|
||||
|
||||
// Capacity Descriptor
|
||||
data[4] = (n_blocks - 1) >> 24;
|
||||
data[5] = (n_blocks - 1) >> 16;
|
||||
data[6] = (n_blocks - 1) >> 8;
|
||||
data[7] = (n_blocks - 1) & 0xFF;
|
||||
data[8] = 0x02; // Formatted media
|
||||
data[9] = block_size >> 16;
|
||||
data[10] = block_size >> 8;
|
||||
data[11] = block_size & 0xFF;
|
||||
*len = 12;
|
||||
scsi->tx_done = true;
|
||||
return true;
|
||||
}; break;
|
||||
case SCSI_READ_CAPACITY_10: {
|
||||
FURI_LOG_D(TAG, "SCSI_READ_CAPACITY_10");
|
||||
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) & 0xFF;
|
||||
data[4] = block_size >> 24;
|
||||
data[5] = block_size >> 16;
|
||||
data[6] = block_size >> 8;
|
||||
data[7] = block_size & 0xFF;
|
||||
*len = 8;
|
||||
scsi->tx_done = true;
|
||||
return true;
|
||||
}; break;
|
||||
case SCSI_MODE_SENSE_6: {
|
||||
FURI_LOG_D(TAG, "SCSI_MODE_SENSE_6 %lu", 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_T(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_FORMAT_CAPACITIES:
|
||||
case SCSI_READ_CAPACITY_10:
|
||||
case SCSI_MODE_SENSE_6:
|
||||
case SCSI_READ_10:
|
||||
return scsi->tx_done;
|
||||
|
||||
case SCSI_TEST_UNIT_READY: {
|
||||
FURI_LOG_D(TAG, "SCSI_TEST_UNIT_READY");
|
||||
return true;
|
||||
}; break;
|
||||
case SCSI_PREVENT_MEDIUM_REMOVAL: {
|
||||
if(len < 6) return false;
|
||||
bool prevent = cmd[5];
|
||||
FURI_LOG_D(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_D(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;
|
||||
}
|
||||
}
|
||||
56
applications/system/mass_storage/helpers/mass_storage_scsi.h
Normal file
56
applications/system/mass_storage/helpers/mass_storage_scsi.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define SCSI_BLOCK_SIZE (0x200UL)
|
||||
|
||||
#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);
|
||||
481
applications/system/mass_storage/helpers/mass_storage_usb.c
Normal file
481
applications/system/mass_storage/helpers/mass_storage_usb.c
Normal file
@@ -0,0 +1,481 @@
|
||||
#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 (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);
|
||||
}
|
||||
@@ -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);
|
||||
151
applications/system/mass_storage/mass_storage_app.c
Normal file
151
applications/system/mass_storage/mass_storage_app.c
Normal file
@@ -0,0 +1,151 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
void mass_storage_app_show_loading_popup(MassStorageApp* app, bool show) {
|
||||
if(show) {
|
||||
// Raise timer priority so that animations can play
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewLoading);
|
||||
} else {
|
||||
// Restore default timer priority
|
||||
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
|
||||
}
|
||||
}
|
||||
|
||||
MassStorageApp* mass_storage_app_alloc(char* arg) {
|
||||
MassStorageApp* app = malloc(sizeof(MassStorageApp));
|
||||
app->file_path = furi_string_alloc();
|
||||
|
||||
if(arg != NULL) {
|
||||
furi_string_set_str(app->file_path, arg);
|
||||
} else {
|
||||
furi_string_set_str(app->file_path, MASS_STORAGE_APP_PATH_FOLDER);
|
||||
}
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->fs_api = furi_record_open(RECORD_STORAGE);
|
||||
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
app->create_image_size = (uint8_t)-1;
|
||||
SDInfo sd_info;
|
||||
if(storage_sd_info(app->fs_api, &sd_info) == FSE_OK) {
|
||||
switch(sd_info.fs_type) {
|
||||
case FST_FAT12:
|
||||
app->create_image_max = 16LL * 1024 * 1024;
|
||||
break;
|
||||
case FST_FAT16:
|
||||
app->create_image_max = 2LL * 1024 * 1024 * 1024;
|
||||
break;
|
||||
case FST_FAT32:
|
||||
app->create_image_max = 4LL * 1024 * 1024 * 1024;
|
||||
break;
|
||||
default:
|
||||
app->create_image_max = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
app->mass_storage_view = mass_storage_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
MassStorageAppViewWork,
|
||||
mass_storage_get_view(app->mass_storage_view));
|
||||
|
||||
app->text_input = text_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, MassStorageAppViewTextInput, text_input_get_view(app->text_input));
|
||||
|
||||
app->loading = loading_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, MassStorageAppViewLoading, loading_get_view(app->loading));
|
||||
|
||||
app->variable_item_list = variable_item_list_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
MassStorageAppViewStart,
|
||||
variable_item_list_get_view(app->variable_item_list));
|
||||
|
||||
app->popup = popup_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, MassStorageAppViewPopup, popup_get_view(app->popup));
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
if(storage_file_exists(app->fs_api, furi_string_get_cstr(app->file_path))) {
|
||||
scene_manager_next_scene(app->scene_manager, MassStorageSceneWork);
|
||||
} else {
|
||||
scene_manager_next_scene(app->scene_manager, MassStorageSceneStart);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
void mass_storage_app_free(MassStorageApp* app) {
|
||||
furi_assert(app);
|
||||
|
||||
// Views
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewWork);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewTextInput);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewStart);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewLoading);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, MassStorageAppViewPopup);
|
||||
|
||||
mass_storage_free(app->mass_storage_view);
|
||||
text_input_free(app->text_input);
|
||||
variable_item_list_free(app->variable_item_list);
|
||||
loading_free(app->loading);
|
||||
popup_free(app->popup);
|
||||
|
||||
// View dispatcher
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
scene_manager_free(app->scene_manager);
|
||||
|
||||
furi_string_free(app->file_path);
|
||||
|
||||
// Close records
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
furi_record_close(RECORD_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
applications/system/mass_storage/mass_storage_app.h
Normal file
11
applications/system/mass_storage/mass_storage_app.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct MassStorageApp MassStorageApp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
67
applications/system/mass_storage/mass_storage_app_i.h
Normal file
67
applications/system/mass_storage/mass_storage_app_i.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#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 <gui/modules/variable_item_list.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <storage/storage.h>
|
||||
#include "views/mass_storage_view.h"
|
||||
#include <mass_storage_icons.h>
|
||||
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define MASS_STORAGE_APP_PATH_FOLDER STORAGE_APP_DATA_PATH_PREFIX
|
||||
#define MASS_STORAGE_APP_EXTENSION ".img"
|
||||
#define MASS_STORAGE_FILE_NAME_LEN 40
|
||||
|
||||
struct MassStorageApp {
|
||||
Gui* gui;
|
||||
Storage* fs_api;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
Popup* popup;
|
||||
DialogsApp* dialogs;
|
||||
TextInput* text_input;
|
||||
VariableItemList* variable_item_list;
|
||||
Loading* loading;
|
||||
|
||||
FuriString* file_path;
|
||||
File* file;
|
||||
MassStorage* mass_storage_view;
|
||||
|
||||
FuriMutex* usb_mutex;
|
||||
MassStorageUsb* usb;
|
||||
|
||||
uint64_t create_image_max;
|
||||
uint8_t create_image_size;
|
||||
char create_image_name[MASS_STORAGE_FILE_NAME_LEN];
|
||||
|
||||
uint32_t bytes_read, bytes_written;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
MassStorageAppViewStart,
|
||||
MassStorageAppViewTextInput,
|
||||
MassStorageAppViewWork,
|
||||
MassStorageAppViewLoading,
|
||||
MassStorageAppViewPopup,
|
||||
} MassStorageAppView;
|
||||
|
||||
enum MassStorageCustomEvent {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
MassStorageCustomEventReserved = 100,
|
||||
|
||||
MassStorageCustomEventEject,
|
||||
};
|
||||
|
||||
void mass_storage_app_show_loading_popup(MassStorageApp* app, bool show);
|
||||
30
applications/system/mass_storage/scenes/mass_storage_scene.c
Normal file
30
applications/system/mass_storage/scenes/mass_storage_scene.c
Normal file
@@ -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,
|
||||
};
|
||||
29
applications/system/mass_storage/scenes/mass_storage_scene.h
Normal file
29
applications/system/mass_storage/scenes/mass_storage_scene.h
Normal file
@@ -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,5 @@
|
||||
ADD_SCENE(mass_storage, start, Start)
|
||||
ADD_SCENE(mass_storage, file_select, FileSelect)
|
||||
ADD_SCENE(mass_storage, work, Work)
|
||||
ADD_SCENE(mass_storage, create_image, CreateImage)
|
||||
ADD_SCENE(mass_storage, create_image_name, CreateImageName)
|
||||
@@ -0,0 +1,188 @@
|
||||
#include "../mass_storage_app_i.h"
|
||||
#include <lib/toolbox/value_index.h>
|
||||
|
||||
enum VarItemListIndex {
|
||||
VarItemListIndexImageSize,
|
||||
VarItemListIndexImageName,
|
||||
VarItemListIndexCreateImage,
|
||||
};
|
||||
|
||||
void mass_storage_scene_create_image_variable_item_list_callback(void* context, uint32_t index) {
|
||||
MassStorageApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
char* name;
|
||||
uint64_t value;
|
||||
} image_sizes[] = {
|
||||
{"1MB", 1LL * 1024 * 1024},
|
||||
{"2MB", 2LL * 1024 * 1024},
|
||||
{"4MB", 4LL * 1024 * 1024},
|
||||
{"8MB", 8LL * 1024 * 1024},
|
||||
{"16MB", 16LL * 1024 * 1024},
|
||||
{"32MB", 32LL * 1024 * 1024},
|
||||
{"64MB", 64LL * 1024 * 1024},
|
||||
{"128MB", 128LL * 1024 * 1024},
|
||||
{"256MB", 256LL * 1024 * 1024},
|
||||
{"512MB", 512LL * 1024 * 1024},
|
||||
{"1GB", 1LL * 1024 * 1024 * 1024},
|
||||
{"2GB", 2LL * 1024 * 1024 * 1024},
|
||||
{"4GB", 4LL * 1024 * 1024 * 1024},
|
||||
{"8GB", 8LL * 1024 * 1024 * 1024},
|
||||
{"16GB", 16LL * 1024 * 1024 * 1024},
|
||||
{"32GB", 32LL * 1024 * 1024 * 1024},
|
||||
{"64GB", 64LL * 1024 * 1024 * 1024},
|
||||
{"128GB", 128LL * 1024 * 1024 * 1024},
|
||||
{"256GB", 256LL * 1024 * 1024 * 1024},
|
||||
{"512GB", 512LL * 1024 * 1024 * 1024},
|
||||
};
|
||||
static void mass_storage_scene_create_image_image_size_changed(VariableItem* item) {
|
||||
MassStorageApp* app = variable_item_get_context(item);
|
||||
app->create_image_size = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, image_sizes[app->create_image_size].name);
|
||||
}
|
||||
|
||||
void mass_storage_scene_create_image_on_enter(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
VariableItemList* variable_item_list = app->variable_item_list;
|
||||
VariableItem* item;
|
||||
|
||||
uint8_t size_count = COUNT_OF(image_sizes);
|
||||
if(app->create_image_max) {
|
||||
for(size_t i = 1; i < size_count; i++) {
|
||||
if(image_sizes[i].value > app->create_image_max) {
|
||||
size_count = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(app->create_image_size == (uint8_t)-1) {
|
||||
app->create_image_size = CLAMP(7, size_count - 2, 0); // 7 = 128MB
|
||||
}
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"Image Size",
|
||||
size_count,
|
||||
mass_storage_scene_create_image_image_size_changed,
|
||||
app);
|
||||
variable_item_set_current_value_index(item, app->create_image_size);
|
||||
variable_item_set_current_value_text(item, image_sizes[app->create_image_size].name);
|
||||
|
||||
item = variable_item_list_add(variable_item_list, "Image Name", 0, NULL, app);
|
||||
variable_item_set_current_value_text(item, app->create_image_name);
|
||||
|
||||
variable_item_list_add(variable_item_list, "Create Image", 0, NULL, app);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
variable_item_list, mass_storage_scene_create_image_variable_item_list_callback, app);
|
||||
|
||||
variable_item_list_set_header(variable_item_list, "Create Disk Image");
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
variable_item_list,
|
||||
scene_manager_get_scene_state(app->scene_manager, MassStorageSceneCreateImage));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewStart);
|
||||
}
|
||||
|
||||
static void popup_callback_ok(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, MassStorageSceneStart, MassStorageSceneFileSelect);
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
scene_manager_next_scene(app->scene_manager, MassStorageSceneFileSelect);
|
||||
}
|
||||
|
||||
static void popup_callback_error(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewStart);
|
||||
}
|
||||
|
||||
bool mass_storage_scene_create_image_on_event(void* context, SceneManagerEvent event) {
|
||||
MassStorageApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(
|
||||
app->scene_manager, MassStorageSceneCreateImage, event.event);
|
||||
consumed = true;
|
||||
switch(event.event) {
|
||||
case VarItemListIndexImageName:
|
||||
scene_manager_next_scene(app->scene_manager, MassStorageSceneCreateImageName);
|
||||
break;
|
||||
case VarItemListIndexCreateImage: {
|
||||
mass_storage_app_show_loading_popup(app, true);
|
||||
const char* name = strnlen(app->create_image_name, sizeof(app->create_image_name)) ?
|
||||
app->create_image_name :
|
||||
image_sizes[app->create_image_size].name;
|
||||
furi_string_printf(
|
||||
app->file_path,
|
||||
"%s/%s%s",
|
||||
MASS_STORAGE_APP_PATH_FOLDER,
|
||||
name,
|
||||
MASS_STORAGE_APP_EXTENSION);
|
||||
|
||||
app->file = storage_file_alloc(app->fs_api);
|
||||
const char* error = NULL;
|
||||
bool success = false;
|
||||
|
||||
do {
|
||||
if(!storage_file_open(
|
||||
app->file,
|
||||
furi_string_get_cstr(app->file_path),
|
||||
FSAM_WRITE,
|
||||
FSOM_CREATE_NEW))
|
||||
break;
|
||||
|
||||
uint64_t size = image_sizes[app->create_image_size].value;
|
||||
if(size == app->create_image_max) size--;
|
||||
if(!storage_file_expand(app->file, size)) break;
|
||||
|
||||
// Format as exFAT
|
||||
if(storage_virtual_init(app->fs_api, app->file) != FSE_OK) break;
|
||||
if(storage_virtual_format(app->fs_api) != FSE_OK) break;
|
||||
if(storage_virtual_quit(app->fs_api) != FSE_OK) break;
|
||||
|
||||
success = true;
|
||||
} while(false);
|
||||
|
||||
if(!success) {
|
||||
error = storage_file_get_error_desc(app->file);
|
||||
FS_Error error = storage_file_get_error(app->file);
|
||||
storage_file_close(app->file);
|
||||
if(error != FSE_EXIST) {
|
||||
storage_common_remove(app->fs_api, furi_string_get_cstr(app->file_path));
|
||||
}
|
||||
}
|
||||
storage_file_free(app->file);
|
||||
mass_storage_app_show_loading_popup(app, false);
|
||||
|
||||
if(error) {
|
||||
popup_set_header(
|
||||
app->popup, "Error Creating Image!", 64, 26, AlignCenter, AlignCenter);
|
||||
popup_set_text(app->popup, error, 64, 40, AlignCenter, AlignCenter);
|
||||
popup_set_callback(app->popup, popup_callback_error);
|
||||
} else {
|
||||
popup_set_header(app->popup, "Image Created!", 64, 32, AlignCenter, AlignCenter);
|
||||
popup_set_text(app->popup, "", 0, 0, AlignLeft, AlignBottom);
|
||||
popup_set_callback(app->popup, popup_callback_ok);
|
||||
}
|
||||
popup_set_context(app->popup, app);
|
||||
popup_set_timeout(app->popup, 0);
|
||||
popup_disable_timeout(app->popup);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewPopup);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void mass_storage_scene_create_image_on_exit(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
variable_item_list_reset(app->variable_item_list);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "../mass_storage_app_i.h"
|
||||
|
||||
enum TextInputIndex {
|
||||
TextInputResultOk,
|
||||
};
|
||||
|
||||
static void mass_storage_scene_create_image_name_text_input_callback(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, TextInputResultOk);
|
||||
}
|
||||
|
||||
void mass_storage_scene_create_image_name_on_enter(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
TextInput* text_input = app->text_input;
|
||||
|
||||
text_input_set_header_text(text_input, "Image name, empty = default");
|
||||
|
||||
text_input_set_minimum_length(text_input, 0);
|
||||
|
||||
text_input_set_result_callback(
|
||||
text_input,
|
||||
mass_storage_scene_create_image_name_text_input_callback,
|
||||
app,
|
||||
app->create_image_name,
|
||||
sizeof(app->create_image_name),
|
||||
false);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewTextInput);
|
||||
}
|
||||
|
||||
bool mass_storage_scene_create_image_name_on_event(void* context, SceneManagerEvent event) {
|
||||
MassStorageApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
switch(event.event) {
|
||||
case TextInputResultOk:
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void mass_storage_scene_create_image_name_on_exit(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
text_input_reset(app->text_input);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "../mass_storage_app_i.h"
|
||||
#include "furi_hal_power.h"
|
||||
|
||||
static bool mass_storage_file_select(MassStorageApp* mass_storage) {
|
||||
furi_assert(mass_storage);
|
||||
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(&browser_options, ".img|.iso", &I_floppydisk_10px);
|
||||
browser_options.base_path = MASS_STORAGE_APP_PATH_FOLDER;
|
||||
browser_options.hide_ext = false;
|
||||
|
||||
// Input events and views are managed by file_select
|
||||
bool res = dialog_file_browser_show(
|
||||
mass_storage->dialogs, mass_storage->file_path, mass_storage->file_path, &browser_options);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool mass_storage_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
// MassStorageApp* mass_storage = context;
|
||||
return false;
|
||||
}
|
||||
|
||||
void mass_storage_scene_file_select_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "../mass_storage_app_i.h"
|
||||
|
||||
enum VarItemListIndex {
|
||||
VarItemListIndexSelectDiskImage,
|
||||
VarItemListIndexCreateDiskImage,
|
||||
};
|
||||
|
||||
static void mass_storage_scene_start_variable_item_list_callback(void* context, uint32_t index) {
|
||||
MassStorageApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void mass_storage_scene_start_on_enter(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
VariableItemList* variable_item_list = app->variable_item_list;
|
||||
|
||||
variable_item_list_add(variable_item_list, "Select Disk Image", 0, NULL, app);
|
||||
|
||||
variable_item_list_add(variable_item_list, "Create Disk Image", 0, NULL, app);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
variable_item_list, mass_storage_scene_start_variable_item_list_callback, app);
|
||||
|
||||
variable_item_list_set_header(variable_item_list, "USB Mass Storage");
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
variable_item_list,
|
||||
scene_manager_get_scene_state(app->scene_manager, MassStorageSceneStart));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewStart);
|
||||
}
|
||||
|
||||
bool mass_storage_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
MassStorageApp* app = context;
|
||||
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(app->scene_manager, MassStorageSceneStart, event.event);
|
||||
consumed = true;
|
||||
switch(event.event) {
|
||||
case VarItemListIndexSelectDiskImage:
|
||||
scene_manager_next_scene(app->scene_manager, MassStorageSceneFileSelect);
|
||||
break;
|
||||
case VarItemListIndexCreateDiskImage:
|
||||
scene_manager_set_scene_state(app->scene_manager, MassStorageSceneCreateImage, 0);
|
||||
scene_manager_next_scene(app->scene_manager, MassStorageSceneCreateImage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void mass_storage_scene_start_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
MassStorageApp* app = context;
|
||||
variable_item_list_reset(app->variable_item_list);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "../mass_storage_app_i.h"
|
||||
#include "../views/mass_storage_view.h"
|
||||
#include "../helpers/mass_storage_usb.h"
|
||||
#include <lib/toolbox/path.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_T(TAG, "file_read lba=%08lX count=%04X out_cap=%08lX", 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_T(TAG, "%lu/%lu", *out_len, count * SCSI_BLOCK_SIZE);
|
||||
app->bytes_read += *out_len;
|
||||
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_T(TAG, "file_write lba=%08lX count=%04X len=%08lX", lba, count, len);
|
||||
if(len != count * SCSI_BLOCK_SIZE) {
|
||||
FURI_LOG_W(TAG, "bad write params count=%u len=%lu", count, len);
|
||||
return false;
|
||||
}
|
||||
if(!storage_file_seek(app->file, lba * SCSI_BLOCK_SIZE, true)) {
|
||||
FURI_LOG_W(TAG, "seek failed");
|
||||
return false;
|
||||
}
|
||||
app->bytes_written += len;
|
||||
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_D(TAG, "EJECT");
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, MassStorageCustomEventEject);
|
||||
}
|
||||
|
||||
bool mass_storage_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||
MassStorageApp* app = context;
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == MassStorageCustomEventEject) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, MassStorageSceneFileSelect);
|
||||
if(!consumed) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, MassStorageSceneStart);
|
||||
}
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
mass_storage_set_stats(app->mass_storage_view, app->bytes_read, app->bytes_written);
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, MassStorageSceneFileSelect);
|
||||
if(!consumed) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, MassStorageSceneStart);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void mass_storage_scene_work_on_enter(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
app->bytes_read = app->bytes_written = 0;
|
||||
|
||||
if(!storage_file_exists(app->fs_api, furi_string_get_cstr(app->file_path))) {
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, MassStorageSceneStart);
|
||||
return;
|
||||
}
|
||||
|
||||
mass_storage_app_show_loading_popup(app, true);
|
||||
|
||||
app->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
FuriString* file_name = furi_string_alloc();
|
||||
path_extract_filename(app->file_path, file_name, true);
|
||||
|
||||
mass_storage_set_file_name(app->mass_storage_view, file_name);
|
||||
app->file = storage_file_alloc(app->fs_api);
|
||||
furi_assert(storage_file_open(
|
||||
app->file,
|
||||
furi_string_get_cstr(app->file_path),
|
||||
FSAM_READ | FSAM_WRITE,
|
||||
FSOM_OPEN_EXISTING));
|
||||
|
||||
SCSIDeviceFunc fn = {
|
||||
.ctx = app,
|
||||
.read = file_read,
|
||||
.write = file_write,
|
||||
.num_blocks = file_num_blocks,
|
||||
.eject = file_eject,
|
||||
};
|
||||
|
||||
app->usb = mass_storage_usb_start(furi_string_get_cstr(file_name), fn);
|
||||
|
||||
furi_string_free(file_name);
|
||||
|
||||
mass_storage_app_show_loading_popup(app, false);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MassStorageAppViewWork);
|
||||
}
|
||||
|
||||
void mass_storage_scene_work_on_exit(void* context) {
|
||||
MassStorageApp* app = context;
|
||||
mass_storage_app_show_loading_popup(app, true);
|
||||
|
||||
if(app->usb_mutex) {
|
||||
furi_mutex_free(app->usb_mutex);
|
||||
app->usb_mutex = NULL;
|
||||
}
|
||||
if(app->usb) {
|
||||
mass_storage_usb_stop(app->usb);
|
||||
app->usb = NULL;
|
||||
}
|
||||
if(app->file) {
|
||||
storage_file_free(app->file);
|
||||
app->file = NULL;
|
||||
}
|
||||
mass_storage_app_show_loading_popup(app, false);
|
||||
}
|
||||
122
applications/system/mass_storage/views/mass_storage_view.c
Normal file
122
applications/system/mass_storage/views/mass_storage_view.c
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "mass_storage_view.h"
|
||||
#include "../mass_storage_app_i.h"
|
||||
#include <gui/elements.h>
|
||||
|
||||
struct MassStorage {
|
||||
View* view;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
FuriString *file_name, *status_string;
|
||||
uint32_t read_speed, write_speed;
|
||||
uint32_t bytes_read, bytes_written;
|
||||
uint32_t update_time;
|
||||
} MassStorageModel;
|
||||
|
||||
static void append_suffixed_byte_count(FuriString* string, uint32_t count) {
|
||||
if(count < 1024) {
|
||||
furi_string_cat_printf(string, "%luB", count);
|
||||
} else if(count < 1024 * 1024) {
|
||||
furi_string_cat_printf(string, "%luK", count / 1024);
|
||||
} else if(count < 1024 * 1024 * 1024) {
|
||||
furi_string_cat_printf(string, "%.1fM", (double)count / (1024 * 1024));
|
||||
} else {
|
||||
furi_string_cat_printf(string, "%.1fG", (double)count / (1024 * 1024 * 1024));
|
||||
}
|
||||
}
|
||||
|
||||
static void mass_storage_draw_callback(Canvas* canvas, void* _model) {
|
||||
MassStorageModel* model = _model;
|
||||
|
||||
canvas_draw_icon(canvas, 8, 14, &I_Drive_112x35);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, canvas_width(canvas) / 2, 0, AlignCenter, AlignTop, "USB Mass Storage");
|
||||
|
||||
canvas_set_font(canvas, FontBatteryPercent);
|
||||
elements_string_fit_width(canvas, model->file_name, 89 - 2);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 92, 24, AlignRight, AlignBottom, furi_string_get_cstr(model->file_name));
|
||||
|
||||
furi_string_set_str(model->status_string, "R:");
|
||||
append_suffixed_byte_count(model->status_string, model->bytes_read);
|
||||
if(model->read_speed) {
|
||||
furi_string_cat_str(model->status_string, "/");
|
||||
append_suffixed_byte_count(model->status_string, model->read_speed);
|
||||
furi_string_cat_str(model->status_string, "s");
|
||||
}
|
||||
canvas_draw_str(canvas, 14, 34, furi_string_get_cstr(model->status_string));
|
||||
|
||||
furi_string_set_str(model->status_string, "W:");
|
||||
append_suffixed_byte_count(model->status_string, model->bytes_written);
|
||||
if(model->write_speed) {
|
||||
furi_string_cat_str(model->status_string, "/");
|
||||
append_suffixed_byte_count(model->status_string, model->write_speed);
|
||||
furi_string_cat_str(model->status_string, "s");
|
||||
}
|
||||
canvas_draw_str(canvas, 14, 43, furi_string_get_cstr(model->status_string));
|
||||
}
|
||||
|
||||
MassStorage* mass_storage_alloc() {
|
||||
MassStorage* mass_storage = malloc(sizeof(MassStorage));
|
||||
|
||||
mass_storage->view = view_alloc();
|
||||
view_allocate_model(mass_storage->view, ViewModelTypeLocking, sizeof(MassStorageModel));
|
||||
with_view_model(
|
||||
mass_storage->view,
|
||||
MassStorageModel * model,
|
||||
{
|
||||
model->file_name = furi_string_alloc();
|
||||
model->status_string = furi_string_alloc();
|
||||
},
|
||||
false);
|
||||
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);
|
||||
with_view_model(
|
||||
mass_storage->view,
|
||||
MassStorageModel * model,
|
||||
{
|
||||
furi_string_free(model->file_name);
|
||||
furi_string_free(model->status_string);
|
||||
},
|
||||
false);
|
||||
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, FuriString* name) {
|
||||
furi_assert(name);
|
||||
with_view_model(
|
||||
mass_storage->view,
|
||||
MassStorageModel * model,
|
||||
{ furi_string_set(model->file_name, name); },
|
||||
true);
|
||||
}
|
||||
|
||||
void mass_storage_set_stats(MassStorage* mass_storage, uint32_t read, uint32_t written) {
|
||||
with_view_model(
|
||||
mass_storage->view,
|
||||
MassStorageModel * model,
|
||||
{
|
||||
uint32_t now = furi_get_tick();
|
||||
model->read_speed = (read - model->bytes_read) * 1000 / (now - model->update_time);
|
||||
model->write_speed =
|
||||
(written - model->bytes_written) * 1000 / (now - model->update_time);
|
||||
model->bytes_read = read;
|
||||
model->bytes_written = written;
|
||||
model->update_time = now;
|
||||
},
|
||||
true);
|
||||
}
|
||||
15
applications/system/mass_storage/views/mass_storage_view.h
Normal file
15
applications/system/mass_storage/views/mass_storage_view.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#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, FuriString* name);
|
||||
|
||||
void mass_storage_set_stats(MassStorage* mass_storage, uint32_t read, uint32_t written);
|
||||
Reference in New Issue
Block a user