summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--cgpt/cgpt.h49
-rw-r--r--cgpt/cgpt_common.c315
-rw-r--r--cgpt/flash_ts_drv.c76
-rw-r--r--firmware/lib/flash_ts.c421
-rw-r--r--firmware/lib/include/flash_ts.h35
-rw-r--r--tests/cgptlib_test.c185
7 files changed, 993 insertions, 96 deletions
diff --git a/Makefile b/Makefile
index 13430167..aa615233 100644
--- a/Makefile
+++ b/Makefile
@@ -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, &sector_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) {