diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/arm/ccn/ccn.c | 13 | ||||
-rw-r--r-- | drivers/arm/ccn/ccn_private.h | 1 | ||||
-rw-r--r-- | drivers/auth/auth_mod.c | 16 | ||||
-rw-r--r-- | drivers/delay_timer/delay_timer.c | 23 | ||||
-rw-r--r-- | drivers/delay_timer/generic_delay_timer.c | 82 | ||||
-rw-r--r-- | drivers/emmc/emmc.c | 347 | ||||
-rw-r--r-- | drivers/gpio/gpio.c | 20 | ||||
-rw-r--r-- | drivers/io/io_block.c | 412 |
8 files changed, 899 insertions, 15 deletions
diff --git a/drivers/arm/ccn/ccn.c b/drivers/arm/ccn/ccn.c index 28d27098..060acdd2 100644 --- a/drivers/arm/ccn/ccn.c +++ b/drivers/arm/ccn/ccn.c @@ -254,6 +254,7 @@ static unsigned long long ccn_master_to_rn_id_map(unsigned long long master_map) assert(ccn_plat_desc); FOR_EACH_PRESENT_MASTER_INTERFACE(iface_id, master_map) { + assert(iface_id < ccn_plat_desc->num_masters); /* Convert the master ID into the node ID */ node_id = ccn_plat_desc->master_to_rn_id_map[iface_id]; @@ -501,3 +502,15 @@ void ccn_program_sys_addrmap(unsigned int sn0_id, } } + +/******************************************************************************* + * This function returns the part0 id from the peripheralID 0 register + * in CCN. This id can be used to distinguish the CCN variant present in the + * system. + ******************************************************************************/ +int ccn_get_part0_id(uintptr_t periphbase) +{ + assert(periphbase); + return (int)(mmio_read_64(periphbase + + MN_PERIPH_ID_0_1_OFFSET) & 0xFF); +} diff --git a/drivers/arm/ccn/ccn_private.h b/drivers/arm/ccn/ccn_private.h index 8b154725..fffa2ca9 100644 --- a/drivers/arm/ccn/ccn_private.h +++ b/drivers/arm/ccn/ccn_private.h @@ -149,6 +149,7 @@ typedef enum rn_types { #define MN_DDC_STAT_OFFSET DOMAIN_CTRL_STAT_OFFSET #define MN_DDC_SET_OFFSET DOMAIN_CTRL_SET_OFFSET #define MN_DDC_CLR_OFFSET DOMAIN_CTRL_CLR_OFFSET +#define MN_PERIPH_ID_0_1_OFFSET 0xFE0 #define MN_ID_OFFSET REGION_ID_OFFSET /* HNF System Address Map register bit masks and shifts */ diff --git a/drivers/auth/auth_mod.c b/drivers/auth/auth_mod.c index 41845561..88ef0b02 100644 --- a/drivers/auth/auth_mod.c +++ b/drivers/auth/auth_mod.c @@ -199,8 +199,9 @@ static int auth_signature(const auth_method_param_sig_t *param, } return_if_error(rc); - /* If the PK is a hash of the key, retrieve the key from the image */ - if (flags & ROTPK_IS_HASH) { + if (flags & (ROTPK_IS_HASH | ROTPK_NOT_DEPLOYED)) { + /* If the PK is a hash of the key or if the ROTPK is not + deployed on the platform, retrieve the key from the image */ pk_hash_ptr = pk_ptr; pk_hash_len = pk_len; rc = img_parser_get_auth_param(img_desc->img_type, @@ -215,9 +216,14 @@ static int auth_signature(const auth_method_param_sig_t *param, pk_ptr, pk_len); return_if_error(rc); - /* Ask the crypto-module to verify the key hash */ - rc = crypto_mod_verify_hash(pk_ptr, pk_len, - pk_hash_ptr, pk_hash_len); + if (flags & ROTPK_NOT_DEPLOYED) { + NOTICE("ROTPK is not deployed on platform. " + "Skipping ROTPK verification.\n"); + } else { + /* Ask the crypto-module to verify the key hash */ + rc = crypto_mod_verify_hash(pk_ptr, pk_len, + pk_hash_ptr, pk_hash_len); + } } else { /* Ask the crypto module to verify the signature */ rc = crypto_mod_verify_signature(data_ptr, data_len, diff --git a/drivers/delay_timer/delay_timer.c b/drivers/delay_timer/delay_timer.c index 0bee876f..ed7ed52e 100644 --- a/drivers/delay_timer/delay_timer.c +++ b/drivers/delay_timer/delay_timer.c @@ -48,19 +48,22 @@ void udelay(uint32_t usec) (ops->clk_div != 0) && (ops->get_timer_value != 0)); - uint32_t start, cnt, delta, delta_us; + uint32_t start, delta, total_delta; + + assert(usec < UINT32_MAX / ops->clk_div); - /* counter is decreasing */ start = ops->get_timer_value(); + + total_delta = (usec * ops->clk_div) / ops->clk_mult; + do { - cnt = ops->get_timer_value(); - if (cnt > start) { - delta = UINT32_MAX - cnt; - delta += start; - } else - delta = start - cnt; - delta_us = (delta * ops->clk_mult) / ops->clk_div; - } while (delta_us < usec); + /* + * If the timer value wraps around, the subtraction will + * overflow and it will still give the correct result. + */ + delta = start - ops->get_timer_value(); /* Decreasing counter */ + + } while (delta < total_delta); } /*********************************************************** diff --git a/drivers/delay_timer/generic_delay_timer.c b/drivers/delay_timer/generic_delay_timer.c new file mode 100644 index 00000000..4c364a30 --- /dev/null +++ b/drivers/delay_timer/generic_delay_timer.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016, 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. + */ + +#include <arch_helpers.h> +#include <assert.h> +#include <bl_common.h> +#include <debug.h> +#include <delay_timer.h> +#include <platform.h> + +/* Ticks elapsed in one second by a signal of 1 MHz */ +#define MHZ_TICKS_PER_SEC 1000000 + +static timer_ops_t ops; + +static uint32_t get_timer_value(void) +{ + /* + * Generic delay timer implementation expects the timer to be a down + * counter. We apply bitwise NOT operator to the tick values returned + * by read_cntpct_el0() to simulate the down counter. The value is + * clipped from 64 to 32 bits. + */ + return (uint32_t)(~read_cntpct_el0()); +} + +void generic_delay_timer_init_args(uint32_t mult, uint32_t div) +{ + ops.get_timer_value = get_timer_value; + ops.clk_mult = mult; + ops.clk_div = div; + + timer_init(&ops); + + VERBOSE("Generic delay timer configured with mult=%u and div=%u\n", + mult, div); +} + +void generic_delay_timer_init(void) +{ + /* Value in ticks */ + unsigned int mult = MHZ_TICKS_PER_SEC; + + /* Value in ticks per second (Hz) */ + unsigned int div = plat_get_syscnt_freq2(); + + /* Reduce multiplier and divider by dividing them repeatedly by 10 */ + while ((mult % 10 == 0) && (div % 10 == 0)) { + mult /= 10; + div /= 10; + } + + generic_delay_timer_init_args(mult, div); +} + diff --git a/drivers/emmc/emmc.c b/drivers/emmc/emmc.c new file mode 100644 index 00000000..5fe28efc --- /dev/null +++ b/drivers/emmc/emmc.c @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2016, 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. + * + * Defines a simple and generic interface to access eMMC device. + */ + +#include <arch_helpers.h> +#include <assert.h> +#include <debug.h> +#include <emmc.h> +#include <errno.h> +#include <string.h> + +static const emmc_ops_t *ops; +static unsigned int emmc_ocr_value; +static emmc_csd_t emmc_csd; + +static int emmc_device_state(void) +{ + emmc_cmd_t cmd; + int ret; + + do { + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD13; + cmd.cmd_arg = EMMC_FIX_RCA << RCA_SHIFT_OFFSET; + cmd.resp_type = EMMC_RESPONSE_R1; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + assert((cmd.resp_data[0] & STATUS_SWITCH_ERROR) == 0); + /* Ignore improbable errors in release builds */ + (void)ret; + } while ((cmd.resp_data[0] & STATUS_READY_FOR_DATA) == 0); + return EMMC_GET_STATE(cmd.resp_data[0]); +} + +static void emmc_set_ext_csd(unsigned int ext_cmd, unsigned int value) +{ + emmc_cmd_t cmd; + int ret, state; + + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD6; + cmd.cmd_arg = EXTCSD_WRITE_BYTES | EXTCSD_CMD(ext_cmd) | + EXTCSD_VALUE(value) | 1; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + /* wait to exit PRG state */ + do { + state = emmc_device_state(); + } while (state == EMMC_STATE_PRG); + /* Ignore improbable errors in release builds */ + (void)ret; +} + +static void emmc_set_ios(int clk, int bus_width) +{ + int ret; + + /* set IO speed & IO bus width */ + if (emmc_csd.spec_vers == 4) + emmc_set_ext_csd(CMD_EXTCSD_BUS_WIDTH, bus_width); + ret = ops->set_ios(clk, bus_width); + assert(ret == 0); + /* Ignore improbable errors in release builds */ + (void)ret; +} + +static int emmc_enumerate(int clk, int bus_width) +{ + emmc_cmd_t cmd; + int ret, state; + + ops->init(); + + /* CMD0: reset to IDLE */ + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD0; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + while (1) { + /* CMD1: get OCR register */ + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD1; + cmd.cmd_arg = OCR_SECTOR_MODE | OCR_VDD_MIN_2V7 | + OCR_VDD_MIN_1V7; + cmd.resp_type = EMMC_RESPONSE_R3; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + emmc_ocr_value = cmd.resp_data[0]; + if (emmc_ocr_value & OCR_POWERUP) + break; + } + + /* CMD2: Card Identification */ + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD2; + cmd.resp_type = EMMC_RESPONSE_R2; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + /* CMD3: Set Relative Address */ + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD3; + cmd.cmd_arg = EMMC_FIX_RCA << RCA_SHIFT_OFFSET; + cmd.resp_type = EMMC_RESPONSE_R1; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + /* CMD9: CSD Register */ + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD9; + cmd.cmd_arg = EMMC_FIX_RCA << RCA_SHIFT_OFFSET; + cmd.resp_type = EMMC_RESPONSE_R2; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + memcpy(&emmc_csd, &cmd.resp_data, sizeof(cmd.resp_data)); + + /* CMD7: Select Card */ + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD7; + cmd.cmd_arg = EMMC_FIX_RCA << RCA_SHIFT_OFFSET; + cmd.resp_type = EMMC_RESPONSE_R1; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + /* wait to TRAN state */ + do { + state = emmc_device_state(); + } while (state != EMMC_STATE_TRAN); + + emmc_set_ios(clk, bus_width); + return ret; +} + +size_t emmc_read_blocks(int lba, uintptr_t buf, size_t size) +{ + emmc_cmd_t cmd; + int ret; + + assert((ops != 0) && + (ops->read != 0) && + ((buf & EMMC_BLOCK_MASK) == 0) && + ((size & EMMC_BLOCK_MASK) == 0)); + + inv_dcache_range(buf, size); + ret = ops->prepare(lba, buf, size); + assert(ret == 0); + + memset(&cmd, 0, sizeof(emmc_cmd_t)); + if (size > EMMC_BLOCK_SIZE) + cmd.cmd_idx = EMMC_CMD18; + else + cmd.cmd_idx = EMMC_CMD17; + if ((emmc_ocr_value & OCR_ACCESS_MODE_MASK) == OCR_BYTE_MODE) + cmd.cmd_arg = lba * EMMC_BLOCK_SIZE; + else + cmd.cmd_arg = lba; + cmd.resp_type = EMMC_RESPONSE_R1; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + ret = ops->read(lba, buf, size); + assert(ret == 0); + + /* wait buffer empty */ + emmc_device_state(); + + if (size > EMMC_BLOCK_SIZE) { + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD12; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + } + /* Ignore improbable errors in release builds */ + (void)ret; + return size; +} + +size_t emmc_write_blocks(int lba, const uintptr_t buf, size_t size) +{ + emmc_cmd_t cmd; + int ret; + + assert((ops != 0) && + (ops->write != 0) && + ((buf & EMMC_BLOCK_MASK) == 0) && + ((size & EMMC_BLOCK_MASK) == 0)); + + clean_dcache_range(buf, size); + ret = ops->prepare(lba, buf, size); + assert(ret == 0); + + memset(&cmd, 0, sizeof(emmc_cmd_t)); + if (size > EMMC_BLOCK_SIZE) + cmd.cmd_idx = EMMC_CMD25; + else + cmd.cmd_idx = EMMC_CMD24; + if ((emmc_ocr_value & OCR_ACCESS_MODE_MASK) == OCR_BYTE_MODE) + cmd.cmd_arg = lba * EMMC_BLOCK_SIZE; + else + cmd.cmd_arg = lba; + cmd.resp_type = EMMC_RESPONSE_R1; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + ret = ops->write(lba, buf, size); + assert(ret == 0); + + /* wait buffer empty */ + emmc_device_state(); + + if (size > EMMC_BLOCK_SIZE) { + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD12; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + } + /* Ignore improbable errors in release builds */ + (void)ret; + return size; +} + +size_t emmc_erase_blocks(int lba, size_t size) +{ + emmc_cmd_t cmd; + int ret, state; + + assert(ops != 0); + assert((size != 0) && ((size % EMMC_BLOCK_SIZE) == 0)); + + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD35; + cmd.cmd_arg = lba; + cmd.resp_type = EMMC_RESPONSE_R1; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD36; + cmd.cmd_arg = lba + (size / EMMC_BLOCK_SIZE) - 1; + cmd.resp_type = EMMC_RESPONSE_R1; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + memset(&cmd, 0, sizeof(emmc_cmd_t)); + cmd.cmd_idx = EMMC_CMD38; + cmd.resp_type = EMMC_RESPONSE_R1B; + ret = ops->send_cmd(&cmd); + assert(ret == 0); + + /* wait to TRAN state */ + do { + state = emmc_device_state(); + } while (state != EMMC_STATE_TRAN); + /* Ignore improbable errors in release builds */ + (void)ret; + return size; +} + +static inline void emmc_rpmb_enable(void) +{ + emmc_set_ext_csd(CMD_EXTCSD_PARTITION_CONFIG, + PART_CFG_BOOT_PARTITION1_ENABLE | + PART_CFG_PARTITION1_ACCESS); +} + +static inline void emmc_rpmb_disable(void) +{ + emmc_set_ext_csd(CMD_EXTCSD_PARTITION_CONFIG, + PART_CFG_BOOT_PARTITION1_ENABLE); +} + +size_t emmc_rpmb_read_blocks(int lba, uintptr_t buf, size_t size) +{ + size_t size_read; + + emmc_rpmb_enable(); + size_read = emmc_read_blocks(lba, buf, size); + emmc_rpmb_disable(); + return size_read; +} + +size_t emmc_rpmb_write_blocks(int lba, const uintptr_t buf, size_t size) +{ + size_t size_written; + + emmc_rpmb_enable(); + size_written = emmc_write_blocks(lba, buf, size); + emmc_rpmb_disable(); + return size_written; +} + +size_t emmc_rpmb_erase_blocks(int lba, size_t size) +{ + size_t size_erased; + + emmc_rpmb_enable(); + size_erased = emmc_erase_blocks(lba, size); + emmc_rpmb_disable(); + return size_erased; +} + +void emmc_init(const emmc_ops_t *ops_ptr, int clk, int width) +{ + assert((ops_ptr != 0) && + (ops_ptr->init != 0) && + (ops_ptr->send_cmd != 0) && + (ops_ptr->set_ios != 0) && + (ops_ptr->prepare != 0) && + (ops_ptr->read != 0) && + (ops_ptr->write != 0) && + (clk != 0) && + ((width == EMMC_BUS_WIDTH_1) || + (width == EMMC_BUS_WIDTH_4) || + (width == EMMC_BUS_WIDTH_8))); + ops = ops_ptr; + + emmc_enumerate(clk, width); +} diff --git a/drivers/gpio/gpio.c b/drivers/gpio/gpio.c index c06172fc..ef6bb9c8 100644 --- a/drivers/gpio/gpio.c +++ b/drivers/gpio/gpio.c @@ -80,6 +80,26 @@ void gpio_set_value(int gpio, int value) ops->set_value(gpio, value); } +void gpio_set_pull(int gpio, int pull) +{ + assert(ops); + assert(ops->set_pull != 0); + assert((pull == GPIO_PULL_NONE) || (pull == GPIO_PULL_UP) || + (pull == GPIO_PULL_DOWN)); + assert(gpio >= 0); + + ops->set_pull(gpio, pull); +} + +int gpio_get_pull(int gpio) +{ + assert(ops); + assert(ops->get_pull != 0); + assert(gpio >= 0); + + return ops->get_pull(gpio); +} + /* * Initialize the gpio. The fields in the provided gpio * ops pointer must be valid. diff --git a/drivers/io/io_block.c b/drivers/io/io_block.c new file mode 100644 index 00000000..198b723b --- /dev/null +++ b/drivers/io/io_block.c @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2016, 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. + */ + +#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> + +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 */ + memset(state, 0, sizeof(block_dev_state_t)); + memset(dev_info, 0, 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; + + 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)); + + 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) { + /* + * 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) { + /* + * Since it's not aligned with block size, + * block buffer is used to store data. + */ + memcpy((void *)buffer, + (void *)(buf->offset + skip), + count - skip); + } + left = left - (count - skip); + } else { + if (skip || padding) { + /* + * 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. + */ + 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) { + /* + * Since it's not aligned with block size, + * 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; + + 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)); + + 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) { + /* + * The beginning address (file_pos) isn't + * aligned with block size, we need to use + * block buffer to write block. Since block + * device is always relied on DMA operation. + */ + 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) { + /* + * 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. + */ + 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; +} |