blob: 128246fed2b428f5fce9f7e04e3a3c3f3ab98e76 [file] [log] [blame]
/*
* Copyright (c) 2016-2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <io_block.h>
#include <io_driver.h>
#include <io_storage.h>
#include <platform_def.h>
#include <string.h>
#include <utils.h>
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;
}