diff options
-rw-r--r-- | libflashrom.c | 73 | ||||
-rw-r--r-- | libflashrom.h | 6 | ||||
-rw-r--r-- | writeprotect.c | 193 | ||||
-rw-r--r-- | writeprotect.h | 9 |
4 files changed, 281 insertions, 0 deletions
diff --git a/libflashrom.c b/libflashrom.c index f0b9cfda..f2288fef 100644 --- a/libflashrom.c +++ b/libflashrom.c @@ -747,4 +747,77 @@ enum flashrom_wp_result flashrom_wp_read_cfg(struct flashrom_wp_cfg *cfg, struct 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 6d02d50f..c9138da2 100644 --- a/libflashrom.h +++ b/libflashrom.h @@ -137,6 +137,7 @@ enum flashrom_wp_mode { 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 *); @@ -148,4 +149,9 @@ void flashrom_wp_get_range(size_t *start, size_t *len, const struct flashrom_wp_ 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 ca9d9ecf..b619a242 100644 --- a/writeprotect.c +++ b/writeprotect.c @@ -158,6 +158,162 @@ static enum flashrom_wp_result get_wp_range(struct wp_range *range, struct flash 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 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 (int 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; + /* + * 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 (size_t 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)); + + for (size_t 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 (size_t 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 (size_t 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 (size_t 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); @@ -218,4 +374,41 @@ enum flashrom_wp_result wp_write_cfg(struct flashctx *flash, const struct flashr 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; + + 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 (size_t 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 d54befad..e27403dc 100644 --- a/writeprotect.h +++ b/writeprotect.h @@ -37,6 +37,12 @@ struct flashrom_wp_cfg { 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 @@ enum flashrom_wp_result wp_write_cfg(struct flashrom_flashctx *, const struct fl /* 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__ */ |