aboutsummaryrefslogtreecommitdiff
path: root/drivers/io/io_block.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/io/io_block.c')
-rw-r--r--drivers/io/io_block.c552
1 files changed, 342 insertions, 210 deletions
diff --git a/drivers/io/io_block.c b/drivers/io/io_block.c
index 40472275..128246fe 100644
--- a/drivers/io/io_block.c
+++ b/drivers/io/io_block.c
@@ -1,293 +1,425 @@
/*
- * Copyright (c) 2014-2015, Linaro Ltd and Contributors. All rights reserved.
- * Copyright (c) 2014-2015, Hisilicon Ltd and Contributors. All rights reserved.
+ * Copyright (c) 2016-2017, ARM Limited and Contributors. All rights reserved.
*
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * Neither the name of ARM nor the names of its contributors may be used
- * to endorse or promote products derived from this software without specific
- * prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
-#include <bl_common.h>
#include <debug.h>
#include <errno.h>
#include <io_block.h>
#include <io_driver.h>
#include <io_storage.h>
-#include <mmio.h>
-#include <stdint.h>
+#include <platform_def.h>
#include <string.h>
+#include <utils.h>
-/* As we need to be able to keep state for seek, only one file can be open
- * at a time. Make this a structure and point to the entity->info. When we
- * can malloc memory we can change this to support more open files.
- */
typedef struct {
- /* Use the 'in_use' flag as any value for base and file_pos could be
- * valid.
- */
- int in_use;
- uintptr_t base;
- size_t file_pos;
- uint32_t flags;
-} file_state_t;
-
-struct block_info {
- struct block_ops ops;
- int init;
- uint32_t flags;
-};
+ io_block_dev_spec_t *dev_spec;
+ uintptr_t base;
+ size_t file_pos;
+ size_t size;
+} block_dev_state_t;
-static file_state_t current_file = {0};
+#define is_power_of_2(x) ((x != 0) && ((x & (x - 1)) == 0))
-static struct block_info block_info;
+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, uintptr_t buffer,
+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 int blk_dev_init(io_dev_info_t *dev_info,
- const uintptr_t init_params);
-static int blk_dev_open(const uintptr_t dev_spec, io_dev_info_t **dev_info);
-static int blk_dev_close(io_dev_info_t *dev_info);
-
-/* Identify the device type as block */
-io_type_t device_type_block(void)
-{
- return IO_TYPE_BLOCK;
-}
-
-static const io_dev_connector_t blk_dev_connector = {
- .dev_open = blk_dev_open
+static const io_dev_connector_t block_dev_connector = {
+ .dev_open = block_dev_open
};
-static const io_dev_funcs_t blk_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 = blk_dev_init,
- .dev_close = blk_dev_close,
+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];
-/* No state associated with this device so structure can be const */
-static const io_dev_info_t blk_dev_info = {
- .funcs = &blk_dev_funcs,
- .info = (uintptr_t)&block_info,
-};
+/* Track number of allocated block state */
+static unsigned int block_dev_count;
-/* Open a connection to the block device */
-static int blk_dev_open(const uintptr_t dev_spec __attribute__((unused)),
- io_dev_info_t **dev_info)
+io_type_t device_type_block(void)
{
- struct block_ops *funcs, *block_spec;
-
- assert(dev_info != NULL);
- *dev_info = (io_dev_info_t *)&blk_dev_info; /* cast away const */
-
- if (dev_spec) {
- funcs = &block_info.ops;
- block_spec = (struct block_ops *)dev_spec;
- funcs->init = block_spec->init;
- funcs->read = block_spec->read;
- funcs->write = block_spec->write;
- }
-
- return IO_SUCCESS;
+ return IO_TYPE_BLOCK;
}
-/* Close a connection to the block device */
-static int blk_dev_close(io_dev_info_t *dev_info)
+/* 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)
{
- /* NOP */
- /* TODO: Consider tracking open files and cleaning them up here */
- return IO_SUCCESS;
+ 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;
}
-
-/* Open a file on the block device */
-/* TODO: Can we do any sensible limit checks on requested memory */
-static int block_open(io_dev_info_t *dev_info, const uintptr_t spec,
- io_entity_t *entity)
+/* 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 = IO_FAIL;
- const io_block_spec_t *block_spec = (io_block_spec_t *)spec;
- struct block_info *info = (struct block_info *)(dev_info->info);
+ int result = -ENOMEM;
+ assert(dev_info != NULL);
- /* Since we need to track open state for seek() we only allow one open
- * spec at a time. When we have dynamic memory we can malloc and set
- * entity->info.
- */
- if (current_file.in_use == 0) {
- assert(block_spec != NULL);
- assert(entity != NULL);
-
- current_file.in_use = 1;
- current_file.base = block_spec->offset;
- /* File cursor offset for seek and incremental reads etc. */
- current_file.file_pos = 0;
- current_file.flags = info->flags;
- entity->info = (uintptr_t)&current_file;
- result = IO_SUCCESS;
- } else {
- WARN("A block device is already active. Close first.\n");
- result = IO_RESOURCES_EXHAUSTED;
+ 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;
}
-/* Seek to a particular file offset on the block device */
-static int block_seek(io_entity_t *entity, int mode, ssize_t offset)
-{
- int result = IO_FAIL;
- /* We only support IO_SEEK_SET for the moment. */
- if (mode == IO_SEEK_SET) {
- assert(entity != NULL);
+/* 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);
- /* TODO: can we do some basic limit checks on seek? */
- ((file_state_t *)entity->info)->file_pos = offset;
- result = IO_SUCCESS;
- } else {
- result = IO_FAIL;
+ 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;
}
-
-/* Read data from a file on the block device */
-static int block_read(io_entity_t *entity, uintptr_t buffer,
- size_t length, size_t *length_read)
+static int block_open(io_dev_info_t *dev_info, const uintptr_t spec,
+ io_entity_t *entity)
{
- file_state_t *fp;
- int result;
+ block_dev_state_t *cur;
+ io_block_spec_t *region;
- assert(entity != NULL);
- assert(buffer != (uintptr_t)NULL);
- assert(length_read != NULL);
+ assert((dev_info->info != (uintptr_t)NULL) &&
+ (spec != (uintptr_t)NULL) &&
+ (entity->info == (uintptr_t)NULL));
- fp = (file_state_t *)entity->info;
+ 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));
- if (!block_info.ops.read) {
- ERROR("There's no read function on the block device.\n");
- return IO_NOT_SUPPORTED;
+ 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;
}
- result = block_info.ops.read(fp->base + fp->file_pos, length,
- buffer, fp->flags);
- if (result) {
- WARN("Failed to read block offset 0x%x\n",
- fp->base + fp->file_pos);
- return result;
+ 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;
- /* advance the file 'cursor' for incremental reads */
- fp->file_pos += length;
- return IO_SUCCESS;
+ return 0;
}
-static int block_write(io_entity_t *entity, uintptr_t buffer,
+static int block_write(io_entity_t *entity, const uintptr_t buffer,
size_t length, size_t *length_written)
{
- file_state_t *fp;
- int result;
-
- assert(entity != NULL);
- assert(buffer != (uintptr_t)NULL);
- assert(length_written != NULL);
-
- fp = (file_state_t *)entity->info;
-
- if (!block_info.ops.write) {
- ERROR("There's no write function on the block device.\n");
- return IO_NOT_SUPPORTED;
- }
- result = block_info.ops.write(fp->base + fp->file_pos, length,
- buffer, fp->flags);
- if (result) {
- WARN("Failed to write block offset 0x%x\n",
- fp->base + fp->file_pos);
- return result;
+ 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;
- /* advance the file 'cursor' for incremental reads */
- fp->file_pos += length;
-
- return IO_SUCCESS;
+ return 0;
}
-/* Close a file on the BLOCK device */
static int block_close(io_entity_t *entity)
{
- assert(entity != NULL);
-
- entity->info = 0;
+ entity->info = (uintptr_t)NULL;
+ return 0;
+}
- /* This would be a mem free() if we had malloc.*/
- memset((void *)&current_file, 0, sizeof(current_file));
+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;
- return IO_SUCCESS;
+ 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 blk_dev_init(io_dev_info_t *dev_info, const uintptr_t init_params)
+static int block_dev_close(io_dev_info_t *dev_info)
{
- struct block_info *info = (struct block_info *)(dev_info->info);
-
- if (!info->init) {
- if (block_info.ops.init)
- block_info.ops.init();
- info->init = 1;
- }
- info->flags = init_params;
- return IO_SUCCESS;
+ return free_dev_info(dev_info);
}
/* Exported functions */
-/* Register the block driver with the IO abstraction */
+/* Register the Block driver with the IO abstraction */
int register_io_dev_block(const io_dev_connector_t **dev_con)
{
- int result = IO_FAIL;
- assert(dev_con != NULL);
+ int result;
- result = io_register_device(&blk_dev_info);
- if (result == IO_SUCCESS)
- *dev_con = &blk_dev_connector;
+ 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;
}