From 306c921ed1437fc79423958a5e4bd8b0e96bc863 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Thu, 13 Oct 2016 14:53:31 -0700 Subject: [PATCH 01/11] atmel-samd: Rework mass storage interaction with underlying block storage to use micropython's VFS interface. This makes mass storage work with any VFS implementation rather than a single one. --- atmel-samd/Makefile | 4 +- atmel-samd/{rom_fs.c => access_vfs.c} | 58 +++- atmel-samd/{rom_fs.h => access_vfs.h} | 17 +- atmel-samd/boards/arduino_zero/conf_access.h | 18 +- .../boards/arduino_zero/mpconfigboard.mk | 2 + .../boards/feather_m0_basic/mpconfigboard.mk | 2 + atmel-samd/{storage.c => internal_flash.c} | 118 +++---- atmel-samd/internal_flash.h | 53 ++++ atmel-samd/main.c | 3 +- atmel-samd/modmachine.c | 2 - atmel-samd/mpconfigport.h | 2 + atmel-samd/spi_flash.c | 299 ++++++++++++++++++ atmel-samd/{storage.h => spi_flash.h} | 32 +- extmod/fsusermount.h | 3 + extmod/vfs_fat_diskio.c | 4 +- 15 files changed, 506 insertions(+), 111 deletions(-) rename atmel-samd/{rom_fs.c => access_vfs.c} (73%) rename atmel-samd/{rom_fs.h => access_vfs.h} (76%) rename atmel-samd/{storage.c => internal_flash.c} (57%) create mode 100644 atmel-samd/internal_flash.h create mode 100644 atmel-samd/spi_flash.c rename atmel-samd/{storage.h => spi_flash.h} (62%) 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 73% rename from atmel-samd/rom_fs.c rename to atmel-samd/access_vfs.c index f0b01fa6b7611..a3e8aaebcd1c5 100644 --- a/atmel-samd/rom_fs.c +++ b/atmel-samd/access_vfs.c @@ -24,10 +24,16 @@ * THE SOFTWARE. */ -#include "rom_fs.h" +#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,9 +41,17 @@ //! 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 @@ -47,9 +61,11 @@ Ctrl_status rom_fs_test_unit_ready(void) //! 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 *uint32_t_nb_sector) { - *uint32_t_nb_sector = storage_get_block_count(); + if (disk_ioctl(VFS_INDEX, GET_SECTOR_COUNT, uint32_t_nb_sector) != RES_OK) { + return CTRL_FAIL; + } return CTRL_GOOD; } @@ -57,20 +73,26 @@ Ctrl_status rom_fs_read_capacity(uint32_t *uint32_t_nb_sector) //! //! @return true if the memory is protected //! -bool rom_fs_wr_protect(void) +bool vfs_wr_protect(void) { - return false; + DSTATUS status = disk_status(VFS_INDEX); + return status == STA_NOINIT || status == STA_PROTECT; } //! 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 +106,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,14 +136,18 @@ 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, 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)) { + DRESULT result = disk_write(VFS_INDEX, sector_buffer, addr + sector, 1); + if (result == RES_PARERR) { + return CTRL_NO_PRESENT; + } + if (result == RES_ERROR) { return CTRL_FAIL; } } 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/storage.c b/atmel-samd/internal_flash.c similarity index 57% rename from atmel-samd/storage.c rename to atmel-samd/internal_flash.c index a4037da4f6217..f7d0aea6c6204 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->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..6d781e7964b35 100644 --- a/atmel-samd/main.c +++ b/atmel-samd/main.c @@ -23,7 +23,6 @@ #include "mpconfigboard.h" #include "modmachine_pin.h" -#include "storage.h" fs_user_mount_t fs_user_mount_flash; @@ -72,6 +71,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() { 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..e38f3fb572fdd 100644 --- a/atmel-samd/mpconfigport.h +++ b/atmel-samd/mpconfigport.h @@ -72,6 +72,8 @@ #define MICROPY_FSUSERMOUNT (1) #define MICROPY_FATFS_MAX_SS (4096) +#define FLASH_BLOCK_SIZE (512) + #define MICROPY_VFS_FAT (1) #define MICROPY_PY_MACHINE (1) #define MICROPY_MODULE_WEAK_LINKS (1) diff --git a/atmel-samd/spi_flash.c b/atmel-samd/spi_flash.c new file mode 100644 index 0000000000000..73717ffca544c --- /dev/null +++ b/atmel-samd/spi_flash.c @@ -0,0 +1,299 @@ +/* + * 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/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 TOTAL_SPI_FLASH_SIZE 0x010000 + +#define SPI_FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_SPI_FLASH_SIZE) +#define SPI_FLASH_PART1_START_BLOCK (0x100) +#define SPI_FLASH_PART1_NUM_BLOCKS (TOTAL_SPI_FLASH_SIZE / SPI_FLASH_BLOCK_SIZE) + +static bool spi_flash_is_initialised = false; + +struct spi_module spi_flash_instance; + +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); + } +} + +uint32_t spi_flash_get_block_size(void) { + return SPI_FLASH_BLOCK_SIZE; +} + +uint32_t spi_flash_get_block_count(void) { + return SPI_FLASH_PART1_START_BLOCK + SPI_FLASH_PART1_NUM_BLOCKS; +} + +void spi_flash_flush(void) { +} + +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_PART1_START_BLOCK + SPI_FLASH_PART1_NUM_BLOCKS) { + // a block in partition 1 + block -= SPI_FLASH_PART1_START_BLOCK; + return SPI_FLASH_MEM_SEG1_START_ADDR + block * SPI_FLASH_BLOCK_SIZE; + } + // bad block + return -1; +} + +bool spi_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 + + for (int i = 0; i < 446; i++) { + dest[i] = 0; + } + + build_partition(dest + 446, 0, 0x01 /* FAT12 */, SPI_FLASH_PART1_START_BLOCK, SPI_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); + + dest[510] = 0x55; + dest[511] = 0xaa; + + return true; + + } else { + // non-MBR block, get data from flash memory + uint32_t src = convert_block_to_flash_addr(block); + if (src == -1) { + // bad block number + return false; + } + enum status_code error_code; + // A block is made up of multiple pages. Read each page + // sequentially. + for (int i = 0; i < SPI_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { + do + { + error_code = nvm_read_buffer(src + i * NVMCTRL_PAGE_SIZE, + dest + i * NVMCTRL_PAGE_SIZE, + NVMCTRL_PAGE_SIZE); + } while (error_code == STATUS_BUSY); + } + return true; + } +} + +bool spi_flash_write_block(const uint8_t *src, uint32_t block) { + if (block == 0) { + // can't write MBR, but pretend we did + return true; + + } else { + // non-MBR block, copy to cache + volatile uint32_t dest = convert_block_to_flash_addr(block); + if (dest == -1) { + // bad block number + return false; + } + enum status_code error_code; + // A block is formed by two rows of flash. We must erase each row + // before we write back to it. + do + { + error_code = nvm_erase_row(dest); + } while (error_code == STATUS_BUSY); + if (error_code != STATUS_OK) { + return false; + } + do + { + error_code = nvm_erase_row(dest + NVMCTRL_ROW_SIZE); + } while (error_code == STATUS_BUSY); + if (error_code != STATUS_OK) { + return false; + } + + // A block is made up of multiple pages. Write each page + // sequentially. + for (int i = 0; i < SPI_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { + do + { + error_code = nvm_write_buffer(dest + i * NVMCTRL_PAGE_SIZE, + src + i * NVMCTRL_PAGE_SIZE, + NVMCTRL_PAGE_SIZE); + } while (error_code == STATUS_BUSY); + if (error_code != STATUS_OK) { + return false; + } + } + return true; + } +} + +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 * SPI_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 * SPI_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; + 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 62% rename from atmel-samd/storage.h rename to atmel-samd/spi_flash.h index 8b814c34e11db..1a8ac0c8e7da5 100644 --- a/atmel-samd/storage.h +++ b/atmel-samd/spi_flash.h @@ -23,31 +23,31 @@ * 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) +#define SPI_FLASH_BLOCK_SIZE (512) -#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..3eb54f8bd4150 100644 --- a/extmod/fsusermount.h +++ b/extmod/fsusermount.h @@ -24,6 +24,9 @@ * 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 diff --git a/extmod/vfs_fat_diskio.c b/extmod/vfs_fat_diskio.c index 3f1902b2e0ad3..5608e06453c81 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); @@ -110,7 +110,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) */ From 853e5fc652a50a5424ef62495fcbf962df7650c3 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 17 Oct 2016 15:10:00 -0700 Subject: [PATCH 02/11] atmel-samd: Ensure at least 2k is left for the stack. --- atmel-samd/boards/samd21x18-bootloader.ld | 8 ++++++++ atmel-samd/boards/samd21x18.ld | 8 ++++++++ 2 files changed, 16 insertions(+) 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.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) } } From aacb1adcd10f6325719bfe4faa0c8593eef94016 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 17 Oct 2016 15:10:38 -0700 Subject: [PATCH 03/11] atmel-samd: Add linker file for bootloaderless board with external flash. --- atmel-samd/boards/samd21x18-external-flash.ld | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 atmel-samd/boards/samd21x18-external-flash.ld 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) } +} From 46f6f9f899f69ca678ef6f3facc5096e57a9787d Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 17 Oct 2016 15:11:19 -0700 Subject: [PATCH 04/11] atmel-samd: Start USB at the end so storage is initialized. --- atmel-samd/main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/atmel-samd/main.c b/atmel-samd/main.c index 6d781e7964b35..cd1eafc234c61 100644 --- a/atmel-samd/main.c +++ b/atmel-samd/main.c @@ -196,6 +196,11 @@ int main(int argc, char **argv) { stack_top = (char*)&stack_dummy; reset_mp(); + // 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; @@ -327,10 +332,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 From bb1822faea6038e09c80eb8bec1b9df616a00ac0 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 14 Oct 2016 12:22:06 -0700 Subject: [PATCH 05/11] atmel-samd: Support external flash on the Metro M0 w/Flash. This uses a scratch flash sector to save data before writing a full sector. --- .../boards/metro_m0_flash/conf_access.h | 18 +- .../boards/metro_m0_flash/mpconfigboard.h | 24 +- .../boards/metro_m0_flash/mpconfigboard.mk | 2 + atmel-samd/boards/metro_m0_flash/pins.c | 2 +- .../samd21x18-bootloader-external-flash.ld | 8 + atmel-samd/spi_flash.c | 279 ++++++++++++++---- atmel-samd/spi_flash.h | 3 +- 7 files changed, 266 insertions(+), 70 deletions(-) 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..b9d2f5f02520f 100644 --- a/atmel-samd/boards/metro_m0_flash/mpconfigboard.h +++ b/atmel-samd/boards/metro_m0_flash/mpconfigboard.h @@ -1,5 +1,3 @@ -// LEDs -#define MICROPY_HW_LED1 PIN_PA17 // red // #define UART_REPL #define USB_REPL @@ -8,3 +6,25 @@ #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/spi_flash.c b/atmel-samd/spi_flash.c index 73717ffca544c..abcc79f8c5b4e 100644 --- a/atmel-samd/spi_flash.c +++ b/atmel-samd/spi_flash.c @@ -38,18 +38,153 @@ #include "spi_flash.h" -#define TOTAL_SPI_FLASH_SIZE 0x010000 - -#define SPI_FLASH_MEM_SEG1_START_ADDR (0x00040000 - TOTAL_SPI_FLASH_SIZE) #define SPI_FLASH_PART1_START_BLOCK (0x100) -#define SPI_FLASH_PART1_NUM_BLOCKS (TOTAL_SPI_FLASH_SIZE / SPI_FLASH_BLOCK_SIZE) + +#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 scratch flash space. +static uint32_t current_sector; + +// A sector is made up of 8 blocks. This tracks which of those blocks in the +// current sector current live in the scratch sector. +static uint8_t dirty_mask; + +#define SCRATCH_SECTOR (flash_size - sector_size) + +static void flash_enable() { + port_pin_set_output_level(SPI_FLASH_CS, false); +} + +static void flash_disable() { + port_pin_set_output_level(SPI_FLASH_CS, true); +} + +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; + while (status == STATUS_OK && (response[1] & 0x1) == 1) { + flash_enable(); + status = spi_transceive_buffer_wait(&spi_flash_instance, status_request, response, 2); + flash_disable(); + } + return status == STATUS_OK; +} + +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; +} + +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; +} + +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; +} + +// Assumes that the sector that address resides in has already been erased. +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; +} + +// Sector 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. For good measure we check that the flash + // is ready after enabling the write too. + if (!wait_for_flash_ready() || !write_enable() || !wait_for_flash_ready()) { + 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; @@ -60,18 +195,74 @@ void spi_flash_init(void) { 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(); + + 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; + + spi_flash_is_initialised = true; } } uint32_t spi_flash_get_block_size(void) { - return SPI_FLASH_BLOCK_SIZE; + return FLASH_BLOCK_SIZE; } uint32_t spi_flash_get_block_count(void) { - return SPI_FLASH_PART1_START_BLOCK + SPI_FLASH_PART1_NUM_BLOCKS; + // We subtract on erase sector size because we're going to use it as a + // staging area for writes. + return SPI_FLASH_PART1_START_BLOCK + (flash_size - sector_size) / FLASH_BLOCK_SIZE; } void spi_flash_flush(void) { + if (current_sector == NO_SECTOR_LOADED) { + return; + } + // 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; + } + // 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); + } + current_sector = NO_SECTOR_LOADED; } static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) { @@ -111,10 +302,10 @@ 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 (SPI_FLASH_PART1_START_BLOCK <= block && block < SPI_FLASH_PART1_START_BLOCK + SPI_FLASH_PART1_NUM_BLOCKS) { + 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 SPI_FLASH_MEM_SEG1_START_ADDR + block * SPI_FLASH_BLOCK_SIZE; + return block * FLASH_BLOCK_SIZE; } // bad block return -1; @@ -129,7 +320,7 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) { dest[i] = 0; } - build_partition(dest + 446, 0, 0x01 /* FAT12 */, SPI_FLASH_PART1_START_BLOCK, SPI_FLASH_PART1_NUM_BLOCKS); + 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); @@ -146,71 +337,45 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) { // bad block number return false; } - enum status_code error_code; - // A block is made up of multiple pages. Read each page - // sequentially. - for (int i = 0; i < SPI_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { - do - { - error_code = nvm_read_buffer(src + i * NVMCTRL_PAGE_SIZE, - dest + i * NVMCTRL_PAGE_SIZE, - NVMCTRL_PAGE_SIZE); - } while (error_code == STATUS_BUSY); - } - return true; + return read_flash(src, dest, FLASH_BLOCK_SIZE); } } -bool spi_flash_write_block(const uint8_t *src, uint32_t block) { +bool spi_flash_write_block(const uint8_t *data, uint32_t block) { if (block == 0) { // can't write MBR, but pretend we did return true; } else { // non-MBR block, copy to cache - volatile uint32_t dest = convert_block_to_flash_addr(block); - if (dest == -1) { + volatile uint32_t address = convert_block_to_flash_addr(block); + if (address == -1) { // bad block number return false; } - enum status_code error_code; - // A block is formed by two rows of flash. We must erase each row - // before we write back to it. - do - { - error_code = nvm_erase_row(dest); - } while (error_code == STATUS_BUSY); - if (error_code != STATUS_OK) { - return false; - } - do - { - error_code = nvm_erase_row(dest + NVMCTRL_ROW_SIZE); - } while (error_code == STATUS_BUSY); - if (error_code != STATUS_OK) { - return false; + // Wait for any previous writes to finish. + wait_for_flash_ready(); + uint32_t this_sector = address & (~(sector_size - 1)); + uint8_t block_index = block % (sector_size / FLASH_BLOCK_SIZE); + uint8_t mask = 1 << (block_index); + if (current_sector != this_sector || (mask & dirty_mask) > 0) { + if (current_sector != NO_SECTOR_LOADED) { + spi_flash_flush(); + } + erase_sector(SCRATCH_SECTOR); + current_sector = this_sector; + dirty_mask = 0; + wait_for_flash_ready(); } - - // A block is made up of multiple pages. Write each page - // sequentially. - for (int i = 0; i < SPI_FLASH_BLOCK_SIZE / NVMCTRL_PAGE_SIZE; i++) { - do - { - error_code = nvm_write_buffer(dest + i * NVMCTRL_PAGE_SIZE, - src + i * NVMCTRL_PAGE_SIZE, - NVMCTRL_PAGE_SIZE); - } while (error_code == STATUS_BUSY); - if (error_code != STATUS_OK) { - return false; - } - } - return true; + uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE; + dirty_mask |= mask; + 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 * SPI_FLASH_BLOCK_SIZE, block_num + i)) { + if (!spi_flash_read_block(dest + i * FLASH_BLOCK_SIZE, block_num + i)) { return 1; // error } } @@ -219,7 +384,7 @@ mp_uint_t spi_flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_ 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 * SPI_FLASH_BLOCK_SIZE, block_num + i)) { + if (!spi_flash_write_block(src + i * FLASH_BLOCK_SIZE, block_num + i)) { return 1; // error } } diff --git a/atmel-samd/spi_flash.h b/atmel-samd/spi_flash.h index 1a8ac0c8e7da5..ec7e23ac5df83 100644 --- a/atmel-samd/spi_flash.h +++ b/atmel-samd/spi_flash.h @@ -28,7 +28,8 @@ #include "mpconfigport.h" -#define SPI_FLASH_BLOCK_SIZE (512) +// Erase sector size. +#define SPI_FLASH_SECTOR_SIZE (0x1000 - 100) #define SPI_FLASH_SYSTICK_MASK (0x1ff) // 512ms #define SPI_FLASH_IDLE_TICK(tick) (((tick) & SPI_FLASH_SYSTICK_MASK) == 2) From 8b1526e95eeb5c324b6009d6d77a622bb131886a Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 18 Oct 2016 14:56:17 -0700 Subject: [PATCH 06/11] atmel-samd: Add a heap based cache for writing to flash. The code will fallback to the flash scratch space when the GC cannot allocate us enough memory. --- atmel-samd/access_vfs.c | 10 +- atmel-samd/main.c | 15 ++- atmel-samd/spi_flash.c | 218 +++++++++++++++++++++++++++++++++------- 3 files changed, 200 insertions(+), 43 deletions(-) diff --git a/atmel-samd/access_vfs.c b/atmel-samd/access_vfs.c index a3e8aaebcd1c5..07d0aa64e0dbf 100644 --- a/atmel-samd/access_vfs.c +++ b/atmel-samd/access_vfs.c @@ -55,18 +55,20 @@ Ctrl_status vfs_test_unit_ready(void) } //! 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 vfs_read_capacity(uint32_t *uint32_t_nb_sector) +Ctrl_status vfs_read_capacity(uint32_t *last_valid_sector) { - if (disk_ioctl(VFS_INDEX, GET_SECTOR_COUNT, uint32_t_nb_sector) != RES_OK) { + if (disk_ioctl(VFS_INDEX, GET_SECTOR_COUNT, last_valid_sector) != RES_OK) { return CTRL_FAIL; } - return CTRL_GOOD; + // 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 diff --git a/atmel-samd/main.c b/atmel-samd/main.c index cd1eafc234c61..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" @@ -160,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 @@ -185,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 @@ -196,6 +200,11 @@ 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(); diff --git a/atmel-samd/spi_flash.c b/atmel-samd/spi_flash.c index abcc79f8c5b4e..5424ee6ea5751 100644 --- a/atmel-samd/spi_flash.c +++ b/atmel-samd/spi_flash.c @@ -29,6 +29,7 @@ #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" @@ -38,7 +39,7 @@ #include "spi_flash.h" -#define SPI_FLASH_PART1_START_BLOCK (0x100) +#define SPI_FLASH_PART1_START_BLOCK (0x1) #define NO_SECTOR_LOADED 0xFFFFFFFF @@ -64,28 +65,36 @@ 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 scratch flash space. +// The currently cached sector in the cache, ram or flash based. static uint32_t current_sector; -// A sector is made up of 8 blocks. This tracks which of those blocks in the -// current sector current live in the scratch sector. -static uint8_t dirty_mask; +// 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; - while (status == STATUS_OK && (response[1] & 0x1) == 1) { + // 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(); @@ -93,6 +102,7 @@ static bool wait_for_flash_ready() { 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; @@ -101,12 +111,14 @@ static bool write_enable() { 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; @@ -122,7 +134,9 @@ static bool read_flash(uint32_t address, uint8_t* data, uint32_t data_length) { return status == STATUS_OK; } -// Assumes that the sector that address resides in has already been erased. +// 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; @@ -149,12 +163,12 @@ static bool write_flash(uint32_t address, const uint8_t* data, uint32_t data_len return true; } -// Sector is really 24 bits. +// 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. For good measure we check that the flash - // is ready after enabling the write too. - if (!wait_for_flash_ready() || !write_enable() || !wait_for_flash_ready()) { + // and then enable the write again. + if (!wait_for_flash_ready() || !write_enable()) { return false; } @@ -221,25 +235,27 @@ void spi_flash_init(void) { 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 on erase sector size because we're going to use it as a - // staging area for writes. + // 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; } -void spi_flash_flush(void) { - if (current_sector == NO_SECTOR_LOADED) { - return; - } +// 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; @@ -253,7 +269,7 @@ void spi_flash_flush(void) { 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; + return false; } // Second, erase the current sector. erase_sector(current_sector); @@ -262,10 +278,123 @@ void spi_flash_flush(void) { 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; + } + // 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; } -static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) { +// 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) { @@ -312,15 +441,15 @@ static uint32_t convert_block_to_flash_addr(uint32_t block) { } bool spi_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 - + // 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 + 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); @@ -329,9 +458,11 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) { 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 + // Non-MBR block, get data from flash memory. uint32_t src = convert_block_to_flash_addr(block); if (src == -1) { // bad block number @@ -342,34 +473,49 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) { } bool spi_flash_write_block(const uint8_t *data, uint32_t block) { - if (block == 0) { - // can't write MBR, but pretend we did + if (block < SPI_FLASH_PART1_START_BLOCK) { + // Fake writing below the flash partition. return true; - } else { - // non-MBR block, copy to cache - volatile uint32_t address = convert_block_to_flash_addr(block); + // 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 = block % (sector_size / FLASH_BLOCK_SIZE); + 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(); + spi_flash_flush_keep_cache(true); + } + if (ram_cache == NULL && !allocate_ram_cache()) { + erase_sector(SCRATCH_SECTOR); + wait_for_flash_ready(); } - erase_sector(SCRATCH_SECTOR); current_sector = this_sector; dirty_mask = 0; - wait_for_flash_ready(); } - uint32_t scratch_address = SCRATCH_SECTOR + block_index * FLASH_BLOCK_SIZE; dirty_mask |= mask; - return write_flash(scratch_address, data, FLASH_BLOCK_SIZE); + // 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); + } } } From eb62d03e338aec65d357336c48d8db4a451748fb Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 21 Oct 2016 11:59:02 -0700 Subject: [PATCH 07/11] atmel-samd: Add flash write activity LED. --- atmel-samd/boards/metro_m0_flash/mpconfigboard.h | 1 + atmel-samd/spi_flash.c | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/atmel-samd/boards/metro_m0_flash/mpconfigboard.h b/atmel-samd/boards/metro_m0_flash/mpconfigboard.h index b9d2f5f02520f..a4e1e3fc54485 100644 --- a/atmel-samd/boards/metro_m0_flash/mpconfigboard.h +++ b/atmel-samd/boards/metro_m0_flash/mpconfigboard.h @@ -4,6 +4,7 @@ #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 diff --git a/atmel-samd/spi_flash.c b/atmel-samd/spi_flash.c index 5424ee6ea5751..fef630eff130e 100644 --- a/atmel-samd/spi_flash.c +++ b/atmel-samd/spi_flash.c @@ -198,7 +198,6 @@ static bool copy_block(uint32_t src_address, uint32_t dest_address) { 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; @@ -218,6 +217,12 @@ void spi_flash_init(void) { 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(); @@ -377,6 +382,9 @@ 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(); @@ -384,6 +392,9 @@ static void spi_flash_flush_keep_cache(bool keep_cache) { 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 From 9eb86e801547169af6914995c9cd7d10bc0c3124 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 21 Oct 2016 15:18:05 -0700 Subject: [PATCH 08/11] Add support for USB writeable, MicroPython read-only volumes. This prevents file system corruption due to two systems mutating it at once. --- atmel-samd/internal_flash.c | 2 +- atmel-samd/spi_flash.c | 2 +- extmod/fsusermount.h | 8 +++++--- extmod/vfs_fat_diskio.c | 5 ++++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/atmel-samd/internal_flash.c b/atmel-samd/internal_flash.c index f7d0aea6c6204..444c174da62ec 100644 --- a/atmel-samd/internal_flash.c +++ b/atmel-samd/internal_flash.c @@ -278,7 +278,7 @@ const mp_obj_type_t internal_flash_type = { }; void flash_init_vfs(fs_user_mount_t *vfs) { - vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL; + 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 diff --git a/atmel-samd/spi_flash.c b/atmel-samd/spi_flash.c index fef630eff130e..812ec84e326bc 100644 --- a/atmel-samd/spi_flash.c +++ b/atmel-samd/spi_flash.c @@ -609,7 +609,7 @@ const mp_obj_type_t spi_flash_type = { }; void flash_init_vfs(fs_user_mount_t *vfs) { - vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL; + 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 diff --git a/extmod/fsusermount.h b/extmod/fsusermount.h index 3eb54f8bd4150..7f2912b7f00f5 100644 --- a/extmod/fsusermount.h +++ b/extmod/fsusermount.h @@ -28,9 +28,11 @@ #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_diskio.c b/extmod/vfs_fat_diskio.c index 5608e06453c81..4d776f6b4bf77 100644 --- a/extmod/vfs_fat_diskio.c +++ b/extmod/vfs_fat_diskio.c @@ -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; From b8ef783052188f944b43481523fe09163d638cf1 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 21 Oct 2016 15:23:26 -0700 Subject: [PATCH 09/11] extmod: Fix getting sector size when the max and min sizes are the same. Also switch the max size back to 512 for atmel-samd to save ram. --- atmel-samd/mpconfigport.h | 4 +++- extmod/vfs_fat.c | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/atmel-samd/mpconfigport.h b/atmel-samd/mpconfigport.h index e38f3fb572fdd..41ef7f6c4cfc1 100644 --- a/atmel-samd/mpconfigport.h +++ b/atmel-samd/mpconfigport.h @@ -70,7 +70,9 @@ #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) 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 From 98c8f2f6a39377a2f0513b5b45799cfa1d52bef8 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 21 Oct 2016 15:24:20 -0700 Subject: [PATCH 10/11] atmel-samd: Update the FatFs sector cache on USB mass storage write to the same sector. This fixes #20, the issue where a listdir or import won't work without a reset when it was run before the file was copied. --- atmel-samd/access_vfs.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/atmel-samd/access_vfs.c b/atmel-samd/access_vfs.c index 07d0aa64e0dbf..cfd5dfe54160e 100644 --- a/atmel-samd/access_vfs.c +++ b/atmel-samd/access_vfs.c @@ -24,6 +24,8 @@ * THE SOFTWARE. */ +#include + #include "access_vfs.h" #include "asf/common/services/usb/class/msc/device/udi_msc.h" @@ -77,8 +79,20 @@ Ctrl_status vfs_read_capacity(uint32_t *last_valid_sector) //! bool vfs_wr_protect(void) { - DSTATUS status = disk_status(VFS_INDEX); - return status == STA_NOINIT || status == STA_PROTECT; + 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 @@ -138,20 +152,36 @@ Ctrl_status vfs_usb_read_10(uint32_t addr, volatile uint16_t nb_sector) //! Not initialized or changed -> CTRL_BUSY //! An error occurred -> CTRL_FAIL //! -Ctrl_status vfs_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 } - DRESULT result = disk_write(VFS_INDEX, sector_buffer, addr + sector, 1); + 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; } From b2834c30a6d96f088560de48834c53066a675dc5 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 21 Oct 2016 16:04:18 -0700 Subject: [PATCH 11/11] atmel-samd: Support reading from the flash cache. --- atmel-samd/spi_flash.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/atmel-samd/spi_flash.c b/atmel-samd/spi_flash.c index 812ec84e326bc..20daed0f7fb5b 100644 --- a/atmel-samd/spi_flash.c +++ b/atmel-samd/spi_flash.c @@ -474,12 +474,32 @@ bool spi_flash_read_block(uint8_t *dest, uint32_t block) { return true; } else { // Non-MBR block, get data from flash memory. - uint32_t src = convert_block_to_flash_addr(block); - if (src == -1) { + uint32_t address = convert_block_to_flash_addr(block); + if (address == -1) { // bad block number return false; } - return read_flash(src, dest, FLASH_BLOCK_SIZE); + + // 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); } }