diff --git a/fs/Kconfig b/fs/Kconfig index b958a930ca9..113084bfe74 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -29,14 +29,6 @@ config FILE_SYSTEM_FAT help Enables FAT file system support. -choice - prompt "Select file system storage device" - depends on FILE_SYSTEM_FAT - default FS_FAT_RAM_DISK - help - The storage device where the file system - resides - config FS_FAT_RAM_DISK bool prompt "RAM Disk" @@ -45,7 +37,11 @@ config FS_FAT_RAM_DISK This option can used to test the file system. -endchoice +config FS_FAT_FLASH_DISK + bool + prompt "Flash" + help + Flash device is used for the file system. if FS_FAT_RAM_DISK @@ -64,4 +60,60 @@ config FS_BLOCK_SIZE endif # FS_RAM_DISK +if FS_FAT_FLASH_DISK + +config FS_FAT_FLASH_DISK_W25QXXDV + bool "W25QXXDV flash component" + default "" + help + Enables to use the W25QXXDV as the storage media + for the file system. + +if FS_FAT_FLASH_DISK_W25QXXDV + +config FS_VOLUME_SIZE + hex + default 0x100000 + help + This is the file system volume size in bytes. + +config FS_BLOCK_SIZE + hex + default 0x1000 + help + This is typically the minimum flash erase size + that the flash component supports. + +config FS_FLASH_DEV_NAME + string + prompt "File system flash storage device name" + depends on SPI_FLASH_W25QXXDV + default SPI_FLASH_W25QXXDV_DRV_NAME + +config FS_FLASH_START + hex + default 0x0 + help + This is start address of the flash for the file + system. + +config FS_FLASH_MAX_RW_SIZE + int + default SPI_FLASH_W25QXXDV_MAX_DATA_LEN + help + This is the maximum number of bytes that the + flash_write API can do per invocation. + API. + +config FS_FLASH_ERASE_ALIGNMENT + hex + default 0x1000 + help + This is the start address alignment required by + the flash component. + +endif # FS_FAT_FLASH_DISK_W25QXXDV + +endif # FS_FAT_FLASH_DISK + endmenu diff --git a/fs/Makefile b/fs/Makefile index 154755759d9..ed9c06378f1 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_FS_FAT_RAM_DISK) += fat_ram_diskio.o +obj-$(CONFIG_FS_FAT_FLASH_DISK) += fat_flash_diskio.o obj-$(CONFIG_FILE_SYSTEM_FAT) += fat_fs.o diff --git a/fs/fat_flash_diskio.c b/fs/fat_flash_diskio.c new file mode 100644 index 00000000000..bd08eba4233 --- /dev/null +++ b/fs/fat_flash_diskio.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2016 Intel Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct device *flash_dev; + +/* flash read-copy-erase-write operation */ +static uint8_t read_copy_buf[CONFIG_FS_BLOCK_SIZE]; +static uint8_t *fs_buff = read_copy_buf; + +/* calculate number of blocks required for a given size */ +#define GET_NUM_BLOCK(total_size, block_size) \ + ((total_size + block_size - 1) / block_size) + +#define GET_SIZE_TO_BOUNDARY(start, block_size) \ + (block_size - (start & (block_size - 1))) + +static off_t lba_to_address(uint32_t sector_num) +{ + off_t flash_addr; + + flash_addr = CONFIG_FS_FLASH_START + sector_num * _MIN_SS; + __ASSERT(flash_addr < (CONFIG_FS_FLASH_START + CONFIG_FS_VOLUME_SIZE), + "FS bound error"); + return flash_addr; +} + +DSTATUS fat_disk_status(void) +{ + if (!flash_dev) { + return STA_NOINIT; + } + + return RES_OK; +} + +DSTATUS fat_disk_initialize(void) +{ + if (flash_dev) { + return RES_OK; + } + + flash_dev = device_get_binding(CONFIG_FS_FLASH_DEV_NAME); + if (!flash_dev) { + return STA_NOINIT; + } + + return RES_OK; +} + +DRESULT fat_disk_read(void *buff, uint32_t start_sector, + uint32_t sector_count) +{ + off_t fl_addr; + uint32_t remaining; + uint32_t len; + uint32_t num_read; + + fl_addr = lba_to_address(start_sector); + remaining = (sector_count * _MIN_SS); + len = CONFIG_FS_FLASH_MAX_RW_SIZE; + + num_read = GET_NUM_BLOCK(remaining, CONFIG_FS_FLASH_MAX_RW_SIZE); + + for (uint32_t i = 0; i < num_read; i++) { + if (remaining < CONFIG_FS_FLASH_MAX_RW_SIZE) { + len = remaining; + } + + if (flash_read(flash_dev, fl_addr, buff, len) != 0) { + return RES_ERROR; + } + + fl_addr += len; + buff += len; + remaining -= len; + } + + return RES_OK; +} + +/* This performs read-copy into an output buffer */ +static DRESULT read_copy_flash_block(off_t start_addr, uint32_t size, + const void *src_buff, + uint8_t *dest_buff) +{ + off_t fl_addr; + uint32_t num_read; + uint32_t offset = 0; + + /* adjust offset if starting address is not erase-aligned address */ + if (start_addr & (CONFIG_FS_FLASH_ERASE_ALIGNMENT - 1)) { + offset = start_addr & (CONFIG_FS_FLASH_ERASE_ALIGNMENT - 1); + } + + /* align starting address to an aligned address for flash erase-write */ + fl_addr = ROUND_DOWN(start_addr, CONFIG_FS_FLASH_ERASE_ALIGNMENT); + + num_read = GET_NUM_BLOCK(CONFIG_FS_BLOCK_SIZE, + CONFIG_FS_FLASH_MAX_RW_SIZE); + + /* read one block from flash */ + for (uint32_t i = 0; i < num_read; i++) { + if (flash_read(flash_dev, + fl_addr + (CONFIG_FS_FLASH_MAX_RW_SIZE * i), + dest_buff + (CONFIG_FS_FLASH_MAX_RW_SIZE * i), + CONFIG_FS_FLASH_MAX_RW_SIZE) != 0) { + return RES_ERROR; + } + } + + /* overwrite with user data */ + memcpy(dest_buff + offset, src_buff, size); + + return RES_OK; +} + +/* input size is either less or equal to a block size, CONFIG_FS_BLOCK_SIZE. */ +static DRESULT update_flash_block(off_t start_addr, uint32_t size, + const void *buff) +{ + off_t fl_addr; + uint8_t *src = (uint8_t *)buff; + uint32_t num_write; + + /* if size is a partial block, perform read-copy with user data */ + if (size < CONFIG_FS_BLOCK_SIZE) { + if (read_copy_flash_block(start_addr, size, buff, fs_buff) != + RES_OK) { + return RES_ERROR; + } + + /* now use the local buffer as the source */ + src = (uint8_t *)fs_buff; + } + + /* always align starting address for flash write operation */ + fl_addr = ROUND_DOWN(start_addr, CONFIG_FS_FLASH_ERASE_ALIGNMENT); + + /* disable write-protection first before erase */ + flash_write_protection_set(flash_dev, false); + if (flash_erase(flash_dev, fl_addr, CONFIG_FS_BLOCK_SIZE) != 0) { + return RES_ERROR; + } + + /* write data to flash */ + num_write = GET_NUM_BLOCK(CONFIG_FS_BLOCK_SIZE, + CONFIG_FS_FLASH_MAX_RW_SIZE); + + for (uint32_t i = 0; i < num_write; i++) { + /* flash_write reenabled write-protection so disable it again */ + flash_write_protection_set(flash_dev, false); + + if (flash_write(flash_dev, fl_addr, src, + CONFIG_FS_FLASH_MAX_RW_SIZE) != 0) { + return RES_ERROR; + } + + fl_addr += CONFIG_FS_FLASH_MAX_RW_SIZE; + src += CONFIG_FS_FLASH_MAX_RW_SIZE; + } + + return RES_OK; +} + +DRESULT fat_disk_write(const void *buff, uint32_t start_sector, + uint32_t sector_count) +{ + off_t fl_addr; + uint32_t remaining; + uint32_t size; + + fl_addr = lba_to_address(start_sector); + remaining = (sector_count * _MIN_SS); + + /* check if start address is erased-aligned address */ + if (fl_addr & (CONFIG_FS_FLASH_ERASE_ALIGNMENT - 1)) { + + /* not aligned */ + /* check if the size goes over flash block boundary */ + if ((fl_addr + remaining) < + ((fl_addr + CONFIG_FS_BLOCK_SIZE) & + ~(CONFIG_FS_BLOCK_SIZE - 1))) { + /* not over block boundary (a partial block also) */ + if (update_flash_block(fl_addr, remaining, buff) != 0) { + return RES_ERROR; + } + return RES_OK; + } + + /* write goes over block boundary */ + size = GET_SIZE_TO_BOUNDARY(fl_addr, CONFIG_FS_BLOCK_SIZE); + + /* write first partial block */ + if (update_flash_block(fl_addr, size, buff) != 0) { + return RES_ERROR; + } + + fl_addr += size; + remaining -= size; + buff += size; + } + + /* start is an erase-aligned address */ + while (remaining) { + if (remaining < CONFIG_FS_BLOCK_SIZE) { + break; + } + + if (update_flash_block(fl_addr, CONFIG_FS_BLOCK_SIZE, + buff) != 0) { + return RES_ERROR; + } + + fl_addr += CONFIG_FS_BLOCK_SIZE; + remaining -= CONFIG_FS_BLOCK_SIZE; + buff += CONFIG_FS_BLOCK_SIZE; + } + + /* remaining partial block */ + if (remaining) { + if (update_flash_block(fl_addr, remaining, buff) != 0) { + return RES_ERROR; + } + } + + return RES_OK; +} + +DRESULT fat_disk_ioctl(uint8_t cmd, void *buff) +{ + switch (cmd) { + case CTRL_SYNC: + return RES_OK; + case GET_SECTOR_COUNT: + *(uint32_t *)buff = CONFIG_FS_VOLUME_SIZE / _MIN_SS; + return RES_OK; + case GET_BLOCK_SIZE: /* in sectors */ + *(uint32_t *)buff = CONFIG_FS_BLOCK_SIZE / _MIN_SS; + return RES_OK; + case CTRL_TRIM: + break; + } + + return RES_PARERR; +}