From 650f53855ec93dd57de8df330ceaf1a248268c2e Mon Sep 17 00:00:00 2001 From: erikkaashoek Date: Sat, 26 Sep 2026 13:14:23 +0200 Subject: [PATCH] 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) --- Makefile | 1 + main.c | 88 +++++++- nanovna.h | 17 ++ ui.c | 87 ++++++++ usb_msc.c | 648 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ usb_msc.h | 23 ++ usbcfg.c | 134 ++++++++++- 7 files changed, 984 insertions(+), 14 deletions(-) create mode 100644 usb_msc.c create mode 100644 usb_msc.h diff --git a/Makefile b/Makefile index d67ecfc..b6e5c9a 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/main.c b/main.c index 1ca930e..e0a4bea 100644 --- a/main.c +++ b/main.c @@ -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; } diff --git a/nanovna.h b/nanovna.h index 1981591..36f589b 100644 --- a/nanovna.h +++ b/nanovna.h @@ -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 /* diff --git a/ui.c b/ui.c index 6ca1266..63744a0 100644 --- a/ui.c +++ b/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 diff --git a/usb_msc.c b/usb_msc.c new file mode 100644 index 0000000..692250b --- /dev/null +++ b/usb_msc.c @@ -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 + +#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__ diff --git a/usb_msc.h b/usb_msc.h new file mode 100644 index 0000000..214561a --- /dev/null +++ b/usb_msc.h @@ -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_ */ diff --git a/usbcfg.c b/usbcfg.c index 398e3cb..20d500e 100644 --- a/usbcfg.c +++ b/usbcfg.c @@ -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 };