/* * Copyright (c) 2019-2022, ARM Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include typedef struct { io_mtd_dev_spec_t *dev_spec; uintptr_t base; unsigned long long pos; /* Offset in bytes */ unsigned long long size; /* Size of device in bytes */ unsigned long long extra_offset; /* Extra offset in bytes */ } mtd_dev_state_t; io_type_t device_type_mtd(void); static int mtd_open(io_dev_info_t *dev_info, const uintptr_t spec, io_entity_t *entity); static int mtd_seek(io_entity_t *entity, int mode, signed long long offset); static int mtd_read(io_entity_t *entity, uintptr_t buffer, size_t length, size_t *length_read); static int mtd_close(io_entity_t *entity); static int mtd_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info); static int mtd_dev_close(io_dev_info_t *dev_info); static const io_dev_connector_t mtd_dev_connector = { .dev_open = mtd_dev_open }; static const io_dev_funcs_t mtd_dev_funcs = { .type = device_type_mtd, .open = mtd_open, .seek = mtd_seek, .read = mtd_read, .close = mtd_close, .dev_close = mtd_dev_close, }; static mtd_dev_state_t state_pool[MAX_IO_MTD_DEVICES]; static io_dev_info_t dev_info_pool[MAX_IO_MTD_DEVICES]; io_type_t device_type_mtd(void) { return IO_TYPE_MTD; } /* Locate a MTD state in the pool, specified by address */ static int find_first_mtd_state(const io_mtd_dev_spec_t *dev_spec, unsigned int *index_out) { unsigned int index; int result = -ENOENT; for (index = 0U; index < MAX_IO_MTD_DEVICES; index++) { /* dev_spec is used as identifier since it's unique */ if (state_pool[index].dev_spec == dev_spec) { result = 0; *index_out = index; break; } } return result; } /* Allocate a device info from the pool */ static int allocate_dev_info(io_dev_info_t **dev_info) { unsigned int index = 0U; int result; result = find_first_mtd_state(NULL, &index); if (result != 0) { return -ENOMEM; } dev_info_pool[index].funcs = &mtd_dev_funcs; dev_info_pool[index].info = (uintptr_t)&state_pool[index]; *dev_info = &dev_info_pool[index]; return 0; } /* Release a device info from the pool */ static int free_dev_info(io_dev_info_t *dev_info) { int result; unsigned int index = 0U; mtd_dev_state_t *state; state = (mtd_dev_state_t *)dev_info->info; result = find_first_mtd_state(state->dev_spec, &index); if (result != 0) { return result; } zeromem(state, sizeof(mtd_dev_state_t)); zeromem(dev_info, sizeof(io_dev_info_t)); return 0; } static int mtd_add_extra_offset(mtd_dev_state_t *cur, size_t *extra_offset) { io_mtd_ops_t *ops = &cur->dev_spec->ops; int ret; if (ops->seek == NULL) { return 0; } ret = ops->seek(cur->base, cur->pos, extra_offset); if (ret != 0) { ERROR("%s: Seek error %d\n", __func__, ret); return ret; } return 0; } static int mtd_open(io_dev_info_t *dev_info, const uintptr_t spec, io_entity_t *entity) { mtd_dev_state_t *cur; io_block_spec_t *region; size_t extra_offset = 0U; int ret; assert((dev_info->info != 0UL) && (entity->info == 0UL)); region = (io_block_spec_t *)spec; cur = (mtd_dev_state_t *)dev_info->info; entity->info = (uintptr_t)cur; cur->base = region->offset; cur->pos = 0U; cur->extra_offset = 0U; ret = mtd_add_extra_offset(cur, &extra_offset); if (ret != 0) { return ret; } cur->base += extra_offset; return 0; } /* Seek to a specific position using offset */ static int mtd_seek(io_entity_t *entity, int mode, signed long long offset) { mtd_dev_state_t *cur; size_t extra_offset = 0U; int ret; assert((entity->info != (uintptr_t)NULL) && (offset >= 0)); cur = (mtd_dev_state_t *)entity->info; switch (mode) { case IO_SEEK_SET: if ((offset >= 0) && ((unsigned long long)offset >= cur->size)) { return -EINVAL; } cur->pos = offset; break; case IO_SEEK_CUR: if (((cur->base + cur->pos + (unsigned long long)offset) >= cur->size) || ((cur->base + cur->pos + (unsigned long long)offset) < cur->base + cur->pos)) { return -EINVAL; } cur->pos += (unsigned long long)offset; break; default: return -EINVAL; } ret = mtd_add_extra_offset(cur, &extra_offset); if (ret != 0) { return ret; } cur->extra_offset = extra_offset; return 0; } static int mtd_read(io_entity_t *entity, uintptr_t buffer, size_t length, size_t *out_length) { mtd_dev_state_t *cur; io_mtd_ops_t *ops; int ret; assert(entity->info != (uintptr_t)NULL); assert((length > 0U) && (buffer != (uintptr_t)NULL)); cur = (mtd_dev_state_t *)entity->info; ops = &cur->dev_spec->ops; assert(ops->read != NULL); VERBOSE("Read at %llx into %lx, length %zu\n", cur->base + cur->pos, buffer, length); if ((cur->base + cur->pos + length) > cur->dev_spec->device_size) { return -EINVAL; } ret = ops->read(cur->base + cur->pos + cur->extra_offset, buffer, length, out_length); if (ret < 0) { return ret; } assert(*out_length == length); cur->pos += *out_length; return 0; } static int mtd_close(io_entity_t *entity) { entity->info = (uintptr_t)NULL; return 0; } static int mtd_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info) { mtd_dev_state_t *cur; io_dev_info_t *info; io_mtd_ops_t *ops; int result; result = allocate_dev_info(&info); if (result != 0) { return -ENOENT; } cur = (mtd_dev_state_t *)info->info; cur->dev_spec = (io_mtd_dev_spec_t *)dev_spec; *dev_info = info; ops = &(cur->dev_spec->ops); if (ops->init != NULL) { result = ops->init(&cur->dev_spec->device_size, &cur->dev_spec->erase_size); } if (result == 0) { cur->size = cur->dev_spec->device_size; } else { cur->size = 0ULL; } return result; } static int mtd_dev_close(io_dev_info_t *dev_info) { return free_dev_info(dev_info); } /* Exported functions */ /* Register the MTD driver in the IO abstraction */ int register_io_dev_mtd(const io_dev_connector_t **dev_con) { int result; result = io_register_device(&dev_info_pool[0]); if (result == 0) { *dev_con = &mtd_dev_connector; } return result; }