zephyr/subsys/disk/disk_access_flash.c

300 lines
6.9 KiB
C

/*
* Copyright (c) 2016 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/types.h>
#include <sys/__assert.h>
#include <sys/util.h>
#include <disk/disk_access.h>
#include <errno.h>
#include <init.h>
#include <device.h>
#include <drivers/flash.h>
#define SECTOR_SIZE 512
static struct device *flash_dev;
/* flash read-copy-erase-write operation */
static u8_t read_copy_buf[CONFIG_DISK_ERASE_BLOCK_SIZE];
static u8_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(u32_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, u8_t *buff,
u32_t start_sector, u32_t sector_count)
{
off_t fl_addr;
u32_t remaining;
u32_t len;
u32_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 (u32_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, u32_t size,
const void *src_buff,
u8_t *dest_buff)
{
off_t fl_addr;
u32_t num_read;
u32_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 (u32_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, u32_t size, const void *buff)
{
off_t fl_addr;
u8_t *src = (u8_t *)buff;
u32_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 = (u8_t *)fs_buff;
}
/* always align starting address for flash write operation */
fl_addr = ROUND_DOWN(start_addr, CONFIG_DISK_FLASH_ERASE_ALIGNMENT);
/* disable write-protection first before erase */
flash_write_protection_set(flash_dev, false);
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 (u32_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_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 u8_t *buff,
u32_t start_sector, u32_t sector_count)
{
off_t fl_addr;
u32_t remaining;
u32_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, u8_t cmd, void *buff)
{
switch (cmd) {
case DISK_IOCTL_CTRL_SYNC:
return 0;
case DISK_IOCTL_GET_SECTOR_COUNT:
*(u32_t *)buff = CONFIG_DISK_VOLUME_SIZE / SECTOR_SIZE;
return 0;
case DISK_IOCTL_GET_SECTOR_SIZE:
*(u32_t *) buff = SECTOR_SIZE;
return 0;
case DISK_IOCTL_GET_ERASE_BLOCK_SZ: /* in sectors */
*(u32_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(struct device *dev)
{
ARG_UNUSED(dev);
return disk_access_register(&flash_disk);
}
SYS_INIT(disk_flash_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);