Add USB DISK mode: SD card as USB mass storage (tinySA ULTRA)

The tinySA ULTRA now enumerates as a composite device: the CDC serial
port plus a mass storage interface (Bulk-Only Transport, SCSI). The SD
card is only presented to the PC in USB DISK mode (STORAGE menu or shell
command 'usbdisk on|off'), which stops sweeping and screen updates so the
mass storage thread owns SPI1 and spi_buffer. Outside the mode the PC
sees an empty card reader, so the serial port never re-enumerates. The
mode ends on touch, jog button, eject from the PC, card removal or
'usbdisk off'.

- usb_msc.c/h: mass storage thread, BOT state machine, SCSI commands
- usbcfg.c: composite descriptors (IAD, bcdDevice 0x0201), bulk EP3,
  requests hook, packet memory reuse on a repeated SET_CONFIGURATION and
  data toggle reset on CLEAR_FEATURE (both specific to this ChibiOS fork)
- ui.c: USB DISK menu item and mode screen
- main.c: 'usbdisk' command; shell commands that could use SPI1 are
  refused while the mode is active

F072 builds are unchanged. Tested on HW V0.5.4 with Windows 11: about
620 KB/s read and 370 KB/s write, data verified against FatFs reads.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
main
erikkaashoek 2 days ago
parent c0d5780bb6
commit 650f53855e

@ -178,6 +178,7 @@ CSRC = $(STARTUPSRC) \
FatFs/ff.c \
FatFs/ffunicode.c \
usbcfg.c \
usb_msc.c \
NANOVNA_STM32_F303/adc.c \
main.c plot.c ui.c ili9341.c tlv320aic3204.c si5351.c numfont20x22.c Font5x7.c Font10x14.c flash.c si4468.c Font7x13b.c rtc.c
else

@ -40,6 +40,9 @@ int32_t frequencyExtra;
static BaseSequentialStream *shell_stream;
threads_queue_t shell_thread;
#ifdef __USE_USB_MSC__
volatile bool shell_direct_cmd = false; // Shell thread executes a command itself (not in the sweep thread)
#endif
// Shell new line
#define VNA_SHELL_NEWLINE_STR "\r\n"
@ -288,6 +291,14 @@ static THD_FUNCTION(Thread1, arg)
redraw_request |= REDRAW_CAL_STATUS | REDRAW_AREA | REDRAW_FREQUENCY;
}
}
#ifdef __USE_USB_MSC__
// "usbdisk on" from the shell
if (msc_enter_request) {
msc_enter_request = false;
operation_requested = OP_NONE;
usb_disk_mode();
}
#endif
// START_PROFILE
// Process UI inputs
if (!(sweep_mode & SWEEP_SELFTEST)) {
@ -1135,6 +1146,25 @@ VNA_SHELL_FUNCTION(cmd_sd_delete)
}
#endif
#ifdef __USE_USB_MSC__
// USB DISK mode: SD card as USB mass storage, runs in the sweep thread until "usbdisk off"
VNA_SHELL_FUNCTION(cmd_usbdisk)
{
if (argc == 0) {
shell_printf("%s\r\n", msc_disk_mode ? "on" : "off");
return;
}
int m = generic_option_cmd("usbdisk", "off|on", argc, argv[0]);
if (m == 1 && !msc_disk_mode) {
chSysLock();
msc_enter_request = true;
operation_requested|=OP_CONSOLE; // abort the current sweep
chSysUnlock();
} else if (m == 0 && msc_disk_mode)
msc_exit_request = true;
}
#endif
config_t config = {
.magic = CONFIG_MAGIC,
.dac_value = 1922,
@ -2478,10 +2508,15 @@ typedef struct {
#define CMD_WAIT_MUTEX 1
#define CMD_RUN_IN_LOAD 2
#define CMD_RUN_IN_UI 4
#ifdef __USE_USB_MSC__
#define CMD_DISK_OK 8 // allowed in USB DISK mode, does not use SPI1 or spi_buffer
#else
#define CMD_DISK_OK 0
#endif
static const VNAShellCommand commands[] =
{
{"version" , cmd_version , 0},
{"reset" , cmd_reset , 0},
{"version" , cmd_version , CMD_DISK_OK},
{"reset" , cmd_reset , CMD_DISK_OK},
{"freq" , cmd_freq , CMD_WAIT_MUTEX | CMD_RUN_IN_LOAD},
#ifdef __USE_RTC__
{"time" , cmd_time , CMD_RUN_IN_LOAD},
@ -2537,17 +2572,17 @@ static const VNAShellCommand commands[] =
#endif
{"capture" , cmd_capture , CMD_WAIT_MUTEX | CMD_RUN_IN_UI},
#ifdef __REMOTE_DESKTOP__
{"refresh" , cmd_refresh , 0},
{"touch" , cmd_touch , 0},
{"release" , cmd_release , 0},
{"refresh" , cmd_refresh , CMD_DISK_OK},
{"touch" , cmd_touch , CMD_DISK_OK},
{"release" , cmd_release , CMD_DISK_OK},
#endif
{"vbat" , cmd_vbat , CMD_WAIT_MUTEX}, // Uses same adc as touch!!!!!
#ifdef ENABLE_VBAT_OFFSET_COMMAND
{"vbat_offset" , cmd_vbat_offset , CMD_RUN_IN_LOAD},
#endif
{"help" , cmd_help , 0},
{"help" , cmd_help , CMD_DISK_OK},
#ifdef ENABLE_INFO_COMMAND
{"info" , cmd_info , 0},
{"info" , cmd_info , CMD_DISK_OK},
#endif
#ifdef ENABLE_COLOR_COMMAND
{"color" , cmd_color , CMD_RUN_IN_LOAD},
@ -2597,8 +2632,11 @@ static const VNAShellCommand commands[] =
{ "sd_read", cmd_sd_read, CMD_WAIT_MUTEX },
{ "sd_delete", cmd_sd_delete, CMD_WAIT_MUTEX },
#endif
#ifdef __USE_USB_MSC__
{ "usbdisk", cmd_usbdisk, CMD_DISK_OK },
#endif
#ifdef ENABLE_THREADS_COMMAND
{"threads" , cmd_threads , 0},
{"threads" , cmd_threads , CMD_DISK_OK},
#endif
#ifdef __SINGLE_LETTER__
{ "y", cmd_y, CMD_WAIT_MUTEX },
@ -2742,6 +2780,13 @@ static void shell_init_connection(void) {
*/
shell_update_speed();
#ifdef __USE_USB_MSC__
/*
* Start the USB mass storage thread before the host can configure the device
*/
msc_init();
#endif
/*
* Activates the USB driver and then the USB bus pull-up on D+.
* Note, a delay is inserted in order to not have to disconnect the cable
@ -2781,6 +2826,13 @@ static void shell_init_connection(void){
sduObjectInit(&SDU1);
sduStart(&SDU1, &serusbcfg);
#ifdef __USE_USB_MSC__
/*
* Start the USB mass storage thread before the host can configure the device
*/
msc_init();
#endif
/*
* Activates the USB driver and then the USB bus pull-up on D+.
* Note, a delay is inserted in order to not have to disconnect the cable
@ -2893,6 +2945,19 @@ static void VNAShell_executeLine(char *line)
uint16_t cmd_flag = scp->flags;
// Skip wait mutex if process UI
if ((cmd_flag & CMD_RUN_IN_UI) && (sweep_mode&SWEEP_UI_MODE)) cmd_flag&=~CMD_WAIT_MUTEX;
#ifdef __USE_USB_MSC__
// In USB DISK mode SPI1 and spi_buffer belong to the mass storage, only run commands that do not use them.
// shell_direct_cmd tells usb_disk_mode() to wait for a command already running in this thread.
chSysLock();
bool refuse = msc_disk_mode && !(cmd_flag & CMD_DISK_OK);
if (!refuse && !(cmd_flag & CMD_WAIT_MUTEX))
shell_direct_cmd = true;
chSysUnlock();
if (refuse) {
shell_printf("usb disk mode active" VNA_SHELL_NEWLINE_STR VNA_SHELL_PROMPT_STR);
return;
}
#endif
if (cmd_flag & CMD_WAIT_MUTEX) {
chSysLock();
shell_function = scp->sc_function;
@ -2925,13 +2990,20 @@ static void VNAShell_executeLine(char *line)
operation_requested = false; // otherwise commands will be aborted
scp->sc_function(shell_nargs - 1, &shell_args[1]);
shell_printf(VNA_SHELL_PROMPT_STR);
#ifdef __USE_USB_MSC__
if (dirty && !msc_disk_mode) {
#else
if (dirty) {
#endif
operation_requested = true; // ensure output is updated
if (MODE_OUTPUT(setting.mode))
draw_menu(); // update screen if in output mode and dirty
else
redraw_request |= REDRAW_CAL_STATUS | REDRAW_AREA | REDRAW_FREQUENCY;
}
#ifdef __USE_USB_MSC__
shell_direct_cmd = false; // after draw_menu(), which uses SPI1
#endif
}
return;
}

@ -109,6 +109,7 @@
//#define __SD_CARD_LOAD__ // Allow run commands from SD card (config.ini in root), if enabled __SD_FILE_BROWSER__ scripts run from *.cmd in it
#define __SD_CARD_DUMP_FIRMWARE__ // Allow dump firmware to SD card
#define __SD_FILE_BROWSER__
#define __USE_USB_MSC__ // USB DISK mode: SD card as USB mass storage next to the USB serial port
#define __LCD_BRIGHTNESS__ // LCD or hardware allow change brightness, add menu item for this
#define __HARMONIC__
#define __NOISE_FIGURE__
@ -1768,6 +1769,22 @@ void save_csv(uint8_t mask);
// not come from SD. Used to default the name when storing a preset back to SD.
extern char sd_preset_path[FF_LFN_BUF];
#endif
#ifdef __USE_USB_MSC__
// USB mass storage (usb_msc.c). While USB DISK mode is active the MSC thread owns SPI1 and spi_buffer.
extern volatile bool msc_disk_mode; // USB DISK mode active
extern volatile bool msc_enter_request; // set by "usbdisk on", handled in the sweep thread
extern volatile bool msc_exit_request; // set by "usbdisk off", handled by usb_disk_mode()
extern volatile bool shell_direct_cmd; // shell thread is executing a command directly
void msc_init(void);
void msc_medium_start(uint32_t blocks);
void msc_medium_stop(void);
bool msc_eject_requested(void);
void usb_disk_mode(void);
void clock_at_48MHz(void);
#endif
#endif
#if defined(__USE_USB_MSC__) && !defined(__USE_SD_CARD__)
#error "__USE_USB_MSC__ needs __USE_SD_CARD__"
#endif
/*

87
ui.c

@ -4393,6 +4393,90 @@ static UI_FUNCTION_CALLBACK(menu_save_traces_cb) {
sa_save_file(FMT_CSV_FILE);
}
#ifdef __USE_USB_MSC__
// USB DISK mode: the SD card is a USB drive on the PC (usb_msc.c), sweeping and screen updates stop.
// While the medium is ready the mass storage thread owns SPI1 and spi_buffer, so nothing is drawn.
void usb_disk_mode(void)
{
int x = 10, y = 10;
DWORD blocks = 0;
const char *error = NULL;
// A command running in the shell thread could still use SPI1, wait until it is finished
chSysLock();
msc_disk_mode = true;
chSysUnlock();
systime_t start = chVTGetSystemTimeX();
while (shell_direct_cmd && chVTGetSystemTimeX() - start < MS2ST(2000))
chThdSleepMilliseconds(5);
if (shell_direct_cmd)
error = "busy, try again";
else if (!SD_Inserted())
error = "no SD card";
else {
#ifdef __MCU_CLOCK_SHIFT__
clock_at_48MHz(); // The sweep shifts the MCU clock, which also clocks USB
#endif
SD_PowerOff(); // Force a full card initialization
if ((disk_initialize(0) & STA_NOINIT) || disk_ioctl(0, GET_SECTOR_COUNT, &blocks) != RES_OK || blocks == 0)
error = "SD card error";
}
ili9341_set_foreground(LCD_FG_COLOR);
ili9341_set_background(LCD_BG_COLOR);
ili9341_clear_screen();
lcd_set_font(FONT_NORMAL);
if (error) {
lcd_printf(x, y, "USB DISK: %s", error);
lcd_printf(x, y + 2*bFONT_STR_HEIGHT, "Touch screen or press to continue");
} else {
lcd_printf(x, y, "USB DISK MODE");
y += 2*bFONT_STR_HEIGHT;
lcd_printf(x, y, "The SD card (%d MB) is a USB drive on the PC.", (int)(blocks / 2048));
y += bFONT_STR_HEIGHT;
lcd_printf(x, y, "Measurements are stopped.");
y += 2*bFONT_STR_HEIGHT;
lcd_printf(x, y, "Eject the drive on the PC,");
y += bFONT_STR_HEIGHT;
lcd_printf(x, y, "then touch the screen or press to exit.");
}
lcd_set_font(FONT_SMALL);
ili9341_bulk_finish(); // No SPI1 use from here until msc_medium_stop()
msc_exit_request = false;
if (!error)
msc_medium_start(blocks);
while (true) {
if (touch_check() == EVT_TOUCH_PRESSED)
break;
if (btn_check() & EVT_BUTTON_SINGLE_CLICK)
break;
if (msc_exit_request)
break;
if (!error && (msc_eject_requested() || !SD_Inserted()))
break;
chThdSleepMilliseconds(40);
#ifdef __WATCHDOG__
wdgReset(&WDGD1);
#endif
}
if (!error)
msc_medium_stop();
SD_PowerOff(); // The PC may have changed the card
msc_exit_request = false;
msc_enter_request = false;
msc_disk_mode = false;
touch_wait_release();
dirty = true; // Restore MCU clock and hardware settings on the next sweep
ui_mode_normal();
redraw_frame();
request_to_redraw_grid();
}
static UI_FUNCTION_CALLBACK(menu_usb_disk_cb) {
(void)item;
(void)data;
usb_disk_mode();
}
#endif
static UI_FUNCTION_ADV_CALLBACK(menu_autoname_acb)
@ -5434,6 +5518,9 @@ static const menuitem_t menu_storage[] = {
{ MT_CALLBACK, FMT_PRS_FILE, "SAVE\nSETTINGS", menu_sdcard_cb},
{ MT_CALLBACK, FMT_CFG_FILE, "SAVE\nCONFIG", menu_sdcard_cb},
{ MT_CALLBACK, FMT_CSV_FILE, "SAVE\nTRACES", menu_save_traces_cb},
#ifdef __USE_USB_MSC__
{ MT_CALLBACK, 0, "USB\nDISK", menu_usb_disk_cb},
#endif
{ MT_SUBMENU, 0, "CONFIG", menu_storage_config },
// { MT_KEYPAD, KM_INTERVAL, "INTERVAL\n\b%s", NULL },
{ MT_NONE, 0, NULL, menu_back} // next-> menu_back

@ -0,0 +1,648 @@
/*
* USB mass storage (Bulk-Only Transport, SCSI transparent command set) for the tinySA ULTRA SD card
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* The software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* The SD card is exposed as a removable disk while "USB DISK" mode is active (usb_disk_mode() in ui.c).
* Outside that mode the LUN reports "medium not present", like an empty card reader, so the
* composite CDC + MSC device never has to re-enumerate.
* While the medium is ready the MSC thread owns SPI1 and spi_buffer: the sweep thread only polls
* touch/buttons and the shell refuses commands that could use SPI1.
*
* The endpoint register handling (data toggle reset on CLEAR_FEATURE, manual transfer abort) is
* specific to the USBv1 driver of this ChibiOS fork: re-check it after a ChibiOS upgrade.
*/
#include "ch.h"
#include "hal.h"
#include "nanovna.h"
#include <string.h>
#ifdef __USE_USB_MSC__
#include "usb_msc.h"
#define MSC_PACKET_SIZE 64
#define MSC_SECTOR_SIZE 512
#define MSC_CHUNK_SECTORS (sizeof(spi_buffer) / MSC_SECTOR_SIZE)
#define MSC_IO_STOP_TIMEOUT MS2ST(2000) // Max wait for a transfer in progress when leaving USB DISK mode
// Bulk-Only Transport
#define CBW_SIGNATURE 0x43425355 // "USBC"
#define CSW_SIGNATURE 0x53425355 // "USBS"
#define CBW_LENGTH 31
#define CSW_LENGTH 13
#define CBW_FLAGS_IN 0x80
#define CSW_PASSED 0
#define CSW_FAILED 1
#define CSW_PHASE_ERROR 2
#define CSW_NONE 0xFF // Transfer aborted by a reset, no CSW
#define MSC_REQ_GET_MAX_LUN 0xFE
#define MSC_REQ_RESET 0xFF
// SCSI commands
#define SCSI_TEST_UNIT_READY 0x00
#define SCSI_REQUEST_SENSE 0x03
#define SCSI_INQUIRY 0x12
#define SCSI_MODE_SENSE6 0x1A
#define SCSI_START_STOP_UNIT 0x1B
#define SCSI_PREVENT_ALLOW 0x1E
#define SCSI_READ_FORMAT_CAP 0x23
#define SCSI_READ_CAPACITY10 0x25
#define SCSI_READ10 0x28
#define SCSI_WRITE10 0x2A
#define SCSI_VERIFY10 0x2F
#define SCSI_SYNC_CACHE10 0x35
#define SCSI_MODE_SENSE10 0x5A
// Sense key, additional sense code and qualifier
#define SENSE(key, asc, ascq) (((uint32_t)(key) << 16) | ((asc) << 8) | (ascq))
#define SENSE_NONE SENSE(0x00, 0x00, 0x00)
#define SENSE_NOT_PRESENT SENSE(0x02, 0x3A, 0x00)
#define SENSE_READ_ERROR SENSE(0x03, 0x11, 0x00)
#define SENSE_WRITE_ERROR SENSE(0x03, 0x0C, 0x00)
#define SENSE_INVALID_OPCODE SENSE(0x05, 0x20, 0x00)
#define SENSE_LBA_RANGE SENSE(0x05, 0x21, 0x00)
#define SENSE_INVALID_FIELD SENSE(0x05, 0x24, 0x00)
#define SENSE_MEDIUM_CHANGED SENSE(0x06, 0x28, 0x00)
// Data phase direction announced by the host
#define DIR_NONE 0
#define DIR_IN 1
#define DIR_OUT 2
// Wake up reasons of the MSC thread
#define EV_IN_DONE 0x01
#define EV_OUT_DONE 0x02
#define EV_HALT_CLEARED 0x04
typedef struct __attribute__((packed)) {
uint32_t signature;
uint32_t tag;
uint32_t data_length;
uint8_t flags;
uint8_t lun;
uint8_t cb_length;
uint8_t cb[16];
} msc_cbw_t;
typedef struct __attribute__((packed)) {
uint32_t signature;
uint32_t tag;
uint32_t residue;
uint8_t status;
} msc_csw_t;
// The USB driver copies whole packets, so the CBW buffer must hold a full packet
static union {
uint8_t raw[MSC_PACKET_SIZE];
msc_cbw_t cbw;
} cbw_buf __attribute__((aligned(4)));
static union {
uint8_t raw[16];
msc_csw_t csw;
} csw_buf __attribute__((aligned(4)));
// Response data of all commands except READ/WRITE (spi_buffer is only used while the medium is ready)
static uint8_t resp[36] __attribute__((aligned(4)));
static const uint8_t inquiry_data[36] = {
0x00, // Direct access block device
0x80, // Removable medium
0x02, // Version
0x02, // Response data format
36 - 5, // Additional length
0x00, 0x00, 0x00,
't','i','n','y','S','A',' ',' ', // Vendor
'U','L','T','R','A',' ','S','D',' ','c','a','r','d',' ',' ',' ', // Product
'1','.','0','0' // Revision
};
static USBInEndpointState ep3in;
static USBOutEndpointState ep3out;
static thread_reference_t msc_tr = NULL;
static volatile uint8_t msc_ev; // EV_xxx bits
static volatile uint16_t msc_epoch; // Changes on USB reset, configuration, BOT reset and forced abort
static volatile bool msc_configured; // EP3 initialized
static volatile bool msc_need_reset; // Invalid CBW: keep both pipes stalled until a BOT reset
static volatile bool msc_ready; // Medium present (USB DISK mode)
static volatile bool msc_ua; // Unit attention pending (medium changed)
static volatile bool msc_eject; // Host ejected the medium
static volatile bool msc_io; // READ/WRITE in progress, uses SPI1 and spi_buffer
static volatile uint32_t msc_blocks; // Medium size in sectors
static uint32_t msc_sense; // Sense data of the last failed command
volatile bool msc_disk_mode = false;
volatile bool msc_enter_request = false;
volatile bool msc_exit_request = false;
static void msc_in_cb(USBDriver *usbp, usbep_t ep);
static void msc_out_cb(USBDriver *usbp, usbep_t ep);
const USBEndpointConfig msc_ep_config = {
USB_EP_MODE_TYPE_BULK,
NULL,
msc_in_cb,
msc_out_cb,
MSC_PACKET_SIZE,
MSC_PACKET_SIZE,
&ep3in,
&ep3out
};
static inline uint32_t umin(uint32_t a, uint32_t b) { return a < b ? a : b; }
static inline uint16_t get_be16(const uint8_t *p) { return ((uint16_t)p[0] << 8) | p[1]; }
static inline uint32_t get_be32(const uint8_t *p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]; }
static inline void put_be32(uint8_t *p, uint32_t v) { p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v; }
//*******************************************************
// ISR side: endpoint callbacks, requests hook, usb_event
//*******************************************************
static void msc_wakeup_I(uint8_t ev) {
msc_ev |= ev;
osalThreadResumeI(&msc_tr, MSG_OK);
}
// Invalidate transfers of the previous epoch
static void msc_new_epoch_I(void) {
msc_epoch++;
msc_ev = 0;
osalThreadResumeI(&msc_tr, MSG_RESET);
}
static void msc_in_cb(USBDriver *usbp, usbep_t ep) {
(void)usbp;
(void)ep;
osalSysLockFromISR();
msc_wakeup_I(EV_IN_DONE);
osalSysUnlockFromISR();
}
static void msc_out_cb(USBDriver *usbp, usbep_t ep) {
(void)usbp;
(void)ep;
osalSysLockFromISR();
msc_wakeup_I(EV_OUT_DONE);
osalSysUnlockFromISR();
}
// Cancel the transfers in progress on the bulk endpoints (the driver has no abort function)
static void msc_abort_ep_I(void) {
if (!msc_configured)
return;
if ((STM32_USB->EPR[MSC_EP] & EPR_STAT_TX_MASK) == EPR_STAT_TX_VALID)
EPR_SET_STAT_TX(MSC_EP, EPR_STAT_TX_NAK);
if ((STM32_USB->EPR[MSC_EP] & EPR_STAT_RX_MASK) == EPR_STAT_RX_VALID)
EPR_SET_STAT_RX(MSC_EP, EPR_STAT_RX_NAK);
ep3in.txsize = ep3in.txcnt; // A pending IN completion ends the transfer
ep3out.rxbuf = cbw_buf.raw; // A pending OUT packet lands in the CBW buffer, not in spi_buffer
ep3out.rxcnt = 0;
ep3out.rxsize = MSC_PACKET_SIZE;
USBD1.transmitting &= ~(1U << MSC_EP);
USBD1.receiving &= ~(1U << MSC_EP);
}
// CLEAR_FEATURE(ENDPOINT_HALT): reset the data toggle to DATA0 (USB 2.0 9.4.5, not done by the driver)
// and change STALL to NAK, unless the pipes must stay stalled until a BOT reset. VALID is left alone.
static void msc_clear_halt_I(bool in) {
uint32_t epr = STM32_USB->EPR[MSC_EP];
uint32_t tog; // Toggle bits, writing 1 flips them
if (in) {
tog = epr & EPR_DTOG_TX;
if (!msc_need_reset && (epr & EPR_STAT_TX_MASK) == EPR_STAT_TX_STALL)
tog |= EPR_STAT_TX_STALL ^ EPR_STAT_TX_NAK;
} else {
tog = epr & EPR_DTOG_RX;
if (!msc_need_reset && (epr & EPR_STAT_RX_MASK) == EPR_STAT_RX_STALL)
tog |= EPR_STAT_RX_STALL ^ EPR_STAT_RX_NAK;
}
// Keep type and address, write 0 to the other toggle bits and 1 to the CTR flags (no change)
STM32_USB->EPR[MSC_EP] = (epr & ~EPR_TOGGLE_MASK) | EPR_CTR_MASK | tog;
}
// Called from the setup packet ISR without the system lock
bool msc_requests_hook(USBDriver *usbp) {
const setup_pack_t *s = &usbp->setup;
uint8_t type = s->bmRequestType & (USB_RTYPE_TYPE_MASK | USB_RTYPE_RECIPIENT_MASK);
bool to_host = (s->bmRequestType & USB_RTYPE_DIR_MASK) == USB_RTYPE_DIR_DEV2HOST;
if (type == (USB_RTYPE_TYPE_CLASS | USB_RTYPE_RECIPIENT_INTERFACE) && (s->wIndex & 0xFF) == MSC_IF) {
if (s->bRequest == MSC_REQ_GET_MAX_LUN && to_host) {
static const uint8_t max_lun = 0;
usbSetupTransfer(usbp, (uint8_t *)&max_lun, 1, NULL);
return true;
}
if (s->bRequest == MSC_REQ_RESET && !to_host) {
// Bulk-Only Mass Storage Reset: cancel the transfers, the host clears the halts next
osalSysLockFromISR();
msc_abort_ep_I();
msc_need_reset = false;
msc_new_epoch_I();
osalSysUnlockFromISR();
usbSetupTransfer(usbp, NULL, 0, NULL);
return true;
}
return false;
}
if (type == (USB_RTYPE_TYPE_STD | USB_RTYPE_RECIPIENT_ENDPOINT) && s->bRequest == USB_REQ_CLEAR_FEATURE &&
s->wValue == USB_FEATURE_ENDPOINT_HALT && (s->wIndex & 0x0F) == MSC_EP && msc_configured) {
osalSysLockFromISR();
msc_clear_halt_I((s->wIndex & 0x80) != 0);
msc_wakeup_I(EV_HALT_CLEARED);
osalSysUnlockFromISR();
usbSetupTransfer(usbp, NULL, 0, NULL);
return true;
}
return false;
}
void msc_usb_reset_I(void) {
msc_configured = false;
msc_need_reset = false;
msc_new_epoch_I();
}
void msc_configured_I(bool configured) {
msc_configured = configured;
msc_need_reset = false;
msc_new_epoch_I();
}
//*******************************************************
// MSC thread side
//*******************************************************
// Wait (system locked) for an event of this epoch, false if the transfer was aborted
static bool msc_wait_S(uint8_t ev, uint16_t epoch) {
while (!(msc_ev & ev)) {
if (msc_epoch != epoch || !msc_configured)
return false;
osalThreadSuspendS(&msc_tr);
}
return msc_epoch == epoch;
}
// Wait (system locked) until the host has cleared a halt of the endpoint
static bool msc_wait_not_stalled_S(uint32_t mask, uint32_t stall, uint16_t epoch) {
while (msc_epoch == epoch && msc_configured && (STM32_USB->EPR[MSC_EP] & mask) == stall) {
msc_ev &= ~EV_HALT_CLEARED;
if (!msc_wait_S(EV_HALT_CLEARED, epoch))
return false;
}
return msc_epoch == epoch && msc_configured;
}
// Send data on the bulk IN endpoint and wait until the host has read it
static bool msc_tx(const uint8_t *buf, uint32_t n, uint16_t epoch) {
chSysLock();
bool ok = msc_wait_not_stalled_S(EPR_STAT_TX_MASK, EPR_STAT_TX_STALL, epoch);
if (ok) {
msc_ev &= ~EV_IN_DONE;
usbStartTransmitI(&USBD1, MSC_EP, buf, n);
ok = msc_wait_S(EV_IN_DONE, epoch);
}
chSysUnlock();
return ok;
}
// Receive up to n bytes (multiple of the packet size) on the bulk OUT endpoint
// Returns the received size, -1 if the transfer was aborted
static int32_t msc_rx(uint8_t *buf, uint32_t n, uint16_t epoch) {
int32_t cnt = -1;
chSysLock();
if (msc_wait_not_stalled_S(EPR_STAT_RX_MASK, EPR_STAT_RX_STALL, epoch)) {
msc_ev &= ~EV_OUT_DONE;
usbStartReceiveI(&USBD1, MSC_EP, buf, n);
if (msc_wait_S(EV_OUT_DONE, epoch))
cnt = ep3out.rxcnt;
}
chSysUnlock();
return cnt;
}
// Stall the pipe in which the host expects more data
static void msc_stall(uint8_t dir, uint16_t epoch) {
chSysLock();
if (msc_epoch == epoch && msc_configured) {
if (dir == DIR_IN)
usbStallTransmitI(&USBD1, MSC_EP);
else
usbStallReceiveI(&USBD1, MSC_EP);
}
chSysUnlock();
}
static bool msc_medium_ready(void) {
if (msc_ready && SD_Inserted())
return true;
msc_sense = SENSE_NOT_PRESENT;
return false;
}
// Report the medium change once to the first command after entering USB DISK mode
static bool msc_unit_attention(void) {
chSysLock();
bool ua = msc_ua;
msc_ua = false;
chSysUnlock();
if (ua)
msc_sense = SENSE_MEDIUM_CHANGED;
return ua;
}
// Start a READ/WRITE data phase, only allowed while the medium is ready
static bool msc_io_begin(void) {
chSysLock();
msc_io = msc_ready;
chSysUnlock();
if (msc_io && SD_Inserted())
return true;
msc_io = false;
msc_sense = SENSE_NOT_PRESENT;
return false;
}
// READ(10) / WRITE(10): the data goes through spi_buffer in chunks of up to MSC_CHUNK_SECTORS
static uint8_t msc_read_write(const uint8_t *cb, uint8_t dir, uint32_t length, uint32_t *done, uint16_t epoch) {
bool write = cb[0] == SCSI_WRITE10;
uint32_t lba = get_be32(&cb[2]);
uint32_t count = get_be16(&cb[7]);
if (msc_unit_attention())
return CSW_FAILED;
if (dir == DIR_NONE) // Host expects no data
return count ? CSW_PHASE_ERROR : CSW_PASSED;
if ((dir == DIR_OUT) != write || length < count * MSC_SECTOR_SIZE) // Wrong direction or host expects less data
return CSW_PHASE_ERROR;
if (!msc_io_begin())
return CSW_FAILED;
uint8_t status = CSW_PASSED;
if (lba + count > msc_blocks || lba + count < lba) {
msc_sense = SENSE_LBA_RANGE;
status = CSW_FAILED;
count = 0;
}
uint8_t *buf = (uint8_t *)spi_buffer;
while (count) {
uint32_t n = umin(count, MSC_CHUNK_SECTORS);
uint32_t size = n * MSC_SECTOR_SIZE;
if (!msc_medium_ready()) { // Leaving USB DISK mode or card removed
status = CSW_FAILED;
break;
}
if (write) {
int32_t r = msc_rx(buf, size, epoch);
if (r < 0) {status = CSW_NONE; break;}
if ((uint32_t)r != size) {status = CSW_PHASE_ERROR; break;} // Short packet, host sent less than announced
if (disk_write(0, buf, lba, n) != RES_OK) {msc_sense = SENSE_WRITE_ERROR; status = CSW_FAILED; break;}
} else {
if (disk_read(0, buf, lba, n) != RES_OK) {msc_sense = SENSE_READ_ERROR; status = CSW_FAILED; break;}
if (!msc_tx(buf, size, epoch)) {status = CSW_NONE; break;}
}
*done += size;
lba += n;
count -= n;
}
msc_io = false;
return status;
}
// All commands except READ/WRITE, the response goes to resp[] and *n is its size
static uint8_t msc_scsi(const uint8_t *cb, uint32_t *n) {
uint8_t op = cb[0];
if (op != SCSI_INQUIRY && op != SCSI_REQUEST_SENSE && msc_unit_attention())
return CSW_FAILED;
switch (op) {
case SCSI_TEST_UNIT_READY:
return msc_medium_ready() ? CSW_PASSED : CSW_FAILED;
case SCSI_REQUEST_SENSE: {
if (msc_sense == SENSE_NONE)
msc_unit_attention();
uint32_t sense = msc_sense;
msc_sense = SENSE_NONE;
memset(resp, 0, 18);
resp[0] = 0x70; // Current error, fixed format
resp[2] = sense >> 16; // Sense key
resp[7] = 18 - 8; // Additional sense length
resp[12] = sense >> 8; // Additional sense code
resp[13] = sense; // Additional sense code qualifier
*n = umin(18, cb[4]);
return CSW_PASSED;
}
case SCSI_INQUIRY: {
uint32_t len;
if (cb[1] & 0x01) { // Vital product data
memset(resp, 0, 8);
if (cb[2] == 0x00) { // Supported pages
resp[3] = 2;
resp[5] = 0x80;
len = 6;
} else if (cb[2] == 0x80) { // Unit serial number: MCU unique ID
const uint32_t *uid = (const uint32_t *)UID_BASE;
resp[1] = 0x80;
resp[3] = 24;
for (int i = 0; i < 24; i++)
resp[4 + i] = "0123456789ABCDEF"[(uid[i / 8] >> (28 - 4 * (i % 8))) & 0x0F];
len = 4 + 24;
} else {
msc_sense = SENSE_INVALID_FIELD;
return CSW_FAILED;
}
} else {
memcpy(resp, inquiry_data, sizeof(inquiry_data));
len = sizeof(inquiry_data);
}
*n = umin(len, get_be16(&cb[3]));
return CSW_PASSED;
}
case SCSI_MODE_SENSE6: // Header only: no block descriptor, not write protected
memset(resp, 0, 4);
resp[0] = 4 - 1; // Mode data length
*n = umin(4, cb[4]);
return CSW_PASSED;
case SCSI_MODE_SENSE10:
memset(resp, 0, 8);
resp[1] = 8 - 2; // Mode data length
*n = umin(8, get_be16(&cb[7]));
return CSW_PASSED;
case SCSI_START_STOP_UNIT:
if ((cb[4] & 0x03) == 0x02) { // LoEj without Start: the host ejects the medium
chSysLock();
msc_ready = false;
msc_eject = true;
chSysUnlock();
}
return CSW_PASSED;
case SCSI_PREVENT_ALLOW:
return CSW_PASSED;
case SCSI_READ_FORMAT_CAP:
if (!msc_medium_ready())
return CSW_FAILED;
memset(resp, 0, 12);
resp[3] = 8; // Capacity list length
put_be32(&resp[4], msc_blocks); // Number of blocks
resp[8] = 0x02; // Formatted media
resp[10] = MSC_SECTOR_SIZE >> 8; // Block length (24 bit)
*n = umin(12, get_be16(&cb[7]));
return CSW_PASSED;
case SCSI_READ_CAPACITY10:
if (!msc_medium_ready())
return CSW_FAILED;
put_be32(&resp[0], msc_blocks - 1); // Last LBA
put_be32(&resp[4], MSC_SECTOR_SIZE);
*n = 8;
return CSW_PASSED;
case SCSI_VERIFY10:
if (!msc_medium_ready())
return CSW_FAILED;
if (cb[1] & 0x02) { // BYTCHK: compare with host data not supported
msc_sense = SENSE_INVALID_FIELD;
return CSW_FAILED;
}
if (get_be32(&cb[2]) + get_be16(&cb[7]) > msc_blocks) {
msc_sense = SENSE_LBA_RANGE;
return CSW_FAILED;
}
return CSW_PASSED;
case SCSI_SYNC_CACHE10: // Writes are not cached
return msc_medium_ready() ? CSW_PASSED : CSW_FAILED;
default:
msc_sense = SENSE_INVALID_OPCODE;
return CSW_FAILED;
}
}
// Execute a valid CBW: data phase and CSW
static void msc_command(uint16_t epoch) {
uint8_t cb[16];
uint32_t tag = cbw_buf.cbw.tag;
uint32_t length = cbw_buf.cbw.data_length;
uint8_t dir = length == 0 ? DIR_NONE : (cbw_buf.cbw.flags & CBW_FLAGS_IN) ? DIR_IN : DIR_OUT;
uint32_t done = 0; // Bytes of the data phase transferred
uint8_t status;
memcpy(cb, cbw_buf.cbw.cb, sizeof(cb));
if (cb[0] != SCSI_REQUEST_SENSE)
msc_sense = SENSE_NONE;
if (cbw_buf.cbw.lun != 0 || cbw_buf.cbw.cb_length == 0 || cbw_buf.cbw.cb_length > 16) {
msc_sense = SENSE_INVALID_FIELD;
status = CSW_FAILED;
} else if (cb[0] == SCSI_READ10 || cb[0] == SCSI_WRITE10) {
status = msc_read_write(cb, dir, length, &done, epoch);
if (status == CSW_NONE)
return;
} else {
uint32_t n = 0;
status = msc_scsi(cb, &n);
if (n) { // The command returns data
if (dir != DIR_IN)
status = CSW_PHASE_ERROR;
else {
n = umin(n, length); // Host expects less: send what it asked for
if (!msc_tx(resp, n, epoch))
return;
done = n;
}
}
}
// Host expects more data: stall its pipe, for IN the CSW follows after the host cleared the halt
if (done < length)
msc_stall(dir, epoch);
csw_buf.csw.signature = CSW_SIGNATURE;
csw_buf.csw.tag = tag;
csw_buf.csw.residue = length - done;
csw_buf.csw.status = status;
msc_tx(csw_buf.raw, CSW_LENGTH, epoch);
}
static THD_WORKING_AREA(waMSC, 384);
static THD_FUNCTION(msc_thread, arg)
{
(void)arg;
chRegSetThreadName("msc");
while (true) {
chSysLock();
while (!msc_configured)
osalThreadSuspendS(&msc_tr);
uint16_t epoch = msc_epoch;
chSysUnlock();
// Wait for a command block wrapper
int32_t n = msc_rx(cbw_buf.raw, MSC_PACKET_SIZE, epoch);
if (n < 0)
continue;
if (n == CBW_LENGTH && cbw_buf.cbw.signature == CBW_SIGNATURE) {
msc_command(epoch);
continue;
}
// Invalid CBW: stall both pipes until the host does a Bulk-Only Mass Storage Reset
chSysLock();
if (msc_epoch == epoch) {
msc_need_reset = true;
usbStallTransmitI(&USBD1, MSC_EP);
usbStallReceiveI(&USBD1, MSC_EP);
while (msc_epoch == epoch)
osalThreadSuspendS(&msc_tr);
}
chSysUnlock();
}
}
void msc_init(void)
{
chThdCreateStatic(waMSC, sizeof(waMSC), NORMALPRIO, msc_thread, NULL);
}
//*******************************************************
// USB DISK mode control (sweep thread)
//*******************************************************
// The caller must have initialized the SD card and must not use SPI1 until msc_medium_stop()
void msc_medium_start(uint32_t blocks)
{
chSysLock();
msc_blocks = blocks;
msc_eject = false;
msc_ua = true;
msc_ready = true;
chSysUnlock();
}
// Returns when the MSC thread no longer uses SPI1 and spi_buffer
void msc_medium_stop(void)
{
chSysLock();
msc_ready = false;
msc_ua = false;
chSysUnlock();
// A READ/WRITE in progress stops at the next chunk
systime_t start = chVTGetSystemTimeX();
while (msc_io && chVTGetSystemTimeX() - start < MSC_IO_STOP_TIMEOUT)
chThdSleepMilliseconds(5);
if (msc_io) {
// The host does not move data (asleep, cable pulled): abort the transfer. Stalling both pipes
// until a BOT reset makes a host that is still connected recover at once.
chSysLock();
if (msc_configured) {
msc_abort_ep_I();
msc_need_reset = true;
usbStallTransmitI(&USBD1, MSC_EP);
usbStallReceiveI(&USBD1, MSC_EP);
}
msc_new_epoch_I();
chSchRescheduleS();
chSysUnlock();
// Only a SD card operation can still be running, it has its own timeouts
while (msc_io)
chThdSleepMilliseconds(5);
}
}
bool msc_eject_requested(void)
{
return msc_eject;
}
#endif // __USE_USB_MSC__

@ -0,0 +1,23 @@
/*
* USB mass storage (Bulk-Only Transport) glue between usbcfg.c and usb_msc.c
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*/
#ifndef _USB_MSC_H_
#define _USB_MSC_H_
#define MSC_IF 2 // USB interface number of the mass storage function
#define MSC_EP 3 // Bulk IN (0x83) / OUT (0x03) endpoint number
extern const USBEndpointConfig msc_ep_config;
// Called from usb_event() with the system locked
void msc_usb_reset_I(void);
void msc_configured_I(bool configured);
// Mass storage class requests and CLEAR_FEATURE(ENDPOINT_HALT) of the bulk endpoints
bool msc_requests_hook(USBDriver *usbp);
#endif /* _USB_MSC_H_ */

@ -15,6 +15,10 @@
*/
#include "hal.h"
#include "nanovna.h"
#ifdef __USE_USB_MSC__
#include "usb_msc.h"
#endif
/* Virtual serial port over USB.*/
SerialUSBDriver SDU1;
@ -30,6 +34,23 @@ SerialUSBDriver SDU1;
* USB Device Descriptor.
*/
static const uint8_t vcom_device_descriptor_data[18] = {
#ifdef __USE_USB_MSC__
/* Composite device: CDC (serial) + mass storage (SD card).*/
USB_DESC_DEVICE (0x0200, /* bcdUSB (2.0), for the IAD. */
0xEF, /* bDeviceClass (Miscellaneous). */
0x02, /* bDeviceSubClass (Common Class). */
0x01, /* bDeviceProtocol (IAD). */
0x40, /* bMaxPacketSize. */
0x0483, /* idVendor (ST). */
0x5740, /* idProduct. */
0x0201, /* bcdDevice, changed with the
composite layout so that Windows
selects the drivers again. */
1, /* iManufacturer. */
2, /* iProduct. */
3, /* iSerialNumber. */
1) /* bNumConfigurations. */
#else
USB_DESC_DEVICE (0x0110, /* bcdUSB (1.1). */
0x02, /* bDeviceClass (CDC). */
0x00, /* bDeviceSubClass. */
@ -42,6 +63,7 @@ static const uint8_t vcom_device_descriptor_data[18] = {
2, /* iProduct. */
3, /* iSerialNumber. */
1) /* bNumConfigurations. */
#endif
};
/*
@ -52,15 +74,32 @@ static const USBDescriptor vcom_device_descriptor = {
vcom_device_descriptor_data
};
/* Configuration Descriptor tree for a CDC.*/
static const uint8_t vcom_configuration_descriptor_data[67] = {
/* Configuration Descriptor tree for a CDC (and mass storage).*/
#ifdef __USE_USB_MSC__
#define VCOM_CONFIGURATION_SIZE 98
#define VCOM_INTERFACES 3
#else
#define VCOM_CONFIGURATION_SIZE 67
#define VCOM_INTERFACES 2
#endif
static const uint8_t vcom_configuration_descriptor_data[VCOM_CONFIGURATION_SIZE] = {
/* Configuration Descriptor.*/
USB_DESC_CONFIGURATION(67, /* wTotalLength. */
0x02, /* bNumInterfaces. */
USB_DESC_CONFIGURATION(VCOM_CONFIGURATION_SIZE, /* wTotalLength. */
VCOM_INTERFACES, /* bNumInterfaces. */
0x01, /* bConfigurationValue. */
0, /* iConfiguration. */
0xC0, /* bmAttributes (self powered). */
50), /* bMaxPower (100mA). */
#ifdef __USE_USB_MSC__
/* Interface Association Descriptor: interfaces 0 and 1 are the CDC
function (protocol 0 as in the interface descriptor).*/
USB_DESC_INTERFACE_ASSOCIATION(0x00, /* bFirstInterface. */
0x02, /* bInterfaceCount. */
0x02, /* bFunctionClass (CDC). */
0x02, /* bFunctionSubClass (ACM). */
0x00, /* bFunctionProtocol. */
0), /* iInterface. */
#endif
/* Interface Descriptor.*/
USB_DESC_INTERFACE (0x00, /* bInterfaceNumber. */
0x00, /* bAlternateSetting. */
@ -126,7 +165,29 @@ static const uint8_t vcom_configuration_descriptor_data[67] = {
USB_DESC_ENDPOINT (USBD1_DATA_REQUEST_EP|0x80, /* bEndpointAddress.*/
0x02, /* bmAttributes (Bulk). */
0x0040, /* wMaxPacketSize. */
0x00) /* bInterval. */
0x00), /* bInterval. */
#ifdef __USE_USB_MSC__
/* Mass storage Interface Descriptor.*/
USB_DESC_INTERFACE (MSC_IF, /* bInterfaceNumber. */
0x00, /* bAlternateSetting. */
0x02, /* bNumEndpoints. */
0x08, /* bInterfaceClass (Mass Storage). */
0x06, /* bInterfaceSubClass (SCSI
transparent command set). */
0x50, /* bInterfaceProtocol (Bulk-Only
Transport). */
0), /* iInterface. */
/* Mass storage bulk OUT Endpoint Descriptor.*/
USB_DESC_ENDPOINT (MSC_EP, /* bEndpointAddress. */
0x02, /* bmAttributes (Bulk). */
0x0040, /* wMaxPacketSize. */
0x00), /* bInterval. */
/* Mass storage bulk IN Endpoint Descriptor.*/
USB_DESC_ENDPOINT (MSC_EP|0x80, /* bEndpointAddress. */
0x02, /* bmAttributes (Bulk). */
0x0040, /* wMaxPacketSize. */
0x00), /* bInterval. */
#endif
};
/*
@ -320,6 +381,11 @@ static const USBEndpointConfig ep2config = {
NULL,
};
#ifdef __USE_USB_MSC__
/* First free packet memory address after the buffer table and EP0.*/
static uint32_t pm_after_ep0;
#endif
/*
* Handles the USB driver global events.
*/
@ -328,20 +394,59 @@ static void usb_event(USBDriver *usbp, usbevent_t event) {
switch (event) {
case USB_EVENT_RESET:
#ifdef __USE_USB_MSC__
{
/* usb_lld_reset() has just allocated the EP0 buffers.*/
pm_after_ep0 = usbp->pmnext;
/* Also invoked from usbStart() with the system locked.*/
syssts_t sts = chSysGetStatusAndLockX();
msc_usb_reset_I();
chSysRestoreStatusX(sts);
}
#endif
return;
case USB_EVENT_ADDRESS:
return;
case USB_EVENT_CONFIGURED:
chSysLockFromISR();
#ifdef __USE_USB_MSC__
/* Fork-specific (re-check after a ChibiOS upgrade): SET_CONFIGURATION does
not free the endpoint packet memory, a repeated one without bus reset
would overflow the 512 byte packet memory with the mass storage
endpoints. It is also raised for configuration 0.*/
usbp->pmnext = pm_after_ep0;
usbp->epc[USBD1_DATA_REQUEST_EP] = NULL;
usbp->epc[USBD1_INTERRUPT_REQUEST_EP] = NULL;
usbp->epc[MSC_EP] = NULL;
usbp->transmitting &= 1U;
usbp->receiving &= 1U;
if (usbp->state != USB_ACTIVE) {
/* SET_CONFIGURATION(0): disable the endpoints.*/
for (usbep_t ep = 1; ep <= MSC_EP; ep++) {
STM32_USB->EPR[ep] = STM32_USB->EPR[ep]; /* Clears the toggle bits.*/
STM32_USB->EPR[ep] = 0;
}
sduDisconnectI(&SDU1);
msc_configured_I(false);
chSysUnlockFromISR();
return;
}
#endif
/* Enables the endpoints specified into the configuration.
Note, this callback is invoked from an ISR so I-Class functions
must be used.*/
usbInitEndpointI(usbp, USBD1_DATA_REQUEST_EP, &ep1config);
usbInitEndpointI(usbp, USBD1_INTERRUPT_REQUEST_EP, &ep2config);
#ifdef __USE_USB_MSC__
usbInitEndpointI(usbp, MSC_EP, &msc_ep_config);
#endif
/* Resetting the state of the CDC subsystem.*/
sduConfigureHookI(&SDU1);
#ifdef __USE_USB_MSC__
msc_configured_I(true);
#endif
chSysUnlockFromISR();
return;
@ -373,13 +478,30 @@ static void sof_handler(USBDriver *usbp) {
osalSysUnlockFromISR();
}
#ifdef __USE_USB_MSC__
/*
* Handles the mass storage requests, the others go to the CDC.
*/
static bool requests_hook(USBDriver *usbp) {
if (msc_requests_hook(usbp))
return true;
/* Never let the CDC handler answer a class request for the mass storage interface.*/
if ((usbp->setup.bmRequestType & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS &&
(usbp->setup.wIndex & 0xFF) == MSC_IF)
return false;
return sduRequestsHook(usbp);
}
#else
#define requests_hook sduRequestsHook
#endif
/*
* USB driver configuration.
*/
const USBConfig usbcfg = {
usb_event,
get_descriptor,
sduRequestsHook,
requests_hook,
sof_handler
};

Loading…
Cancel
Save

Powered by TurnKey Linux.