Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions applications/external/mass_storage/application.fam
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
App(
appid="mass_storage",
name="Mass Storage",
apptype=FlipperAppType.EXTERNAL,
entry_point="mass_storage_app",
requires=[
"gui",
"dialogs",
],
stack_size=2 * 1024,
order=20,
fap_icon="hid_usb_10px.png",
fap_libs=["assets"],
fap_category="USB",
)
222 changes: 222 additions & 0 deletions applications/external/mass_storage/helpers/mass_storage_scsi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#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_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) & 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_I(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_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;
}
}
56 changes: 56 additions & 0 deletions applications/external/mass_storage/helpers/mass_storage_scsi.h
Original file line number Diff line number Diff line change
@@ -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);
Loading