libflashrom,writeprotect: add flashrom_wp_get_available_ranges()
Generate list of available ranges by enumerating all possible values
that range bits (BPx, TB, ...) can take and using the chip's range
decoding function to get the range that is selected by each one.
Tested: flashrom --wp-list
Change-Id: Id51f038f03305c8536d80313e52f77d27835f34d
Signed-off-by: Nikolai Artemiev <nartemiev@google.com>
Original-Reviewed-on: https://review.coreboot.org/c/flashrom/+/58481
Original-Reviewed-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/flashrom-stable/+/70971
Reviewed-by: Nico Huber <nico.h@gmx.de>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/libflashrom.c b/libflashrom.c
index 119f81f..41abb35 100644
--- a/libflashrom.c
+++ b/libflashrom.c
@@ -773,4 +773,77 @@
return FLASHROM_WP_ERR_OTHER;
}
+/**
+ * @brief Get a list of protection ranges supported by the flash chip.
+ *
+ * @param[out] ranges Points to a pointer of type struct flashrom_wp_ranges
+ * that will be set if available ranges are found. Finding
+ * available ranges may not always be possible, even if the
+ * chip's protection range can be read or modified. *ranges
+ * must be freed using @ref flashrom_wp_ranges_free.
+ * @param[in] flash The flash context used to access the chip.
+ * @return 0 on success
+ * >0 on failure
+ */
+enum flashrom_wp_result flashrom_wp_get_available_ranges(struct flashrom_wp_ranges **list, struct flashrom_flashctx *flash)
+{
+ /*
+ * TODO: Call custom implementation if the programmer is opaque, as
+ * direct WP operations require SPI access. We actually can't implement
+ * this in linux_mtd right now, but we should adopt a proper generic
+ * architechure to match the read and write functions anyway.
+ */
+ if (flash->mst->buses_supported & BUS_SPI)
+ return wp_get_available_ranges(list, flash);
+
+ return FLASHROM_WP_ERR_OTHER;
+}
+
+/**
+ * @brief Get a number of protection ranges in a range list.
+ *
+ * @param[in] ranges The range list to get the count from.
+ * @return Number of ranges in the list.
+ */
+size_t flashrom_wp_ranges_get_count(const struct flashrom_wp_ranges *list)
+{
+ return list->count;
+}
+
+/**
+ * @brief Get a protection range from a range list.
+ *
+ * @param[out] start Points to a size_t to write the range's start to.
+ * @param[out] len Points to a size_t to write the range's length to.
+ * @param[in] ranges The range list to get the range from.
+ * @param[in] index Index of the range to get.
+ * @return 0 on success
+ * >0 on failure
+ */
+enum flashrom_wp_result flashrom_wp_ranges_get_range(size_t *start, size_t *len, const struct flashrom_wp_ranges *list, unsigned int index)
+{
+ if (index >= list->count)
+ return FLASHROM_WP_ERR_OTHER;
+
+ *start = list->ranges[index].start;
+ *len = list->ranges[index].len;
+
+ return 0;
+}
+
+/**
+ * @brief Free a WP range list.
+ *
+ * @param[out] cfg Pointer to the flashrom_wp_ranges to free.
+ */
+void flashrom_wp_ranges_release(struct flashrom_wp_ranges *list)
+{
+ if (!list)
+ return;
+
+ free(list->ranges);
+ free(list);
+}
+
+
/** @} */ /* end flashrom-wp */
diff --git a/libflashrom.h b/libflashrom.h
index 2890d0b..1819861 100644
--- a/libflashrom.h
+++ b/libflashrom.h
@@ -135,6 +135,7 @@
FLASHROM_WP_MODE_PERMANENT
};
struct flashrom_wp_cfg;
+struct flashrom_wp_ranges;
enum flashrom_wp_result flashrom_wp_cfg_new(struct flashrom_wp_cfg **);
void flashrom_wp_cfg_release(struct flashrom_wp_cfg *);
@@ -146,4 +147,9 @@
enum flashrom_wp_result flashrom_wp_read_cfg(struct flashrom_wp_cfg *, struct flashrom_flashctx *);
enum flashrom_wp_result flashrom_wp_write_cfg(struct flashrom_flashctx *, const struct flashrom_wp_cfg *);
+enum flashrom_wp_result flashrom_wp_get_available_ranges(struct flashrom_wp_ranges **, struct flashrom_flashctx *);
+size_t flashrom_wp_ranges_get_count(const struct flashrom_wp_ranges *);
+enum flashrom_wp_result flashrom_wp_ranges_get_range(size_t *start, size_t *len, const struct flashrom_wp_ranges *, unsigned int index);
+void flashrom_wp_ranges_release(struct flashrom_wp_ranges *);
+
#endif /* !__LIBFLASHROM_H__ */
diff --git a/writeprotect.c b/writeprotect.c
index a42a798..483f9e9 100644
--- a/writeprotect.c
+++ b/writeprotect.c
@@ -159,6 +159,165 @@
return FLASHROM_WP_OK;
}
+/** Write protect bit values and the range they will activate. */
+struct wp_range_and_bits {
+ struct wp_bits bits;
+ struct wp_range range;
+};
+
+/**
+ * Comparator used for sorting ranges in get_ranges_and_wp_bits().
+ *
+ * Ranges are ordered by these attributes, in decreasing significance:
+ * (range length, range start, cmp bit, sec bit, tb bit, bp bits)
+ */
+static int compare_ranges(const void *aa, const void *bb)
+{
+ const struct wp_range_and_bits
+ *a = (const struct wp_range_and_bits *)aa,
+ *b = (const struct wp_range_and_bits *)bb;
+ int i;
+
+ int ord = 0;
+
+ if (ord == 0)
+ ord = a->range.len - b->range.len;
+
+ if (ord == 0)
+ ord = a->range.start - b->range.start;
+
+ if (ord == 0)
+ ord = a->bits.cmp - b->bits.cmp;
+
+ if (ord == 0)
+ ord = a->bits.sec - b->bits.sec;
+
+ if (ord == 0)
+ ord = a->bits.tb - b->bits.tb;
+
+ for (i = a->bits.bp_bit_count - 1; i >= 0; i--) {
+ if (ord == 0)
+ ord = a->bits.bp[i] - b->bits.bp[i];
+ }
+
+ return ord;
+}
+
+static bool can_write_bit(const struct reg_bit_info bit)
+{
+ /*
+ * TODO: check if the programmer supports writing the register that the
+ * bit is in. For example, some chipsets may only allow SR1 to be
+ * written.
+ */
+
+ return bit.reg != INVALID_REG && bit.writability == RW;
+}
+
+/**
+ * Enumerate all protection ranges that the chip supports and that are able to
+ * be activated, given limitations such as OTP bits or programmer-enforced
+ * restrictions. Returns a list of deduplicated wp_range_and_bits structures.
+ *
+ * Allocates a buffer that must be freed by the caller with free().
+ */
+static enum flashrom_wp_result get_ranges_and_wp_bits(struct flashctx *flash, struct wp_bits bits, struct wp_range_and_bits **ranges, size_t *count)
+{
+ const struct reg_bit_map *reg_bits = &flash->chip->reg_bits;
+ size_t i;
+ /*
+ * Create a list of bits that affect the chip's protection range in
+ * range_bits. Each element is a pointer to a member of the wp_bits
+ * structure that will be modified.
+ *
+ * Some chips have range bits that cannot be changed (e.g. MX25L6473E
+ * has a one-time programmable TB bit). Rather than enumerating all
+ * possible values for unwritable bits, just read their values from the
+ * chip to ensure we only enumerate ranges that are actually available.
+ */
+ uint8_t *range_bits[ARRAY_SIZE(bits.bp) + 1 /* TB */ + 1 /* SEC */ + 1 /* CMP */];
+ size_t bit_count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(bits.bp); i++) {
+ if (can_write_bit(reg_bits->bp[i]))
+ range_bits[bit_count++] = &bits.bp[i];
+ }
+
+ if (can_write_bit(reg_bits->tb))
+ range_bits[bit_count++] = &bits.tb;
+
+ if (can_write_bit(reg_bits->sec))
+ range_bits[bit_count++] = &bits.sec;
+
+ if (can_write_bit(reg_bits->cmp))
+ range_bits[bit_count++] = &bits.cmp;
+
+ /* Allocate output buffer */
+ *count = 1 << bit_count;
+ *ranges = calloc(*count, sizeof(struct wp_range_and_bits));
+
+ size_t range_index;
+ for (range_index = 0; range_index < *count; range_index++) {
+ /*
+ * Extract bits from the range index and assign them to members
+ * of the wp_bits structure. The loop bounds ensure that all
+ * bit combinations will be enumerated.
+ */
+ for (i = 0; i < bit_count; i++)
+ *range_bits[i] = (range_index >> i) & 1;
+
+ struct wp_range_and_bits *output = &(*ranges)[range_index];
+
+ output->bits = bits;
+ enum flashrom_wp_result ret = get_wp_range(&output->range, flash, &bits);
+ if (ret != FLASHROM_WP_OK) {
+ free(*ranges);
+ return ret;
+ }
+
+ /* Debug: print range bits and range */
+ msg_gspew("Enumerated range: ");
+ if (bits.cmp_bit_present)
+ msg_gspew("CMP=%u ", bits.cmp);
+ if (bits.sec_bit_present)
+ msg_gspew("SEC=%u ", bits.sec);
+ if (bits.tb_bit_present)
+ msg_gspew("TB=%u ", bits.tb);
+ for (i = 0; i < bits.bp_bit_count; i++) {
+ size_t j = bits.bp_bit_count - i - 1;
+ msg_gspew("BP%zu=%u ", j, bits.bp[j]);
+ }
+ msg_gspew(" start=0x%08zx length=0x%08zx ",
+ output->range.start, output->range.len);
+ }
+
+ /* Sort ranges. Ensures consistency if there are duplicate ranges. */
+ qsort(*ranges, *count, sizeof(struct wp_range_and_bits), compare_ranges);
+
+ /* Remove duplicates */
+ size_t output_index = 0;
+ struct wp_range *last_range = NULL;
+
+ for (i = 0; i < *count; i++) {
+ bool different_to_last =
+ (last_range == NULL) ||
+ ((*ranges)[i].range.start != last_range->start) ||
+ ((*ranges)[i].range.len != last_range->len);
+
+ if (different_to_last) {
+ /* Move range to the next free position */
+ (*ranges)[output_index] = (*ranges)[i];
+ output_index++;
+ /* Keep track of last non-duplicate range */
+ last_range = &(*ranges)[i].range;
+ }
+ }
+ /* Reduce count to only include non-duplicate ranges */
+ *count = output_index;
+
+ return FLASHROM_WP_OK;
+}
+
static bool chip_supported(struct flashctx *flash)
{
return (flash->chip != NULL) && (flash->chip->decode_range != NULL);
@@ -219,4 +378,42 @@
return ret;
}
+enum flashrom_wp_result wp_get_available_ranges(struct flashrom_wp_ranges **list, struct flashrom_flashctx *flash)
+{
+ struct wp_bits bits;
+ struct wp_range_and_bits *range_pairs = NULL;
+ size_t count;
+ size_t i;
+
+ if (!chip_supported(flash))
+ return FLASHROM_WP_ERR_CHIP_UNSUPPORTED;
+
+ enum flashrom_wp_result ret = read_wp_bits(&bits, flash);
+ if (ret != FLASHROM_WP_OK)
+ return ret;
+
+ ret = get_ranges_and_wp_bits(flash, bits, &range_pairs, &count);
+ if (ret != FLASHROM_WP_OK)
+ return ret;
+
+ *list = calloc(1, sizeof(struct flashrom_wp_ranges));
+ struct wp_range *ranges = calloc(count, sizeof(struct wp_range));
+
+ if (!(*list) || !ranges) {
+ free(*list);
+ free(ranges);
+ ret = FLASHROM_WP_ERR_OTHER;
+ goto out;
+ }
+ (*list)->count = count;
+ (*list)->ranges = ranges;
+
+ for (i = 0; i < count; i++)
+ ranges[i] = range_pairs[i].range;
+
+out:
+ free(range_pairs);
+ return ret;
+}
+
/** @} */ /* end flashrom-wp */
diff --git a/writeprotect.h b/writeprotect.h
index d54befa..e27403d 100644
--- a/writeprotect.h
+++ b/writeprotect.h
@@ -37,6 +37,12 @@
struct wp_range range;
};
+/* Collection of multiple write protection ranges. */
+struct flashrom_wp_ranges {
+ struct wp_range *ranges;
+ size_t count;
+};
+
/*
* Description of a chip's write protection configuration.
*
@@ -77,4 +83,7 @@
/* Read WP configuration from the chip */
enum flashrom_wp_result wp_read_cfg(struct flashrom_wp_cfg *, struct flashrom_flashctx *);
+/* Get a list of protection ranges supported by the chip */
+enum flashrom_wp_result wp_get_available_ranges(struct flashrom_wp_ranges **, struct flashrom_flashctx *);
+
#endif /* !__WRITEPROTECT_H__ */