diff --git a/docs/mimxrt/quickref.rst b/docs/mimxrt/quickref.rst index 06f91f7f50312..6e016cb67dd06 100644 --- a/docs/mimxrt/quickref.rst +++ b/docs/mimxrt/quickref.rst @@ -540,6 +540,71 @@ port and LAN(1) for the 1G port. For details of the network interface refer to the class :ref:`network.LAN `. + +External drive mode +------------------- + +On some boards a mode is enabled, that mounts the board's internal file system as +external USB drive, called MSC support. Data of that drive can be accessed and changed by the PC. +In that state, access to the internal drive by the MicroPython is limited to +read-only mode, avoiding file corruption. Changes made by the PC to the file system may not be visible +at the board until the drive is ejected by the PC or a soft reset of the board +is made. + +To enable write access to by the board, eject the drive at the PC **and** perform +a soft-reset on the board, either by pushing Ctrl-D at REPL or calling machine.soft_reset(). + +The external drive mode (MSC mode) is enabled of disabled according to the following rules: + + a) For a FAT file system, MSC will be enabled by default. + b) For a LFS file system, MSC will be disabled by default. + c) The setting can be overridden by a file with the name + set_usb_mode.py, which can define the usb_mode with the lines: + + usb_mode = "vcp+msc" # enable MSC + + or + + usb_mode = "vcp" # do not enable MSC + + If the file set_usb_mode.py does not exist or is faulty, the default is used. + +The read-only state of the local file system access can be told by an IOCTL call of the file +system's block device.:: + + import mimxrt + + bdev = mimxrt.Flash() + readonly = bdev.ioctl(7, 0) + +If the drive is in read-only state, bdev.ioctl(7, 0) returns `True`. + +The file system of the board has to be of FAT type for mounting and using with standard PC +tools. But FAT is not enforced at the board. If the board's file system is littlefs, MSC +mode is disable by default. If enabled in boot.py, the file system will be attached to the +PC and will be accessible as a drive (e.g. /dev/sdc using Linux), but by default +there is no file access. In that case, the files are locally still writeable. +Changing the board's file system to FAT can be done then by formatting it from the PC. +Alternatively, you can erase the root sector. Then, the FAT file system will be +created at the next power-up. For erasing the root sector, write:: + + from mimxrt import Flash + Flash().ioctl(6, 0) + +Using littlefs-fuse for Linux you can mount the board's littlefs file system to the PC. +See: https://github.com/littlefs-project/littlefs-fuse +The block_size if 4096, the block_count depends on the size of the filesystem. e.g.:: + + # mounting the lfs2 file system of a Teensy 4.1 board at the PC + mkdir mount + sudo ./lfs --block_size=4096 --block_count=1791 -o allow_other -o nonempty /dev/sdc mount + cd mount + ls -l + +In that case, the exclusive write access is NOT enforced. So be careful to only write +to the file system by the PC. + + Transferring files ------------------ diff --git a/extmod/vfs.h b/extmod/vfs.h index f577d3e337c87..f05bda3c77d2c 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -51,6 +51,7 @@ #define MP_BLOCKDEV_IOCTL_BLOCK_COUNT (4) #define MP_BLOCKDEV_IOCTL_BLOCK_SIZE (5) #define MP_BLOCKDEV_IOCTL_BLOCK_ERASE (6) +#define MP_BLOCKDEV_IOCTL_STATUS (7) // At the moment the VFS protocol just has import_stat, but could be extended to other methods typedef struct _mp_vfs_proto_t { diff --git a/extmod/vfs_fat_diskio.c b/extmod/vfs_fat_diskio.c index 1bcd471f2162e..47e6575f647ed 100644 --- a/extmod/vfs_fat_diskio.c +++ b/extmod/vfs_fat_diskio.c @@ -114,6 +114,7 @@ DRESULT disk_ioctl( [GET_SECTOR_COUNT] = MP_BLOCKDEV_IOCTL_BLOCK_COUNT, [GET_SECTOR_SIZE] = MP_BLOCKDEV_IOCTL_BLOCK_SIZE, [IOCTL_INIT] = MP_BLOCKDEV_IOCTL_INIT, + [IOCTL_STATUS] = MP_BLOCKDEV_IOCTL_STATUS, }; uint8_t bp_op = op_map[cmd & 7]; mp_obj_t ret = mp_const_none; @@ -147,16 +148,29 @@ DRESULT disk_ioctl( *((DWORD *)buff) = 1; // erase block size in units of sector size return RES_OK; - case IOCTL_INIT: - case IOCTL_STATUS: { - DSTATUS stat; + case IOCTL_INIT: { + DSTATUS stat = 0; if (ret != mp_const_none && MP_OBJ_SMALL_INT_VALUE(ret) != 0) { // error initialising stat = STA_NOINIT; - } else if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL) { - stat = STA_PROTECT; } else { - stat = 0; + // IOCTL_INIT only returns non 0 for all errors/flags. To return + // a more accurate disk state, IOCTL_STATUS is called again here. + ret = mp_vfs_blockdev_ioctl(&vfs->blockdev, MP_BLOCKDEV_IOCTL_STATUS, 0); + if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL || + (ret != mp_const_none && MP_OBJ_SMALL_INT_VALUE(ret) != 0)) { + stat = STA_PROTECT; + } + } + *((DSTATUS *)buff) = stat; + return RES_OK; + } + + case IOCTL_STATUS: { + DSTATUS stat = 0; + if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL || + (ret != mp_const_none && MP_OBJ_SMALL_INT_VALUE(ret) != 0)) { + stat = STA_PROTECT; } *((DSTATUS *)buff) = stat; return RES_OK; diff --git a/ports/mimxrt/Makefile b/ports/mimxrt/Makefile index b73d3f4db9aa4..8c0953623c479 100644 --- a/ports/mimxrt/Makefile +++ b/ports/mimxrt/Makefile @@ -180,6 +180,7 @@ SRC_C += \ dma_manager.c \ drivers/bus/softspi.c \ drivers/dht/dht.c \ + flash.c \ eth.c \ fatfs_port.c \ hal/pwm_backport.c \ @@ -204,6 +205,7 @@ SRC_C += \ modutime.c \ mphalport.c \ mpnetworkport.c \ + msc_disk.c \ network_lan.c \ pendsv.c \ pin.c \ diff --git a/ports/mimxrt/boards/MIMXRT1011.ld b/ports/mimxrt/boards/MIMXRT1011.ld index 908eefffd643f..713ab10203c30 100644 --- a/ports/mimxrt/boards/MIMXRT1011.ld +++ b/ports/mimxrt/boards/MIMXRT1011.ld @@ -28,7 +28,7 @@ ocrm_start = 0x20200000; ocrm_size = 0x00010000; /* 20kiB stack. */ -__stack_size__ = 0x5000; +__stack_size__ = 0x4800; _estack = __StackTop; _sstack = __StackLimit; diff --git a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h index 390e91814d16a..cfd48b4244fa6 100644 --- a/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1020_EVK/mpconfigboard.h @@ -3,6 +3,8 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-1020evk" +#define MICROPY_HW_USB_MSC (1) + // i.MX RT1020 EVK has 1 board LED // Todo: think about replacing the define with searching in the generated pins? #define MICROPY_HW_LED1_PIN (pin_GPIO_AD_B0_05) diff --git a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h index 134c9637ea57e..c46bdcf53c70e 100644 --- a/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1050_EVK/mpconfigboard.h @@ -3,6 +3,9 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-1050evk" +// Disable MSC for the device with Hyperflash +#define MICROPY_HW_USB_MSC (0) + // MIMXRT1050_EVKB has 1 user LED #define MICROPY_HW_LED1_PIN (pin_GPIO_AD_B0_09) #define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin)) diff --git a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h index d37050eb56041..4787e80d6b29e 100644 --- a/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h +++ b/ports/mimxrt/boards/MIMXRT1170_EVK/mpconfigboard.h @@ -3,6 +3,8 @@ #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-1070evk" +#define MICROPY_HW_USB_MSC (1) + #define MICROPY_EVENT_POLL_HOOK \ do { \ extern void mp_handle_pending(bool); \ diff --git a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h index d63c0dc403900..48f35f95f35dd 100644 --- a/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h +++ b/ports/mimxrt/boards/OLIMEX_RT1010/mpconfigboard.h @@ -5,6 +5,8 @@ #define MICROPY_HW_USB_PID 0x0046 #define MICROPY_PY_UOS_DUPTERM_BUILTIN_STREAM (0) +#define MICROPY_HW_USB_MSC (1) + // Olimex RT1010-Py has 1 board LED #define MICROPY_HW_LED1_PIN (pin_GPIO_11) #define MICROPY_HW_LED_ON(pin) (mp_hal_pin_high(pin)) diff --git a/ports/mimxrt/boards/TEENSY41/mpconfigboard.h b/ports/mimxrt/boards/TEENSY41/mpconfigboard.h index 56740f48ea3d0..4ac1f70afcb71 100644 --- a/ports/mimxrt/boards/TEENSY41/mpconfigboard.h +++ b/ports/mimxrt/boards/TEENSY41/mpconfigboard.h @@ -2,6 +2,7 @@ #define MICROPY_HW_MCU_NAME "MIMXRT1062DVJ6A" #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-teensy41" +#define MICROPY_HW_USB_MSC (1) // Teensy 4.1 has 1 board LED #define MICROPY_HW_LED1_PIN (pin_GPIO_B0_03) diff --git a/ports/mimxrt/flash.c b/ports/mimxrt/flash.c new file mode 100644 index 0000000000000..36a909f92f957 --- /dev/null +++ b/ports/mimxrt/flash.c @@ -0,0 +1,134 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Philipp Ebensberger + * + * 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 "flash.h" + +// --------------------------------------------------------------------+ +// Global Function Definitions +// --------------------------------------------------------------------+ +void flash_init(void) { + // Upload the custom flash configuration + // This should be performed by the boot ROM but for some reason it is not. + FLEXSPI_UpdateLUT(BOARD_FLEX_SPI, 0, + qspiflash_config.memConfig.lookupTable, + ARRAY_SIZE(qspiflash_config.memConfig.lookupTable)); + + // Configure FLEXSPI IP FIFO access. + BOARD_FLEX_SPI->MCR0 &= ~(FLEXSPI_MCR0_ARDFEN_MASK); + BOARD_FLEX_SPI->MCR0 &= ~(FLEXSPI_MCR0_ATDFEN_MASK); + BOARD_FLEX_SPI->MCR0 |= FLEXSPI_MCR0_ARDFEN(0); + BOARD_FLEX_SPI->MCR0 |= FLEXSPI_MCR0_ATDFEN(0); + + FLEXSPI_EnableIPParallelMode(BOARD_FLEX_SPI, true); +} + +// flash_erase_block(erase_addr) +// erases the block starting at addr. Block size according to the flash properties. +status_t flash_erase_block(uint32_t erase_addr) { + status_t status = kStatus_Fail; + + SCB_CleanInvalidateDCache(); + SCB_DisableDCache(); + __disable_irq(); + + status = flexspi_nor_flash_erase_block(BOARD_FLEX_SPI, erase_addr); + + __enable_irq(); + SCB_EnableDCache(); + + return status; +} + +// flash_erase_sector(erase_addr_bytes) +// erases the sector starting at addr. Sector size according to the flash properties. +status_t flash_erase_sector(uint32_t erase_addr) { + status_t status = kStatus_Fail; + + SCB_CleanInvalidateDCache(); + SCB_DisableDCache(); + __disable_irq(); + + status = flexspi_nor_flash_erase_sector(BOARD_FLEX_SPI, erase_addr); + + __enable_irq(); + SCB_EnableDCache(); + + return status; +} + +// flash_read_block(flash_src_addr_bytes, data_dest, length_bytes) +// read length_byte data to the source address +// It is just a shim to provide the same structure for read_block and write_block. +void inline flash_read_block(uint32_t src_addr, uint8_t *dest, uint32_t length) { + memcpy(dest, (uint8_t *)(BOARD_FLEX_SPI_ADDR_BASE + src_addr), length); +} + +// flash_write_block(flash_dest_addr_bytes, data_source, length_bytes) +// writes length_byte data to the destination address +// the vfs driver takes care for erasing the sector if required +status_t flash_write_block(uint32_t dest_addr, const uint8_t *src, uint32_t length) { + #if FLASH_DISABLE_OP == 1 + return kStatus_Success; + #else + status_t status = kStatus_Fail; + uint32_t size; + uint32_t next_addr; + + if (length == 0) { + status = kStatus_Success; // Nothing to do + } else { + + SCB_CleanInvalidateDCache(); + SCB_DisableDCache(); + + // write data in chunks not crossing a page boundary + while (length > 0) { + next_addr = dest_addr - (dest_addr % PAGE_SIZE_BYTES) + PAGE_SIZE_BYTES; // next page boundary + size = next_addr - dest_addr; // maximal chunk length + if (size > length) { // compare against remaining data size + size = length; + } + + __disable_irq(); + + status = flexspi_nor_flash_page_program(BOARD_FLEX_SPI, dest_addr, (uint32_t *)src, size); + + __enable_irq(); + + if (status != kStatus_Success) { + break; + } + length -= size; + src += size; + dest_addr += size; + } + + SCB_EnableDCache(); + + } + return status; + #endif +} diff --git a/ports/mimxrt/flash.h b/ports/mimxrt/flash.h new file mode 100644 index 0000000000000..9b3f5affee42d --- /dev/null +++ b/ports/mimxrt/flash.h @@ -0,0 +1,63 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Philipp Ebensberger + * + * 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. + */ + +#ifndef MICROPY_INCLUDED_MIMXRT_FLASH_H +#define MICROPY_INCLUDED_MIMXRT_FLASH_H + +#include BOARD_FLASH_OPS_HEADER_H + +// --------------------------------------------------------------------+ +// Defines and Macros +// --------------------------------------------------------------------+ +#define SECTOR_SIZE_BYTES (qspiflash_config.sectorSize) +#define PAGE_SIZE_BYTES (qspiflash_config.pageSize) +#define BLOCK_SIZE_BYTES (qspiflash_config.blockSize) + +#define MICROPY_HW_FLASH_STORAGE_BASE (((uint32_t)&__vfs_start) - ((uint32_t)&__flash_start)) +#define MICROPY_HW_FLASH_STORAGE_BYTES (((uint32_t)&__vfs_end) - ((uint32_t)&__vfs_start)) + +extern uint8_t __vfs_start; +extern uint8_t __vfs_end; +extern uint8_t __flash_start; + +extern flexspi_nor_config_t qspiflash_config; + +enum { + MOUNTED = 0, + EJECTED, + TRANSIT +}; + +// --------------------------------------------------------------------+ +// Global Function Declarations +// --------------------------------------------------------------------+ +void flash_init(void); +__attribute__((section(".ram_functions"))) status_t flash_erase_sector(uint32_t erase_addr); +__attribute__((section(".ram_functions"))) status_t flash_erase_block(uint32_t erase_addr); +__attribute__((section(".ram_functions"))) status_t flash_write_block(uint32_t dest_addr, const uint8_t *src, uint32_t length); +__attribute__((section(".ram_functions"))) void flash_read_block(uint32_t src_addr, uint8_t *dest, uint32_t length); + +#endif // MICROPY_INCLUDED_MIMXRT_FLASH_H diff --git a/ports/mimxrt/hal/flexspi_flash_config.h b/ports/mimxrt/hal/flexspi_flash_config.h index 80526880be0b1..a43010678207c 100644 --- a/ports/mimxrt/hal/flexspi_flash_config.h +++ b/ports/mimxrt/hal/flexspi_flash_config.h @@ -222,6 +222,7 @@ typedef struct _FlexSPIConfig #define NOR_CMD_LUT_SEQ_IDX_ENTERQPI 10 #define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 11 #define NOR_CMD_LUT_SEQ_IDX_EXITQPI 12 +#define NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK 13 #define HYPERFLASH_CMD_LUT_SEQ_IDX_READDATA 0 #define HYPERFLASH_CMD_LUT_SEQ_IDX_WRITEDATA 1 diff --git a/ports/mimxrt/hal/flexspi_hyper_flash.c b/ports/mimxrt/hal/flexspi_hyper_flash.c index a9f17f2d1c19c..5e5d87166d850 100644 --- a/ports/mimxrt/hal/flexspi_hyper_flash.c +++ b/ports/mimxrt/hal/flexspi_hyper_flash.c @@ -175,6 +175,11 @@ status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address) { return status; } +status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address) __attribute__((section(".ram_functions"))); +status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address) { + return flexspi_nor_flash_erase_sector(base, address); // HyperFlash does not support block erase! +} + status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t address, const uint32_t *src, uint32_t size) __attribute__((section(".ram_functions"))); status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t address, const uint32_t *src, uint32_t size) { status_t status; diff --git a/ports/mimxrt/hal/flexspi_hyper_flash.h b/ports/mimxrt/hal/flexspi_hyper_flash.h index fe9acea429f62..c62c8f0a5e81c 100644 --- a/ports/mimxrt/hal/flexspi_hyper_flash.h +++ b/ports/mimxrt/hal/flexspi_hyper_flash.h @@ -46,6 +46,7 @@ status_t flexspi_nor_hyperflash_cfi(FLEXSPI_Type *base); void flexspi_hyper_flash_init(void); void flexspi_nor_update_lut(void); status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address); +status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address); status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t address, const uint32_t *src, uint32_t size); static inline uint32_t flexspi_get_frequency(void) { diff --git a/ports/mimxrt/hal/flexspi_nor_flash.c b/ports/mimxrt/hal/flexspi_nor_flash.c index 48792ec487eb8..956fb657db6dd 100644 --- a/ports/mimxrt/hal/flexspi_nor_flash.c +++ b/ports/mimxrt/hal/flexspi_nor_flash.c @@ -165,6 +165,37 @@ status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address) { return status; } +status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address) __attribute__((section(".ram_functions"))); +status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address) { + status_t status; + flexspi_transfer_t flashXfer; + + /* Write enable */ + status = flexspi_nor_write_enable(base, address); + + if (status != kStatus_Success) { + return status; + } + + /* Erase sector */ + flashXfer.deviceAddress = address; + flashXfer.port = kFLEXSPI_PortA1; + flashXfer.cmdType = kFLEXSPI_Command; + flashXfer.SeqNumber = 1; + flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_ERASEBLOCK; + status = FLEXSPI_TransferBlocking(base, &flashXfer); + + if (status != kStatus_Success) { + return status; + } + + status = flexspi_nor_wait_bus_busy(base); + + flexspi_nor_reset(base); + + return status; +} + status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size) __attribute__((section(".ram_functions"))); status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size) { status_t status; diff --git a/ports/mimxrt/hal/flexspi_nor_flash.h b/ports/mimxrt/hal/flexspi_nor_flash.h index 69d57da6919e4..ae2945c3677a8 100644 --- a/ports/mimxrt/hal/flexspi_nor_flash.h +++ b/ports/mimxrt/hal/flexspi_nor_flash.h @@ -44,6 +44,7 @@ status_t flexspi_nor_get_vendor_id(FLEXSPI_Type *base, uint8_t *vendorId); status_t flexspi_nor_init(void); void flexspi_nor_update_lut(void); status_t flexspi_nor_flash_erase_sector(FLEXSPI_Type *base, uint32_t address); +status_t flexspi_nor_flash_erase_block(FLEXSPI_Type *base, uint32_t address); status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t address, const uint32_t *src, uint32_t size); #endif // MICROPY_INCLUDED_MIMXRT_HAL_FLEXSPI_NOR_FLASH_H diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 907de373dee6a..53f26d89cf0e4 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -50,11 +50,11 @@ extern uint8_t _sstack, _estack, _gc_heap_start, _gc_heap_end; void board_init(void); +void update_msc_state(void); int main(void) { board_init(); ticks_init(); - tusb_init(); led_init(); pendsv_init(); @@ -87,11 +87,15 @@ int main(void) { // Execute _boot.py to set up the filesystem. pyexec_frozen_module("_boot.py"); + // deferred tusb_init allowing a fs to be created before MSC access + tusb_init(); + // Execute user scripts. int ret = pyexec_file_if_exists("boot.py"); if (ret & PYEXEC_FORCED_EXIT) { goto soft_reset_exit; } + // Do not execute main.py if boot.py failed if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL && ret != 0) { ret = pyexec_file_if_exists("main.py"); @@ -121,6 +125,9 @@ int main(void) { #if MICROPY_PY_NETWORK mod_network_deinit(); #endif + #if CFG_TUD_MSC + update_msc_state(); + #endif machine_pwm_deinit_all(); soft_timer_deinit(); gc_sweep_all(); diff --git a/ports/mimxrt/mimxrt_flash.c b/ports/mimxrt/mimxrt_flash.c index 536fb6098170e..7ff2710fa022e 100644 --- a/ports/mimxrt/mimxrt_flash.c +++ b/ports/mimxrt/mimxrt_flash.c @@ -30,25 +30,10 @@ #include "py/runtime.h" #include "extmod/vfs.h" #include "modmimxrt.h" +#include "flash.h" #include BOARD_FLASH_OPS_HEADER_H -#define SECTOR_SIZE_BYTES (qspiflash_config.sectorSize) -#define PAGE_SIZE_BYTES (qspiflash_config.pageSize) - -#ifndef MICROPY_HW_FLASH_STORAGE_BYTES -#define MICROPY_HW_FLASH_STORAGE_BYTES (((uint32_t)&__vfs_end) - ((uint32_t)&__vfs_start)) -#endif - -#ifndef MICROPY_HW_FLASH_STORAGE_BASE -#define MICROPY_HW_FLASH_STORAGE_BASE (((uint32_t)&__vfs_start) - ((uint32_t)&__flash_start)) -#endif - // Linker symbols -extern uint8_t __vfs_start; -extern uint8_t __vfs_end; -extern uint8_t __flash_start; - -extern flexspi_nor_config_t qspiflash_config; typedef struct _mimxrt_flash_obj_t { mp_obj_base_t base; @@ -60,51 +45,7 @@ STATIC mimxrt_flash_obj_t mimxrt_flash_obj = { .base = { &mimxrt_flash_type } }; -// flash_erase_block(erase_addr_bytes) -// erases the sector starting at addr. Sector size according to the flash properties. -status_t flash_erase_block(uint32_t erase_addr) __attribute__((section(".ram_functions"))); -status_t flash_erase_block(uint32_t erase_addr) { - status_t status; - SCB_CleanInvalidateDCache(); - SCB_DisableDCache(); - __disable_irq(); - status = flexspi_nor_flash_erase_sector(BOARD_FLEX_SPI, erase_addr); - __enable_irq(); - SCB_EnableDCache(); - return status; -} - -// flash_write_block(flash_dest_addr_bytes, data_source, length_bytes) -// writes length_byte data to the destination address -// the vfs driver takes care for erasing the sector if required -status_t flash_write_block(uint32_t dest_addr, const uint8_t *src, uint32_t length) __attribute__((section(".ram_functions"))); -status_t flash_write_block(uint32_t dest_addr, const uint8_t *src, uint32_t length) { - status_t status = 0; - uint32_t size; - uint32_t next_addr; - - SCB_CleanInvalidateDCache(); - SCB_DisableDCache(); - // write data in chunks not crossing a page boundary - while (length > 0) { - next_addr = dest_addr - (dest_addr % PAGE_SIZE_BYTES) + PAGE_SIZE_BYTES; // next page boundary - size = next_addr - dest_addr; // maximal chunk length - if (size > length) { // compare against remaining data size - size = length; - } - __disable_irq(); - status = flexspi_nor_flash_page_program(BOARD_FLEX_SPI, dest_addr, (uint32_t *)src, size); - __enable_irq(); - if (status != kStatus_Success) { - break; - } - length -= size; - src += size; - dest_addr += size; - } - SCB_EnableDCache(); - return status; -} +extern uint8_t tud_msc_state; STATIC mp_obj_t mimxrt_flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { // Check args. @@ -142,7 +83,7 @@ STATIC mp_obj_t mimxrt_flash_readblocks(size_t n_args, const mp_obj_t *args) { if (n_args == 4) { offset += mp_obj_get_int(args[3]); } - memcpy(bufinfo.buf, (uint8_t *)(BOARD_FLEX_SPI_ADDR_BASE + self->flash_base + offset), bufinfo.len); + flash_read_block(self->flash_base + offset, bufinfo.buf, bufinfo.len); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mimxrt_flash_readblocks_obj, 3, 4, mimxrt_flash_readblocks); @@ -161,7 +102,7 @@ STATIC mp_obj_t mimxrt_flash_writeblocks(size_t n_args, const mp_obj_t *args) { uint32_t offset = mp_obj_get_int(args[1]) * SECTOR_SIZE_BYTES; if (n_args == 3) { - status = flash_erase_block(self->flash_base + offset); + status = flash_erase_sector(self->flash_base + offset); if (status != kStatus_Success) { mp_raise_msg_varg(&mp_type_OSError, MP_ERROR_TEXT("flash erase command failed with %d"), status); @@ -199,9 +140,15 @@ STATIC mp_obj_t mimxrt_flash_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t a return MP_OBJ_NEW_SMALL_INT(SECTOR_SIZE_BYTES); case MP_BLOCKDEV_IOCTL_BLOCK_ERASE: { uint32_t offset = mp_obj_get_int(arg_in) * SECTOR_SIZE_BYTES; - status = flash_erase_block(self->flash_base + offset); + status = flash_erase_sector(self->flash_base + offset); return MP_OBJ_NEW_SMALL_INT(status != kStatus_Success); } + case MP_BLOCKDEV_IOCTL_STATUS: + #if MICROPY_HW_USB_MSC + return MP_OBJ_NEW_SMALL_INT(tud_msc_state != EJECTED); + #else + return MP_OBJ_NEW_SMALL_INT(false); + #endif default: return mp_const_none; } diff --git a/ports/mimxrt/modmimxrt.c b/ports/mimxrt/modmimxrt.c index 4756efc0a27e7..294f40ee3a2d0 100644 --- a/ports/mimxrt/modmimxrt.c +++ b/ports/mimxrt/modmimxrt.c @@ -28,9 +28,35 @@ #include "py/runtime.h" #include "modmimxrt.h" +#if MICROPY_HW_USB_MSC + +#define USB_MODE_VCP "vcp" +#define USB_MODE_VCP_MSC "vcp+msc" + +extern void set_msc_enabled(bool state); + +STATIC mp_obj_t mimxrt_usb_mode(mp_obj_t mode_in) { + size_t slen; + const char *s = mp_obj_str_get_data(mode_in, &slen); + if (strncmp(s, USB_MODE_VCP, slen) == 0) { + set_msc_enabled(false); + } else if (strncmp(s, USB_MODE_VCP_MSC, slen) == 0) { + set_msc_enabled(true); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("Invalid usb_mode")); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mimxrt_usb_mode_obj, mimxrt_usb_mode); +#endif + STATIC const mp_rom_map_elem_t mimxrt_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mimxrt) }, { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&mimxrt_flash_type) }, + #if MICROPY_HW_USB_MSC + { MP_ROM_QSTR(MP_QSTR_usb_mode), MP_ROM_PTR(&mimxrt_usb_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_MSC), MP_ROM_INT(1) }, + #endif }; STATIC MP_DEFINE_CONST_DICT(mimxrt_module_globals, mimxrt_module_globals_table); diff --git a/ports/mimxrt/modules/_boot.py b/ports/mimxrt/modules/_boot.py index 56f12a4ce1590..5b419e74c8f94 100644 --- a/ports/mimxrt/modules/_boot.py +++ b/ports/mimxrt/modules/_boot.py @@ -7,17 +7,65 @@ import mimxrt from machine import Pin +FS_UNDEF = const(0) +FS_LITTLEFS = const(1) +FS_FAT = const(2) + + +def fs_type(bdev): + b = bytearray(512) + bdev.readblocks(0, b) + if b[40:48] == b"littlefs" or b[8:16] == b"littlefs": + return FS_LITTLEFS + elif b[510:512] == b"\x55\xaa": + return FS_FAT + else: + return FS_UNDEF + + bdev = mimxrt.Flash() -try: - vfs = os.VfsLfs2(bdev, progsize=256) -except: - os.VfsLfs2.mkfs(bdev, progsize=256) - vfs = os.VfsLfs2(bdev, progsize=256) -os.mount(vfs, "/flash") + +# try to mount the fs accorfing to the boot sector +# if that fails, (re-)create it with a preference for FAT on +# boards with MSC support. +fs = fs_type(bdev) + +if fs == FS_FAT or (fs == FS_UNDEF and hasattr(mimxrt, "MSC")): + try: + vfs = os.VfsFat(bdev) + os.mount(vfs, "/flash") + except: + os.VfsFat.mkfs(bdev) + vfs = os.VfsFat(bdev) + os.mount(vfs, "/flash") + usb_mode = "vcp+msc" +else: + try: + vfs = os.VfsLfs2(bdev, progsize=256) + os.mount(vfs, "/flash") + except: + os.VfsLfs2.mkfs(bdev, progsize=256) + vfs = os.VfsLfs2(bdev, progsize=256) + os.mount(vfs, "/flash") + usb_mode = "vcp" + os.chdir("/flash") sys.path.append("/flash") sys.path.append("/flash/lib") +try: + from set_usb_mode import usb_mode +except: + pass + +# Configure USB mode according to the default or the +# config file in the file system +if hasattr(mimxrt, "usb_mode"): + try: + mimxrt.usb_mode(usb_mode) + except: + pass + # do not mount the SD card if SKIPSD exists. try: os.stat("SKIPSD") diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 873e9e0d189b6..0e8a9c4cf7716 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -26,6 +26,8 @@ // Options controlling how MicroPython is built, overriding defaults in py/mpconfig.h +#ifndef MICROPY_INCLUDED_MPCONFIGPORT_H +#define MICROPY_INCLUDED_MPCONFIGPORT_H // Board specific definitions #include "mpconfigboard.h" #include "fsl_common.h" @@ -59,6 +61,7 @@ uint32_t trng_random_u32(void); #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_SCHEDULER_DEPTH (8) +#define MICROPY_SCHEDULER_STATIC_NODES (1) #define MICROPY_VFS (1) #define MICROPY_MODULE_FROZEN_MPY (1) #define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool @@ -99,6 +102,7 @@ uint32_t trng_random_u32(void); // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (1) #define MICROPY_FATFS_RPATH (2) +// to be redefined depending on the flash type. #define MICROPY_FATFS_MAX_SS (4096) #define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ @@ -133,6 +137,17 @@ uint32_t trng_random_u32(void); #define MICROPY_PY_PENDSV_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV); #define MICROPY_PY_PENDSV_EXIT restore_irq_pri(atomic_state); +// by default enable MSC support, unless disabled at some boards +// Boards with hyperflash must disbale MSC support. +#ifndef MICROPY_HW_USB_MSC +#define MICROPY_HW_USB_MSC (0) +#endif + +#if MICROPY_HW_USB_MSC +#define MICROPY_FATFS_USE_LABEL (1) +#define MICROPY_FATFS_MULTI_PARTITION (1) +#endif + // Hooks to add builtins __attribute__((always_inline)) static inline void enable_irq(uint32_t state) { @@ -223,3 +238,5 @@ typedef long mp_off_t; // Need to provide a declaration/definition of alloca() #include + +#endif // MICROPY_INCLUDED_MPCONFIGPORT_H diff --git a/ports/mimxrt/msc_disk.c b/ports/mimxrt/msc_disk.c new file mode 100644 index 0000000000000..7fdf6e773a6de --- /dev/null +++ b/ports/mimxrt/msc_disk.c @@ -0,0 +1,140 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Damien P. George + * + * 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 "tusb.h" +#if CFG_TUD_MSC +#include "flash.h" +#include BOARD_FLASH_OPS_HEADER_H +#include "stdlib.h" + +// This implementation does Not support Flash sector caching. +// MICROPY_FATFS_MAX_SS must be identical to SECTOR_SIZE_BYTES +#define BLOCK_SIZE (SECTOR_SIZE_BYTES) +#define BLOCK_COUNT (MICROPY_HW_FLASH_STORAGE_BYTES / BLOCK_SIZE) +#define FLASH_BASE_ADDR (MICROPY_HW_FLASH_STORAGE_BASE) + +uint8_t tud_msc_state = EJECTED; + +static bool msc_enabled = true; + +void update_msc_state(void) { + if (tud_msc_state == TRANSIT) { + tud_msc_state = EJECTED; + } +} + +void set_msc_enabled(bool state) { + msc_enabled = state; +} + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + if (msc_enabled) { + const char vid[] = "Micropy"; + const char pid[] = "Mass Storage"; + const char rev[] = "1.0"; + + strncpy((char *)vendor_id, vid, 8); + strncpy((char *)product_id, pid, 16); + strncpy((char *)product_rev, rev, 4); + tud_msc_state = MOUNTED; + } +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) { + if (tud_msc_state != MOUNTED || !msc_enabled) { + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + return false; + } + return true; +} + +// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size +// Application update block count and block size +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) { + if (msc_enabled) { + *block_size = BLOCK_SIZE; + *block_count = BLOCK_COUNT; + } +} + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + if (load_eject && msc_enabled) { + if (start) { + // load disk storage + tud_msc_state = MOUNTED; + } else { + // unload disk storage + tud_msc_state = TRANSIT; + } + } + return true; +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of copied bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) { + flash_read_block(FLASH_BASE_ADDR + lba * BLOCK_SIZE, buffer, bufsize); + return bufsize; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { + uint32_t count = bufsize / BLOCK_SIZE; + // Erase count sectors starting at lba + for (int n = 0; n < count; n++) { + flash_erase_sector(FLASH_BASE_ADDR + (lba + n) * BLOCK_SIZE); + } + flash_write_block(FLASH_BASE_ADDR + lba * BLOCK_SIZE, buffer, count * BLOCK_SIZE); + return count * BLOCK_SIZE; +} + +// Callback invoked when received an SCSI command not in built-in list below +// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE +// - READ10 and WRITE10 has their own callbacks +int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) { + int32_t resplen = 0; + switch (scsi_cmd[0]) { + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + // Sync the logical unit if needed. + break; + + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + // negative means error -> tinyusb could stall and/or response with failed status + resplen = -1; + break; + } + return resplen; +} + +#endif diff --git a/ports/mimxrt/tusb_config.h b/ports/mimxrt/tusb_config.h index 862fb6c52d2e5..bc32695fa0fd1 100644 --- a/ports/mimxrt/tusb_config.h +++ b/ports/mimxrt/tusb_config.h @@ -25,6 +25,8 @@ #ifndef MICROPY_INCLUDED_MIMXRT_TUSB_CONFIG_H #define MICROPY_INCLUDED_MIMXRT_TUSB_CONFIG_H +#include "mpconfigport.h" + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED) #define CFG_TUSB_OS (OPT_OS_NONE) @@ -33,4 +35,11 @@ #define CFG_TUD_CDC_RX_BUFSIZE (512) #define CFG_TUD_CDC_TX_BUFSIZE (512) +#if MICROPY_HW_USB_MSC +// Board and hardware specific configuration +#define CFG_TUD_MSC (1) +// Set MSC EP buffer size to FatFS block size to avoid partial read/writes (offset arg). +#define CFG_TUD_MSC_BUFSIZE (MICROPY_FATFS_MAX_SS) +#endif + #endif // MICROPY_INCLUDED_MIMXRT_TUSB_CONFIG_H diff --git a/ports/mimxrt/tusb_port.c b/ports/mimxrt/tusb_port.c index f359d44e4d276..16bdcd054c673 100644 --- a/ports/mimxrt/tusb_port.c +++ b/ports/mimxrt/tusb_port.c @@ -39,23 +39,37 @@ #define MICROPY_HW_USB_MANUFACTURER_STRING ("MicroPython") #endif +#if CFG_TUD_MSC +#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MSC_DESC_LEN) +#else #define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN) +#endif + #define USBD_MAX_POWER_MA (250) #define USBD_ITF_CDC (0) // needs 2 interfaces +#define USBD_ITF_MSC (2) +#if CFG_TUD_MSC +#define USBD_ITF_MAX (3) +#else #define USBD_ITF_MAX (2) +#endif #define USBD_CDC_EP_CMD (0x81) #define USBD_CDC_EP_OUT (0x02) -#define USBD_CDC_EP_IN (0x82) +#define USBD_CDC_EP_IN (0x82) #define USBD_CDC_CMD_MAX_SIZE (8) #define USBD_CDC_IN_OUT_MAX_SIZE (512) +#define EPNUM_MSC_OUT (0x03) +#define EPNUM_MSC_IN (0x83) + #define USBD_STR_0 (0x00) #define USBD_STR_MANUF (0x01) #define USBD_STR_PRODUCT (0x02) #define USBD_STR_SERIAL (0x03) #define USBD_STR_CDC (0x04) +#define USBD_STR_MSC (0x05) // Note: descriptors returned from callbacks must exist long enough for transfer to complete @@ -82,6 +96,9 @@ static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { TUD_CDC_DESCRIPTOR(USBD_ITF_CDC, USBD_STR_CDC, USBD_CDC_EP_CMD, USBD_CDC_CMD_MAX_SIZE, USBD_CDC_EP_OUT, USBD_CDC_EP_IN, USBD_CDC_IN_OUT_MAX_SIZE), + #if CFG_TUD_MSC + TUD_MSC_DESCRIPTOR(USBD_ITF_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 512), + #endif }; static const char *const usbd_desc_str[] = { @@ -89,6 +106,9 @@ static const char *const usbd_desc_str[] = { [USBD_STR_PRODUCT] = MICROPY_HW_BOARD_NAME, [USBD_STR_SERIAL] = "00000000000000000000", [USBD_STR_CDC] = "Board CDC", + #if CFG_TUD_MSC + [USBD_STR_MSC] = "Board MSC", + #endif }; const uint8_t *tud_descriptor_device_cb(void) {