diff --git a/atmel-samd/Makefile b/atmel-samd/Makefile index 5d3af6e39a754..fbdfa3f93b5c5 100644 --- a/atmel-samd/Makefile +++ b/atmel-samd/Makefile @@ -143,6 +143,7 @@ SRC_ASF = $(addprefix asf/sam0/,\ ) SRC_C = \ + access_vfs.c \ builtin_open.c \ fatfs_port.c \ main.c \ @@ -156,9 +157,8 @@ SRC_C = \ modutime.c \ mphalport.c \ pin_named_pins.c \ - rom_fs.c \ samdneopixel.c \ - storage.c \ + $(FLASH_IMPL) \ asf/common/services/sleepmgr/samd/sleepmgr.c \ asf/common/services/storage/ctrl_access/ctrl_access.c \ asf/common/services/usb/class/cdc/device/udi_cdc.c \ diff --git a/atmel-samd/rom_fs.c b/atmel-samd/access_vfs.c similarity index 58% rename from atmel-samd/rom_fs.c rename to atmel-samd/access_vfs.c index f0b01fa6b7611..cfd5dfe54160e 100644 --- a/atmel-samd/rom_fs.c +++ b/atmel-samd/access_vfs.c @@ -24,10 +24,18 @@ * THE SOFTWARE. */ -#include "rom_fs.h" +#include + +#include "access_vfs.h" #include "asf/common/services/usb/class/msc/device/udi_msc.h" -#include "storage.h" +#include "extmod/fsusermount.h" +#include "lib/fatfs/diskio.h" +#include "py/mpconfig.h" +#include "py/mpstate.h" +#include "py/misc.h" + +#define VFS_INDEX 0 //! This function tests memory state, and starts memory initialization //! @return Ctrl_status @@ -35,42 +43,72 @@ //! Memory unplug -> CTRL_NO_PRESENT //! Not initialized or changed -> CTRL_BUSY //! An error occurred -> CTRL_FAIL -Ctrl_status rom_fs_test_unit_ready(void) +Ctrl_status vfs_test_unit_ready(void) { - return CTRL_GOOD; + if (VFS_INDEX >= MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount))) { + return CTRL_FAIL; + } + DSTATUS status = disk_status(VFS_INDEX); + if (status == STA_NOINIT) { + return CTRL_NO_PRESENT; + } + + return CTRL_GOOD; } //! This function returns the address of the last valid sector -//! @param uint32_t_nb_sector Pointer to number of sectors (sector=512 bytes) +//! @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 rom_fs_read_capacity(uint32_t *uint32_t_nb_sector) +Ctrl_status vfs_read_capacity(uint32_t *last_valid_sector) { - *uint32_t_nb_sector = storage_get_block_count(); - return CTRL_GOOD; + if (disk_ioctl(VFS_INDEX, 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 rom_fs_wr_protect(void) +bool vfs_wr_protect(void) { - return false; + if (VFS_INDEX >= MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount))) { + return true; + } + fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[VFS_INDEX]; + if (vfs == NULL) { + return true; + } + + // This is used to determine the writeability of the disk from USB. + if (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 rom_fs_removal(void) +bool vfs_removal(void) { return true; } +bool vfs_unload(bool unload) +{ + return unload; +} + // 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 @@ -84,11 +122,17 @@ bool rom_fs_removal(void) //! Not initialized or changed -> CTRL_BUSY //! An error occurred -> CTRL_FAIL //! -Ctrl_status rom_fs_usb_read_10(uint32_t addr, volatile uint16_t nb_sector) +Ctrl_status vfs_usb_read_10(uint32_t addr, volatile uint16_t nb_sector) { uint8_t sector_buffer[FLASH_BLOCK_SIZE]; for (uint16_t sector = 0; sector < nb_sector; sector++) { - storage_read_block(sector_buffer, addr + sector); + DRESULT result = disk_read(VFS_INDEX, 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, FLASH_BLOCK_SIZE, NULL)) { return CTRL_FAIL; // transfer aborted } @@ -108,16 +152,36 @@ Ctrl_status rom_fs_usb_read_10(uint32_t addr, volatile uint16_t nb_sector) //! Not initialized or changed -> CTRL_BUSY //! An error occurred -> CTRL_FAIL //! -Ctrl_status rom_fs_usb_write_10(uint32_t addr, uint16_t nb_sector) +Ctrl_status vfs_usb_write_10(uint32_t addr, volatile uint16_t nb_sector) { uint8_t sector_buffer[FLASH_BLOCK_SIZE]; for (uint16_t sector = 0; sector < nb_sector; sector++) { if (!udi_msc_trans_block(false, sector_buffer, FLASH_BLOCK_SIZE, NULL)) { return CTRL_FAIL; // transfer aborted } - if (!storage_write_block(sector_buffer, addr + sector)) { + uint32_t sector_address = addr + sector; + DRESULT result = disk_write(VFS_INDEX, 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. + fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[VFS_INDEX]; + volatile uint16_t x = addr; + (void) x; + #if _MAX_SS != _MIN_SS + if (vfs->ssize == FLASH_BLOCK_SIZE) { + #else + // The compiler can optimize this away. + if (_MAX_SS == FLASH_BLOCK_SIZE) { + #endif + if (sector_address == vfs->fatfs.winsect && sector_address > 0) { + memcpy(vfs->fatfs.win, sector_buffer, FLASH_BLOCK_SIZE); + } + } } return CTRL_GOOD; } diff --git a/atmel-samd/rom_fs.h b/atmel-samd/access_vfs.h similarity index 76% rename from atmel-samd/rom_fs.h rename to atmel-samd/access_vfs.h index 8b1fe2de9e41e..07af89e229ade 100644 --- a/atmel-samd/rom_fs.h +++ b/atmel-samd/access_vfs.h @@ -24,17 +24,20 @@ * 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. + #ifndef __MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H__ #define __MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H__ #include "asf/common/services/storage/ctrl_access/ctrl_access.h" - -Ctrl_status rom_fs_test_unit_ready(void); -Ctrl_status rom_fs_read_capacity(uint32_t *u32_nb_sector); -bool rom_fs_wr_protect(void); -bool rom_fs_removal(void); -Ctrl_status rom_fs_usb_read_10(uint32_t addr, uint16_t nb_sector); -Ctrl_status rom_fs_usb_write_10(uint32_t addr, uint16_t nb_sector); +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); +bool vfs_unload(bool); +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); #endif // __MICROPY_INCLUDED_ATMEL_SAMD_ROM_FS_H__ diff --git a/atmel-samd/boards/arduino_zero/conf_access.h b/atmel-samd/boards/arduino_zero/conf_access.h index 8cf104e69c04e..25109b75cab63 100644 --- a/atmel-samd/boards/arduino_zero/conf_access.h +++ b/atmel-samd/boards/arduino_zero/conf_access.h @@ -68,15 +68,15 @@ /*! \name LUN 0 Definitions */ //! @{ -#define LUN_0_INCLUDE "rom_fs.h" -#define Lun_0_test_unit_ready rom_fs_test_unit_ready -#define Lun_0_read_capacity rom_fs_read_capacity -#define Lun_0_unload NULL /* Can not be unloaded */ -#define Lun_0_wr_protect rom_fs_wr_protect -#define Lun_0_removal rom_fs_removal -#define Lun_0_usb_read_10 rom_fs_usb_read_10 -#define Lun_0_usb_write_10 rom_fs_usb_write_10 -#define LUN_0_NAME "\"On-Chip ROM\"" +#define LUN_0_INCLUDE "access_vfs.h" +#define Lun_0_test_unit_ready vfs_test_unit_ready +#define Lun_0_read_capacity vfs_read_capacity +#define Lun_0_unload NULL +#define Lun_0_wr_protect vfs_wr_protect +#define Lun_0_removal vfs_removal +#define Lun_0_usb_read_10 vfs_usb_read_10 +#define Lun_0_usb_write_10 vfs_usb_write_10 +#define LUN_0_NAME "\"MicroPython VFS[0]\"" //! @} #define MEM_USB LUN_USB diff --git a/atmel-samd/boards/arduino_zero/mpconfigboard.mk b/atmel-samd/boards/arduino_zero/mpconfigboard.mk index 1b9956932995a..848b9fca3a59e 100644 --- a/atmel-samd/boards/arduino_zero/mpconfigboard.mk +++ b/atmel-samd/boards/arduino_zero/mpconfigboard.mk @@ -1,3 +1,5 @@ LD_FILE = boards/samd21x18-bootloader.ld USB_VID = 0x2341 USB_PID = 0x824D + +FLASH_IMPL = internal_flash.c diff --git a/atmel-samd/boards/feather_m0_basic/mpconfigboard.mk b/atmel-samd/boards/feather_m0_basic/mpconfigboard.mk index d7f5c739bbf77..2b5c263ace7ad 100644 --- a/atmel-samd/boards/feather_m0_basic/mpconfigboard.mk +++ b/atmel-samd/boards/feather_m0_basic/mpconfigboard.mk @@ -1,3 +1,5 @@ LD_FILE = boards/samd21x18-bootloader.ld USB_VID = 0x239A USB_PID = 0x8015 + +FLASH_IMPL = internal_flash.c diff --git a/atmel-samd/boards/metro_m0_flash/conf_access.h b/atmel-samd/boards/metro_m0_flash/conf_access.h index 8cf104e69c04e..25109b75cab63 100644 --- a/atmel-samd/boards/metro_m0_flash/conf_access.h +++ b/atmel-samd/boards/metro_m0_flash/conf_access.h @@ -68,15 +68,15 @@ /*! \name LUN 0 Definitions */ //! @{ -#define LUN_0_INCLUDE "rom_fs.h" -#define Lun_0_test_unit_ready rom_fs_test_unit_ready -#define Lun_0_read_capacity rom_fs_read_capacity -#define Lun_0_unload NULL /* Can not be unloaded */ -#define Lun_0_wr_protect rom_fs_wr_protect -#define Lun_0_removal rom_fs_removal -#define Lun_0_usb_read_10 rom_fs_usb_read_10 -#define Lun_0_usb_write_10 rom_fs_usb_write_10 -#define LUN_0_NAME "\"On-Chip ROM\"" +#define LUN_0_INCLUDE "access_vfs.h" +#define Lun_0_test_unit_ready vfs_test_unit_ready +#define Lun_0_read_capacity vfs_read_capacity +#define Lun_0_unload NULL +#define Lun_0_wr_protect vfs_wr_protect +#define Lun_0_removal vfs_removal +#define Lun_0_usb_read_10 vfs_usb_read_10 +#define Lun_0_usb_write_10 vfs_usb_write_10 +#define LUN_0_NAME "\"MicroPython VFS[0]\"" //! @} #define MEM_USB LUN_USB diff --git a/atmel-samd/boards/metro_m0_flash/mpconfigboard.h b/atmel-samd/boards/metro_m0_flash/mpconfigboard.h index b577ca663b8df..a4e1e3fc54485 100644 --- a/atmel-samd/boards/metro_m0_flash/mpconfigboard.h +++ b/atmel-samd/boards/metro_m0_flash/mpconfigboard.h @@ -1,10 +1,31 @@ -// LEDs -#define MICROPY_HW_LED1 PIN_PA17 // red // #define UART_REPL #define USB_REPL #define MICROPY_HW_BOARD_NAME "Adafruit Metro M0 with Flash (Experimental)" #define MICROPY_HW_MCU_NAME "samd21g18" +#define MICROPY_HW_LED_MSC PIN_PA17 #define MICROPY_HW_LED_TX PIN_PA27 #define MICROPY_HW_LED_RX PIN_PB03 + +#define SPI_FLASH_BAUDRATE (1000000) + +// Off-board flash +// #define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E +// #define SPI_FLASH_PAD0_PINMUX PINMUX_PA16C_SERCOM1_PAD0 // MISO D11 +// Use default pinmux for the chip select since we manage it ourselves. +// #define SPI_FLASH_PAD1_PINMUX PINMUX_DEFAULT +// #define SPI_FLASH_PAD2_PINMUX PINMUX_PA18C_SERCOM1_PAD2 // MOSI D10 +// #define SPI_FLASH_PAD3_PINMUX PINMUX_PA19C_SERCOM1_PAD3 // SCK D12 +// #define SPI_FLASH_CS PIN_PA17 +// #define SPI_FLASH_SERCOM SERCOM1 + +// On-board flash +#define SPI_FLASH_MUX_SETTING SPI_SIGNAL_MUX_SETTING_E +#define SPI_FLASH_PAD0_PINMUX PINMUX_PA12D_SERCOM4_PAD0 // MISO +// Use default pinmux for the chip select since we manage it ourselves. +#define SPI_FLASH_PAD1_PINMUX PINMUX_DEFAULT // CS +#define SPI_FLASH_PAD2_PINMUX PINMUX_PB10D_SERCOM4_PAD2 // MOSI +#define SPI_FLASH_PAD3_PINMUX PINMUX_PB11D_SERCOM4_PAD3 // SCK +#define SPI_FLASH_CS PIN_PA13 +#define SPI_FLASH_SERCOM SERCOM4 diff --git a/atmel-samd/boards/metro_m0_flash/mpconfigboard.mk b/atmel-samd/boards/metro_m0_flash/mpconfigboard.mk index 253932c144d05..0893f1819eb6b 100644 --- a/atmel-samd/boards/metro_m0_flash/mpconfigboard.mk +++ b/atmel-samd/boards/metro_m0_flash/mpconfigboard.mk @@ -1,3 +1,5 @@ LD_FILE = boards/samd21x18-bootloader-external-flash.ld USB_VID = 0x239A USB_PID = 0x8015 + +FLASH_IMPL = spi_flash.c diff --git a/atmel-samd/boards/metro_m0_flash/pins.c b/atmel-samd/boards/metro_m0_flash/pins.c index 26754b4f49291..143abba173acd 100644 --- a/atmel-samd/boards/metro_m0_flash/pins.c +++ b/atmel-samd/boards/metro_m0_flash/pins.c @@ -92,7 +92,7 @@ PIN(PA19, false, NO_ADC_INPUT, TIMER(TC3, 0, 1, 1, PIN_PA19E_TC3_WO1, MUX_PA19E_TC3_WO1), TIMER(0, TCC0, 3, 3, PIN_PA19F_TCC0_WO3, MUX_PA19F_TCC0_WO3), SERCOM(SERCOM1, 3, PINMUX_PA19C_SERCOM1_PAD3), - SERCOM(SERCOM3, 3, PINMUX_PA19C_SERCOM1_PAD3)); + SERCOM(SERCOM3, 3, PINMUX_PA19D_SERCOM3_PAD3)); PIN(PA17, false, NO_ADC_INPUT, TIMER(0, TCC2, 1, 1, PIN_PA17E_TCC2_WO1, MUX_PA17E_TCC2_WO1), TIMER(0, TCC0, 3, 7, PIN_PA17F_TCC0_WO7, MUX_PA17F_TCC0_WO7), diff --git a/atmel-samd/boards/samd21x18-bootloader-external-flash.ld b/atmel-samd/boards/samd21x18-bootloader-external-flash.ld index 0003c7239caf1..574dbb001b6c9 100644 --- a/atmel-samd/boards/samd21x18-bootloader-external-flash.ld +++ b/atmel-samd/boards/samd21x18-bootloader-external-flash.ld @@ -64,5 +64,13 @@ SECTIONS _ebss = .; } >RAM + /* this just checks there is enough RAM for a minimal stack */ + .stack : + { + . = ALIGN(4); + . = . + 0x800; /* Reserve a minimum of 2K for the stack. */ + . = ALIGN(4); + } >RAM + .ARM.attributes 0 : { *(.ARM.attributes) } } diff --git a/atmel-samd/boards/samd21x18-bootloader.ld b/atmel-samd/boards/samd21x18-bootloader.ld index 89ff084fb7ada..b43c292b1697b 100644 --- a/atmel-samd/boards/samd21x18-bootloader.ld +++ b/atmel-samd/boards/samd21x18-bootloader.ld @@ -64,5 +64,13 @@ SECTIONS _ebss = .; } >RAM + /* this just checks there is enough RAM for a minimal stack */ + .stack : + { + . = ALIGN(4); + . = . + 0x800; /* Reserve a minimum of 2K for the stack. */ + . = ALIGN(4); + } >RAM + .ARM.attributes 0 : { *(.ARM.attributes) } } diff --git a/atmel-samd/boards/samd21x18-external-flash.ld b/atmel-samd/boards/samd21x18-external-flash.ld new file mode 100644 index 0000000000000..0c2e71df15d96 --- /dev/null +++ b/atmel-samd/boards/samd21x18-external-flash.ld @@ -0,0 +1,75 @@ +/* + GNU linker script for SAMD21 +*/ + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000 /* 256 KiB */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x008000 /* 32 KiB */ +} + +/* top end of the stack */ +_estack = ORIGIN(RAM) + LENGTH(RAM); + +/* define output sections */ +SECTIONS +{ + /* The program code and other data goes into FLASH */ + .text : + { + . = ALIGN(4); + _sfixed = .; + KEEP(*(.vectors)) /* isr vector table */ + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + + . = ALIGN(4); + _etext = .; /* define a global symbol at end of code */ + _sidata = _etext; /* This is used by the startup in order to initialize the .data section */ + } >FLASH + + /* This is the initialized data section + The program executes knowing that the data is in the RAM + but the loader puts the initial values in the FLASH (inidata). + It is one task of the startup to copy the initial values from FLASH to RAM. */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _srelocate = .; /* create a global symbol at data start; used by startup code in order to initialise the .data section in RAM */ + *(.ramfunc) + *(.ramfunc*) + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + + . = ALIGN(4); + _erelocate = .; /* define a global symbol at data end; used by startup code in order to initialise the .data section in RAM */ + } >RAM + + /* Uninitialized data section */ + .bss : + { + . = ALIGN(4); + _sbss = .; + _szero = .; /* define a global symbol at bss start; used by startup code */ + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ezero = .; /* define a global symbol at bss end; used by startup code */ + _ebss = .; + } >RAM + + /* this just checks there is enough RAM for a minimal stack */ + .stack : + { + . = ALIGN(4); + . = . + 0x800; /* Reserve a minimum of 2K for the stack. */ + . = ALIGN(4); + } >RAM + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/atmel-samd/boards/samd21x18.ld b/atmel-samd/boards/samd21x18.ld index 0f1168c578a8a..995d2754facc2 100644 --- a/atmel-samd/boards/samd21x18.ld +++ b/atmel-samd/boards/samd21x18.ld @@ -63,5 +63,13 @@ SECTIONS _ebss = .; } >RAM + /* this just checks there is enough RAM for a minimal stack */ + .stack : + { + . = ALIGN(4); + . = . + 0x800; /* Reserve a minimum of 2K for the stack. */ + . = ALIGN(4); + } >RAM + .ARM.attributes 0 : { *(.ARM.attributes) } } diff --git a/atmel-samd/storage.c b/atmel-samd/internal_flash.c similarity index 56% rename from atmel-samd/storage.c rename to atmel-samd/internal_flash.c index a4037da4f6217..444c174da62ec 100644 --- a/atmel-samd/storage.c +++ b/atmel-samd/internal_flash.c @@ -34,35 +34,35 @@ #include "asf/sam0/drivers/nvm/nvm.h" -#include "storage.h" +#include "internal_flash.h" -#define TOTAL_FLASH_SIZE 0x010000 +#define TOTAL_INTERNAL_FLASH_SIZE 0x010000 -#define FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_FLASH_SIZE) -#define FLASH_PART1_START_BLOCK (0x100) -#define FLASH_PART1_NUM_BLOCKS (TOTAL_FLASH_SIZE / FLASH_BLOCK_SIZE) +#define INTERNAL_FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_INTERNAL_FLASH_SIZE) +#define INTERNAL_FLASH_PART1_START_BLOCK (0x100) +#define INTERNAL_FLASH_PART1_NUM_BLOCKS (TOTAL_INTERNAL_FLASH_SIZE / INTERNAL_FLASH_BLOCK_SIZE) -static bool flash_is_initialised = false; +static bool internal_flash_is_initialised = false; -void storage_init(void) { - if (!flash_is_initialised) { +void internal_flash_init(void) { + if (!internal_flash_is_initialised) { struct nvm_config config_nvm; nvm_get_config_defaults(&config_nvm); config_nvm.manual_page_write = false; nvm_set_config(&config_nvm); - flash_is_initialised = true; + internal_flash_is_initialised = true; } } -uint32_t storage_get_block_size(void) { - return FLASH_BLOCK_SIZE; +uint32_t internal_flash_get_block_size(void) { + return INTERNAL_FLASH_BLOCK_SIZE; } -uint32_t storage_get_block_count(void) { - return FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS; +uint32_t internal_flash_get_block_count(void) { + return INTERNAL_FLASH_PART1_START_BLOCK + INTERNAL_FLASH_PART1_NUM_BLOCKS; } -void storage_flush(void) { +void internal_flash_flush(void) { } static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) { @@ -102,16 +102,16 @@ static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_blo } static uint32_t convert_block_to_flash_addr(uint32_t block) { - if (FLASH_PART1_START_BLOCK <= block && block < FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS) { + if (INTERNAL_FLASH_PART1_START_BLOCK <= block && block < INTERNAL_FLASH_PART1_START_BLOCK + INTERNAL_FLASH_PART1_NUM_BLOCKS) { // a block in partition 1 - block -= FLASH_PART1_START_BLOCK; - return FLASH_MEM_SEG1_START_ADDR + block * FLASH_BLOCK_SIZE; + block -= INTERNAL_FLASH_PART1_START_BLOCK; + return INTERNAL_FLASH_MEM_SEG1_START_ADDR + block * INTERNAL_FLASH_BLOCK_SIZE; } // bad block return -1; } -bool storage_read_block(uint8_t *dest, uint32_t block) { +bool internal_flash_read_block(uint8_t *dest, uint32_t block) { //printf("RD %u\n", block); if (block == 0) { // fake the MBR so we can decide on our own partition table @@ -120,7 +120,7 @@ bool storage_read_block(uint8_t *dest, uint32_t block) { dest[i] = 0; } - build_partition(dest + 446, 0, 0x01 /* FAT12 */, FLASH_PART1_START_BLOCK, FLASH_PART1_NUM_BLOCKS); + build_partition(dest + 446, 0, 0x01 /* FAT12 */, INTERNAL_FLASH_PART1_START_BLOCK, INTERNAL_FLASH_PART1_NUM_BLOCKS); build_partition(dest + 462, 0, 0, 0, 0); build_partition(dest + 478, 0, 0, 0, 0); build_partition(dest + 494, 0, 0, 0, 0); @@ -140,7 +140,7 @@ bool storage_read_block(uint8_t *dest, uint32_t block) { enum status_code error_code; // A block is made up of multiple pages. Read each page // sequentially. - for (int i = 0; i < FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { + for (int i = 0; i < INTERNAL_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { do { error_code = nvm_read_buffer(src + i * NVMCTRL_PAGE_SIZE, @@ -152,7 +152,7 @@ bool storage_read_block(uint8_t *dest, uint32_t block) { } } -bool storage_write_block(const uint8_t *src, uint32_t block) { +bool internal_flash_write_block(const uint8_t *src, uint32_t block) { if (block == 0) { // can't write MBR, but pretend we did return true; @@ -184,7 +184,7 @@ bool storage_write_block(const uint8_t *src, uint32_t block) { // A block is made up of multiple pages. Write each page // sequentially. - for (int i = 0; i < FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { + for (int i = 0; i < INTERNAL_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { do { error_code = nvm_write_buffer(dest + i * NVMCTRL_PAGE_SIZE, @@ -199,18 +199,18 @@ bool storage_write_block(const uint8_t *src, uint32_t block) { } } -mp_uint_t storage_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { +mp_uint_t internal_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { for (size_t i = 0; i < num_blocks; i++) { - if (!storage_read_block(dest + i * FLASH_BLOCK_SIZE, block_num + i)) { + if (!internal_flash_read_block(dest + i * INTERNAL_FLASH_BLOCK_SIZE, block_num + i)) { return 1; // error } } return 0; // success } -mp_uint_t storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { +mp_uint_t internal_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { for (size_t i = 0; i < num_blocks; i++) { - if (!storage_write_block(src + i * FLASH_BLOCK_SIZE, block_num + i)) { + if (!internal_flash_write_block(src + i * INTERNAL_FLASH_BLOCK_SIZE, block_num + i)) { return 1; // error } } @@ -223,68 +223,68 @@ mp_uint_t storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t // Expose the flash as an object with the block protocol. // there is a singleton Flash object -STATIC const mp_obj_base_t flash_obj = {&flash_type}; +STATIC const mp_obj_base_t internal_flash_obj = {&internal_flash_type}; -STATIC mp_obj_t flash_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +STATIC mp_obj_t internal_flash_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // check arguments mp_arg_check_num(n_args, n_kw, 0, 0, false); // return singleton object - return (mp_obj_t)&flash_obj; + return (mp_obj_t)&internal_flash_obj; } -STATIC mp_obj_t flash_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { +STATIC mp_obj_t internal_flash_obj_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); - mp_uint_t ret = storage_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE); + mp_uint_t ret = internal_flash_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE); return MP_OBJ_NEW_SMALL_INT(ret); } -STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_readblocks_obj, flash_readblocks); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(internal_flash_obj_readblocks_obj, internal_flash_obj_readblocks); -STATIC mp_obj_t flash_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { +STATIC mp_obj_t internal_flash_obj_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); - mp_uint_t ret = storage_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE); + mp_uint_t ret = internal_flash_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE); return MP_OBJ_NEW_SMALL_INT(ret); } -STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_writeblocks_obj, flash_writeblocks); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(internal_flash_obj_writeblocks_obj, internal_flash_obj_writeblocks); -STATIC mp_obj_t flash_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { +STATIC mp_obj_t internal_flash_obj_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { mp_int_t cmd = mp_obj_get_int(cmd_in); switch (cmd) { - case BP_IOCTL_INIT: storage_init(); return MP_OBJ_NEW_SMALL_INT(0); - case BP_IOCTL_DEINIT: storage_flush(); return MP_OBJ_NEW_SMALL_INT(0); // TODO properly - case BP_IOCTL_SYNC: storage_flush(); return MP_OBJ_NEW_SMALL_INT(0); - case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(storage_get_block_count()); - case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(storage_get_block_size()); + case BP_IOCTL_INIT: internal_flash_init(); return MP_OBJ_NEW_SMALL_INT(0); + case BP_IOCTL_DEINIT: internal_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); // TODO properly + case BP_IOCTL_SYNC: internal_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); + case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(internal_flash_get_block_count()); + case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(internal_flash_get_block_size()); default: return mp_const_none; } } -STATIC MP_DEFINE_CONST_FUN_OBJ_3(flash_ioctl_obj, flash_ioctl); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(internal_flash_obj_ioctl_obj, internal_flash_obj_ioctl); -STATIC const mp_map_elem_t flash_locals_dict_table[] = { - { MP_OBJ_NEW_QSTR(MP_QSTR_readblocks), (mp_obj_t)&flash_readblocks_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_writeblocks), (mp_obj_t)&flash_writeblocks_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_ioctl), (mp_obj_t)&flash_ioctl_obj }, +STATIC const mp_map_elem_t internal_flash_obj_locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_readblocks), (mp_obj_t)&internal_flash_obj_readblocks_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_writeblocks), (mp_obj_t)&internal_flash_obj_writeblocks_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ioctl), (mp_obj_t)&internal_flash_obj_ioctl_obj }, }; -STATIC MP_DEFINE_CONST_DICT(flash_locals_dict, flash_locals_dict_table); +STATIC MP_DEFINE_CONST_DICT(internal_flash_obj_locals_dict, internal_flash_obj_locals_dict_table); -const mp_obj_type_t flash_type = { +const mp_obj_type_t internal_flash_type = { { &mp_type_type }, - .name = MP_QSTR_Flash, - .make_new = flash_make_new, - .locals_dict = (mp_obj_t)&flash_locals_dict, + .name = MP_QSTR_InternalFlash, + .make_new = internal_flash_obj_make_new, + .locals_dict = (mp_obj_t)&internal_flash_obj_locals_dict, }; void flash_init_vfs(fs_user_mount_t *vfs) { - vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL; - vfs->readblocks[0] = (mp_obj_t)&flash_readblocks_obj; - vfs->readblocks[1] = (mp_obj_t)&flash_obj; - vfs->readblocks[2] = (mp_obj_t)storage_read_blocks; // native version - vfs->writeblocks[0] = (mp_obj_t)&flash_writeblocks_obj; - vfs->writeblocks[1] = (mp_obj_t)&flash_obj; - vfs->writeblocks[2] = (mp_obj_t)storage_write_blocks; // native version - vfs->u.ioctl[0] = (mp_obj_t)&flash_ioctl_obj; - vfs->u.ioctl[1] = (mp_obj_t)&flash_obj; + vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL | FSUSER_USB_WRITEABLE; + vfs->readblocks[0] = (mp_obj_t)&internal_flash_obj_readblocks_obj; + vfs->readblocks[1] = (mp_obj_t)&internal_flash_obj; + vfs->readblocks[2] = (mp_obj_t)internal_flash_read_blocks; // native version + vfs->writeblocks[0] = (mp_obj_t)&internal_flash_obj_writeblocks_obj; + vfs->writeblocks[1] = (mp_obj_t)&internal_flash_obj; + vfs->writeblocks[2] = (mp_obj_t)internal_flash_write_blocks; // native version + vfs->u.ioctl[0] = (mp_obj_t)&internal_flash_obj_ioctl_obj; + vfs->u.ioctl[1] = (mp_obj_t)&internal_flash_obj; } diff --git a/atmel-samd/internal_flash.h b/atmel-samd/internal_flash.h new file mode 100644 index 0000000000000..38886c930496e --- /dev/null +++ b/atmel-samd/internal_flash.h @@ -0,0 +1,53 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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. + */ +#ifndef __MICROPY_INCLUDED_ATMEL_SAMD_INTERNAL_FLASH_H__ +#define __MICROPY_INCLUDED_ATMEL_SAMD_INTERNAL_FLASH_H__ + +#include "mpconfigport.h" + +#define INTERNAL_FLASH_BLOCK_SIZE (512) + +#define INTERNAL_FLASH_SYSTICK_MASK (0x1ff) // 512ms +#define INTERNAL_FLASH_IDLE_TICK(tick) (((tick) & INTERNAL_FLASH_SYSTICK_MASK) == 2) + +void internal_flash_init(void); +uint32_t internal_flash_get_block_size(void); +uint32_t internal_flash_get_block_count(void); +void internal_flash_irq_handler(void); +void internal_flash_flush(void); +bool internal_flash_read_block(uint8_t *dest, uint32_t block); +bool internal_flash_write_block(const uint8_t *src, uint32_t block); + +// these return 0 on success, non-zero on error +mp_uint_t internal_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks); +mp_uint_t internal_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks); + +extern const struct _mp_obj_type_t internal_flash_type; + +struct _fs_user_mount_t; +void flash_init_vfs(struct _fs_user_mount_t *vfs); + +#endif // __MICROPY_INCLUDED_ATMEL_SAMD_INTERNAL_FLASH_H__ diff --git a/atmel-samd/main.c b/atmel-samd/main.c index 77a0f53fd849e..ff970f15c9b8c 100644 --- a/atmel-samd/main.c +++ b/atmel-samd/main.c @@ -10,6 +10,7 @@ #include "py/gc.h" #include "lib/fatfs/ff.h" +#include "lib/fatfs/diskio.h" #include "lib/utils/pyexec.h" #include "extmod/fsusermount.h" @@ -23,7 +24,6 @@ #include "mpconfigboard.h" #include "modmachine_pin.h" -#include "storage.h" fs_user_mount_t fs_user_mount_flash; @@ -72,6 +72,8 @@ static const char fresh_readme_txt[] = "Please visit http://micropython.org/help/ for further help.\r\n" ; +extern void flash_init_vfs(fs_user_mount_t *vfs); + // we don't make this function static because it needs a lot of stack and we // want it to be executed without using stack within main() function void init_flash_fs() { @@ -159,6 +161,12 @@ static char *stack_top; static char heap[16384]; void reset_mp() { + // Sync the file systems in case any used RAM from the GC to cache. As soon + // as we re-init the GC all bets are off on the cache. + disk_ioctl(0, CTRL_SYNC, NULL); + disk_ioctl(1, CTRL_SYNC, NULL); + disk_ioctl(2, CTRL_SYNC, NULL); + #if MICROPY_ENABLE_GC gc_init(heap, heap + sizeof(heap)); #endif @@ -184,9 +192,6 @@ int main(int argc, char **argv) { samd21_init(); #endif - // Initialise the local flash filesystem. - // Create it if needed, mount in on /flash, and set it as current dir. - init_flash_fs(); int stack_dummy; // Store the location of stack_dummy as an approximation for the top of the @@ -195,6 +200,16 @@ int main(int argc, char **argv) { stack_top = (char*)&stack_dummy; reset_mp(); + // Initialise the local flash filesystem after the gc in case we need to + // grab memory from it. Create it if needed, mount in on /flash, and set it + // as current dir. + init_flash_fs(); + + // Start USB after getting everything going. + #ifdef USB_REPL + udc_start(); + #endif + // Main script is finished, so now go into REPL mode. // The REPL mode can change, or it can request a soft reset. int exit_code = 0; @@ -326,10 +341,6 @@ void samd21_init(void) { // pin_conf.direction = PORT_PIN_DIR_OUTPUT; // port_pin_set_config(MICROPY_HW_LED1, &pin_conf); // port_pin_set_output_level(MICROPY_HW_LED1, false); - - #ifdef USB_REPL - udc_start(); - #endif } #endif diff --git a/atmel-samd/modmachine.c b/atmel-samd/modmachine.c index 67bccc5fdc7f3..791e09ada6346 100644 --- a/atmel-samd/modmachine.c +++ b/atmel-samd/modmachine.c @@ -37,7 +37,6 @@ #include "modmachine_dac.h" #include "modmachine_pin.h" #include "modmachine_pwm.h" -#include "storage.h" #if MICROPY_PY_MACHINE // TODO(tannewt): Add the machine_ prefix to all types so that we don't risk @@ -49,7 +48,6 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&pin_type) }, { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&pwm_type) }, - { MP_ROM_QSTR(MP_QSTR_Flash), MP_ROM_PTR(&flash_type) }, { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, }; diff --git a/atmel-samd/mpconfigport.h b/atmel-samd/mpconfigport.h index 0ebce19583ace..41ef7f6c4cfc1 100644 --- a/atmel-samd/mpconfigport.h +++ b/atmel-samd/mpconfigport.h @@ -70,7 +70,11 @@ #define MICROPY_FATFS_VOLUMES (4) #define MICROPY_FATFS_MULTI_PARTITION (1) #define MICROPY_FSUSERMOUNT (1) -#define MICROPY_FATFS_MAX_SS (4096) +// Only enable this if you really need it. It allocates a byte cache of this +// size. +// #define MICROPY_FATFS_MAX_SS (4096) + +#define FLASH_BLOCK_SIZE (512) #define MICROPY_VFS_FAT (1) #define MICROPY_PY_MACHINE (1) diff --git a/atmel-samd/spi_flash.c b/atmel-samd/spi_flash.c new file mode 100644 index 0000000000000..20daed0f7fb5b --- /dev/null +++ b/atmel-samd/spi_flash.c @@ -0,0 +1,641 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 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 +#include + +#include "asf/sam0/drivers/sercom/spi/spi.h" + +#include "py/gc.h" +#include "py/obj.h" +#include "py/runtime.h" +#include "lib/fatfs/ff.h" +#include "extmod/fsusermount.h" + +#include "asf/sam0/drivers/nvm/nvm.h" + +#include "spi_flash.h" + +#define SPI_FLASH_PART1_START_BLOCK (0x1) + +#define NO_SECTOR_LOADED 0xFFFFFFFF + +#define CMD_READ_JEDEC_ID 0x9f +#define CMD_READ_DATA 0x03 +#define CMD_SECTOR_ERASE 0x20 +// #define CMD_SECTOR_ERASE CMD_READ_JEDEC_ID +#define CMD_ENABLE_WRITE 0x06 +#define CMD_PAGE_PROGRAM 0x02 +// #define CMD_PAGE_PROGRAM CMD_READ_JEDEC_ID +#define CMD_READ_STATUS 0x05 + +static bool spi_flash_is_initialised = false; + +struct spi_module spi_flash_instance; + +// The total size of the flash. +static uint32_t flash_size; + +// The erase sector size. +static uint32_t sector_size; + +// The page size. Its the maximum number of bytes that can be written at once. +static uint32_t page_size; + +// The currently cached sector in the cache, ram or flash based. +static uint32_t current_sector; + +// Track which blocks (up to 32) in the current sector currently live in the +// cache. +static uint32_t dirty_mask; + +// We use this when we can allocate the whole cache in RAM. +static uint8_t** ram_cache; + +// Address of the scratch flash sector. +#define SCRATCH_SECTOR (flash_size - sector_size) + +// Enable the flash over SPI. +static void flash_enable() { + port_pin_set_output_level(SPI_FLASH_CS, false); +} + +// Disable the flash over SPI. +static void flash_disable() { + port_pin_set_output_level(SPI_FLASH_CS, true); +} + +// Wait until both the write enable and write in progress bits have cleared. +static bool wait_for_flash_ready() { + uint8_t status_request[2] = {CMD_READ_STATUS, 0x00}; + uint8_t response[2] = {0x00, 0x01}; + enum status_code status = STATUS_OK; + // Both the write enable and write in progress bits should be low. + while (status == STATUS_OK && ((response[1] & 0x1) == 1 || (response[1] & 0x2) == 2)) { + flash_enable(); + status = spi_transceive_buffer_wait(&spi_flash_instance, status_request, response, 2); + flash_disable(); + } + return status == STATUS_OK; +} + +// Turn on the write enable bit so we can program and erase the flash. +static bool write_enable() { + flash_enable(); + uint8_t command = CMD_ENABLE_WRITE; + enum status_code status = spi_write_buffer_wait(&spi_flash_instance, &command, 1); + flash_disable(); + return status == STATUS_OK; +} + +// Pack the low 24 bits of the address into a uint8_t array. +static void address_to_bytes(uint32_t address, uint8_t* bytes) { + bytes[0] = (address >> 16) & 0xff; + bytes[1] = (address >> 8) & 0xff; + bytes[2] = address & 0xff; +} + +// Read data_length's worth of bytes starting at address into data. +static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) { + wait_for_flash_ready(); + enum status_code status; + // We can read as much as we want sequentially. + uint8_t read_request[4] = {CMD_READ_DATA, 0x00, 0x00, 0x00}; + address_to_bytes(address, read_request + 1); + flash_enable(); + status = spi_write_buffer_wait(&spi_flash_instance, read_request, 4); + if (status == STATUS_OK) { + status = spi_read_buffer_wait(&spi_flash_instance, data, data_length, 0x00); + } + flash_disable(); + return status == STATUS_OK; +} + +// Writes data_length's worth of bytes starting at address from data. Assumes +// that the sector that address resides in has already been erased. So make sure +// to run erase_sector. +static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_length) { + if (page_size == 0) { + return false; + } + for (uint32_t bytes_written = 0; + bytes_written < data_length; + bytes_written += page_size) { + if (!wait_for_flash_ready() || !write_enable()) { + return false; + } + flash_enable(); + uint8_t command[4] = {CMD_PAGE_PROGRAM, 0x00, 0x00, 0x00}; + address_to_bytes(address + bytes_written, command + 1); + enum status_code status; + status = spi_write_buffer_wait(&spi_flash_instance, command, 4); + if (status == STATUS_OK) { + status = spi_write_buffer_wait(&spi_flash_instance, data + bytes_written, page_size); + } + flash_disable(); + if (status != STATUS_OK) { + return false; + } + } + return true; +} + +// Erases the given sector. Make sure you copied all of the data out of it you +// need! Also note, sector_address is really 24 bits. +static bool erase_sector(uint32_t sector_address) { + // Before we erase the sector we need to wait for any writes to finish and + // and then enable the write again. + if (!wait_for_flash_ready() || !write_enable()) { + return false; + } + + uint8_t erase_request[4] = {CMD_SECTOR_ERASE, 0x00, 0x00, 0x00}; + address_to_bytes(sector_address, erase_request + 1); + + flash_enable(); + enum status_code status = spi_write_buffer_wait(&spi_flash_instance, erase_request, 4); + flash_disable(); + return status == STATUS_OK; +} + +// Sector is really 24 bits. +static bool copy_block(uint32_t src_address, uint32_t dest_address) { + // Copy page by page to minimize RAM buffer. + uint8_t buffer[page_size]; + for (int i = 0; i < FLASH_BLOCK_SIZE / page_size; i++) { + if (!read_flash(src_address + i * page_size, buffer, page_size)) { + return false; + } + if (!write_flash(dest_address + i * page_size, buffer, page_size)) { + return false; + } + } + return true; +} + +void spi_flash_init(void) { + if (!spi_flash_is_initialised) { + struct spi_config config_spi_master; + spi_get_config_defaults(&config_spi_master); + config_spi_master.mux_setting = SPI_FLASH_MUX_SETTING; + config_spi_master.pinmux_pad0 = SPI_FLASH_PAD0_PINMUX; + config_spi_master.pinmux_pad1 = SPI_FLASH_PAD1_PINMUX; + config_spi_master.pinmux_pad2 = SPI_FLASH_PAD2_PINMUX; + config_spi_master.pinmux_pad3 = SPI_FLASH_PAD3_PINMUX; + config_spi_master.mode_specific.master.baudrate = SPI_FLASH_BAUDRATE; + spi_init(&spi_flash_instance, SPI_FLASH_SERCOM, &config_spi_master); + spi_enable(&spi_flash_instance); + + // Manage chip select ourselves. + struct port_config pin_conf; + port_get_config_defaults(&pin_conf); + + pin_conf.direction = PORT_PIN_DIR_OUTPUT; + port_pin_set_config(SPI_FLASH_CS, &pin_conf); + flash_disable(); + + // Activity LED for flash writes. + #ifdef MICROPY_HW_LED_MSC + port_pin_set_config(MICROPY_HW_LED_MSC, &pin_conf); + port_pin_set_output_level(MICROPY_HW_LED_MSC, false); + #endif + + uint8_t jedec_id_request[4] = {CMD_READ_JEDEC_ID, 0x00, 0x00, 0x00}; + uint8_t response[4] = {0x00, 0x00, 0x00, 0x00}; + flash_enable(); + volatile enum status_code status = spi_transceive_buffer_wait(&spi_flash_instance, jedec_id_request, response, 4); + flash_disable(); + (void) status; + if (response[1] == 0x01 && response[2] == 0x40 && response[3] == 0x15) { + flash_size = 1 << 21; // 2 MiB + sector_size = 1 << 12; // 4 KiB + page_size = 256; // 256 bytes + } else { + // Unknown flash chip! + flash_size = 0; + } + + current_sector = NO_SECTOR_LOADED; + dirty_mask = 0; + ram_cache = NULL; + + spi_flash_is_initialised = true; + } +} + +// The size of each individual block. +uint32_t spi_flash_get_block_size(void) { + return FLASH_BLOCK_SIZE; +} + +// The total number of available blocks. +uint32_t spi_flash_get_block_count(void) { + // We subtract one erase sector size because we may use it as a staging area + // for writes. + return SPI_FLASH_PART1_START_BLOCK + (flash_size - sector_size) / FLASH_BLOCK_SIZE; +} + +// Flush the cache that was written to the scratch portion of flash. Only used +// when ram is tight. +static bool flush_scratch_flash() { + // First, copy out any blocks that we haven't touched from the sector we've + // cached. + bool copy_to_scratch_ok = true; + for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) { + if ((dirty_mask & (1 << i)) == 0) { + copy_to_scratch_ok = copy_to_scratch_ok && + copy_block(current_sector + i * FLASH_BLOCK_SIZE, + SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE); + } + } + if (!copy_to_scratch_ok) { + // TODO(tannewt): Do more here. We opted to not erase and copy bad data + // in. We still risk losing the data written to the scratch sector. + return false; + } + // Second, erase the current sector. + erase_sector(current_sector); + // Finally, copy the new version into it. + for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) { + copy_block(SCRATCH_SECTOR + i * FLASH_BLOCK_SIZE, + current_sector + i * FLASH_BLOCK_SIZE); + } + return true; +} + +// Attempts to allocate a new set of page buffers for caching a full sector in +// ram. Each page is allocated separately so that the GC doesn't need to provide +// one huge block. We can free it as we write if we want to also. +static bool allocate_ram_cache() { + uint8_t blocks_per_sector = sector_size / FLASH_BLOCK_SIZE; + uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size; + ram_cache = gc_alloc(blocks_per_sector * pages_per_block * sizeof(uint32_t), false); + if (ram_cache == NULL) { + return false; + } + // Declare i and j outside the loops in case we fail to allocate everything + // we need. In that case we'll give it back. + int i = 0; + int j = 0; + bool success = true; + for (i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) { + for (int j = 0; j < pages_per_block; j++) { + uint8_t *page_cache = gc_alloc(page_size, false); + if (page_cache == NULL) { + success = false; + break; + } + ram_cache[i * pages_per_block + j] = page_cache; + } + if (!success) { + break; + } + } + // We couldn't allocate enough so give back what we got. + if (!success) { + for (; i >= 0; i--) { + for (; j >= 0; j--) { + gc_free(ram_cache[i * pages_per_block + j]); + } + j = pages_per_block - 1; + } + gc_free(ram_cache); + ram_cache = NULL; + } + return success; +} + +// Flush the cached sector from ram onto the flash. We'll free the cache unless +// keep_cache is true. +static bool flush_ram_cache(bool keep_cache) { + // First, copy out any blocks that we haven't touched from the sector + // we've cached. If we don't do this we'll erase the data during the sector + // erase below. + bool copy_to_ram_ok = true; + uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size; + for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) { + if ((dirty_mask & (1 << i)) == 0) { + for (int j = 0; j < pages_per_block; j++) { + copy_to_ram_ok = read_flash( + current_sector + (i * pages_per_block + j) * page_size, + ram_cache[i * pages_per_block + j], + page_size); + if (!copy_to_ram_ok) { + break; + } + } + } + if (!copy_to_ram_ok) { + break; + } + } + + if (!copy_to_ram_ok) { + return false; + } + // Second, erase the current sector. + erase_sector(current_sector); + // Lastly, write all the data in ram that we've cached. + for (int i = 0; i < sector_size / FLASH_BLOCK_SIZE; i++) { + for (int j = 0; j < pages_per_block; j++) { + write_flash(current_sector + (i * pages_per_block + j) * page_size, + ram_cache[i * pages_per_block + j], + page_size); + if (!keep_cache) { + gc_free(ram_cache[i * pages_per_block + j]); + } + } + } + // We're done with the cache for now so give it back. + if (!keep_cache) { + gc_free(ram_cache); + ram_cache = NULL; + } + return true; +} + +// Delegates to the correct flash flush method depending on the existing cache. +static void spi_flash_flush_keep_cache(bool keep_cache) { + if (current_sector == NO_SECTOR_LOADED) { + return; + } + #ifdef MICROPY_HW_LED_MSC + port_pin_set_output_level(MICROPY_HW_LED_MSC, true); + #endif + // If we've cached to the flash itself flush from there. + if (ram_cache == NULL) { + flush_scratch_flash(); + } else { + flush_ram_cache(keep_cache); + } + current_sector = NO_SECTOR_LOADED; + #ifdef MICROPY_HW_LED_MSC + port_pin_set_output_level(MICROPY_HW_LED_MSC, false); + #endif +} + +// External flash function used. If called externally we assume we won't need +// the cache after. +void spi_flash_flush(void) { + spi_flash_flush_keep_cache(false); +} + +// Builds a partition entry for the MBR. +static void build_partition(uint8_t *buf, int boot, int type, + uint32_t start_block, uint32_t num_blocks) { + buf[0] = boot; + + if (num_blocks == 0) { + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + } else { + buf[1] = 0xff; + buf[2] = 0xff; + buf[3] = 0xff; + } + + buf[4] = type; + + if (num_blocks == 0) { + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + } else { + buf[5] = 0xff; + buf[6] = 0xff; + buf[7] = 0xff; + } + + buf[8] = start_block; + buf[9] = start_block >> 8; + buf[10] = start_block >> 16; + buf[11] = start_block >> 24; + + buf[12] = num_blocks; + buf[13] = num_blocks >> 8; + buf[14] = num_blocks >> 16; + buf[15] = num_blocks >> 24; +} + +static uint32_t convert_block_to_flash_addr(uint32_t block) { + if (SPI_FLASH_PART1_START_BLOCK <= block && block < spi_flash_get_block_count()) { + // a block in partition 1 + block -= SPI_FLASH_PART1_START_BLOCK; + return block * FLASH_BLOCK_SIZE; + } + // bad block + return -1; +} + +bool spi_flash_read_block(uint8_t *dest, uint32_t block) { + if (block == 0) { + // Fake the MBR so we can decide on our own partition table + for (int i = 0; i < 446; i++) { + dest[i] = 0; + } + + build_partition(dest + 446, 0, 0x01 /* FAT12 */, + SPI_FLASH_PART1_START_BLOCK, + spi_flash_get_block_count() - SPI_FLASH_PART1_START_BLOCK); + build_partition(dest + 462, 0, 0, 0, 0); + build_partition(dest + 478, 0, 0, 0, 0); + build_partition(dest + 494, 0, 0, 0, 0); + + dest[510] = 0x55; + dest[511] = 0xaa; + + return true; + } else if (block < SPI_FLASH_PART1_START_BLOCK) { + memset(dest, 0, FLASH_BLOCK_SIZE); + return true; + } else { + // Non-MBR block, get data from flash memory. + uint32_t address = convert_block_to_flash_addr(block); + if (address == -1) { + // bad block number + return false; + } + + // Mask out the lower bits that designate the address within the sector. + uint32_t this_sector = address & (~(sector_size - 1)); + uint8_t block_index = (address / FLASH_BLOCK_SIZE) % (sector_size / FLASH_BLOCK_SIZE); + uint8_t mask = 1 << (block_index); + // We're reading from the currently cached sector. + if (current_sector == this_sector && (mask & dirty_mask) > 0) { + if (ram_cache != NULL) { + uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size; + for (int i = 0; i < pages_per_block; i++) { + memcpy(dest + i * page_size, + ram_cache[block_index * pages_per_block + i], + page_size); + } + return true; + } else { + uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE; + return read_flash(scratch_address, dest, FLASH_BLOCK_SIZE); + } + } + return read_flash(address, dest, FLASH_BLOCK_SIZE); + } +} + +bool spi_flash_write_block(const uint8_t *data, uint32_t block) { + if (block < SPI_FLASH_PART1_START_BLOCK) { + // Fake writing below the flash partition. + return true; + } else { + // Non-MBR block, copy to cache + uint32_t address = convert_block_to_flash_addr(block); + if (address == -1) { + // bad block number + return false; + } + // Wait for any previous writes to finish. + wait_for_flash_ready(); + // Mask out the lower bits that designate the address within the sector. + uint32_t this_sector = address & (~(sector_size - 1)); + uint8_t block_index = (address / FLASH_BLOCK_SIZE) % (sector_size / FLASH_BLOCK_SIZE); + uint8_t mask = 1 << (block_index); + // Flush the cache if we're moving onto a sector our we're writing the + // same block again. + if (current_sector != this_sector || (mask & dirty_mask) > 0) { + if (current_sector != NO_SECTOR_LOADED) { + spi_flash_flush_keep_cache(true); + } + if (ram_cache == NULL && !allocate_ram_cache()) { + erase_sector(SCRATCH_SECTOR); + wait_for_flash_ready(); + } + current_sector = this_sector; + dirty_mask = 0; + } + dirty_mask |= mask; + // Copy the block to the appropriate cache. + if (ram_cache != NULL) { + uint8_t pages_per_block = FLASH_BLOCK_SIZE / page_size; + for (int i = 0; i < pages_per_block; i++) { + memcpy(ram_cache[block_index * pages_per_block + i], + data + i * page_size, + page_size); + } + return true; + } else { + uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE; + return write_flash(scratch_address, data, FLASH_BLOCK_SIZE); + } + } +} + +mp_uint_t spi_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) { + for (size_t i = 0; i < num_blocks; i++) { + if (!spi_flash_read_block(dest + i * FLASH_BLOCK_SIZE, block_num + i)) { + return 1; // error + } + } + return 0; // success +} + +mp_uint_t spi_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) { + for (size_t i = 0; i < num_blocks; i++) { + if (!spi_flash_write_block(src + i * FLASH_BLOCK_SIZE, block_num + i)) { + return 1; // error + } + } + return 0; // success +} + +/******************************************************************************/ +// MicroPython bindings +// +// Expose the flash as an object with the block protocol. + +// there is a singleton Flash object +STATIC const mp_obj_base_t spi_flash_obj = {&spi_flash_type}; + +STATIC mp_obj_t spi_flash_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 0, 0, false); + + // return singleton object + return (mp_obj_t)&spi_flash_obj; +} + +STATIC mp_obj_t spi_flash_obj_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); + mp_uint_t ret = spi_flash_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE); + return MP_OBJ_NEW_SMALL_INT(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_obj_readblocks_obj, spi_flash_obj_readblocks); + +STATIC mp_obj_t spi_flash_obj_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); + mp_uint_t ret = spi_flash_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FLASH_BLOCK_SIZE); + return MP_OBJ_NEW_SMALL_INT(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_obj_writeblocks_obj, spi_flash_obj_writeblocks); + +STATIC mp_obj_t spi_flash_obj_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) { + mp_int_t cmd = mp_obj_get_int(cmd_in); + switch (cmd) { + case BP_IOCTL_INIT: spi_flash_init(); return MP_OBJ_NEW_SMALL_INT(0); + case BP_IOCTL_DEINIT: spi_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); // TODO properly + case BP_IOCTL_SYNC: spi_flash_flush(); return MP_OBJ_NEW_SMALL_INT(0); + case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(spi_flash_get_block_count()); + case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(spi_flash_get_block_size()); + default: return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(spi_flash_obj_ioctl_obj, spi_flash_obj_ioctl); + +STATIC const mp_map_elem_t spi_flash_obj_locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_readblocks), (mp_obj_t)&spi_flash_obj_readblocks_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_writeblocks), (mp_obj_t)&spi_flash_obj_writeblocks_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ioctl), (mp_obj_t)&spi_flash_obj_ioctl_obj }, +}; + +STATIC MP_DEFINE_CONST_DICT(spi_flash_obj_locals_dict, spi_flash_obj_locals_dict_table); + +const mp_obj_type_t spi_flash_type = { + { &mp_type_type }, + .name = MP_QSTR_SPIFlash, + .make_new = spi_flash_obj_make_new, + .locals_dict = (mp_obj_t)&spi_flash_obj_locals_dict, +}; + +void flash_init_vfs(fs_user_mount_t *vfs) { + vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL | FSUSER_USB_WRITEABLE; + vfs->readblocks[0] = (mp_obj_t)&spi_flash_obj_readblocks_obj; + vfs->readblocks[1] = (mp_obj_t)&spi_flash_obj; + vfs->readblocks[2] = (mp_obj_t)spi_flash_read_blocks; // native version + vfs->writeblocks[0] = (mp_obj_t)&spi_flash_obj_writeblocks_obj; + vfs->writeblocks[1] = (mp_obj_t)&spi_flash_obj; + vfs->writeblocks[2] = (mp_obj_t)spi_flash_write_blocks; // native version + vfs->u.ioctl[0] = (mp_obj_t)&spi_flash_obj_ioctl_obj; + vfs->u.ioctl[1] = (mp_obj_t)&spi_flash_obj; +} diff --git a/atmel-samd/storage.h b/atmel-samd/spi_flash.h similarity index 61% rename from atmel-samd/storage.h rename to atmel-samd/spi_flash.h index 8b814c34e11db..ec7e23ac5df83 100644 --- a/atmel-samd/storage.h +++ b/atmel-samd/spi_flash.h @@ -23,31 +23,32 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef __MICROPY_INCLUDED_ATMEL_SAMD_STORAGE_H__ -#define __MICROPY_INCLUDED_ATMEL_SAMD_STORAGE_H__ +#ifndef __MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H__ +#define __MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H__ #include "mpconfigport.h" -#define FLASH_BLOCK_SIZE (512) +// Erase sector size. +#define SPI_FLASH_SECTOR_SIZE (0x1000 - 100) -#define STORAGE_SYSTICK_MASK (0x1ff) // 512ms -#define STORAGE_IDLE_TICK(tick) (((tick) & STORAGE_SYSTICK_MASK) == 2) +#define SPI_FLASH_SYSTICK_MASK (0x1ff) // 512ms +#define SPI_FLASH_IDLE_TICK(tick) (((tick) & SPI_FLASH_SYSTICK_MASK) == 2) -void storage_init(void); -uint32_t storage_get_block_size(void); -uint32_t storage_get_block_count(void); -void storage_irq_handler(void); -void storage_flush(void); -bool storage_read_block(uint8_t *dest, uint32_t block); -bool storage_write_block(const uint8_t *src, uint32_t block); +void spi_flash_init(void); +uint32_t spi_flash_get_block_size(void); +uint32_t spi_flash_get_block_count(void); +void spi_flash_irq_handler(void); +void spi_flash_flush(void); +bool spi_flash_read_block(uint8_t *dest, uint32_t block); +bool spi_flash_write_block(const uint8_t *src, uint32_t block); // these return 0 on success, non-zero on error -mp_uint_t storage_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks); -mp_uint_t storage_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks); +mp_uint_t spi_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks); +mp_uint_t spi_flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks); -extern const struct _mp_obj_type_t flash_type; +extern const struct _mp_obj_type_t spi_flash_type; struct _fs_user_mount_t; void flash_init_vfs(struct _fs_user_mount_t *vfs); -#endif // __MICROPY_INCLUDED_ATMEL_SAMD_STORAGE_H__ +#endif // __MICROPY_INCLUDED_ATMEL_SAMD_SPI_FLASH_H__ diff --git a/extmod/fsusermount.h b/extmod/fsusermount.h index e1f26f2ce8f15..7f2912b7f00f5 100644 --- a/extmod/fsusermount.h +++ b/extmod/fsusermount.h @@ -24,10 +24,15 @@ * THE SOFTWARE. */ +#include "lib/fatfs/ff.h" +#include "py/obj.h" + // these are the values for fs_user_mount_t.flags -#define FSUSER_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func -#define FSUSER_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount -#define FSUSER_HAVE_IOCTL (0x0004) // new protocol with ioctl +#define FSUSER_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func +#define FSUSER_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount +#define FSUSER_HAVE_IOCTL (0x0004) // new protocol with ioctl +// Device is write-able over USB and read-only to MicroPython. +#define FSUSER_USB_WRITEABLE (0x0008) // constants for block protocol ioctl #define BP_IOCTL_INIT (1) diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index 6e827fc664883..a0501adbb4323 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -278,7 +278,11 @@ STATIC mp_obj_t fat_vfs_statvfs(mp_obj_t vfs_in, mp_obj_t path_in) { mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL)); - t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * fatfs->ssize); // f_bsize + #if _MIN_SS != _MAX_SS + t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * fatfs->ssize); // f_bsize + #else + t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * _MIN_SS); // f_bsize + #endif t->items[1] = t->items[0]; // f_frsize t->items[2] = MP_OBJ_NEW_SMALL_INT((fatfs->n_fatent - 2) * fatfs->csize); // f_blocks t->items[3] = MP_OBJ_NEW_SMALL_INT(nclst); // f_bfree diff --git a/extmod/vfs_fat_diskio.c b/extmod/vfs_fat_diskio.c index 3f1902b2e0ad3..4d776f6b4bf77 100644 --- a/extmod/vfs_fat_diskio.c +++ b/extmod/vfs_fat_diskio.c @@ -90,7 +90,7 @@ DSTATUS disk_initialize ( /*-----------------------------------------------------------------------*/ DSTATUS disk_status ( - BYTE pdrv /* Physical drive nmuber (0..) */ + BYTE pdrv /* Physical drive number (0..) */ ) { fs_user_mount_t *vfs = disk_get_device(pdrv); @@ -98,7 +98,10 @@ DSTATUS disk_status ( return STA_NOINIT; } - if (vfs->writeblocks[0] == MP_OBJ_NULL) { + // This is used to determine the writeability of the disk from MicroPython. + // So, if its USB writeable we make it read-only from MicroPython. + if (vfs->writeblocks[0] == MP_OBJ_NULL || + (vfs->flags & FSUSER_USB_WRITEABLE) != 0) { return STA_PROTECT; } else { return 0; @@ -110,7 +113,7 @@ DSTATUS disk_status ( /*-----------------------------------------------------------------------*/ DRESULT disk_read ( - BYTE pdrv, /* Physical drive nmuber (0..) */ + BYTE pdrv, /* Physical drive number (0..) */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address (LBA) */ UINT count /* Number of sectors to read (1..128) */