summaryrefslogtreecommitdiffstats
path: root/flashrom.c
diff options
context:
space:
mode:
Diffstat (limited to 'flashrom.c')
-rw-r--r--flashrom.c350
1 files changed, 350 insertions, 0 deletions
diff --git a/flashrom.c b/flashrom.c
index 25e53f20c..273eb9971 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -5,6 +5,8 @@
* Copyright (C) 2004 Tyan Corp <yhlu@tyan.com>
* Copyright (C) 2005-2008 coresystems GmbH
* Copyright (C) 2008,2009 Carl-Daniel Hailfinger
+ * Copyright (C) 2016 secunet Security Networks AG
+ * (Written by Nico Huber <nico.huber@secunet.com> for secunet)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1245,6 +1247,14 @@ notfound:
if (!flash->chip)
return -1;
+ /* Fill fallback layout covering the whole chip. */
+ struct single_layout *const fallback = &flash->fallback_layout;
+ fallback->base.entries = &fallback->entry;
+ fallback->base.num_entries = 1;
+ fallback->entry.start = 0;
+ fallback->entry.end = flash->chip->total_size * 1024 - 1;
+ fallback->entry.included = true;
+ strcpy(fallback->entry.name, "complete flash");
tmp = flashbuses_to_text(flash->chip->bustype);
msg_cinfo("%s %s flash chip \"%s\" (%d kB, %s) ", force ? "Assuming" : "Found",
@@ -1640,6 +1650,346 @@ int erase_and_write_flash(struct flashctx *flash, uint8_t *oldcontents, uint8_t
return ret;
}
+static const struct flashrom_layout *get_layout(const struct flashctx *const flashctx)
+{
+ if (flashctx->layout && flashctx->layout->num_entries)
+ return flashctx->layout;
+ else
+ return &flashctx->fallback_layout.base;
+}
+
+/**
+ * @brief Reads the included layout regions into a buffer.
+ *
+ * If there is no layout set in the given flash context, the whole chip will
+ * be read.
+ *
+ * @param flashctx Flash context to be used.
+ * @param buffer Buffer of full chip size to read into.
+ * @return 0 on success,
+ * 1 if any read fails.
+ */
+static int read_by_layout(struct flashctx *const flashctx, uint8_t *const buffer)
+{
+ const struct flashrom_layout *const layout = get_layout(flashctx);
+
+ size_t i;
+ for (i = 0; i < layout->num_entries; ++i) {
+ if (!layout->entries[i].included)
+ continue;
+
+ const chipoff_t region_start = layout->entries[i].start;
+ const chipsize_t region_len = layout->entries[i].end - layout->entries[i].start + 1;
+
+ if (flashctx->chip->read(flashctx, buffer + region_start, region_start, region_len))
+ return 1;
+ }
+ return 0;
+}
+
+typedef int (*erasefn_t)(struct flashctx *, unsigned int addr, unsigned int len);
+/**
+ * @private
+ *
+ * For read-erase-write, `curcontents` and `newcontents` shall point
+ * to buffers of the chip's size. Both are supposed to be prefilled
+ * with at least the included layout regions of the current flash
+ * contents (`curcontents`) and the data to be written to the flash
+ * (`newcontents`).
+ *
+ * For erase, `curcontents` and `newcontents` shall be NULL-pointers.
+ *
+ * The `chipoff_t` values are used internally by `walk_by_layout()`.
+ */
+struct walk_info {
+ uint8_t *curcontents;
+ const uint8_t *newcontents;
+ chipoff_t region_start;
+ chipoff_t region_end;
+ chipoff_t erase_start;
+ chipoff_t erase_end;
+};
+/* returns 0 on success, 1 to retry with another erase function, 2 for immediate abort */
+typedef int (*per_blockfn_t)(struct flashctx *, const struct walk_info *, erasefn_t);
+
+static int walk_eraseblocks(struct flashctx *const flashctx,
+ struct walk_info *const info,
+ const size_t erasefunction, const per_blockfn_t per_blockfn)
+{
+ int ret;
+ size_t i, j;
+ bool first = true;
+ struct block_eraser *const eraser = &flashctx->chip->block_erasers[erasefunction];
+
+ info->erase_start = 0;
+ for (i = 0; i < NUM_ERASEREGIONS; ++i) {
+ /* count==0 for all automatically initialized array
+ members so the loop below won't be executed for them. */
+ for (j = 0; j < eraser->eraseblocks[i].count; ++j, info->erase_start = info->erase_end + 1) {
+ info->erase_end = info->erase_start + eraser->eraseblocks[i].size - 1;
+
+ /* Skip any eraseblock that is completely outside the current region. */
+ if (info->erase_end < info->region_start)
+ continue;
+ if (info->region_end < info->erase_start)
+ break;
+
+ /* Print this for every block except the first one. */
+ if (first)
+ first = false;
+ else
+ msg_cdbg(", ");
+ msg_cdbg("0x%06x-0x%06x:", info->erase_start, info->erase_end);
+
+ ret = per_blockfn(flashctx, info, eraser->block_erase);
+ if (ret)
+ return ret;
+ }
+ if (info->region_end < info->erase_start)
+ break;
+ }
+ msg_cdbg("\n");
+ return 0;
+}
+
+static int walk_by_layout(struct flashctx *const flashctx, struct walk_info *const info,
+ const per_blockfn_t per_blockfn)
+{
+ const struct flashrom_layout *const layout = get_layout(flashctx);
+
+ all_skipped = true;
+ msg_cinfo("Erasing and writing flash chip... ");
+
+ size_t i;
+ for (i = 0; i < layout->num_entries; ++i) {
+ if (!layout->entries[i].included)
+ continue;
+
+ info->region_start = layout->entries[i].start;
+ info->region_end = layout->entries[i].end;
+
+ size_t j;
+ int error = 1; /* retry as long as it's 1 */
+ for (j = 0; j < NUM_ERASEFUNCTIONS; ++j) {
+ if (j != 0)
+ msg_cinfo("Looking for another erase function.\n");
+ msg_cdbg("Trying erase function %zi... ", j);
+ if (check_block_eraser(flashctx, j, 1))
+ continue;
+
+ error = walk_eraseblocks(flashctx, info, j, per_blockfn);
+ if (error != 1)
+ break;
+
+ if (info->curcontents) {
+ msg_cinfo("Reading current flash chip contents... ");
+ if (read_by_layout(flashctx, info->curcontents)) {
+ /* Now we are truly screwed. Read failed as well. */
+ msg_cerr("Can't read anymore! Aborting.\n");
+ /* We have no idea about the flash chip contents, so
+ retrying with another erase function is pointless. */
+ error = 2;
+ break;
+ }
+ msg_cinfo("done. ");
+ }
+ }
+ if (error == 1)
+ msg_cinfo("No usable erase functions left.\n");
+ if (error) {
+ msg_cerr("FAILED!\n");
+ return 1;
+ }
+ }
+ if (all_skipped)
+ msg_cinfo("\nWarning: Chip content is identical to the requested image.\n");
+ msg_cinfo("Erase/write done.\n");
+ return 0;
+}
+
+static int erase_block(struct flashctx *const flashctx,
+ const struct walk_info *const info, const erasefn_t erasefn)
+{
+ const unsigned int erase_len = info->erase_end + 1 - info->erase_start;
+
+ all_skipped = false;
+
+ msg_cdbg("E");
+ if (erasefn(flashctx, info->erase_start, erase_len))
+ return 1;
+ if (check_erased_range(flashctx, info->erase_start, erase_len)) {
+ msg_cerr("ERASE FAILED!\n");
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * @brief Erases the included layout regions.
+ *
+ * If there is no layout set in the given flash context, the whole chip will
+ * be erased.
+ *
+ * @param flashctx Flash context to be used.
+ * @param buffer Buffer of full chip size to read into.
+ * @return 0 on success,
+ * 1 if all available erase functions failed.
+ */
+int erase_by_layout(struct flashctx *const flashctx)
+{
+ struct walk_info info = { 0 };
+ return walk_by_layout(flashctx, &info, &erase_block);
+}
+
+static int read_erase_write_block(struct flashctx *const flashctx,
+ const struct walk_info *const info, const erasefn_t erasefn)
+{
+ const chipsize_t erase_len = info->erase_end + 1 - info->erase_start;
+ const bool region_unaligned = info->region_start > info->erase_start ||
+ info->erase_end > info->region_end;
+ const uint8_t *newcontents = NULL;
+ int ret = 2;
+
+ /*
+ * If the region is not erase-block aligned, merge current flash con-
+ * tents into `info->curcontents` and a new buffer `newc`. The former
+ * is necessary since we have no guarantee that the full erase block
+ * was already read into `info->curcontents`. For the latter a new
+ * buffer is used since `info->newcontents` might contain data for
+ * other unaligned regions that touch this erase block too.
+ */
+ if (region_unaligned) {
+ msg_cdbg("R");
+ uint8_t *const newc = malloc(erase_len);
+ if (!newc) {
+ msg_cerr("Out of memory!\n");
+ return 1;
+ }
+ memcpy(newc, info->newcontents + info->erase_start, erase_len);
+
+ /* Merge data preceding the current region. */
+ if (info->region_start > info->erase_start) {
+ const chipoff_t start = info->erase_start;
+ const chipsize_t len = info->region_start - info->erase_start;
+ if (flashctx->chip->read(flashctx, newc, start, len)) {
+ msg_cerr("Can't read! Aborting.\n");
+ goto _free_ret;
+ }
+ memcpy(info->curcontents + start, newc, len);
+ }
+ /* Merge data following the current region. */
+ if (info->erase_end > info->region_end) {
+ const chipoff_t start = info->region_end + 1;
+ const chipoff_t rel_start = start - info->erase_start; /* within this erase block */
+ const chipsize_t len = info->erase_end - info->region_end;
+ if (flashctx->chip->read(flashctx, newc + rel_start, start, len)) {
+ msg_cerr("Can't read! Aborting.\n");
+ goto _free_ret;
+ }
+ memcpy(info->curcontents + start, newc + rel_start, len);
+ }
+
+ newcontents = newc;
+ } else {
+ newcontents = info->newcontents + info->erase_start;
+ }
+
+ ret = 1;
+ bool skipped = true;
+ uint8_t *const curcontents = info->curcontents + info->erase_start;
+ if (need_erase(curcontents, newcontents, erase_len, flashctx->chip->gran)) {
+ if (erase_block(flashctx, info, erasefn))
+ goto _free_ret;
+ /* Erase was successful. Adjust curcontents. */
+ memset(curcontents, 0xff, erase_len);
+ skipped = false;
+ }
+
+ unsigned int starthere = 0, lenhere = 0, writecount = 0;
+ /* get_next_write() sets starthere to a new value after the call. */
+ while ((lenhere = get_next_write(curcontents + starthere, newcontents + starthere,
+ erase_len - starthere, &starthere, flashctx->chip->gran))) {
+ if (!writecount++)
+ msg_cdbg("W");
+ /* Needs the partial write function signature. */
+ if (flashctx->chip->write(flashctx, newcontents + starthere,
+ info->erase_start + starthere, lenhere))
+ goto _free_ret;
+ starthere += lenhere;
+ skipped = false;
+ }
+ if (skipped)
+ msg_cdbg("S");
+ else
+ all_skipped = false;
+
+ /* Update curcontents, other regions with overlapping erase blocks
+ might rely on this. */
+ memcpy(curcontents, newcontents, erase_len);
+ ret = 0;
+
+_free_ret:
+ if (region_unaligned)
+ free((void *)newcontents);
+ return ret;
+}
+
+/**
+ * @brief Writes the included layout regions from a given image.
+ *
+ * If there is no layout set in the given flash context, the whole image
+ * will be written.
+ *
+ * @param flashctx Flash context to be used.
+ * @param curcontents A buffer of full chip size with current chip contents of included regions.
+ * @param newcontents The new image to be written.
+ * @return 0 on success,
+ * 1 if anything has gone wrong.
+ */
+int write_by_layout(struct flashctx *const flashctx,
+ void *const curcontents, const void *const newcontents)
+{
+ struct walk_info info;
+ info.curcontents = curcontents;
+ info.newcontents = newcontents;
+ return walk_by_layout(flashctx, &info, read_erase_write_block);
+}
+
+/**
+ * @brief Compares the included layout regions with content from a buffer.
+ *
+ * If there is no layout set in the given flash context, the whole chip's
+ * contents will be compared.
+ *
+ * @param flashctx Flash context to be used.
+ * @param curcontents A buffer of full chip size to read current chip contents into.
+ * @param newcontents The new image to compare to.
+ * @return 0 on success,
+ * 1 if reading failed,
+ * 3 if the contents don't match.
+ */
+int verify_by_layout(struct flashctx *const flashctx,
+ void *const curcontents, const uint8_t *const newcontents)
+{
+ const struct flashrom_layout *const layout = get_layout(flashctx);
+
+ size_t i;
+ for (i = 0; i < layout->num_entries; ++i) {
+ if (!layout->entries[i].included)
+ continue;
+
+ const chipoff_t region_start = layout->entries[i].start;
+ const chipsize_t region_len = layout->entries[i].end - layout->entries[i].start + 1;
+
+ if (flashctx->chip->read(flashctx, curcontents + region_start, region_start, region_len))
+ return 1;
+ if (compare_range(newcontents + region_start, curcontents + region_start,
+ region_start, region_len))
+ return 3;
+ }
+ return 0;
+}
+
static void nonfatal_help_message(void)
{
msg_gerr("Good, writing to the flash chip apparently didn't do anything.\n");