diff options
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | cgpt/cgpt.h | 49 | ||||
-rw-r--r-- | cgpt/cgpt_common.c | 315 | ||||
-rw-r--r-- | cgpt/flash_ts_drv.c | 76 | ||||
-rw-r--r-- | firmware/lib/flash_ts.c | 421 | ||||
-rw-r--r-- | firmware/lib/include/flash_ts.h | 35 | ||||
-rw-r--r-- | tests/cgptlib_test.c | 185 |
7 files changed, 993 insertions, 96 deletions
@@ -321,6 +321,8 @@ HOSTLIB_SRCS = \ cgpt/cgpt_repair.c \ cgpt/cgpt_prioritize.c \ cgpt/cgpt_common.c \ + cgpt/flash_ts_drv.c \ + firmware/lib/flash_ts.c \ host/arch/${ARCH}/lib/crossystem_arch.c \ host/lib/crossystem.c \ host/lib/file_keys.c \ @@ -352,6 +354,8 @@ TINYHOSTLIB_SRCS = \ cgpt/cgpt_repair.c \ cgpt/cgpt_prioritize.c \ cgpt/cgpt_common.c \ + cgpt/flash_ts_drv.c \ + firmware/lib/flash_ts.c \ utility/dump_kernel_config_lib.c \ firmware/lib/cgptlib/crc32.c \ firmware/lib/cgptlib/cgptlib_internal.c \ @@ -383,7 +387,9 @@ CGPT_SRCS = \ cgpt/cmd_legacy.c \ cgpt/cmd_prioritize.c \ cgpt/cmd_repair.c \ - cgpt/cmd_show.c + cgpt/cmd_show.c \ + cgpt/flash_ts_drv.c \ + firmware/lib/flash_ts.c CGPT_OBJS = ${CGPT_SRCS:%.c=${BUILD}/%.o} ALL_OBJS += ${CGPT_OBJS} diff --git a/cgpt/cgpt.h b/cgpt/cgpt.h index e55c32fc..3c99179f 100644 --- a/cgpt/cgpt.h +++ b/cgpt/cgpt.h @@ -13,6 +13,7 @@ #include "endian.h" #include "gpt.h" #include "cgptlib.h" +#include "mtdlib.h" struct legacy_partition { @@ -45,7 +46,9 @@ void PMBRToStr(struct pmbr *pmbr, char *str, unsigned int buflen); struct drive { int fd; /* file descriptor */ uint64_t size; /* total size (in bytes) */ + int is_mtd; GptData gpt; + MtdData mtd; struct pmbr pmbr; }; @@ -55,6 +58,52 @@ int DriveOpen(const char *drive_path, struct drive *drive, int mode); int DriveClose(struct drive *drive, int update_as_needed); int CheckValid(const struct drive *drive); +/* Loads sectors from 'drive'. + * *buf is pointed to an allocated memory when returned, and should be + * freed. + * + * drive -- open drive. + * buf -- pointer to buffer pointer + * sector -- offset of starting sector (in sectors) + * sector_bytes -- bytes per sector + * sector_count -- number of sectors to load + * + * Returns CGPT_OK for successful. Aborts if any error occurs. + */ +int Load(struct drive *drive, uint8_t **buf, + const uint64_t sector, + const uint64_t sector_bytes, + const uint64_t sector_count); + +/* Saves sectors to 'drive'. + * + * drive -- open drive + * buf -- pointer to buffer + * sector -- starting sector offset + * sector_bytes -- bytes per sector + * sector_count -- number of sector to save + * + * Returns CGPT_OK for successful, CGPT_FAILED for failed. + */ +int Save(struct drive *drive, const uint8_t *buf, + const uint64_t sector, + const uint64_t sector_bytes, + const uint64_t sector_count); + + +/* GUID conversion functions. Accepted format: + * + * "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" + * + * At least GUID_STRLEN bytes should be reserved in 'str' (included the tailing + * '\0'). + */ +#define GUID_STRLEN 37 +int StrToGuid(const char *str, Guid *guid); +void GuidToStr(const Guid *guid, char *str, unsigned int buflen); +int GuidEqual(const Guid *guid1, const Guid *guid2); +int IsZero(const Guid *guid); + /* Constant global type values to compare against */ extern const Guid guid_chromeos_firmware; extern const Guid guid_chromeos_kernel; diff --git a/cgpt/cgpt_common.c b/cgpt/cgpt_common.c index b4f71f76..29a207a9 100644 --- a/cgpt/cgpt_common.c +++ b/cgpt/cgpt_common.c @@ -23,6 +23,7 @@ #include "cgpt.h" #include "cgptlib_internal.h" #include "crc32.h" +#include "flash_ts.h" #include "vboot_host.h" void Error(const char *format, ...) { @@ -44,24 +45,13 @@ int CheckValid(const struct drive *drive) { return CGPT_OK; } -/* Loads sectors from 'fd'. - * *buf is pointed to an allocated memory when returned, and should be - * freed by cgpt_close(). - * - * fd -- file descriptot. - * buf -- pointer to buffer pointer - * sector -- offset of starting sector (in sectors) - * sector_bytes -- bytes per sector - * sector_count -- number of sectors to load - * - * Returns CGPT_OK for successful. Aborts if any error occurs. - */ -static int Load(const int fd, uint8_t **buf, +int Load(struct drive *drive, uint8_t **buf, const uint64_t sector, const uint64_t sector_bytes, const uint64_t sector_count) { int count; /* byte count to read */ int nread; + int fd = drive->fd; require(buf); if (!sector_count || !sector_bytes) { @@ -121,22 +111,13 @@ int WritePMBR(struct drive *drive) { return CGPT_OK; } -/* Saves sectors to 'fd'. - * - * fd -- file descriptot. - * buf -- pointer to buffer - * sector -- starting sector offset - * sector_bytes -- bytes per sector - * sector_count -- number of sector to save - * - * Returns CGPT_OK for successful, CGPT_FAILED for failed. - */ -static int Save(const int fd, const uint8_t *buf, +int Save(struct drive *drive, const uint8_t *buf, const uint64_t sector, const uint64_t sector_bytes, const uint64_t sector_count) { int count; /* byte count to write */ int nwrote; + int fd = drive->fd; require(buf); count = sector_bytes * sector_count; @@ -151,6 +132,204 @@ static int Save(const int fd, const uint8_t *buf, return CGPT_OK; } +static int get_hex_char_value(char ch) { + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } + if (ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } + if (ch >= 'A' && ch <= 'F') { + return ch - 'A' + 10; + } + return -1; +} + +int FlashGet(const char *key, uint8_t *data, uint32_t *bufsz) { + char *hex = (char*)malloc(*bufsz * 2); + char *read; + uint32_t written = 0; + + flash_ts_get(key, hex, *bufsz * 2); + + /* Hex -> binary */ + for (read = hex; read < hex + *bufsz * 2 && *read != '\0'; read += 2) { + int c0, c1; + c0 = get_hex_char_value(read[0]); + c1 = get_hex_char_value(read[1]); + if (c0 < 0 || c1 < 0) { + free(hex); + return -1; + } + + data[written++] = (c0 << 4) + c1; + } + *bufsz = written; + free(hex); + return 0; +} + +int FlashSet(const char *key, const uint8_t *data, uint32_t bufsz) { + char *hex = (char*)malloc(bufsz * 2 + 1); + const char *hex_chars = "0123456789ABCDEF"; + int ret; + uint32_t i; + + /* Binary -> hex, we need some encoding because FTS only stores C strings */ + for (i = 0; i < bufsz; i++) { + hex[i * 2] = hex_chars[data[i] >> 4]; + hex[i * 2 + 1] = hex_chars[data[i] & 0xF]; + } + /* Buffer must be NUL-terminated. */ + hex[bufsz * 2] = '\0'; + ret = flash_ts_set(key, hex); + free(hex); + return ret; +} + +int MtdLoad(struct drive *drive, int sector_bytes) { + int ret; + uint32_t old_crc, new_crc; + uint32_t sz; + MtdData *mtd = &drive->mtd; + + mtd->sector_bytes = sector_bytes; + + ret = flash_ts_init(mtd->fts_block_offset, + mtd->fts_block_size, + mtd->flash_page_bytes, + mtd->flash_block_bytes, + mtd->sector_bytes, /* Needed for Load() and Save() */ + drive); + if (ret) + return ret; + + memset(&mtd->primary, 0, sizeof(mtd->primary)); + sz = sizeof(mtd->primary); + ret = FlashGet(MTD_DRIVE_SIGNATURE, (uint8_t *)&mtd->primary, &sz); + if (ret) + return ret; + + /* Read less than expected */ + if (sz < MTD_DRIVE_V1_SIZE) + return -1; + + if (memcmp(mtd->primary.signature, MTD_DRIVE_SIGNATURE, + sizeof(mtd->primary.signature))) { + return -1; + } + + old_crc = mtd->primary.crc32; + mtd->primary.crc32 = 0; + new_crc = Crc32(&mtd->primary, MTD_DRIVE_V1_SIZE); + mtd->primary.crc32 = old_crc; + + if (old_crc != new_crc) { + return -1; + } + + mtd->current_kernel = -1; + mtd->current_priority = 0; + mtd->modified = 0; + return 0; +} + +int MtdSave(struct drive *drive) { + MtdData *mtd = &drive->mtd; + + mtd->primary.crc32 = 0; + mtd->primary.crc32 = Crc32(&mtd->primary, MTD_DRIVE_V1_SIZE); + + return FlashSet(MTD_DRIVE_SIGNATURE, (uint8_t *)&mtd->primary, + sizeof(mtd->primary)); +} + +int GptLoad(struct drive *drive, uint32_t sector_bytes) { + drive->gpt.sector_bytes = sector_bytes; + if (drive->size % drive->gpt.sector_bytes) { + Error("Media size (%llu) is not a multiple of sector size(%d)\n", + (long long unsigned int)drive->size, drive->gpt.sector_bytes); + return -1; + } + drive->gpt.drive_sectors = drive->size / drive->gpt.sector_bytes; + + // Read the data. + if (CGPT_OK != Load(drive, &drive->gpt.primary_header, + GPT_PMBR_SECTOR, + drive->gpt.sector_bytes, GPT_HEADER_SECTOR)) { + return -1; + } + if (CGPT_OK != Load(drive, &drive->gpt.secondary_header, + drive->gpt.drive_sectors - GPT_PMBR_SECTOR, + drive->gpt.sector_bytes, GPT_HEADER_SECTOR)) { + return -1; + } + if (CGPT_OK != Load(drive, &drive->gpt.primary_entries, + GPT_PMBR_SECTOR + GPT_HEADER_SECTOR, + drive->gpt.sector_bytes, GPT_ENTRIES_SECTORS)) { + return -1; + } + if (CGPT_OK != Load(drive, &drive->gpt.secondary_entries, + drive->gpt.drive_sectors - GPT_HEADER_SECTOR + - GPT_ENTRIES_SECTORS, + drive->gpt.sector_bytes, GPT_ENTRIES_SECTORS)) { + return -1; + } + return 0; +} + +int GptSave(struct drive *drive) { + int errors = 0; + if (drive->gpt.modified & GPT_MODIFIED_HEADER1) { + if (CGPT_OK != Save(drive, drive->gpt.primary_header, + GPT_PMBR_SECTOR, + drive->gpt.sector_bytes, GPT_HEADER_SECTOR)) { + errors++; + Error("Cannot write primary header: %s\n", strerror(errno)); + } + } + + if (drive->gpt.modified & GPT_MODIFIED_HEADER2) { + if(CGPT_OK != Save(drive, drive->gpt.secondary_header, + drive->gpt.drive_sectors - GPT_PMBR_SECTOR, + drive->gpt.sector_bytes, GPT_HEADER_SECTOR)) { + errors++; + Error("Cannot write secondary header: %s\n", strerror(errno)); + } + } + if (drive->gpt.modified & GPT_MODIFIED_ENTRIES1) { + if (CGPT_OK != Save(drive, drive->gpt.primary_entries, + GPT_PMBR_SECTOR + GPT_HEADER_SECTOR, + drive->gpt.sector_bytes, GPT_ENTRIES_SECTORS)) { + errors++; + Error("Cannot write primary entries: %s\n", strerror(errno)); + } + } + if (drive->gpt.modified & GPT_MODIFIED_ENTRIES2) { + if (CGPT_OK != Save(drive, drive->gpt.secondary_entries, + drive->gpt.drive_sectors - GPT_HEADER_SECTOR + - GPT_ENTRIES_SECTORS, + drive->gpt.sector_bytes, GPT_ENTRIES_SECTORS)) { + errors++; + Error("Cannot write secondary entries: %s\n", strerror(errno)); + } + } + + if (drive->gpt.primary_header) + free(drive->gpt.primary_header); + drive->gpt.primary_header = 0; + if (drive->gpt.primary_entries) + free(drive->gpt.primary_entries); + drive->gpt.primary_entries = 0; + if (drive->gpt.secondary_header) + free(drive->gpt.secondary_header); + drive->gpt.secondary_header = 0; + if (drive->gpt.secondary_entries) + free(drive->gpt.secondary_entries); + drive->gpt.secondary_entries = 0; + return errors ? -1 : 0; +} + // Opens a block device or file, loads raw GPT data from it. // mode should be O_RDONLY or O_RDWR @@ -159,6 +338,8 @@ static int Save(const int fd, const uint8_t *buf, // Returns CGPT_OK if success and information are stored in 'drive'. */ int DriveOpen(const char *drive_path, struct drive *drive, int mode) { struct stat stat; + uint32_t sector_bytes; + int is_mtd = 0; require(drive_path); require(drive); @@ -166,6 +347,7 @@ int DriveOpen(const char *drive_path, struct drive *drive, int mode) { // Clear struct for proper error handling. memset(drive, 0, sizeof(struct drive)); + drive->is_mtd = is_mtd; drive->fd = open(drive_path, mode | O_LARGEFILE | O_NOFOLLOW); if (drive->fd == -1) { Error("Can't open %s: %s\n", drive_path, strerror(errno)); @@ -181,43 +363,24 @@ int DriveOpen(const char *drive_path, struct drive *drive, int mode) { Error("Can't read drive size from %s: %s\n", drive_path, strerror(errno)); goto error_close; } - if (ioctl(drive->fd, BLKSSZGET, &drive->gpt.sector_bytes) < 0) { + if (ioctl(drive->fd, BLKSSZGET, §or_bytes) < 0) { Error("Can't read sector size from %s: %s\n", drive_path, strerror(errno)); goto error_close; } } else { - drive->gpt.sector_bytes = 512; /* bytes */ + sector_bytes = 512; /* bytes */ drive->size = stat.st_size; } - if (drive->size % drive->gpt.sector_bytes) { - Error("Media size (%llu) is not a multiple of sector size(%d)\n", - (long long unsigned int)drive->size, drive->gpt.sector_bytes); - goto error_close; - } - drive->gpt.drive_sectors = drive->size / drive->gpt.sector_bytes; - // Read the data. - if (CGPT_OK != Load(drive->fd, &drive->gpt.primary_header, - GPT_PMBR_SECTOR, - drive->gpt.sector_bytes, GPT_HEADER_SECTOR)) { - goto error_close; - } - if (CGPT_OK != Load(drive->fd, &drive->gpt.secondary_header, - drive->gpt.drive_sectors - GPT_PMBR_SECTOR, - drive->gpt.sector_bytes, GPT_HEADER_SECTOR)) { - goto error_close; - } - if (CGPT_OK != Load(drive->fd, &drive->gpt.primary_entries, - GPT_PMBR_SECTOR + GPT_HEADER_SECTOR, - drive->gpt.sector_bytes, GPT_ENTRIES_SECTORS)) { - goto error_close; - } - if (CGPT_OK != Load(drive->fd, &drive->gpt.secondary_entries, - drive->gpt.drive_sectors - GPT_HEADER_SECTOR - - GPT_ENTRIES_SECTORS, - drive->gpt.sector_bytes, GPT_ENTRIES_SECTORS)) { - goto error_close; + if (is_mtd) { + if (MtdLoad(drive, sector_bytes)) { + goto error_close; + } + } else { + if (GptLoad(drive, sector_bytes)) { + goto error_close; + } } // We just load the data. Caller must validate it. @@ -233,38 +396,13 @@ int DriveClose(struct drive *drive, int update_as_needed) { int errors = 0; if (update_as_needed) { - if (drive->gpt.modified & GPT_MODIFIED_HEADER1) { - if (CGPT_OK != Save(drive->fd, drive->gpt.primary_header, - GPT_PMBR_SECTOR, - drive->gpt.sector_bytes, GPT_HEADER_SECTOR)) { - errors++; - Error("Cannot write primary header: %s\n", strerror(errno)); - } - } - - if (drive->gpt.modified & GPT_MODIFIED_HEADER2) { - if(CGPT_OK != Save(drive->fd, drive->gpt.secondary_header, - drive->gpt.drive_sectors - GPT_PMBR_SECTOR, - drive->gpt.sector_bytes, GPT_HEADER_SECTOR)) { - errors++; - Error("Cannot write secondary header: %s\n", strerror(errno)); - } - } - if (drive->gpt.modified & GPT_MODIFIED_ENTRIES1) { - if (CGPT_OK != Save(drive->fd, drive->gpt.primary_entries, - GPT_PMBR_SECTOR + GPT_HEADER_SECTOR, - drive->gpt.sector_bytes, GPT_ENTRIES_SECTORS)) { + if (drive->is_mtd) { + if (MtdSave(drive)) { errors++; - Error("Cannot write primary entries: %s\n", strerror(errno)); } - } - if (drive->gpt.modified & GPT_MODIFIED_ENTRIES2) { - if (CGPT_OK != Save(drive->fd, drive->gpt.secondary_entries, - drive->gpt.drive_sectors - GPT_HEADER_SECTOR - - GPT_ENTRIES_SECTORS, - drive->gpt.sector_bytes, GPT_ENTRIES_SECTORS)) { + } else { + if (GptSave(drive)) { errors++; - Error("Cannot write secondary entries: %s\n", strerror(errno)); } } } @@ -276,19 +414,6 @@ int DriveClose(struct drive *drive, int update_as_needed) { close(drive->fd); - if (drive->gpt.primary_header) - free(drive->gpt.primary_header); - drive->gpt.primary_header = 0; - if (drive->gpt.primary_entries) - free(drive->gpt.primary_entries); - drive->gpt.primary_entries = 0; - if (drive->gpt.secondary_header) - free(drive->gpt.secondary_header); - drive->gpt.secondary_header = 0; - if (drive->gpt.secondary_entries) - free(drive->gpt.secondary_entries); - drive->gpt.secondary_entries = 0; - return errors ? CGPT_FAILED : CGPT_OK; } diff --git a/cgpt/flash_ts_drv.c b/cgpt/flash_ts_drv.c new file mode 100644 index 00000000..8c918866 --- /dev/null +++ b/cgpt/flash_ts_drv.c @@ -0,0 +1,76 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Utility for ChromeOS-specific GPT partitions, Please see corresponding .c + * files for more details. + */ + +#include "flash_ts.h" + +#include "cgpt.h" +#include "errno.h" +#include "stdio.h" +#include "string.h" + +inline int page_to_sector(const nand_geom *nand, int page) { + return page * (nand->szofpg / nand->szofsector); +} + +int nand_read_page(const nand_geom *nand, int page, void *buf, int size) { + uint8_t *page_buff; + + if (size > nand->szofpg) { + return -1; + } + if (Load((struct drive *)nand->user, &page_buff, + page_to_sector(nand, page), nand->szofsector, + (size + nand->szofsector - 1) / nand->szofsector)) { + + // page may be not erased. return default data. + memset(buf, 0xff, size); + return 0; + } + memcpy(buf, page_buff, size); + free(page_buff); + return 0; +} + +int nand_write_page(const nand_geom *nand, + int page, const void *buf, int size) { + void *page_buff; + int ret; + + if (size > nand->szofpg) { + return -1; + } + page_buff = malloc(nand->szofpg); + if (!page_buff) + return -1; + + memset(page_buff, 0xff, nand->szofpg); + memcpy(page_buff, buf, size < nand->szofpg ? size : nand->szofpg); + + ret = Save((struct drive *)nand->user, page_buff, page_to_sector(nand, page), + nand->szofsector, nand->szofpg / nand->szofsector); + free(page_buff); + return ret; +} + +int nand_erase_block(const nand_geom *nand, int block) { + int sector = block * (nand->szofblk / nand->szofsector); + int res; + void *erase_buff = malloc(nand->szofblk); + if (!erase_buff) + return -1; + + memset(erase_buff, 0xff, nand->szofblk); + res = Save((struct drive *)nand->user, erase_buff, sector, + nand->szofsector, nand->szofblk / nand->szofsector); + free(erase_buff); + return res; +} + +int nand_is_bad_block(const nand_geom *nand, int block) { + return 0; +} diff --git a/firmware/lib/flash_ts.c b/firmware/lib/flash_ts.c new file mode 100644 index 00000000..5e74460c --- /dev/null +++ b/firmware/lib/flash_ts.c @@ -0,0 +1,421 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* *** THIS CODE HAS NOT BEEN SECURITY REVIEWED *** + * It lives in the firmware directory because that's where it needs to go + * eventually, but at the moment it is used only by usermode tools. + * Security review must be completed before this code is used in the + * firmware. + * See issue 246680 + */ + +#include "flash_ts.h" + +#include "errno.h" +#include "stdio.h" +#include "string.h" +#include "utility.h" + +// These match the linux driver +#define FLASH_TS_MAGIC 0x53542a46 + +#define FLASH_TS_HEADER_SIZE 24 +#define FLASH_TS_MAX_SIZE 16384 +#define FLASH_TS_MAX_ELEMENT_SIZE (FLASH_TS_MAX_SIZE - FLASH_TS_HEADER_SIZE) + +typedef struct { + uint32_t magic; + uint32_t crc; + uint32_t length; + uint32_t version; + char data[FLASH_TS_MAX_ELEMENT_SIZE]; +} __attribute__((packed)) flash_ts; + +typedef struct { + size_t start_block; // Partition start offset (in erase blocks) + size_t end_block; // Partition end offset (in erase blocks) + size_t chunk_size; // Minimum element size + size_t pages_per_block, chunks_per_block, pages_per_chunk; + nand_geom nand; + + size_t cached_block; + size_t current_block; + + flash_ts current; + flash_ts temp; +} flash_ts_state; + + +static flash_ts_state state; + +size_t pow2(size_t x) { + size_t v = 1; + while (v < x) + v <<= 1; + return v; +} + +static inline uint32_t flash_ts_crc(const flash_ts *cache) +{ + const unsigned char *p; + uint32_t crc = 0; + size_t len; + + /* skip magic and crc fields */ + len = cache->length + 2 * sizeof(uint32_t); + p = (const unsigned char*)&cache->length; + + while (len--) { + int i; + + crc ^= *p++; + for (i = 0; i < 8; i++) + crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0); + } + return crc ^ ~0; +} + +static inline int flash_ts_check_crc(const flash_ts *ts) { + return ts->crc == flash_ts_crc(ts); +} + +static int is_blank(const void *ptr, size_t sz) { + const unsigned char *p = (const unsigned char*)ptr; + const unsigned char *end = p + sz; + while (p < end) + if (*p++ != 0xff) + return 0; + return 1; +} + +static int is_pow2(size_t v) { + return v && (v & (v - 1)) == 0; +} + +/* Scan the entire partition to find the latest version */ +static void flash_ts_scan_partition(flash_ts_state *ts) { + size_t block; + + for (block = ts->start_block; block < ts->end_block; block++) { + if (!nand_is_bad_block(&ts->nand, block)) { + size_t chunk; + size_t page_base = block * ts->pages_per_block; + + for (chunk = 0; chunk < ts->chunks_per_block; + chunk++, page_base += ts->pages_per_chunk) { + if (nand_read_page(&ts->nand, page_base, + &ts->temp, sizeof(ts->temp))) { + continue; + } + if (ts->temp.magic != FLASH_TS_MAGIC || + ts->temp.version <= ts->current.version || + !flash_ts_check_crc(&ts->temp)) { + if (is_blank(&ts->temp, sizeof(ts->temp))) { + // Since we only write sequentially, a blank chunk means no more + // data in this block. + break; + } + continue; + } + + // It's good & newer than our current version + ts->current_block = block; + Memcpy(&ts->current, &ts->temp, sizeof(ts->current)); + } + } + } +} + +static char *flash_ts_search(flash_ts *ts, const char *key) { + char *str = &ts->data[0]; + size_t keylen = strlen(key); + + while(*str && str + keylen < &ts->data[ts->length]) { + // Format: name=value\0name2=value2\0 ... keyn=valuen\0\0 + if (!Memcmp(str, key, keylen) && str[keylen] == '=') { + return &str[keylen + 1]; + } else { + str += strlen(str) + 1; // Skip to next key + } + } + return NULL; +} + +static int flash_ts_find_writeable_chunk(flash_ts_state *ts, uint32_t block) { + uint32_t page_base = block * ts->pages_per_block; + uint32_t page_end = (block + 1) * ts->pages_per_block; + + for(; page_base < page_end; page_base += ts->pages_per_chunk) { + if(!nand_read_page(&ts->nand, page_base, + &ts->temp, sizeof(ts->temp))) { + if (is_blank(&ts->temp, sizeof(ts->temp))) + return page_base; + } + } + + return -1; +} + +static int in_range(const flash_ts_state *ts, uint32_t block) { + return block >= ts->start_block && block < ts->end_block; +} + +static int flash_try_write(flash_ts_state *ts, uint32_t page) { + return nand_write_page(&ts->nand, page, &ts->current, sizeof(ts->current)) || + nand_read_page(&ts->nand, page, &ts->temp, sizeof(ts->temp)) || + Memcmp(&ts->current, &ts->temp, sizeof(ts->current)); +} + + +static int flash_ts_find_writeable_spot(flash_ts_state *ts, + uint32_t *page_ofs) { + uint32_t block; + if (in_range(ts, ts->cached_block)) { + // We have a starting position to scan from + block = ts->cached_block; + } else { + block = ts->start_block; + VBDEBUG(("Cached block not in range - starting from %u\n", block)); + } + for (; block < ts->end_block; block++) { + int chunk; + if (nand_is_bad_block(&ts->nand, block)) { + VBDEBUG(("Skipping bad block %u\n", block)); + continue; + } + + chunk = flash_ts_find_writeable_chunk(ts, block); + if (chunk < 0) { + VBDEBUG(("No free chunks in block %u\n", block)); + continue; + } + + VBDEBUG(("Free chunk %d in block %u\n", chunk, block)); + *page_ofs = chunk; + ts->cached_block = block; + return 0; + } + return -1; +} + +static int flash_try_erase(flash_ts_state *ts, int block) { + return nand_is_bad_block(&ts->nand, block) || + nand_erase_block(&ts->nand, block); +} + +static int flash_erase_any_block(flash_ts_state *ts, uint32_t hint) { + uint32_t block; + for (block = hint; block < ts->end_block; block++) { + if (!flash_try_erase(ts, block)) { + ts->cached_block = block; + VBDEBUG(("Erased block %u\n", block)); + return 0; + } + } + + if (hint > ts->end_block) + hint = ts->end_block; + + for (block = ts->start_block; block < hint; block++) { + if (!flash_try_erase(ts, block)) { + ts->cached_block = block; + VBDEBUG(("Erased block %u\n", block)); + return 0; + } + } + return -1; +} + +static int flash_ts_write(flash_ts_state *ts) { + int passes = 3; + uint32_t page; + + + ts->cached_block = ts->current_block; + ts->current.version++; + ts->current.crc = flash_ts_crc(&ts->current); + VBDEBUG(("flash_ts_write() - %u bytes, crc %08X\n", + ts->current.length, ts->current.crc)); + + while(passes--) { + if (flash_ts_find_writeable_spot(ts, &page)) { + if (ts->cached_block == ts->end_block) { + uint32_t block; + + // Partition full! + // Erase a block to get some space + if (in_range(ts, ts->current_block) && + ts->current_block != ts->end_block - 1) { + // We don't want to overwrite our good copy if we can avoid it. + block = ts->current_block + 1; + } else { + block = ts->start_block; + } + VBDEBUG(("Partition full - begin erasing from block %u\n", block)); + + // Erase block, and try again. + if (flash_erase_any_block(ts, block)) { + // Failed to erase anything, so abort. + VBDEBUG(("All erases failed, aborting\n")); + return -ENOMEM; + } + continue; + } else { + // Try again, re-scan everything. + ts->cached_block = ts->end_block; + continue; + } + } + + if (flash_try_write(ts, page)) { + // Write failure, or read-back failure, try again with the next block. + VBDEBUG(("Write failure, retry\n")); + ts->cached_block++; + continue; + } + + VBDEBUG(("Successfully written v%u @ %u\n", ts->current.version, page)); + ts->current_block = ts->cached_block; + return 0; + } + + VBDEBUG(("Out of tries\n")); + return -EAGAIN; +} + +// Set value, returns 0 on success +int flash_ts_set(const char *key, const char *value) { + flash_ts *ts = &state.current; + char *at; + size_t keylen = strlen(key); + size_t value_len = strlen(value); + + if (keylen == 0) { + VBDEBUG(("0-length key - illegal\n")); + return -1; + } + + if (strchr(key, '=')) { + VBDEBUG(("key contains '=' - illegal\n")); + return -1; + } + + Memcpy(&state.temp, &state.current, sizeof(state.temp)); + + at = flash_ts_search(ts, key); + if (at) { + size_t old_value_len; + + // Already exists + if (!strcmp(at, value)) { + // No change + VBDEBUG(("Values are the same, not writing\n")); + return 0; + } + + old_value_len = strlen(at); + if (value_len == old_value_len) { + // Overwrite it + Memcpy(at, value, value_len); + VBDEBUG(("Values are the same length, overwrite\n")); + } else { + // Remove it + // if value_len == 0, then we're done + // if value_len != old_value_len, then we do the append below + char *src = at - (keylen + 1); + char *end = &ts->data[ts->length]; + char *from = at + old_value_len + 1; + + VBDEBUG(("Delete old value\n")); + memmove(src, from, end - from); + ts->length -= (from-src); + ts->data[ts->length - 1] = '\0'; + at = NULL; // Enter the append branch below + } + } else if (value_len == 0) { + // Removing non-existent entry + return 0; + } + + if (!at && value_len > 0) { + // Append it + + if (ts->length + keylen + 1 + value_len + 1 > FLASH_TS_MAX_ELEMENT_SIZE) { + // Not enough space, restore previous + VBDEBUG(("Not enough space to write %d data bytes\n", value_len)); + Memcpy(&state.current, &state.temp, sizeof(state.temp)); + return -1; + } + + VBDEBUG(("Append new value\n")); + at = &ts->data[ts->length - 1]; + strcpy(at, key); + at[keylen] = '='; + strcpy(at + keylen + 1, value); + ts->length += keylen + 1 + value_len + 1; + ts->data[ts->length-1] = '\0'; + } + + return flash_ts_write(&state); +} + +void flash_ts_get(const char *key, char *value, unsigned int size) { + flash_ts_state *ts = &state; + const char *at; + + at = flash_ts_search(&ts->current, key); + if (at) { + strncpy(value, at, size); + } else { + *value = '\0'; + } +} + +int flash_ts_init(unsigned int start_block, unsigned int blocks, + unsigned int szofpg, unsigned int szofblk, + unsigned int szofsector, void *user) { + flash_ts_state *ts = &state; + + if (!is_pow2(szofpg) || !is_pow2(szofblk) || !is_pow2(szofsector) || + szofsector > szofpg || szofpg > szofblk || blocks == 0) + return -ENODEV; + + Memset(ts, 0, sizeof(*ts)); + + // Page <= chunk <= block + // Page is minimum writable unit + // Chunk is actual write unit + // Block is erase unit + ts->start_block = start_block; + ts->end_block = start_block + blocks; + ts->pages_per_block = szofblk / szofpg; + + ts->nand.user = user; + ts->nand.szofpg = szofpg; + ts->nand.szofblk = szofblk; + ts->nand.szofsector = szofsector; + + // Calculate our write size, this mirrors the linux driver's logic + ts->chunk_size = pow2((sizeof(flash_ts) + szofpg - 1) & ~(szofpg - 1)); + if (!is_pow2(ts->chunk_size)) + return -ENODEV; + + ts->pages_per_chunk = ts->chunk_size / szofpg; + if (ts->pages_per_chunk == 0 || ts->chunk_size > szofblk) + return -ENODEV; + + ts->chunks_per_block = szofblk / ts->chunk_size; + + ts->current.version = 0; + ts->current.length = 1; + ts->current.magic = FLASH_TS_MAGIC; + ts->current.crc = flash_ts_crc(&ts->current); + ts->current.data[0] = '\0'; + ts->current_block = ts->end_block; + + flash_ts_scan_partition(ts); + + return 0; +} diff --git a/firmware/lib/include/flash_ts.h b/firmware/lib/include/flash_ts.h new file mode 100644 index 00000000..e5771bb1 --- /dev/null +++ b/firmware/lib/include/flash_ts.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef _FLASH_TS_H +#define _FLASH_TS_H + +typedef struct { + unsigned int szofpg; /* Erase unit */ + unsigned int szofblk; /* Write unit */ + unsigned int szofsector; /* Sector size used by the rest of cgpt */ + void *user; +} nand_geom; + +int flash_ts_init(unsigned int start_block, unsigned int blocks, + unsigned int szofpg, unsigned int szofblk, + unsigned int szofsector, void *user); + +/* Get/set value, returns 0 on success */ +int flash_ts_set(const char *key, const char *value); +void flash_ts_get(const char *key, char *value, unsigned int size); + +/* Get value as an integer, if missing/invalid return 'default_value' */ +int flash_ts_get_int(const char *key, int default_value); + + +/* These must be implemented outside the driver. */ +int nand_read_page(const nand_geom *nand, int page, void *buf, int size); +int nand_write_page(const nand_geom *nand, int page, const void *buf, int size); +int nand_erase_block(const nand_geom *nand, int block); +int nand_is_bad_block(const nand_geom *nand, int block); + + + +#endif /* _FLASH_TS_H */ diff --git a/tests/cgptlib_test.c b/tests/cgptlib_test.c index 9e446c26..ba9cd05b 100644 --- a/tests/cgptlib_test.c +++ b/tests/cgptlib_test.c @@ -5,13 +5,17 @@ #include <string.h> +#include "../cgpt/cgpt.h" #include "cgptlib_internal.h" #include "cgptlib_test.h" #include "crc32.h" #include "crc32_test.h" +#include "errno.h" +#include "flash_ts.h" #include "gpt.h" #include "mtdlib.h" #include "test_common.h" +#define _STUB_IMPLEMENTATION_ #include "utility.h" /* @@ -46,6 +50,15 @@ static const Guid guid_zero = {{{0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0}}}}; static const Guid guid_kernel = GPT_ENT_TYPE_CHROMEOS_KERNEL; static const Guid guid_rootfs = GPT_ENT_TYPE_CHROMEOS_ROOTFS; +// cgpt_common.c requires these be defined if linked in. +const char *progname = "CGPT-TEST"; +const char *command = "TEST"; + +// Ramdisk for flash ts testing. +static uint8_t *nand_drive = NULL; +static uint32_t nand_drive_sz; +static uint8_t *nand_bad_block_map = NULL; + /* * Copy a random-for-this-program-only Guid into the dest. The num parameter * completely determines the Guid. @@ -1747,6 +1760,177 @@ static int ErrorTextTest(void) return TEST_OK; } +int nand_read_page(const nand_geom *nand, int page, void *buf, int size) { + uint32_t ofs = page * nand->szofpg; + uint32_t sz = size; + if (ofs + sz > nand_drive_sz) { + return -1; + } + Memcpy(buf, nand_drive + ofs, sz); + return 0; +} + +int nand_write_page(const nand_geom *nand, int page, + const void *buf, int size) { + uint32_t ofs = page * nand->szofpg; + uint32_t sz = size; + uint32_t i; + if (ofs + sz > nand_drive_sz) { + return -1; + } + for (i = 0; i < sz; i++) { + if (nand_drive[ofs + i] != 0xff) { + return -1; + } + } + Memcpy(nand_drive + ofs, buf, sz); + return 0; +} + +int nand_erase_block(const nand_geom *nand, int block) { + uint32_t ofs = block * nand->szofblk; + uint32_t sz = nand->szofblk; + if (ofs + sz > nand_drive_sz) { + return -1; + } + if (!--nand_bad_block_map[block]) { + return -1; + } + Memset(nand_drive + ofs, 0xFF, sz); + return 0; +} + +int nand_is_bad_block(const nand_geom *nand, int block) { + return nand_bad_block_map[block] == 0; +} + + +static void nand_make_ramdisk() { + if (nand_drive) { + free(nand_drive); + } + if (nand_bad_block_map) { + free(nand_bad_block_map); + } + nand_drive_sz = 1024 * 1024 * 16; + nand_drive = (uint8_t *)malloc(nand_drive_sz); + nand_bad_block_map = (uint8_t *)malloc(nand_drive_sz / 512); + Memset(nand_drive, 0xff, nand_drive_sz); + Memset(nand_bad_block_map, 0xff, nand_drive_sz / 512); +} + +static int MtdFtsTest() { + int MtdLoad(struct drive *drive, int sector_bytes); + int MtdSave(struct drive *drive); + int FlashGet(const char *key, uint8_t *data, uint32_t *bufsz); + int FlashSet(const char *key, const uint8_t *data, uint32_t bufsz); + + int i, j, err; + + struct { + int result; + unsigned int offset, size, block_size_bytes, page_size_bytes; + } cases[] = { + { 0, 1, 2, 1024 * 1024, 1024 * 4 }, + { 0, 1, 2, 1024 * 1024, 1024 * 16 }, + + /* Failure cases, non-power-of-2 */ + { -ENODEV, 1, 2, 5000000, 1024 * 16 }, + { -ENODEV, 1, 2, 1024 * 1024, 65535 }, + + /* Page > block */ + { -ENODEV, 1, 2, 1024 * 16, 1024 * 1024 }, + }; + + + /* Check if the FTS store works */ + for (i = 0; i < ARRAY_SIZE(cases); i++) { + nand_make_ramdisk(); + EXPECT(cases[i].result == flash_ts_init(cases[i].offset, cases[i].size, + cases[i].page_size_bytes, + cases[i].block_size_bytes, 512, 0)); + + if (cases[i].result == 0) { + /* We should have a working FTS store now */ + char buffer[64]; + uint8_t blob[256], blob_read[256]; + uint32_t sz = sizeof(blob_read); + struct drive drive; + + /* Test the low level API */ + EXPECT(0 == flash_ts_set("some_key", "some value")); + flash_ts_get("some_key", buffer, sizeof(buffer)); + EXPECT(0 == strcmp(buffer, "some value")); + + /* Check overwrite */ + EXPECT(0 == flash_ts_set("some_key", "some other value")); + flash_ts_get("some_key", buffer, sizeof(buffer)); + EXPECT(0 == strcmp(buffer, "some other value")); + + /* Check delete */ + EXPECT(0 == flash_ts_set("some_key", "")); + + /* Verify that re-initialization pulls the right record. */ + flash_ts_init(cases[i].offset, cases[i].size, cases[i].page_size_bytes, + cases[i].block_size_bytes, 512, 0); + flash_ts_get("some_key", buffer, sizeof(buffer)); + EXPECT(0 == strcmp(buffer, "")); + + /* Fill up the disk, eating all erase cycles */ + for (j = 0; j < nand_drive_sz / 512; j++) { + nand_bad_block_map[j] = 2; + } + for (j = 0; j < 999999; j++) { + char str[32]; + sprintf(str, "%d", j); + err = flash_ts_set("some_new_key", str); + if (err) { + EXPECT(err == -ENOMEM); + break; + } + + /* Make sure we can figure out where the latest is. */ + flash_ts_init(cases[i].offset, cases[i].size, cases[i].page_size_bytes, + cases[i].block_size_bytes, 512, 0); + flash_ts_get("some_new_key", buffer, sizeof(buffer)); + EXPECT(0 == strcmp(buffer, str)); + } + EXPECT(j < 999999); + + /* We need our drive back. */ + nand_make_ramdisk(); + flash_ts_init(cases[i].offset, cases[i].size, cases[i].page_size_bytes, + cases[i].block_size_bytes, 512, 0); + + + for (j = 0; j < 256; j++) { + blob[j] = j; + } + + /* Hex conversion / blob storage */ + EXPECT(0 == FlashSet("some_blob", blob, sizeof(blob))); + EXPECT(0 == FlashGet("some_blob", blob_read, &sz)); + EXPECT(sz == sizeof(blob_read)); + EXPECT(0 == Memcmp(blob, blob_read, sizeof(blob))); + + BuildTestMtdData(&drive.mtd); + drive.mtd.flash_block_bytes = cases[i].block_size_bytes; + drive.mtd.flash_page_bytes = cases[i].page_size_bytes; + drive.mtd.fts_block_offset = cases[i].offset; + drive.mtd.fts_block_size = cases[i].size; + drive.mtd.sector_bytes = 512; + drive.mtd.drive_sectors = nand_drive_sz / 512; + + /* MTD-level API */ + EXPECT(0 == MtdSave(&drive)); + Memset(&drive.mtd.primary, 0, sizeof(drive.mtd.primary)); + EXPECT(0 == MtdLoad(&drive, 512)); + } + } + + return TEST_OK; +} + int main(int argc, char *argv[]) { int i; @@ -1793,6 +1977,7 @@ int main(int argc, char *argv[]) { TEST_CASE(TestCrc32TestVectors), }, { TEST_CASE(GetKernelGuidTest), }, { TEST_CASE(ErrorTextTest), }, + { TEST_CASE(MtdFtsTest), }, }; for (i = 0; i < sizeof(test_cases)/sizeof(test_cases[0]); ++i) { |