| /* |
| * Copyright (c) 2016 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <string.h> |
| #include <zephyr/types.h> |
| #include <zephyr/sys/__assert.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/drivers/disk.h> |
| #include <errno.h> |
| #include <zephyr/init.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/flash.h> |
| |
| #define SECTOR_SIZE CONFIG_DISK_FLASH_SECTOR_SIZE |
| |
| static const struct device *flash_dev; |
| |
| /* flash read-copy-erase-write operation */ |
| static uint8_t __aligned(4) read_copy_buf[CONFIG_DISK_ERASE_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_DISK_FLASH_START + sector_num * SECTOR_SIZE; |
| |
| __ASSERT(flash_addr < (CONFIG_DISK_FLASH_START + |
| CONFIG_DISK_VOLUME_SIZE), "FS bound error"); |
| |
| return flash_addr; |
| } |
| |
| static int disk_flash_access_status(struct disk_info *disk) |
| { |
| if (!flash_dev) { |
| return DISK_STATUS_NOMEDIA; |
| } |
| |
| return DISK_STATUS_OK; |
| } |
| |
| static int disk_flash_access_init(struct disk_info *disk) |
| { |
| if (flash_dev) { |
| return 0; |
| } |
| |
| flash_dev = device_get_binding(CONFIG_DISK_FLASH_DEV_NAME); |
| if (!flash_dev) { |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static int disk_flash_access_read(struct disk_info *disk, uint8_t *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 * SECTOR_SIZE); |
| len = CONFIG_DISK_FLASH_MAX_RW_SIZE; |
| |
| num_read = GET_NUM_BLOCK(remaining, CONFIG_DISK_FLASH_MAX_RW_SIZE); |
| |
| for (uint32_t i = 0; i < num_read; i++) { |
| if (remaining < CONFIG_DISK_FLASH_MAX_RW_SIZE) { |
| len = remaining; |
| } |
| |
| if (flash_read(flash_dev, fl_addr, buff, len) != 0) { |
| return -EIO; |
| } |
| |
| fl_addr += len; |
| buff += len; |
| remaining -= len; |
| } |
| |
| return 0; |
| } |
| |
| /* This performs read-copy into an output buffer */ |
| static int 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 = 0U; |
| |
| /* adjust offset if starting address is not erase-aligned address */ |
| if (start_addr & (CONFIG_DISK_FLASH_ERASE_ALIGNMENT - 1)) { |
| offset = start_addr & (CONFIG_DISK_FLASH_ERASE_ALIGNMENT - 1); |
| } |
| |
| /* align starting address to an aligned address for flash erase-write */ |
| fl_addr = ROUND_DOWN(start_addr, CONFIG_DISK_FLASH_ERASE_ALIGNMENT); |
| |
| num_read = GET_NUM_BLOCK(CONFIG_DISK_ERASE_BLOCK_SIZE, |
| CONFIG_DISK_FLASH_MAX_RW_SIZE); |
| |
| /* read one block from flash */ |
| for (uint32_t i = 0; i < num_read; i++) { |
| int rc; |
| |
| rc = flash_read(flash_dev, |
| fl_addr + (CONFIG_DISK_FLASH_MAX_RW_SIZE * i), |
| dest_buff + (CONFIG_DISK_FLASH_MAX_RW_SIZE * i), |
| CONFIG_DISK_FLASH_MAX_RW_SIZE); |
| if (rc != 0) { |
| return -EIO; |
| } |
| } |
| |
| /* overwrite with user data */ |
| memcpy(dest_buff + offset, src_buff, size); |
| |
| return 0; |
| } |
| |
| /* input size is either less or equal to a block size, |
| * CONFIG_DISK_ERASE_BLOCK_SIZE. |
| */ |
| static int 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_DISK_ERASE_BLOCK_SIZE) { |
| int rc; |
| |
| rc = read_copy_flash_block(start_addr, size, buff, fs_buff); |
| if (rc != 0) { |
| return -EIO; |
| } |
| |
| /* 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_DISK_FLASH_ERASE_ALIGNMENT); |
| |
| if (flash_erase(flash_dev, fl_addr, CONFIG_DISK_ERASE_BLOCK_SIZE) |
| != 0) { |
| return -EIO; |
| } |
| |
| /* write data to flash */ |
| num_write = GET_NUM_BLOCK(CONFIG_DISK_ERASE_BLOCK_SIZE, |
| CONFIG_DISK_FLASH_MAX_RW_SIZE); |
| |
| for (uint32_t i = 0; i < num_write; i++) { |
| if (flash_write(flash_dev, fl_addr, src, |
| CONFIG_DISK_FLASH_MAX_RW_SIZE) != 0) { |
| return -EIO; |
| } |
| |
| fl_addr += CONFIG_DISK_FLASH_MAX_RW_SIZE; |
| src += CONFIG_DISK_FLASH_MAX_RW_SIZE; |
| } |
| |
| return 0; |
| } |
| |
| static int disk_flash_access_write(struct disk_info *disk, const uint8_t *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 * SECTOR_SIZE); |
| |
| /* check if start address is erased-aligned address */ |
| if (fl_addr & (CONFIG_DISK_FLASH_ERASE_ALIGNMENT - 1)) { |
| off_t block_bnd; |
| |
| /* not aligned */ |
| /* check if the size goes over flash block boundary */ |
| block_bnd = fl_addr + CONFIG_DISK_ERASE_BLOCK_SIZE; |
| block_bnd = block_bnd & ~(CONFIG_DISK_ERASE_BLOCK_SIZE - 1); |
| if ((fl_addr + remaining) < block_bnd) { |
| /* not over block boundary (a partial block also) */ |
| if (update_flash_block(fl_addr, remaining, buff) != 0) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| /* write goes over block boundary */ |
| size = GET_SIZE_TO_BOUNDARY(fl_addr, |
| CONFIG_DISK_ERASE_BLOCK_SIZE); |
| |
| /* write first partial block */ |
| if (update_flash_block(fl_addr, size, buff) != 0) { |
| return -EIO; |
| } |
| |
| fl_addr += size; |
| remaining -= size; |
| buff += size; |
| } |
| |
| /* start is an erase-aligned address */ |
| while (remaining) { |
| int rc; |
| |
| if (remaining < CONFIG_DISK_ERASE_BLOCK_SIZE) { |
| break; |
| } |
| |
| rc = update_flash_block(fl_addr, CONFIG_DISK_ERASE_BLOCK_SIZE, |
| buff); |
| if (rc != 0) { |
| return -EIO; |
| } |
| |
| fl_addr += CONFIG_DISK_ERASE_BLOCK_SIZE; |
| remaining -= CONFIG_DISK_ERASE_BLOCK_SIZE; |
| buff += CONFIG_DISK_ERASE_BLOCK_SIZE; |
| } |
| |
| /* remaining partial block */ |
| if (remaining) { |
| if (update_flash_block(fl_addr, remaining, buff) != 0) { |
| return -EIO; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int disk_flash_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buff) |
| { |
| switch (cmd) { |
| case DISK_IOCTL_CTRL_SYNC: |
| return 0; |
| case DISK_IOCTL_GET_SECTOR_COUNT: |
| *(uint32_t *)buff = CONFIG_DISK_VOLUME_SIZE / SECTOR_SIZE; |
| return 0; |
| case DISK_IOCTL_GET_SECTOR_SIZE: |
| *(uint32_t *) buff = SECTOR_SIZE; |
| return 0; |
| case DISK_IOCTL_GET_ERASE_BLOCK_SZ: /* in sectors */ |
| *(uint32_t *)buff = CONFIG_DISK_ERASE_BLOCK_SIZE / SECTOR_SIZE; |
| return 0; |
| default: |
| break; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static const struct disk_operations flash_disk_ops = { |
| .init = disk_flash_access_init, |
| .status = disk_flash_access_status, |
| .read = disk_flash_access_read, |
| .write = disk_flash_access_write, |
| .ioctl = disk_flash_access_ioctl, |
| }; |
| |
| static struct disk_info flash_disk = { |
| .name = CONFIG_DISK_FLASH_VOLUME_NAME, |
| .ops = &flash_disk_ops, |
| }; |
| |
| static int disk_flash_init(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| return disk_access_register(&flash_disk); |
| } |
| |
| SYS_INIT(disk_flash_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |