From 51cd4da76edd1941edf4a933e7d6d7628c3feec2 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 10 Oct 2017 11:03:12 -0700 Subject: [PATCH] atmel-samd: Add mass storage support. Fixes #260 --- atmel-samd/Makefile | 6 +- atmel-samd/access_vfs.c | 191 ------------ atmel-samd/asf4_conf/samd21/hpl_usb_config.h | 8 +- atmel-samd/asf4_conf/samd51/hpl_usb_config.h | 8 +- atmel-samd/background.c | 3 +- atmel-samd/internal_flash.c | 4 - atmel-samd/mpconfigport.h | 4 + atmel-samd/supervisor/filesystem.c | 1 + atmel-samd/usb.c | 24 +- atmel-samd/usb_mass_storage.c | 289 ++++++++++++++++++ .../{access_vfs.h => usb_mass_storage.h} | 29 +- 11 files changed, 344 insertions(+), 223 deletions(-) delete mode 100644 atmel-samd/access_vfs.c create mode 100644 atmel-samd/usb_mass_storage.c rename atmel-samd/{access_vfs.h => usb_mass_storage.h} (59%) diff --git a/atmel-samd/Makefile b/atmel-samd/Makefile index f557c88541..c5faf3f75a 100644 --- a/atmel-samd/Makefile +++ b/atmel-samd/Makefile @@ -44,6 +44,7 @@ INC += -I. \ -Iasf4/$(CHIP_FAMILY)/usb \ -Iasf4/$(CHIP_FAMILY)/usb/class/cdc \ -Iasf4/$(CHIP_FAMILY)/usb/class/hid \ + -Iasf4/$(CHIP_FAMILY)/usb/class/msc \ -Iasf4/$(CHIP_FAMILY)/usb/device \ -Iasf4_conf/$(CHIP_FAMILY) \ -Iboards/$(BOARD) \ @@ -181,6 +182,7 @@ SRC_ASF := \ hpl/tc/hpl_tc.c \ hpl/usb/hpl_usb.c \ usb/class/cdc/device/cdcdf_acm.c \ + usb/class/msc/device/mscdf.c \ usb/device/usbdc.c \ usb/usb_protocol.c \ hal/utils/src/utils_list.c \ @@ -204,8 +206,7 @@ endif SRC_ASF := $(addprefix asf4/$(CHIP_FAMILY)/, $(SRC_ASF)) # Skip this source for now. -# access_vfs.c \ - shared_dma.c \ +# shared_dma.c \ SRC_C = \ background.c \ @@ -216,6 +217,7 @@ SRC_C = \ $(CHIP_FAMILY)_pins.c \ tick.c \ usb.c \ + usb_mass_storage.c \ $(FLASH_IMPL) \ bindings/samd/__init__.c \ boards/$(BOARD)/board.c \ diff --git a/atmel-samd/access_vfs.c b/atmel-samd/access_vfs.c deleted file mode 100644 index b2e21f7833..0000000000 --- a/atmel-samd/access_vfs.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Scott Shawcroft for Adafruit Industries - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include - -#include "access_vfs.h" -#include "autoreload.h" - -#include "extmod/vfs.h" -#include "extmod/vfs_fat.h" -#include "lib/oofatfs/ff.h" -#include "lib/oofatfs/diskio.h" -#include "lib/oofatfs/ffconf.h" -#include "py/mpconfig.h" -#include "py/mphal.h" -#include "py/mpstate.h" -#include "py/misc.h" - -#define VFS_INDEX 0 - -// The root FS is always at the end of the list. -static fs_user_mount_t* get_vfs(int index) { - mp_vfs_mount_t* current_mount = MP_STATE_VM(vfs_mount_table); - if (current_mount == NULL) { - return NULL; - } - while (current_mount->next != NULL) { - current_mount = current_mount->next; - } - return current_mount->obj; -} - -//! This function tests memory state, and starts memory initialization -//! @return Ctrl_status -//! It is ready -> CTRL_GOOD -//! Memory unplug -> CTRL_NO_PRESENT -//! Not initialized or changed -> CTRL_BUSY -//! An error occurred -> CTRL_FAIL -Ctrl_status vfs_test_unit_ready(void) -{ - fs_user_mount_t* current_mount = get_vfs(VFS_INDEX); - if (current_mount != NULL) { - return CTRL_GOOD; - } - return CTRL_NO_PRESENT; -} - -//! This function returns the address of the last valid sector -//! @param uint32_t_nb_sector Pointer to the last valid sector (sector=512 bytes) -//! @return Ctrl_status -//! It is ready -> CTRL_GOOD -//! Memory unplug -> CTRL_NO_PRESENT -//! Not initialized or changed -> CTRL_BUSY -//! An error occurred -> CTRL_FAIL -Ctrl_status vfs_read_capacity(uint32_t *last_valid_sector) -{ - fs_user_mount_t * vfs = get_vfs(VFS_INDEX); - if (vfs == NULL || - disk_ioctl(vfs, GET_SECTOR_COUNT, last_valid_sector) != RES_OK) { - return CTRL_FAIL; - } - // Subtract one from the sector count to get the last valid sector. - (*last_valid_sector)--; - return CTRL_GOOD; -} - -//! This function returns the write-protected mode -//! -//! @return true if the memory is protected -//! -bool vfs_wr_protect(void) -{ - fs_user_mount_t * vfs = get_vfs(VFS_INDEX); - // This is used to determine the writeability of the disk from USB. - if (vfs == NULL || vfs->writeblocks[0] == MP_OBJ_NULL || - (vfs->flags & FSUSER_USB_WRITEABLE) == 0) { - return true; - } - return false; -} - -//! This function informs about the memory type -//! -//! @return true if the memory is removable -//! -bool vfs_removal(void) -{ - return true; -} - -// TODO(tannewt): Transfer more than a single sector at a time if we need more -// speed. -//! This function transfers the memory data to the USB MSC interface -//! -//! @param addr Sector address to start read -//! @param nb_sector Number of sectors to transfer (sector=512 bytes) -//! -//! @return Ctrl_status -//! It is ready -> CTRL_GOOD -//! Memory unplug -> CTRL_NO_PRESENT -//! Not initialized or changed -> CTRL_BUSY -//! An error occurred -> CTRL_FAIL -//! -Ctrl_status vfs_usb_read_10(uint32_t addr, volatile uint16_t nb_sector) -{ - fs_user_mount_t * vfs = get_vfs(VFS_INDEX); - uint8_t sector_buffer[FILESYSTEM_BLOCK_SIZE]; - for (uint16_t sector = 0; sector < nb_sector; sector++) { - DRESULT result = disk_read(vfs, sector_buffer, addr + sector, 1); - if (result == RES_PARERR) { - return CTRL_NO_PRESENT; - } - if (result == RES_ERROR) { - return CTRL_FAIL; - } - if (!udi_msc_trans_block(true, sector_buffer, FILESYSTEM_BLOCK_SIZE, NULL)) { - return CTRL_FAIL; // transfer aborted - } - } - return CTRL_GOOD; -} - -//! This function transfers the USB MSC data to the memory -//! -//! @param addr Sector address to start write -//! @param nb_sector Number of sectors to transfer (sector=512 bytes) -//! -//! @return Ctrl_status -//! It is ready -> CTRL_GOOD -//! Memory unplug -> CTRL_NO_PRESENT -//! Not initialized or changed -> CTRL_BUSY -//! An error occurred -> CTRL_FAIL -//! -Ctrl_status vfs_usb_write_10(uint32_t addr, volatile uint16_t nb_sector) -{ - if (vfs_wr_protect()) { - return CTRL_FAIL; - } - fs_user_mount_t * vfs = get_vfs(VFS_INDEX); - uint8_t sector_buffer[FILESYSTEM_BLOCK_SIZE]; - for (uint16_t sector = 0; sector < nb_sector; sector++) { - if (!udi_msc_trans_block(false, sector_buffer, FILESYSTEM_BLOCK_SIZE, NULL)) { - return CTRL_FAIL; // transfer aborted - } - uint32_t sector_address = addr + sector; - DRESULT result = disk_write(vfs, sector_buffer, sector_address, 1); - if (result == RES_PARERR) { - return CTRL_NO_PRESENT; - } - if (result == RES_ERROR) { - return CTRL_FAIL; - } - // Since by getting here we assume the mount is read-only to MicroPython - // lets update the cached FatFs sector if its the one we just wrote. - #if _MAX_SS != _MIN_SS - if (vfs->ssize == FILESYSTEM_BLOCK_SIZE) { - #else - // The compiler can optimize this away. - if (_MAX_SS == FILESYSTEM_BLOCK_SIZE) { - #endif - if (sector_address == vfs->fatfs.winsect && sector_address > 0) { - memcpy(vfs->fatfs.win, sector_buffer, FILESYSTEM_BLOCK_SIZE); - } - } - } - autoreload_start(); - return CTRL_GOOD; -} diff --git a/atmel-samd/asf4_conf/samd21/hpl_usb_config.h b/atmel-samd/asf4_conf/samd21/hpl_usb_config.h index b052887d6b..0a895dbf82 100644 --- a/atmel-samd/asf4_conf/samd21/hpl_usb_config.h +++ b/atmel-samd/asf4_conf/samd21/hpl_usb_config.h @@ -39,7 +39,7 @@ // Max possible (by "Max Endpoint Number" config) // usbd_num_ep_sp #ifndef CONF_USB_D_NUM_EP_SP -#define CONF_USB_D_NUM_EP_SP CONF_USB_N_4 +#define CONF_USB_D_NUM_EP_SP CONF_USB_D_EP_N_MAX #endif // @@ -60,7 +60,7 @@ // The number of physical endpoints - 1 // usbd_arch_max_ep_n #ifndef CONF_USB_D_MAX_EP_N -#define CONF_USB_D_MAX_EP_N CONF_USB_N_2 +#define CONF_USB_D_MAX_EP_N CONF_USB_D_EP_N_MAX #endif // USB Speed Limit @@ -156,7 +156,7 @@ // <1024=> Cached by 1024 bytes buffer (interrupt or isochronous EP) // usb_ep2_I_CACHE #ifndef CONF_USB_EP2_I_CACHE -#define CONF_USB_EP2_I_CACHE 0 +#define CONF_USB_EP2_I_CACHE 64 #endif // @@ -308,7 +308,7 @@ // <1024=> Cached by 1024 bytes buffer (interrupt or isochronous EP) // usb_ep6_I_CACHE #ifndef CONF_USB_EP6_I_CACHE -#define CONF_USB_EP6_I_CACHE 0 +#define CONF_USB_EP6_I_CACHE 64 #endif // diff --git a/atmel-samd/asf4_conf/samd51/hpl_usb_config.h b/atmel-samd/asf4_conf/samd51/hpl_usb_config.h index b052887d6b..0f8155ecb3 100644 --- a/atmel-samd/asf4_conf/samd51/hpl_usb_config.h +++ b/atmel-samd/asf4_conf/samd51/hpl_usb_config.h @@ -28,6 +28,8 @@ // Max number of endpoints supported // Limits the number of endpoints (described by EP address) can be used in app. +// NOTE(tannewt): This not only limits the number of endpoints but also the +// addresses. In other words, even if you use endpoint 6 you need to set this to 11. // 1 (EP0 only) // 2 (EP0 + 1 endpoint) // 3 (EP0 + 2 endpoints) @@ -39,7 +41,7 @@ // Max possible (by "Max Endpoint Number" config) // usbd_num_ep_sp #ifndef CONF_USB_D_NUM_EP_SP -#define CONF_USB_D_NUM_EP_SP CONF_USB_N_4 +#define CONF_USB_D_NUM_EP_SP CONF_USB_D_N_EP_MAX #endif // @@ -60,7 +62,7 @@ // The number of physical endpoints - 1 // usbd_arch_max_ep_n #ifndef CONF_USB_D_MAX_EP_N -#define CONF_USB_D_MAX_EP_N CONF_USB_N_2 +#define CONF_USB_D_MAX_EP_N CONF_USB_D_EP_N_MAX #endif // USB Speed Limit @@ -308,7 +310,7 @@ // <1024=> Cached by 1024 bytes buffer (interrupt or isochronous EP) // usb_ep6_I_CACHE #ifndef CONF_USB_EP6_I_CACHE -#define CONF_USB_EP6_I_CACHE 0 +#define CONF_USB_EP6_I_CACHE 64 #endif // diff --git a/atmel-samd/background.c b/atmel-samd/background.c index cda39592fa..6515e448b4 100644 --- a/atmel-samd/background.c +++ b/atmel-samd/background.c @@ -26,8 +26,9 @@ #include "background.h" // #include "common-hal/audioio/AudioOut.h" +#include "usb_mass_storage.h" void run_background_tasks(void) { // audioout_background(); - // udi_msc_process_trans(); + usb_msc_background(); } diff --git a/atmel-samd/internal_flash.c b/atmel-samd/internal_flash.c index 53a0a0c241..35eb52da23 100644 --- a/atmel-samd/internal_flash.c +++ b/atmel-samd/internal_flash.c @@ -172,8 +172,6 @@ bool internal_flash_write_block(const uint8_t *src, uint32_t block) { return false; } int32_t error_code; - // A block is formed by two rows of flash. We must erase each row - // before we write back to it. error_code = flash_erase(&internal_flash_desc, dest, FILESYSTEM_BLOCK_SIZE / flash_get_page_size(&internal_flash_desc)); @@ -181,8 +179,6 @@ bool internal_flash_write_block(const uint8_t *src, uint32_t block) { return false; } - // A block is made up of multiple pages. Write each page - // sequentially. error_code = flash_append(&internal_flash_desc, dest, src, FILESYSTEM_BLOCK_SIZE); if (error_code != ERR_NONE) { return false; diff --git a/atmel-samd/mpconfigport.h b/atmel-samd/mpconfigport.h index a871dfd790..e6443fe8b8 100644 --- a/atmel-samd/mpconfigport.h +++ b/atmel-samd/mpconfigport.h @@ -132,6 +132,10 @@ typedef long mp_off_t; #include "mpconfigboard.h" #include "include/sam.h" +// ASF4 defines. +#define CONF_USB_COMPOSITE_CDC_ACM_EN 1 +#define CONF_USB_COMPOSITE_MSC_EN 1 + #ifdef SAMD21 #define CIRCUITPY_MCU_FAMILY samd21 #define MICROPY_PY_SYS_PLATFORM "Atmel SAMD21" diff --git a/atmel-samd/supervisor/filesystem.c b/atmel-samd/supervisor/filesystem.c index ac2eb24214..f189ed8b99 100644 --- a/atmel-samd/supervisor/filesystem.c +++ b/atmel-samd/supervisor/filesystem.c @@ -65,6 +65,7 @@ void filesystem_init(bool create_allowed) { // set label f_setlabel(&vfs_fat->fatfs, "CIRCUITPY"); + flash_flush(); } else if (res != FR_OK) { return; } diff --git a/atmel-samd/usb.c b/atmel-samd/usb.c index 11a3d4eb94..5aee96767f 100644 --- a/atmel-samd/usb.c +++ b/atmel-samd/usb.c @@ -38,12 +38,14 @@ // #include "hiddf_keyboard.h" #include "usb/class/hid/device/hiddf_generic.h" #include "usb/class/composite/device/composite_desc.h" +#include "usb/class/msc/device/mscdf.h" #include "peripheral_clk_config.h" #include "hpl/pm/hpl_pm_base.h" #include "hpl/gclk/hpl_gclk_base.h" #include "lib/utils/interrupt_char.h" #include "reset.h" +#include "usb_mass_storage.h" #include "supervisor/shared/autoreload.h" @@ -108,6 +110,9 @@ static void init_hardware(void) { extern uint32_t *_usb_ep1_cache; static bool usb_device_cb_bulk_out(const uint8_t ep, const enum usb_xfer_code rc, const uint32_t count) { + if (rc == USB_XFER_RESET) { + return false; + } volatile hal_atomic_t flags; atomic_enter_critical(&flags); // If our buffer can't fit the data received, then error out. @@ -182,13 +187,25 @@ static bool usb_device_cb_line_coding_c(const usb_cdc_line_coding_t* coding) void init_usb(void) { init_hardware(); + mp_cdc_enabled = false; + usbdc_init(ctrl_buffer); /* usbdc_register_funcion inside */ cdcdf_acm_init(); + + mscdf_init(1); // hiddf_mouse_init(); // hiddf_keyboard_init(); + mscdf_register_callback(MSCDF_CB_INQUIRY_DISK, (FUNC_PTR)usb_msc_inquiry_info); + mscdf_register_callback(MSCDF_CB_GET_DISK_CAPACITY, (FUNC_PTR)usb_msc_get_capacity); + mscdf_register_callback(MSCDF_CB_START_READ_DISK, (FUNC_PTR)usb_msc_new_read); + mscdf_register_callback(MSCDF_CB_START_WRITE_DISK, (FUNC_PTR)usb_msc_new_write); + mscdf_register_callback(MSCDF_CB_EJECT_DISK, (FUNC_PTR)usb_msc_disk_eject); + mscdf_register_callback(MSCDF_CB_TEST_DISK_READY, (FUNC_PTR)usb_msc_disk_is_ready); + mscdf_register_callback(MSCDF_CB_XFER_BLOCKS_DONE, (FUNC_PTR)usb_msc_xfer_done); + int32_t result = usbdc_start(&multi_desc); while (result != ERR_NONE) {} usbdc_attach(); @@ -226,11 +243,6 @@ int usb_read(void) { return 0; } - // Disable autoreload if someone is using the repl. - // TODO(tannewt): Check that we're actually in the REPL. It could be an - // input() call from a script. - autoreload_disable(); - // Copy from head. int data; CRITICAL_SECTION_ENTER(); @@ -242,8 +254,6 @@ int usb_read(void) { } CRITICAL_SECTION_LEAVE(); - //usb_write((uint8_t *)&data, 1); - return data; } diff --git a/atmel-samd/usb_mass_storage.c b/atmel-samd/usb_mass_storage.c new file mode 100644 index 0000000000..a0c1ca2a65 --- /dev/null +++ b/atmel-samd/usb_mass_storage.c @@ -0,0 +1,289 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include "usb_mass_storage.h" +#include "supervisor/shared/autoreload.h" + +#include "hal/utils/include/err_codes.h" +#include "hal/utils/include/utils.h" +#include "usb/class/msc/device/mscdf.h" + +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "lib/oofatfs/ff.h" +#include "lib/oofatfs/diskio.h" +#include "lib/oofatfs/ffconf.h" +#include "py/mpconfig.h" +#include "py/mphal.h" +#include "py/mpstate.h" +#include "py/misc.h" + +// The root FS is always at the end of the list. +static fs_user_mount_t* get_vfs(int lun) { + // TODO(tannewt): Return the mount which matches the lun where 0 is the end + // and is counted in reverse. + if (lun > 0) { + return NULL; + } + mp_vfs_mount_t* current_mount = MP_STATE_VM(vfs_mount_table); + if (current_mount == NULL) { + return NULL; + } + while (current_mount->next != NULL) { + current_mount = current_mount->next; + } + return current_mount->obj; +} + +/* Inquiry Information */ +// This is designed to handle the common case where we have an internal file +// system and an optional SD card. +static uint8_t inquiry_info[2][36]; + +/* Capacities of Disk */ +static uint8_t format_capa[2][8]; + +/** + * \brief Eject Disk + * \param[in] lun logic unit number + * \return Operation status. + */ +int32_t usb_msc_disk_eject(uint8_t lun) { + if (lun > 1) { + return ERR_NOT_FOUND; + } + // TODO(tannewt): Should we flush here? + return ERR_NONE; +} + +/** + * \brief Inquiry whether Disk is ready + * \param[in] lun logic unit number + * \return Operation status. + */ +int32_t usb_msc_disk_is_ready(uint8_t lun) { + if (lun > 1) { + return ERR_NOT_FOUND; + } + + fs_user_mount_t* current_mount = get_vfs(lun); + // Return ERR_NOT_READY if not ready, otherwise ERR_NONE. + if (current_mount != NULL) { + return ERR_NONE; + } + return ERR_NOT_READY; +} + +/** + * \brief Callback invoked when inquiry data command received + * \param[in] lun logic unit number + * \return Operation status. + */ +uint8_t *usb_msc_inquiry_info(uint8_t lun) { + if (lun > 1) { + return NULL; + } else { + for (uint8_t i = 0; i < 36; i++) { + inquiry_info[lun][i] = 0; + } + inquiry_info[lun][1] = (0x1 << 7); + inquiry_info[lun][3] = 0x01; + inquiry_info[lun][4] = 31; + return &inquiry_info[lun][0]; + } +} + +/** + * \brief Callback invoked when read format capacities command received + * \param[in] lun logic unit number + */ +uint8_t *usb_msc_get_capacity(uint8_t lun) { + if (lun > 1) { + return NULL; + } else { + fs_user_mount_t * vfs = get_vfs(lun); + uint32_t last_valid_sector = 0; + uint32_t sector_size = 0; + if (vfs == NULL || + disk_ioctl(vfs, GET_SECTOR_COUNT, &last_valid_sector) != RES_OK || + disk_ioctl(vfs, GET_SECTOR_SIZE, §or_size) != RES_OK) { + return NULL; + } + // Subtract one from the sector count to get the last valid sector. + last_valid_sector--; + + format_capa[lun][0] = (uint8_t)(last_valid_sector >> 24); + format_capa[lun][1] = (uint8_t)(last_valid_sector >> 16); + format_capa[lun][2] = (uint8_t)(last_valid_sector >> 8); + format_capa[lun][3] = (uint8_t)(last_valid_sector >> 0); + format_capa[lun][4] = (uint8_t)(sector_size >> 24); + format_capa[lun][5] = (uint8_t)(sector_size >> 16); + format_capa[lun][6] = (uint8_t)(sector_size >> 8); + format_capa[lun][7] = (uint8_t)(sector_size >> 0); + + // 8 byte response. First 4 bytes are last block address. Second 4 + // bytes are sector size. + return &format_capa[lun][0]; + } +} + +// USB transfer state. +volatile bool usb_busy; +volatile bool active_read; +volatile bool active_write; +volatile uint8_t active_lun; +volatile uint32_t active_addr; +volatile uint32_t active_nblocks; +volatile bool sector_loaded; +COMPILER_ALIGNED(4) uint8_t sector_buffer[512]; + +/** + * \brief Callback invoked when a new read blocks command received + * \param[in] lun logic unit number + * \param[in] addr start address of disk to be read + * \param[in] nblocks block amount to be read + * \return Operation status. + */ +int32_t usb_msc_new_read(uint8_t lun, uint32_t addr, uint32_t nblocks) { + if (lun > 1) { + return ERR_DENIED; + } + + // Store transfer info so we can service it in the "background". + active_lun = lun; + active_addr = addr; + active_nblocks = nblocks; + active_read = true; + + return ERR_NONE; +} + +/** + * \brief Callback invoked when a new write blocks command received + * \param[in] lun logic unit number + * \param[in] addr start address of disk to be written + * \param[in] nblocks block amount to be written + * \return Operation status. + */ +int32_t usb_msc_new_write(uint8_t lun, uint32_t addr, uint32_t nblocks) { + if (lun > 1) { + return ERR_DENIED; + } + + fs_user_mount_t * vfs = get_vfs(lun); + // This is used to determine the writeability of the disk from USB. + if (vfs == NULL || vfs->writeblocks[0] == MP_OBJ_NULL /*|| + (vfs->flags & FSUSER_USB_WRITEABLE) == 0*/) { + return ERR_DENIED; + } + + // Store transfer info so we can service it in the "background". + active_lun = lun; + active_addr = addr; + active_nblocks = nblocks; + active_write = true; + sector_loaded = false; + + // Return ERR_DENIED when the file system is read-only to the USB host. + + return ERR_NONE; +} + +/** + * \brief Callback invoked when a blocks transfer is done + * \param[in] lun logic unit number + * \return Operation status. + */ +int32_t usb_msc_xfer_done(uint8_t lun) { + if (lun > 1) { + return ERR_DENIED; + } + + if (active_read) { + active_addr += 1; + active_nblocks--; + } + + if (active_write) { + sector_loaded = true; + } + usb_busy = false; + + return ERR_NONE; +} + +// The start_read callback begins a read transaction which we accept but delay our response until the "main thread" calls usb_msc_background. Once it does, we read immediately from the drive into our cache and trigger the USB DMA to output the sector. Once the sector is transmitted, xfer_done will be called. +void usb_msc_background(void) { + if (active_read && !usb_busy) { + if (active_nblocks == 0) { + mscdf_xfer_blocks(false, NULL, 0); + active_read = false; + return; + } + fs_user_mount_t * vfs = get_vfs(active_lun); + disk_read(vfs, sector_buffer, active_addr, 1); + // TODO(tannewt): Check the read result. + mscdf_xfer_blocks(true, sector_buffer, 1); + usb_busy = true; + } + if (active_write && !usb_busy) { + if (sector_loaded) { + fs_user_mount_t * vfs = get_vfs(active_lun); + disk_write(vfs, sector_buffer, active_addr, 1); + // Since by getting here we assume the mount is read-only to + // MicroPython lets update the cached FatFs sector if its the one + // we just wrote. + #if _MAX_SS != _MIN_SS + if (vfs->ssize == FILESYSTEM_BLOCK_SIZE) { + #else + // The compiler can optimize this away. + if (_MAX_SS == FILESYSTEM_BLOCK_SIZE) { + #endif + if (active_addr == vfs->fatfs.winsect && active_addr > 0) { + memcpy(vfs->fatfs.win, + sector_buffer, + FILESYSTEM_BLOCK_SIZE); + } + } + sector_loaded = false; + active_addr += 1; + active_nblocks--; + } + // Load more blocks from USB if they are needed. + if (active_nblocks > 0) { + int32_t result = mscdf_xfer_blocks(false, sector_buffer, 1); + while (result != ERR_NONE) {} + usb_busy = true; + } else { + mscdf_xfer_blocks(false, NULL, 0); + active_write = false; + // This write is complete, start the autoreload clock. + autoreload_start(); + } + } +} diff --git a/atmel-samd/access_vfs.h b/atmel-samd/usb_mass_storage.h similarity index 59% rename from atmel-samd/access_vfs.h rename to atmel-samd/usb_mass_storage.h index fac50632fc..d8906a16f2 100644 --- a/atmel-samd/access_vfs.h +++ b/atmel-samd/usb_mass_storage.h @@ -24,17 +24,24 @@ * THE SOFTWARE. */ -// This adapts the ASF access API to MicroPython's VFS API so we can expose all -// VFS block devices as Lun's over USB mass storage control. +// This adapts the ASF4 USB mass storage API to MicroPython's VFS API so we can +// expose all VFS block devices as Lun's over USB mass storage control. -#ifndef MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H -#define MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H +#ifndef MICROPY_INCLUDED_ATMEL_SAMD_USB_MASS_STORAGE_H +#define MICROPY_INCLUDED_ATMEL_SAMD_USB_MASS_STORAGE_H -Ctrl_status vfs_test_unit_ready(void); -Ctrl_status vfs_read_capacity(uint32_t *u32_nb_sector); -bool vfs_wr_protect(void); -bool vfs_removal(void); -Ctrl_status vfs_usb_read_10(uint32_t addr, uint16_t nb_sector); -Ctrl_status vfs_usb_write_10(uint32_t addr, uint16_t nb_sector); +#include -#endif // MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H +// "background" task that actually manages loading to and from the file systems. +void usb_msc_background(void); + +// Callbacks that hook into ASF4's USB stack. +int32_t usb_msc_disk_eject(uint8_t lun); +int32_t usb_msc_disk_is_ready(uint8_t lun); +int32_t usb_msc_new_read(uint8_t lun, uint32_t addr, uint32_t nblocks); +int32_t usb_msc_new_write(uint8_t lun, uint32_t addr, uint32_t nblocks); +int32_t usb_msc_xfer_done(uint8_t lun); +uint8_t *usb_msc_inquiry_info(uint8_t lun); +uint8_t *usb_msc_get_capacity(uint8_t lun); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_USB_MASS_STORAGE_H