/* * Copyright (c) 2016-2017, ARM Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include typedef struct { io_block_dev_spec_t *dev_spec; uintptr_t base; size_t file_pos; size_t size; } block_dev_state_t; #define is_power_of_2(x) ((x != 0) && ((x & (x - 1)) == 0)) io_type_t device_type_block(void); static int block_open(io_dev_info_t *dev_info, const uintptr_t spec, io_entity_t *entity); static int block_seek(io_entity_t *entity, int mode, ssize_t offset); static int block_read(io_entity_t *entity, uintptr_t buffer, size_t length, size_t *length_read); static int block_write(io_entity_t *entity, const uintptr_t buffer, size_t length, size_t *length_written); static int block_close(io_entity_t *entity); static int block_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info); static int block_dev_close(io_dev_info_t *dev_info); static const io_dev_connector_t block_dev_connector = { .dev_open = block_dev_open }; static const io_dev_funcs_t block_dev_funcs = { .type = device_type_block, .open = block_open, .seek = block_seek, .size = NULL, .read = block_read, .write = block_write, .close = block_close, .dev_init = NULL, .dev_close = block_dev_close, }; static block_dev_state_t state_pool[MAX_IO_BLOCK_DEVICES]; static io_dev_info_t dev_info_pool[MAX_IO_BLOCK_DEVICES]; /* Track number of allocated block state */ static unsigned int block_dev_count; io_type_t device_type_block(void) { return IO_TYPE_BLOCK; } /* Locate a block state in the pool, specified by address */ static int find_first_block_state(const io_block_dev_spec_t *dev_spec, unsigned int *index_out) { int result = -ENOENT; for (int index = 0; index < MAX_IO_BLOCK_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 and return a pointer to it */ static int allocate_dev_info(io_dev_info_t **dev_info) { int result = -ENOMEM; assert(dev_info != NULL); if (block_dev_count < MAX_IO_BLOCK_DEVICES) { unsigned int index = 0; result = find_first_block_state(NULL, &index); assert(result == 0); /* initialize dev_info */ dev_info_pool[index].funcs = &block_dev_funcs; dev_info_pool[index].info = (uintptr_t)&state_pool[index]; *dev_info = &dev_info_pool[index]; ++block_dev_count; } return result; } /* Release a device info to the pool */ static int free_dev_info(io_dev_info_t *dev_info) { int result; unsigned int index = 0; block_dev_state_t *state; assert(dev_info != NULL); state = (block_dev_state_t *)dev_info->info; result = find_first_block_state(state->dev_spec, &index); if (result == 0) { /* free if device info is valid */ zeromem(state, sizeof(block_dev_state_t)); zeromem(dev_info, sizeof(io_dev_info_t)); --block_dev_count; } return result; } static int block_open(io_dev_info_t *dev_info, const uintptr_t spec, io_entity_t *entity) { block_dev_state_t *cur; io_block_spec_t *region; assert((dev_info->info != (uintptr_t)NULL) && (spec != (uintptr_t)NULL) && (entity->info == (uintptr_t)NULL)); region = (io_block_spec_t *)spec; cur = (block_dev_state_t *)dev_info->info; assert(((region->offset % cur->dev_spec->block_size) == 0) && ((region->length % cur->dev_spec->block_size) == 0)); cur->base = region->offset; cur->size = region->length; cur->file_pos = 0; entity->info = (uintptr_t)cur; return 0; } /* parameter offset is relative address at here */ static int block_seek(io_entity_t *entity, int mode, ssize_t offset) { block_dev_state_t *cur; assert(entity->info != (uintptr_t)NULL); cur = (block_dev_state_t *)entity->info; assert((offset >= 0) && (offset < cur->size)); switch (mode) { case IO_SEEK_SET: cur->file_pos = offset; break; case IO_SEEK_CUR: cur->file_pos += offset; break; default: return -EINVAL; } assert(cur->file_pos < cur->size); return 0; } static int block_read(io_entity_t *entity, uintptr_t buffer, size_t length, size_t *length_read) { block_dev_state_t *cur; io_block_spec_t *buf; io_block_ops_t *ops; size_t aligned_length, skip, count, left, padding, block_size; int lba; int buffer_not_aligned; assert(entity->info != (uintptr_t)NULL); cur = (block_dev_state_t *)entity->info; ops = &(cur->dev_spec->ops); buf = &(cur->dev_spec->buffer); block_size = cur->dev_spec->block_size; assert((length <= cur->size) && (length > 0) && (ops->read != 0)); if ((buffer & (block_size - 1)) != 0) { /* * buffer isn't aligned with block size. * Block device always relies on DMA operation. * It's better to make the buffer as block size aligned. */ buffer_not_aligned = 1; } else { buffer_not_aligned = 0; } skip = cur->file_pos % block_size; aligned_length = ((skip + length) + (block_size - 1)) & ~(block_size - 1); padding = aligned_length - (skip + length); left = aligned_length; do { lba = (cur->file_pos + cur->base) / block_size; if (left >= buf->length) { /* * Since left is larger, it's impossible to padding. * * If buffer isn't aligned, we need to use aligned * buffer instead. */ if (skip || buffer_not_aligned) { /* * The beginning address (file_pos) isn't * aligned with block size, we need to use * block buffer to read block. Since block * device is always relied on DMA operation. */ count = ops->read(lba, buf->offset, buf->length); } else { count = ops->read(lba, buffer, buf->length); } assert(count == buf->length); cur->file_pos += count - skip; if (skip || buffer_not_aligned) { /* * Since there's not aligned block size caused * by skip or not aligned buffer, block buffer * is used to store data. */ memcpy((void *)buffer, (void *)(buf->offset + skip), count - skip); } left = left - (count - skip); } else { if (skip || padding || buffer_not_aligned) { /* * The beginning address (file_pos) isn't * aligned with block size, we have to read * full block by block buffer instead. * The size isn't aligned with block size. * Use block buffer to avoid overflow. * * If buffer isn't aligned, use block buffer * to avoid DMA error. */ count = ops->read(lba, buf->offset, left); } else count = ops->read(lba, buffer, left); assert(count == left); left = left - (skip + padding); cur->file_pos += left; if (skip || padding || buffer_not_aligned) { /* * Since there's not aligned block size or * buffer, block buffer is used to store data. */ memcpy((void *)buffer, (void *)(buf->offset + skip), left); } /* It's already the last block operation */ left = 0; } skip = cur->file_pos % block_size; } while (left > 0); *length_read = length; return 0; } static int block_write(io_entity_t *entity, const uintptr_t buffer, size_t length, size_t *length_written) { block_dev_state_t *cur; io_block_spec_t *buf; io_block_ops_t *ops; size_t aligned_length, skip, count, left, padding, block_size; int lba; int buffer_not_aligned; assert(entity->info != (uintptr_t)NULL); cur = (block_dev_state_t *)entity->info; ops = &(cur->dev_spec->ops); buf = &(cur->dev_spec->buffer); block_size = cur->dev_spec->block_size; assert((length <= cur->size) && (length > 0) && (ops->read != 0) && (ops->write != 0)); if ((buffer & (block_size - 1)) != 0) { /* * buffer isn't aligned with block size. * Block device always relies on DMA operation. * It's better to make the buffer as block size aligned. */ buffer_not_aligned = 1; } else { buffer_not_aligned = 0; } skip = cur->file_pos % block_size; aligned_length = ((skip + length) + (block_size - 1)) & ~(block_size - 1); padding = aligned_length - (skip + length); left = aligned_length; do { lba = (cur->file_pos + cur->base) / block_size; if (left >= buf->length) { /* Since left is larger, it's impossible to padding. */ if (skip || buffer_not_aligned) { /* * The beginning address (file_pos) isn't * aligned with block size or buffer isn't * aligned, we need to use block buffer to * write block. */ count = ops->read(lba, buf->offset, buf->length); assert(count == buf->length); memcpy((void *)(buf->offset + skip), (void *)buffer, count - skip); count = ops->write(lba, buf->offset, buf->length); } else count = ops->write(lba, buffer, buf->length); assert(count == buf->length); cur->file_pos += count - skip; left = left - (count - skip); } else { if (skip || padding || buffer_not_aligned) { /* * The beginning address (file_pos) isn't * aligned with block size, we need to avoid * poluate data in the beginning. Reading and * skipping the beginning is the only way. * The size isn't aligned with block size. * Use block buffer to avoid overflow. * * If buffer isn't aligned, use block buffer * to avoid DMA error. */ count = ops->read(lba, buf->offset, left); assert(count == left); memcpy((void *)(buf->offset + skip), (void *)buffer, left - skip - padding); count = ops->write(lba, buf->offset, left); } else count = ops->write(lba, buffer, left); assert(count == left); cur->file_pos += left - (skip + padding); /* It's already the last block operation */ left = 0; } skip = cur->file_pos % block_size; } while (left > 0); *length_written = length; return 0; } static int block_close(io_entity_t *entity) { entity->info = (uintptr_t)NULL; return 0; } static int block_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info) { block_dev_state_t *cur; io_block_spec_t *buffer; io_dev_info_t *info; size_t block_size; int result; assert(dev_info != NULL); result = allocate_dev_info(&info); if (result) return -ENOENT; cur = (block_dev_state_t *)info->info; /* dev_spec is type of io_block_dev_spec_t. */ cur->dev_spec = (io_block_dev_spec_t *)dev_spec; buffer = &(cur->dev_spec->buffer); block_size = cur->dev_spec->block_size; assert((block_size > 0) && (is_power_of_2(block_size) != 0) && ((buffer->offset % block_size) == 0) && ((buffer->length % block_size) == 0)); *dev_info = info; /* cast away const */ (void)block_size; (void)buffer; return 0; } static int block_dev_close(io_dev_info_t *dev_info) { return free_dev_info(dev_info); } /* Exported functions */ /* Register the Block driver with the IO abstraction */ int register_io_dev_block(const io_dev_connector_t **dev_con) { int result; assert(dev_con != NULL); /* * Since dev_info isn't really used in io_register_device, always * use the same device info at here instead. */ result = io_register_device(&dev_info_pool[0]); if (result == 0) *dev_con = &block_dev_connector; return result; }