| /* Copyright 2018 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. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdbool.h> |
| |
| #include "action_descriptor.h" |
| #include "chipdrivers.h" |
| #include "flash.h" |
| #include "layout.h" |
| #include "programmer.h" |
| |
| |
| /* |
| * Unfortunate global state. |
| */ |
| static bool dry_run = false; |
| |
| /* |
| * This module analyses the contents of 'before' and 'after' flash images and |
| * based on the images' differences prepares a list of processing actions to |
| * take. |
| * |
| * The goal is to prepare actions using the chip's erase capability in a most |
| * efficient way: erasing smallest possible portions of the chip gives the |
| * highest granularity, but if many small areas need to be erased, erasing a |
| * larger area, even if re-writing it completely, is more efficient. The |
| * breakdown is somewhere at 60%. |
| * |
| * Each flash chip description in flash.c includes a set of erase command |
| * descriptors, different commands allowing to erase blocks of fixed different |
| * sizes. Sometimes the erase command for a certain block size does not cover |
| * the entire chip. This module preprocesses the flash chip description to |
| * compile an array of erase commands with their block size indices such that |
| * it is guaranteed that the command can be used to erase anywhere in the chip |
| * where erase is required based on the differences between 'before' and |
| * 'after' images. |
| * |
| * 'eraser_index' below is the index into the 'block_erasers' array of the |
| * flash chip descriptor, points to the function to use to erase the block of |
| * a certain size. |
| * |
| * The erase command could potentially operate on blocks of different sizes, |
| * 'region_index' is the index into the 'block_erasers.eraseblocks' array |
| * which defines what block size would be used by this erase command. |
| */ |
| struct eraser { |
| int eraser_index; |
| int region_index; |
| }; |
| |
| /* |
| * A helper structure which holds information about blocks of a given size |
| * which require writing and or erasing. |
| * |
| * The actual map of the blocks is pointed at by the 'block_map' field, one |
| * byte per block. Block might need an erase, or just a write, depending on |
| * the contents of 'before' and 'after' flash images. |
| * |
| * The 'limit' field holds the number of blocks of this size, which is |
| * equivalent to one block of the next larger size in term of time required |
| * for erasing/programming. |
| */ |
| struct range_map { |
| size_t block_size; |
| int limit; |
| struct b_map { |
| uint8_t need_change:1; |
| uint8_t need_erase:1; |
| } *block_map; |
| }; |
| |
| /* |
| * A debug function printing out the array or processing units from an the |
| * action descriptor. |
| */ |
| static void dump_descriptor(struct action_descriptor *descriptor) |
| { |
| struct processing_unit *pu = descriptor->processing_units; |
| |
| while (pu->num_blocks) { |
| msg_pdbg("%06zx..%06zx %6zx x %zd eraser %d\n", pu->offset, |
| pu->offset + pu->num_blocks * pu->block_size - 1, |
| pu->block_size, pu->num_blocks, |
| pu->block_eraser_index); |
| pu++; |
| } |
| } |
| |
| /* |
| * Do not allow use of unsupported erasers functions. |
| * |
| * On some Intel platforms the ICH SPI controller is restricting the set of |
| * SPI command codes the AP can issue, in particular limiting the set of erase |
| * functions to just two of them. |
| * |
| * This function creates a local copy of the flash chip descriptor found in |
| * the main table, filtering out unsupported erase function pointers, when |
| * necessary. |
| * |
| * flash: pointer to the master flash context, including the original chip |
| * descriptor. |
| * chip: pointer to a flash chip descriptor copy, potentially with just a |
| * subset of erasers included. |
| */ |
| static void fix_erasers_if_needed(struct flashchip *chip, |
| struct flashctx *flash) |
| { |
| int i; |
| |
| /* Need to copy no matter what. */ |
| *chip = *flash->chip; |
| |
| /* |
| * ich_generation is set to the chipset type when running on an x86 |
| * device, even when flashrom was invoked to program the EC. |
| * |
| * But ICH type does not affect EC programming path, so no need to |
| * check if the eraser is supported in that case. |
| */ |
| if ((g_ich_generation == CHIPSET_ICH_UNKNOWN) || programming_ec()) { |
| msg_pdbg("%s: kept all erasers\n", __func__); |
| return; |
| } |
| |
| /* |
| * We are dealing with an Intel controller; different chipsets allow |
| * different erase commands. Let's check the commands and allow only |
| * those which the controller accepts. |
| */ |
| dry_run = true; |
| for (i = 0; i < NUM_ERASEFUNCTIONS; i++) { |
| |
| /* Assume it is not allowed. */ |
| if (!chip->block_erasers[i].block_erase) |
| continue; |
| |
| if (!chip->block_erasers[i].block_erase |
| (flash, 0, flash->chip->total_size * 1024)) { |
| msg_pdbg("%s: kept eraser at %d\n", __func__, i); |
| continue; |
| } |
| |
| chip->block_erasers[i].block_erase = NULL; |
| } |
| dry_run = false; |
| } |
| |
| /* |
| * Prepare a list of erasers available on this chip, sorted by the block size, |
| * from lower to higher. |
| * |
| * @flash pointer to the flash context |
| * @erase_size maximum offset which needs to be erased |
| * @sorted_erasers pointer to the array of eraser structures, large enough to |
| * fit NUM_ERASEFUNCTIONS elements. |
| * |
| * Returns number of elements put into the 'sorted_erasers' array. |
| */ |
| static size_t fill_sorted_erasers(struct flashctx *flash, |
| size_t erase_size, |
| struct eraser *sorted_erasers) |
| { |
| size_t j, k; |
| size_t chip_eraser; |
| size_t chip_region; |
| struct flashchip chip; /* Local copy, potentially altered. */ |
| /* |
| * In case chip description does not include any functions covering |
| * the entire space (this could happen when the description comes from |
| * the Chrome OS TP driver for instance), use the best effort. |
| * |
| * The structure below saves information about the eraser which covers |
| * the most of the chip space, it is used if no valid functions were |
| * found, which allows programming to succeed. |
| * |
| * The issue be further investigated under b/110474116. |
| */ |
| struct { |
| int max_total; |
| int alt_function; |
| int alt_region; |
| } fallback = {}; |
| |
| fix_erasers_if_needed(&chip, flash); |
| |
| /* Iterate over all available erase functions/block sizes. */ |
| for (j = k = 0; k < NUM_ERASEFUNCTIONS; k++) { |
| size_t new_block_size; |
| int m, n; |
| |
| /* Make sure there is a function in is slot */ |
| if (!chip.block_erasers[k].block_erase) |
| continue; |
| |
| /* |
| * Make sure there is a (block size * count) combination which |
| * would erase up to required offset into the chip. |
| * |
| * If this is not the case, but the current total size exceeds |
| * the previously saved fallback total size, make the current |
| * block the best available fallback case. |
| */ |
| for (n = 0; n < NUM_ERASEREGIONS; n++) { |
| const struct eraseblock *eb = |
| chip.block_erasers[k].eraseblocks + n; |
| int total = eb->size * eb->count; |
| |
| if (total >= erase_size) |
| break; |
| |
| if (total > fallback.max_total) { |
| fallback.max_total = total; |
| fallback.alt_region = n; |
| fallback.alt_function = k; |
| } |
| } |
| |
| if (n == NUM_ERASEREGIONS) { |
| /* |
| * This function will not erase far enough into the |
| * chip. |
| */ |
| continue; |
| } |
| |
| new_block_size = chip.block_erasers[k].eraseblocks[n].size; |
| |
| /* |
| * Place this block in the sorted position in the |
| * sorted_erasers array. |
| */ |
| for (m = 0; m < j; m++) { |
| size_t old_block_size; |
| |
| chip_eraser = sorted_erasers[m].eraser_index; |
| chip_region = sorted_erasers[m].region_index; |
| |
| old_block_size = chip.block_erasers |
| [chip_eraser].eraseblocks[chip_region].size; |
| |
| if (old_block_size < new_block_size) |
| continue; |
| |
| /* Do not keep duplicates in the sorted array. */ |
| if (old_block_size == new_block_size) { |
| j--; |
| break; |
| } |
| |
| memmove(sorted_erasers + m + 1, |
| sorted_erasers + m, |
| sizeof(sorted_erasers[0]) * (j - m)); |
| break; |
| } |
| sorted_erasers[m].eraser_index = k; |
| sorted_erasers[m].region_index = n; |
| j++; |
| } |
| |
| if (j) { |
| msg_pdbg("%s: found %zd valid erasers\n", __func__, j); |
| return j; |
| } |
| |
| if (!fallback.max_total) { |
| msg_cerr("No erasers found for this chip (%s:%s)!\n", |
| chip.vendor, chip.name); |
| exit(1); |
| } |
| |
| sorted_erasers[0].eraser_index = fallback.alt_function; |
| sorted_erasers[0].region_index = fallback.alt_region; |
| msg_pwarn("%s: using fallback eraser: " |
| "region %d, function %d total %#x vs %#zx\n", |
| __func__, fallback.alt_region, fallback.alt_function, |
| fallback.max_total, erase_size); |
| |
| return 1; |
| } |
| |
| /* |
| * When it is determined that the larger block will have to be erased because |
| * a large enough number of the blocks of the previous smaller size need to be |
| * erased, all blocks of smaller sizes falling into the range of addresses of |
| * this larger block will not have to be erased/written individually, so they |
| * need to be unmarked for erase/change. |
| * |
| * This function recursively invokes itself to clean all smaller size blocks |
| * which are in the range of the current larger block. |
| * |
| * @upper_level_map pointer to the element of the range map array where the |
| * current block belongs. |
| * @block_index index of the current block in the map of the blocks of |
| * the current range map element. |
| * @i index of this range map in the array of range maps, |
| * guaranteed to be 1 or above, so that there is always a |
| * smaller block size range map at i - 1. |
| */ |
| static void clear_all_nested(struct range_map *upper_level_map, |
| size_t block_index, |
| unsigned i) |
| { |
| struct range_map *this_level_map = upper_level_map - 1; |
| int range_start; |
| int range_end; |
| int j; |
| |
| range_start = upper_level_map->block_size * block_index; |
| range_end = range_start + upper_level_map->block_size; |
| |
| for (j = range_start / this_level_map->block_size; |
| j < range_end / this_level_map->block_size; |
| j++) { |
| this_level_map->block_map[j].need_change = 0; |
| this_level_map->block_map[j].need_erase = 0; |
| if (i > 1) |
| clear_all_nested(this_level_map, j, i - 1); |
| } |
| } |
| |
| /* |
| * Once all lowest range size blocks which need to be erased have been |
| * identified, we need to see if there are so many of them that they maybe be |
| * folded into larger size blocks, so that a single larger erase operation is |
| * required instead of many smaller ones. |
| * |
| * @maps pointer to the array of range_map structures, sorted by block |
| * size from lower to higher, only the lower size bock map has |
| * been filled up. |
| * @num_maps number of elements in the maps array. |
| * @chip_size size of the flash chip, in bytes. |
| */ |
| static void fold_range_maps(struct range_map *maps, |
| size_t num_maps, |
| size_t chip_size) |
| { |
| int block_index; |
| unsigned i; |
| struct range_map *map; |
| |
| /* |
| * First go from bottom to top, marking higher size blocks which need |
| * to be erased based on the count of lower size blocks marked for |
| * erasing which fall into the range of addresses covered by the |
| * larger size block. |
| * |
| * Starting from the second element of the array, as the first element |
| * is the only one filled up so far. |
| */ |
| for (i = 1; i < num_maps; i++) { |
| int block_mult; |
| |
| map = maps + i; |
| |
| /* How many lower size blocks fit into this block. */ |
| block_mult = map->block_size / map[-1].block_size; |
| |
| for (block_index = 0; |
| block_index < (chip_size/map->block_size); |
| block_index++) { |
| int lower_start; |
| int lower_end; |
| int lower_index; |
| int erase_marked_blocks; |
| int change_marked_blocks; |
| |
| lower_start = block_index * block_mult; |
| lower_end = lower_start + block_mult; |
| erase_marked_blocks = 0; |
| change_marked_blocks = 0; |
| |
| for (lower_index = lower_start; |
| lower_index < lower_end; |
| lower_index++) { |
| |
| if (map[-1].block_map[lower_index].need_erase) |
| erase_marked_blocks++; |
| |
| if (map[-1].block_map[lower_index].need_change) |
| change_marked_blocks++; |
| } |
| |
| /* |
| * Mark larger block for erasing; if any of the |
| * smaller size blocks was marked as 'need_change', |
| * mark the larger size block as well. |
| */ |
| if (erase_marked_blocks > map[-1].limit) { |
| map->block_map[block_index].need_erase = 1; |
| map->block_map[block_index].need_change = |
| change_marked_blocks ? 1 : 0; |
| } |
| } |
| } |
| |
| /* |
| * Now let's go larger to smaller block sizes, to make sure that all |
| * nested blocks of a bigger block marked for erasing are not marked |
| * for erasing any more; erasing the encompassing block will sure |
| * erase all nested blocks of all smaller sizes. |
| */ |
| for (i = num_maps - 1; i > 0; i--) { |
| map = maps + i; |
| |
| for (block_index = 0; |
| block_index < (chip_size/map->block_size); |
| block_index++) { |
| if (!map->block_map[block_index].need_erase) |
| continue; |
| |
| clear_all_nested(map, block_index, i); |
| } |
| } |
| } |
| |
| /* |
| * A function to fill the processing_units array of the action descriptor with |
| * a set of processing units, which describe flash chip blocks which need to |
| * be erased/programmed to to accomplish the action requested by user when |
| * invoking flashrom. |
| * |
| * This set of processing units is determined based on comparing old and new |
| * flashrom contents. |
| * |
| * First, blocks which are required to be erased and/or written are identified |
| * at the finest block size granularity. |
| * |
| * Then the distribution of those blocks is analyzed, and if enough of smaller |
| * blocks in a single larger block address range need to be erased, the larger |
| * block is marked for erasing. |
| * |
| * This same process is applied again to increasingly larger block sizes until |
| * the largest granularity blocks are marked as appropriate. |
| * |
| * After this the range map array is scanned from larger block sizes to |
| * smaller; each time when a larger block marked for erasing is detected, all |
| * smaller size blocks in the same address range are unmarked for erasing. |
| * |
| * In the end only blocks which need to be modified remain marked, and at the |
| * finest possible granularity. The list of these blocks is added to the |
| * 'processing_units' array of the descriptor and becomes the list of actions |
| * to be take to program the flash chip. |
| * |
| * @descriptor descriptor structure to fill, allocated by the caller. |
| * @sorted_erasers pointer to an array of eraser descriptors, sorted by |
| * block size. |
| * @chip_erasers pointer to the array of erasers from this flash |
| * chip's descriptor. |
| * @chip_size size of this chip in bytes |
| * @num_sorted_erasers size of the sorted_erasers array |
| * @erased_value value contained in all bytes of the erased flash |
| */ |
| static void fill_action_descriptor(struct action_descriptor *descriptor, |
| struct eraser *sorted_erasers, |
| struct block_eraser* chip_erasers, |
| size_t chip_size, |
| size_t num_sorted_erasers, |
| unsigned erased_value) |
| { |
| const uint8_t *newc; |
| const uint8_t *oldc; |
| int consecutive_blocks; |
| size_t block_size; |
| struct b_map *block_map; |
| struct range_map range_maps[num_sorted_erasers]; |
| unsigned i; |
| unsigned pu_index; |
| |
| /* |
| * This array has enough room to hold helper structures, one for each |
| * available block size. |
| */ |
| memset(range_maps, 0, sizeof(range_maps)); |
| |
| /* |
| * Initialize range_maps array: allocate space for block_map arrays on |
| * every entry (block maps are used to keep track of blocks which need |
| * to be erased/written) and calculate the limit where smaller blocks |
| * should be replaced by the next larger size block. |
| */ |
| for (i = 0; i < num_sorted_erasers; i++) { |
| size_t larger_block_size; |
| size_t map_size; |
| size_t num_blocks; |
| unsigned function; |
| unsigned region; |
| |
| function = sorted_erasers[i].eraser_index; |
| region = sorted_erasers[i].region_index; |
| block_size = chip_erasers[function].eraseblocks[region].size; |
| |
| range_maps[i].block_size = block_size; |
| |
| /* |
| * Allocate room for the map where blocks which require |
| * writing/erasing will be marked. |
| */ |
| num_blocks = chip_size/block_size; |
| map_size = num_blocks * sizeof(struct b_map); |
| range_maps[i].block_map = malloc(map_size); |
| if (!range_maps[i].block_map) { |
| msg_cerr("%s: Failed to allocate %zd bytes\n", |
| __func__, map_size); |
| exit(1); |
| } |
| memset(range_maps[i].block_map, 0, map_size); |
| |
| /* |
| * Limit is calculated for all block sizes but the largest |
| * one, because there is no way to further consolidate the |
| * largest blocks. |
| */ |
| if (i < (num_sorted_erasers - 1)) { |
| function = sorted_erasers[i + 1].eraser_index; |
| region = sorted_erasers[i + 1].region_index; |
| larger_block_size = chip_erasers |
| [function].eraseblocks[region].size; |
| |
| /* |
| * How many of the lower size blocks need to be have |
| * to be erased before it is worth moving to the |
| * larger size. |
| * |
| * The admittedly arbitrary rule of thumb here is if |
| * 70% or more of the lower size blocks need to be |
| * erased, forget the lower size blocks and move to |
| * the higher size one. |
| */ |
| range_maps[i].limit = ((larger_block_size / |
| block_size) * 7) / 10; |
| } |
| } |
| |
| /* Cache pointers to 'before' and 'after' contents. */ |
| oldc = descriptor->oldcontents; |
| newc = descriptor->newcontents; |
| |
| /* Now, let's fill up the map for the smallest bock size. */ |
| block_size = range_maps[0].block_size; |
| block_map = range_maps[0].block_map; |
| for (i = 0; i < chip_size; i++) { |
| int block_index; |
| |
| if (oldc[i] == newc[i]) |
| continue; |
| |
| block_index = i/block_size; |
| |
| if (oldc[i] != erased_value) |
| block_map[block_index].need_erase = 1; |
| |
| if (newc[i] != erased_value) |
| block_map[block_index].need_change = 1; |
| |
| if (block_map[block_index].need_erase && |
| block_map[block_index].need_change) { |
| /* Can move to the next block. */ |
| i += range_maps[0].block_size; |
| i &= ~(range_maps[0].block_size - 1); |
| i--; /* adjust for increment in the for loop */ |
| } |
| } |
| |
| /* Now let's see what can be folded into larger blocks. */ |
| fold_range_maps(range_maps, num_sorted_erasers, chip_size); |
| |
| /* Finally we can fill the action descriptor. */ |
| consecutive_blocks = 0; |
| pu_index = 0; /* Number of initialized processing units. */ |
| for (i = 0; i < num_sorted_erasers; i++) { |
| int j; |
| struct processing_unit *pu; |
| size_t map_size = chip_size/range_maps[i].block_size; |
| |
| for (j = 0; j < map_size; j++) { |
| |
| block_map = range_maps[i].block_map + j; |
| |
| if (block_map->need_erase || block_map->need_change) { |
| consecutive_blocks++; |
| continue; |
| } |
| |
| if (!consecutive_blocks) |
| continue; |
| |
| /* Add programming/erasing uint. */ |
| pu = descriptor->processing_units + pu_index++; |
| |
| pu->block_size = range_maps[i].block_size; |
| pu->offset = (j - consecutive_blocks) * pu->block_size; |
| pu->num_blocks = consecutive_blocks; |
| pu->block_eraser_index = sorted_erasers[i].eraser_index; |
| pu->block_region_index = sorted_erasers[i].region_index; |
| |
| consecutive_blocks = 0; |
| } |
| |
| free(range_maps[i].block_map); |
| |
| if (!consecutive_blocks) |
| continue; |
| |
| /* |
| * Add last programming/erasing unit for current block |
| * size. |
| */ |
| pu = descriptor->processing_units + pu_index++; |
| |
| pu->block_size = range_maps[i].block_size; |
| pu->offset = (j - consecutive_blocks) * pu->block_size; |
| pu->num_blocks = consecutive_blocks; |
| pu->block_eraser_index = sorted_erasers[i].eraser_index; |
| pu->block_region_index = sorted_erasers[i].region_index; |
| consecutive_blocks = 0; |
| } |
| |
| descriptor->processing_units[pu_index].num_blocks = 0; |
| } |
| |
| /* |
| * In case layout is used, return the largest offset of the end of all |
| * included sections. If layout is not used, return zero. |
| */ |
| static size_t top_section_offset(void) |
| { |
| size_t top = 0; |
| int i; |
| struct flashrom_layout *const layout = get_global_layout(); |
| |
| for (i = 0; i < layout->num_entries; i++) { |
| |
| if (!layout->entries[i].included) |
| continue; |
| |
| if (layout->entries[i].end > top) |
| top = layout->entries[i].end; |
| } |
| |
| return top; |
| } |
| |
| bool is_dry_run() |
| { |
| return dry_run; |
| } |
| |
| struct action_descriptor *prepare_action_descriptor(struct flashctx *flash, |
| void *oldcontents, |
| void *newcontents, |
| int do_diff) |
| { |
| struct eraser sorted_erasers[NUM_ERASEFUNCTIONS]; |
| size_t i; |
| size_t num_erasers; |
| int max_units; |
| size_t block_size = 0; |
| struct action_descriptor *descriptor; |
| size_t chip_size = flash->chip->total_size * 1024; |
| |
| /* |
| * Find the maximum size of the area which might have to be erased, |
| * this is needed to ensure that the picked erase function can go all |
| * the way to the requred offset, as some of the erase functions |
| * operate only on part of the chip starting at offset zero. |
| * |
| * Not an efficient way to do it, but this is acceptable on the host. |
| */ |
| if (do_diff) { |
| /* |
| * If we are doing diffs, look for the largest offset where |
| * the difference is, this is the highest offset which might |
| * need to be erased. |
| */ |
| for (i = 0; i < chip_size; i++) |
| if (((uint8_t *)newcontents)[i] != |
| ((uint8_t *)oldcontents)[i]) |
| block_size = i + 1; |
| } else { |
| /* |
| * We are not doing diffs, if user specified sections to |
| * program - use the highest offset of the highest section as |
| * the limit. |
| */ |
| block_size = top_section_offset(); |
| |
| if (!block_size) |
| /* User did not specify any sections. */ |
| block_size = chip_size; |
| } |
| |
| num_erasers = fill_sorted_erasers(flash, block_size, sorted_erasers); |
| |
| /* |
| * Let's allocate enough memory for the worst case action descriptor |
| * size, when we need to program half the chip using the smallest block |
| * size. |
| */ |
| block_size = flash->chip->block_erasers |
| [sorted_erasers[0].eraser_index].eraseblocks |
| [sorted_erasers[0].region_index].size; |
| max_units = chip_size / (2 * block_size) + 1; |
| descriptor = malloc(sizeof(struct action_descriptor) + |
| sizeof(struct processing_unit) * max_units); |
| if (!descriptor) { |
| msg_cerr("Failed to allocate room for %d processing units!\n", |
| max_units); |
| exit(1); |
| } |
| |
| descriptor->newcontents = newcontents; |
| descriptor->oldcontents = oldcontents; |
| |
| fill_action_descriptor(descriptor, sorted_erasers, |
| flash->chip->block_erasers, chip_size, |
| num_erasers, ERASED_VALUE(flash)); |
| |
| dump_descriptor(descriptor); |
| |
| return descriptor; |
| } |