diff --git a/Makefile b/Makefile index 2ebd37051..b37e807bd 100644 --- a/Makefile +++ b/Makefile @@ -388,7 +388,7 @@ CHIP_OBJS = jedec.o stm50.o w39.o w29ee011.o \ ############################################################################### # Library code. -LIB_OBJS = libflashrom.o layout.o flashrom.o udelay.o parallel.o programmer.o programmer_table.o \ +LIB_OBJS = libflashrom.o layout.o erasure_layout.o flashrom.o udelay.o parallel.o programmer.o programmer_table.o \ helpers.o helpers_fileio.o ich_descriptors.o fmap.o platform/endian_$(ENDIAN).o platform/memaccess.o diff --git a/erasure_layout.c b/erasure_layout.c new file mode 100644 index 000000000..05376de8e --- /dev/null +++ b/erasure_layout.c @@ -0,0 +1,406 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2022 Aarya Chaumal + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include "include/flash.h" +#include "include/layout.h" +#include "include/erasure_layout.h" + +static size_t calculate_block_count(const struct flashchip *chip, size_t eraser_idx) +{ + size_t block_count = 0; + + chipoff_t addr = 0; + for (size_t i = 0; addr < chip->total_size * 1024; i++) { + const struct eraseblock *block = &chip->block_erasers[eraser_idx].eraseblocks[i]; + block_count += block->count; + addr += block->size * block->count; + } + + return block_count; +} + +static void init_eraseblock(struct erase_layout *layout, size_t idx, size_t block_num, + chipoff_t start_addr, chipoff_t end_addr, size_t *sub_block_index) +{ + struct eraseblock_data *edata = &layout[idx].layout_list[block_num]; + edata->start_addr = start_addr; + edata->end_addr = end_addr; + edata->selected = false; + edata->block_num = block_num; + + if (!idx) + return; + + edata->first_sub_block_index = *sub_block_index; + struct eraseblock_data *subedata = &layout[idx - 1].layout_list[*sub_block_index]; + while (subedata->start_addr >= start_addr && subedata->end_addr <= end_addr && + *sub_block_index < layout[idx-1].block_count) { + (*sub_block_index)++; + subedata++; + } + edata->last_sub_block_index = *sub_block_index - 1; +} + +/* + * @brief Function to free the created erase_layout + * + * @param layout pointer to allocated layout + * @param erasefn_count number of erase functions for which the layout was created + * + */ +void free_erase_layout(struct erase_layout *layout, unsigned int erasefn_count) +{ + if (!layout) + return; + for (size_t i = 0; i < erasefn_count; i++) { + free(layout[i].layout_list); + } + free(layout); +} + +/* + * @brief Function to create an erase layout + * + * @param flashctx flash context + * @param e_layout address to the pointer to store the layout + * @return 0 on success, + * -1 if layout creation fails + * + * This function creates a layout of which erase functions erase which regions + * of the flash chip. This helps to optimally select the erase functions for + * erase/write operations. + */ +int create_erase_layout(struct flashctx *const flashctx, struct erase_layout **e_layout) +{ + const struct flashchip *chip = flashctx->chip; + const size_t erasefn_count = count_usable_erasers(flashctx); + struct erase_layout *layout = calloc(erasefn_count, sizeof(struct erase_layout)); + + if (!layout) { + msg_gerr("Out of memory!\n"); + return -1; + } + + if (!erasefn_count) { + msg_gerr("No erase functions supported\n"); + return 0; + } + + size_t layout_idx = 0; + for (size_t eraser_idx = 0; eraser_idx < NUM_ERASEFUNCTIONS; eraser_idx++) { + if (check_block_eraser(flashctx, eraser_idx, 0)) + continue; + + layout[layout_idx].eraser = &chip->block_erasers[eraser_idx]; + const size_t block_count = calculate_block_count(flashctx->chip, eraser_idx); + size_t sub_block_index = 0; + + layout[layout_idx].block_count = block_count; + layout[layout_idx].layout_list = (struct eraseblock_data *)calloc(block_count, + sizeof(struct eraseblock_data)); + + if (!layout[layout_idx].layout_list) { + free_erase_layout(layout, layout_idx); + return -1; + } + + size_t block_num = 0; + chipoff_t start_addr = 0; + + for (int i = 0; block_num < block_count; i++) { + const struct eraseblock *block = &chip->block_erasers[eraser_idx].eraseblocks[i]; + + for (size_t num = 0; num < block->count; num++) { + chipoff_t end_addr = start_addr + block->size - 1; + init_eraseblock(layout, layout_idx, block_num, + start_addr, end_addr, &sub_block_index); + block_num += 1; + start_addr = end_addr + 1; + } + } + layout_idx++; + } + + *e_layout = layout; + return layout_idx; +} + +/* + * @brief Function to align start and address of the region boundaries + * + * @param layout erase layout + * @param flashctx flash context + * @param region_start pointer to start address of the region to align + * @param region_end pointer to end address of the region to align + * + * This function aligns start and end address of the region (in struct walk_info) + * to some erase sector boundaries and modify the region start and end addresses + * to match nearest erase sector boundaries. This function will be used in the + * new algorithm for erase function selection. + */ +static void align_region(const struct erase_layout *layout, struct flashctx *const flashctx, + chipoff_t *region_start, chipoff_t *region_end) +{ + chipoff_t start_diff = UINT_MAX, end_diff = UINT_MAX; + const size_t erasefn_count = count_usable_erasers(flashctx); + for (size_t i = 0; i < erasefn_count; i++) { + for (size_t j = 0; j < layout[i].block_count; j++) { + const struct eraseblock_data *ll = &layout[i].layout_list[j]; + if (ll->start_addr <= *region_start) + start_diff = (*region_start - ll->start_addr) > start_diff ? + start_diff : (*region_start - ll->start_addr); + if (ll->end_addr >= *region_end) + end_diff = (ll->end_addr - *region_end) > end_diff ? + end_diff : (ll->end_addr - *region_end); + } + } + + if (start_diff) { + msg_cinfo("Region start not sector aligned! Extending start boundaries...\n"); + *region_start = *region_start - start_diff; + } + if (end_diff) { + msg_cinfo("Region end not sector aligned! Extending end boundaries...\n"); + *region_end = *region_end + end_diff; + } +} + +/* + * @brief Function to select the list of sectors that need erasing + * + * @param flashctx flash context + * @param layout erase layout + * @param findex index of the erase function + * @param block_num index of the block to erase according to the erase function index + * @param curcontents buffer containg the current contents of the flash + * @param newcontents buffer containg the new contents of the flash + * @param rstart start address of the region + * @rend rend end address of the region + */ +static void select_erase_functions(struct flashctx *flashctx, const struct erase_layout *layout, + size_t findex, size_t block_num, uint8_t *curcontents, uint8_t *newcontents, + chipoff_t rstart, chipoff_t rend) +{ + struct eraseblock_data *ll = &layout[findex].layout_list[block_num]; + if (!findex) { + if (ll->start_addr >= rstart && ll->end_addr <= rend) { + chipoff_t start_addr = ll->start_addr; + chipoff_t end_addr = ll->end_addr; + const chipsize_t erase_len = end_addr - start_addr + 1; + const uint8_t erased_value = ERASED_VALUE(flashctx); + ll->selected = need_erase(curcontents + start_addr, newcontents + start_addr, erase_len, + flashctx->chip->gran, erased_value); + } + } else { + int count = 0; + const int sub_block_start = ll->first_sub_block_index; + const int sub_block_end = ll->last_sub_block_index; + + for (int j = sub_block_start; j <= sub_block_end; j++) { + select_erase_functions(flashctx, layout, findex - 1, j, curcontents, newcontents, + rstart, rend); + if (layout[findex - 1].layout_list[j].selected) + count++; + } + + const int total_blocks = sub_block_end - sub_block_start + 1; + if (count && count > total_blocks/2) { + if (ll->start_addr >= rstart && ll->end_addr <= rend) { + for (int j = sub_block_start; j <= sub_block_end; j++) + layout[findex - 1].layout_list[j].selected = false; + ll->selected = true; + } + } + } +} + +/* + * @brief wrapper to use the erase algorithm + * + * @param flashctx flash context + * @param region_start start address of the region + * @param region_end end address of the region + * @param curcontents buffer containg the current contents of the flash + * @param newcontents buffer containg the new contents of the flash + * @param erase_layout erase layout + * @param all_skipped pointer to the flag to chec if any block was erased + */ +int erase_write(struct flashctx *const flashctx, chipoff_t region_start, chipoff_t region_end, + uint8_t *curcontents, uint8_t *newcontents, + struct erase_layout *erase_layout, bool *all_skipped) +{ + const size_t erasefn_count = count_usable_erasers(flashctx); + int ret = 0; + size_t i; + chipoff_t old_start = region_start, old_end = region_end; + align_region(erase_layout, flashctx, ®ion_start, ®ion_end); + + uint8_t *old_start_buf = NULL, *old_end_buf = NULL; + old_start_buf = (uint8_t *)malloc(old_start - region_start); + if (!old_start_buf) { + msg_cerr("Not enough memory!\n"); + ret = -1; + goto _end; + } + old_end_buf = (uint8_t *)malloc(region_end - old_end); + if (!old_end_buf) { + msg_cerr("Not enough memory!\n"); + ret = -1; + goto _end; + } + + if (old_start - region_start) { + read_flash(flashctx, curcontents + region_start, region_start, old_start - region_start); + memcpy(old_start_buf, newcontents + region_start, old_start - region_start); + memcpy(newcontents + region_start, curcontents + region_start, old_start - region_start); + } + if (region_end - old_end) { + read_flash(flashctx, curcontents + old_end, old_end, region_end - old_end); + memcpy(old_end_buf, newcontents + old_end, region_end - old_end); + memcpy(newcontents + old_end, curcontents + old_end, region_end - old_end); + } + + // select erase functions + for (i = 0; i < erase_layout[erasefn_count - 1].block_count; i++) { + if (erase_layout[erasefn_count - 1].layout_list[i].start_addr <= region_end && + region_start <= erase_layout[erasefn_count - 1].layout_list[i].end_addr) + select_erase_functions(flashctx, erase_layout, + erasefn_count - 1, i, + curcontents, newcontents, + region_start, region_end); + } + + for (i = 0; i < erasefn_count; i++) { + for (size_t j = 0; j < erase_layout[i].block_count; j++) { + if (!erase_layout[i].layout_list[j].selected) continue; + + // erase + chipoff_t start_addr = erase_layout[i].layout_list[j].start_addr; + unsigned int block_len = erase_layout[i].layout_list[j].end_addr - start_addr + 1; + const uint8_t erased_value = ERASED_VALUE(flashctx); + // execute erase + erasefunc_t *erasefn = lookup_erase_func_ptr(erase_layout[i].eraser); + + if (!flashctx->flags.skip_unwritable_regions) { + if (check_for_unwritable_regions(flashctx, start_addr, block_len)) + goto _end; + } + + unsigned int len; + for (unsigned int addr = start_addr; addr < start_addr + block_len; addr += len) { + struct flash_region region; + get_flash_region(flashctx, addr, ®ion); + + len = min(start_addr + block_len, region.end) - addr; + + if (region.write_prot) { + msg_gdbg("%s: cannot erase inside %s " + "region (%#08x..%#08x), skipping range (%#08x..%#08x).\n", + __func__, region.name, + region.start, region.end - 1, + addr, addr + len - 1); + free(region.name); + continue; + } + + msg_gdbg("%s: %s region (%#08x..%#08x) is " + "writable, erasing range (%#08x..%#08x).\n", + __func__, region.name, + region.start, region.end - 1, + addr, addr + len - 1); + free(region.name); + + if (erasefn(flashctx, addr, len)) + goto _end; + if (check_erased_range(flashctx, addr, len)) { + msg_cerr("ERASE FAILED!\n"); + goto _end; + } + } + + ret = erasefn(flashctx, start_addr, block_len); + if (ret) { + msg_cerr("Failed to execute erase command " + "for offset %#x to %#x.\n", + start_addr, start_addr + block_len); + ret = -1; + goto _end; + } + + // adjust curcontents + memset(curcontents+start_addr, erased_value, block_len); + // after erase make it unselected again + erase_layout[i].layout_list[j].selected = false; + msg_cdbg("E(%x:%x)", start_addr, start_addr + block_len - 1); + // verify erase + ret = check_erased_range(flashctx, start_addr, block_len); + if (ret) { + msg_cerr("Verifying flash. Erase failed for range %#x : %#x, Abort.\n", + start_addr, start_addr + block_len - 1); + goto _end; + } + + *all_skipped = false; + } + } + + // write + unsigned int start_here = 0, len_here = 0, erase_len = region_end - region_start + 1; + while ((len_here = get_next_write(curcontents + region_start + start_here, + newcontents + region_start + start_here, + erase_len - start_here, &start_here, + flashctx->chip->gran))) { + // execute write + ret = write_flash(flashctx, + newcontents + region_start + start_here, + region_start + start_here, len_here); + if (ret) { + msg_cerr("Write failed at %#zx, Abort.\n", i); + ret = -1; + goto _end; + } + + // adjust curcontents + memcpy(curcontents + region_start + start_here, + newcontents + region_start + start_here, len_here); + msg_cdbg("W(%x:%x)", region_start + start_here, region_start + start_here + len_here - 1); + + *all_skipped = false; + } + // verify write + ret = verify_range(flashctx, newcontents + region_start, region_start, region_end - region_start); + if (ret) { + msg_cerr("Verifying flash. Write failed for range %#x : %#x, Abort.\n", + region_start, region_end); + goto _end; + } + +_end: + memcpy(newcontents + region_start, old_start_buf, old_start - region_start); + memcpy(newcontents + old_end, old_end_buf, region_end - old_end); + + free(old_start_buf); + free(old_end_buf); + + msg_cinfo("Erase/write done from %x to %x\n", region_start, region_end); + return ret; +} diff --git a/include/erasure_layout.h b/include/erasure_layout.h new file mode 100644 index 000000000..b8b5f089a --- /dev/null +++ b/include/erasure_layout.h @@ -0,0 +1,41 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2022 Aarya Chaumal + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __ERASURE_LAYOUT_H__ +#define __ERASURE_LAYOUT_H__ 1 + +struct eraseblock_data { + chipoff_t start_addr; + chipoff_t end_addr; + bool selected; + size_t block_num; + size_t first_sub_block_index; + size_t last_sub_block_index; +}; + +struct erase_layout { + struct eraseblock_data* layout_list; + size_t block_count; + const struct block_eraser *eraser; +}; + +void free_erase_layout(struct erase_layout *layout, unsigned int erasefn_count); +int create_erase_layout(struct flashctx *const flashctx, struct erase_layout **erase_layout); +int erase_write(struct flashctx *const flashctx, chipoff_t region_start, chipoff_t region_end, + uint8_t* curcontents, uint8_t* newcontents, + struct erase_layout *erase_layout, bool *all_skipped); + +#endif /* !__ERASURE_LAYOUT_H__ */ diff --git a/meson.build b/meson.build index 57ac438db..2575fe1df 100644 --- a/meson.build +++ b/meson.build @@ -53,6 +53,7 @@ srcs = files( 'bitbang_spi.c', 'edi.c', 'en29lv640b.c', + 'erasure_layout.c', 'flashchips.c', 'flashrom.c', 'fmap.c',