diff options
author | Che-Liang Chiou <clchiou@chromium.org> | 2011-03-30 14:58:17 +0800 |
---|---|---|
committer | Simon Glass <sjg@chromium.org> | 2011-08-24 10:00:43 -0700 |
commit | 88299441f8dc6d00b1fcc742b754d804649ca156 (patch) | |
tree | 025e9f4d26ba05b7c069e6ad7f7c8c18cc8d0b80 /lib | |
parent | 71e94739ce1ef991e6d3630033b3d0ca4530682b (diff) |
Add implementation of SPI firmware storage interface
R=robotboy@chromium.org,waihong@chromium.org
BUG=chromium-os:13063
TEST=see cl/6676109
Review URL: http://codereview.chromium.org/6696066
Change-Id: Ief655c3055cf8e0857a67085f4c76d8862a7228b
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chromeos/firmware_storage_spi.c | 217 |
1 files changed, 215 insertions, 2 deletions
diff --git a/lib/chromeos/firmware_storage_spi.c b/lib/chromeos/firmware_storage_spi.c index 5f21cb8e752..fc71a2ad15b 100644 --- a/lib/chromeos/firmware_storage_spi.c +++ b/lib/chromeos/firmware_storage_spi.c @@ -11,10 +11,223 @@ /* Implementation of firmware storage access interface for SPI */ #include <common.h> +#include <malloc.h> +#include <spi_flash.h> #include <chromeos/firmware_storage.h> +#define PREFIX "firmware_storage_spi: " + +#ifndef CONFIG_SF_DEFAULT_SPEED +# define CONFIG_SF_DEFAULT_SPEED 1000000 +#endif +#ifndef CONFIG_SF_DEFAULT_MODE +# define CONFIG_SF_DEFAULT_MODE SPI_MODE_3 +#endif + +struct context_t { + struct spi_flash *flash; + u32 offset; +}; + +static off_t seek_spi(void *context, off_t offset, enum whence_t whence) +{ + struct context_t *cxt = context; + u32 next_offset; + + if (whence == SEEK_SET) + next_offset = offset; + else if (whence == SEEK_CUR) + next_offset = cxt->offset + offset; + else if (whence == SEEK_END) + next_offset = cxt->flash->size + offset; + else { + debug(PREFIX "unknown whence value: %d\n", whence); + return -1; + } + + if (next_offset < 0) { + debug(PREFIX "negative offset: %d\n", next_offset); + return -1; + } + + if (next_offset > cxt->flash->size) { + debug(PREFIX "offset overflow: 0x%08x > 0x%08x\n", + next_offset, cxt->flash->size); + return -1; + } + + cxt->offset = next_offset; + return cxt->offset; +} + +/* + * Check the right-exclusive range [offset:offset+*count_ptr), and adjust + * value pointed by <count_ptr> to form a valid range when needed. + * + * Return 0 if it is possible to form a valid range. and non-zero if not. + */ +static int border_check(struct spi_flash *flash, u32 offset, + size_t *count_ptr) +{ + if (offset >= flash->size) { + debug(PREFIX "at EOF\n"); + return -1; + } + + if (offset + *count_ptr > flash->size) + *count_ptr = flash->size - offset; + + return 0; +} + +static ssize_t read_spi(void *context, void *buf, size_t count) +{ + struct context_t *cxt = context; + + if (border_check(cxt->flash, cxt->offset, &count)) + return -1; + + if (cxt->flash->read(cxt->flash, cxt->offset, count, buf)) { + debug(PREFIX "SPI read fail\n"); + return -1; + } + + cxt->offset += count; + return count; +} + +/* + * FIXME: It is a reasonable assumption that sector size = 4096 bytes. + * Nevertheless, comparing to coding this magic number here, there should be a + * better way (maybe rewrite driver interface?) to expose this parameter from + * eeprom driver. + */ +#define SECTOR_SIZE 0x1000 + +/* + * Align the right-exclusive range [*offset_ptr:*offset_ptr+*length_ptr) with + * SECTOR_SIZE. + * After alignment adjustment, both offset and length will be multiple of + * SECTOR_SIZE, and will be larger than or equal to the original range. + */ +static void align_to_sector(size_t *offset_ptr, size_t *length_ptr) +{ + debug(PREFIX "before adjustment\n"); + debug(PREFIX "offset: 0x%lx\n", *offset_ptr); + debug(PREFIX "length: 0x%lx\n", *length_ptr); + + /* Adjust if offset is not multiple of SECTOR_SIZE */ + if (*offset_ptr & (SECTOR_SIZE - 1ul)) { + *offset_ptr &= ~(SECTOR_SIZE - 1ul); + } + + /* Adjust if length is not multiple of SECTOR_SIZE */ + if (*length_ptr & (SECTOR_SIZE - 1ul)) { + *length_ptr &= ~(SECTOR_SIZE - 1ul); + *length_ptr += SECTOR_SIZE; + } + + debug(PREFIX "after adjustment\n"); + debug(PREFIX "offset: 0x%lx\n", *offset_ptr); + debug(PREFIX "length: 0x%lx\n", *length_ptr); +} + +static ssize_t write_spi(void *context, const void *buf, size_t count) +{ + struct context_t *cxt = context; + struct spi_flash *flash = cxt->flash; + uint8_t static_buf[SECTOR_SIZE]; + uint8_t *backup_buf; + ssize_t ret = -1; + size_t offset, length, tmp; + int status; + + /* We will erase <length> bytes starting from <offset> */ + offset = cxt->offset; + length = count; + align_to_sector(&offset, &length); + + tmp = length; + if (border_check(flash, offset, &tmp)) + return -1; + if (tmp != length) { + debug(PREFIX "cannot erase range [%08lx:%08lx]: %08lx\n", + offset, offset + length, offset + tmp); + return -1; + } + + backup_buf = length > sizeof(static_buf) ? malloc(length) : static_buf; + + if ((status = flash->read(flash, offset, length, backup_buf))) { + debug(PREFIX "cannot backup data: %d\n", status); + goto EXIT; + } + + if ((status = flash->erase(flash, offset, length))) { + debug(PREFIX "SPI erase fail: %d\n", status); + goto EXIT; + } + + debug(PREFIX "cxt->offset: 0x%08lx\n", cxt->offset); + debug(PREFIX "offset: 0x%08lx\n", offset); + + /* combine data we want to write and backup data */ + memcpy(backup_buf + (cxt->offset - offset), buf, count); + + if (flash->write(flash, offset, length, backup_buf)) { + debug(PREFIX "SPI write fail\n"); + goto EXIT; + } + + cxt->offset += count; + ret = count; + +EXIT: + if (backup_buf != static_buf) + free(backup_buf); + + return ret; +} + +static int close_spi(void *context) +{ + struct context_t *cxt = context; + + spi_flash_free(cxt->flash); + free(cxt); + + return 0; +} + +static int lock_spi(void *context) +{ + /* TODO Implement lock device */ + return 0; +} + int firmware_storage_init_spi(firmware_storage_t *file) { - /* TODO Implement interface to SPI */ - return -1; + const unsigned int bus = 0; + const unsigned int cs = 0; + const unsigned int max_hz = CONFIG_SF_DEFAULT_SPEED; + const unsigned int spi_mode = CONFIG_SF_DEFAULT_MODE; + struct context_t *cxt; + + cxt = malloc(sizeof(*cxt)); + cxt->offset = 0; + cxt->flash = spi_flash_probe(bus, cs, max_hz, spi_mode); + if (!cxt->flash) { + debug(PREFIX "fail to init SPI flash at %u:%u\n", bus, cs); + free(cxt); + return -1; + } + + file->seek = seek_spi; + file->read = read_spi; + file->write = write_spi; + file->close = close_spi; + file->lock_device = lock_spi; + file->context = (void*) cxt; + + return 0; } |