1 /*
2 * This file is part of the flashrom project.
3 *
4 * Copyright (C) 2022 Aarya Chaumal
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17 #include <limits.h>
18 #include <stdbool.h>
19 #include <stdlib.h>
20 #include <limits.h>
21 #include <string.h>
22
23 #include "flash.h"
24 #include "layout.h"
25 #include "erasure_layout.h"
26
calculate_block_count(const struct flashchip * chip,size_t eraser_idx)27 static size_t calculate_block_count(const struct flashchip *chip, size_t eraser_idx)
28 {
29 size_t block_count = 0;
30
31 chipoff_t addr = 0;
32 for (size_t i = 0; addr < chip->total_size * 1024; i++) {
33 const struct eraseblock *block = &chip->block_erasers[eraser_idx].eraseblocks[i];
34 block_count += block->count;
35 addr += block->size * block->count;
36 }
37
38 return block_count;
39 }
40
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)41 static void init_eraseblock(struct erase_layout *layout, size_t idx, size_t block_num,
42 chipoff_t start_addr, chipoff_t end_addr, size_t *sub_block_index)
43 {
44 struct eraseblock_data *edata = &layout[idx].layout_list[block_num];
45 edata->start_addr = start_addr;
46 edata->end_addr = end_addr;
47 edata->selected = false;
48 edata->block_num = block_num;
49
50 if (!idx)
51 return;
52
53 edata->first_sub_block_index = *sub_block_index;
54 struct eraseblock_data *subedata = &layout[idx - 1].layout_list[*sub_block_index];
55 while (*sub_block_index < layout[idx-1].block_count &&
56 subedata->start_addr >= start_addr && subedata->end_addr <= end_addr) {
57 (*sub_block_index)++;
58 subedata++;
59 }
60 edata->last_sub_block_index = *sub_block_index - 1;
61 }
62
63 /*
64 * @brief Function to free the created erase_layout
65 *
66 * @param layout pointer to allocated layout
67 * @param erasefn_count number of erase functions for which the layout was created
68 *
69 */
free_erase_layout(struct erase_layout * layout,unsigned int erasefn_count)70 void free_erase_layout(struct erase_layout *layout, unsigned int erasefn_count)
71 {
72 if (!layout)
73 return;
74 for (size_t i = 0; i < erasefn_count; i++) {
75 free(layout[i].layout_list);
76 }
77 free(layout);
78 }
79
80 /*
81 * @brief Function to create an erase layout
82 *
83 * @param flashctx flash context
84 * @param e_layout address to the pointer to store the layout
85 * @return 0 on success,
86 * -1 if layout creation fails
87 *
88 * This function creates a layout of which erase functions erase which regions
89 * of the flash chip. This helps to optimally select the erase functions for
90 * erase/write operations.
91 */
create_erase_layout(struct flashctx * const flashctx,struct erase_layout ** e_layout)92 int create_erase_layout(struct flashctx *const flashctx, struct erase_layout **e_layout)
93 {
94 const struct flashchip *chip = flashctx->chip;
95 const size_t erasefn_count = count_usable_erasers(flashctx);
96 if (!erasefn_count) {
97 msg_gerr("No erase functions supported\n");
98 return 0;
99 }
100
101 struct erase_layout *layout = calloc(erasefn_count, sizeof(struct erase_layout));
102 if (!layout) {
103 msg_gerr("Out of memory!\n");
104 return -1;
105 }
106
107 size_t layout_idx = 0;
108 for (size_t eraser_idx = 0; eraser_idx < NUM_ERASEFUNCTIONS; eraser_idx++) {
109 if (check_block_eraser(flashctx, eraser_idx, 0))
110 continue;
111
112 layout[layout_idx].eraser = &chip->block_erasers[eraser_idx];
113 const size_t block_count = calculate_block_count(flashctx->chip, eraser_idx);
114 size_t sub_block_index = 0;
115
116 layout[layout_idx].block_count = block_count;
117 layout[layout_idx].layout_list = (struct eraseblock_data *)calloc(block_count,
118 sizeof(struct eraseblock_data));
119
120 if (!layout[layout_idx].layout_list) {
121 free_erase_layout(layout, layout_idx);
122 return -1;
123 }
124
125 size_t block_num = 0;
126 chipoff_t start_addr = 0;
127
128 for (int i = 0; block_num < block_count; i++) {
129 const struct eraseblock *block = &chip->block_erasers[eraser_idx].eraseblocks[i];
130
131 for (size_t num = 0; num < block->count; num++) {
132 chipoff_t end_addr = start_addr + block->size - 1;
133 init_eraseblock(layout, layout_idx, block_num,
134 start_addr, end_addr, &sub_block_index);
135 block_num += 1;
136 start_addr = end_addr + 1;
137 }
138 }
139 layout_idx++;
140 }
141
142 *e_layout = layout;
143 return layout_idx;
144 }
145
146 /*
147 * @brief Function to align start and address of the region boundaries
148 *
149 * @param layout erase layout
150 * @param flashctx flash context
151 * @param region_start pointer to start address of the region to align
152 * @param region_end pointer to end address of the region to align
153 *
154 * This function aligns start and end address of the region (in struct walk_info)
155 * to some erase sector boundaries and modify the region start and end addresses
156 * to match nearest erase sector boundaries. This function will be used in the
157 * new algorithm for erase function selection.
158 */
align_region(const struct erase_layout * layout,struct flashctx * const flashctx,chipoff_t * region_start,chipoff_t * region_end)159 static void align_region(const struct erase_layout *layout, struct flashctx *const flashctx,
160 chipoff_t *region_start, chipoff_t *region_end)
161 {
162 chipoff_t start_diff = UINT_MAX, end_diff = UINT_MAX;
163 const size_t erasefn_count = count_usable_erasers(flashctx);
164 for (size_t i = 0; i < erasefn_count; i++) {
165 for (size_t j = 0; j < layout[i].block_count; j++) {
166 const struct eraseblock_data *ll = &layout[i].layout_list[j];
167 if (ll->start_addr <= *region_start)
168 start_diff = (*region_start - ll->start_addr) > start_diff ?
169 start_diff : (*region_start - ll->start_addr);
170 if (ll->end_addr >= *region_end)
171 end_diff = (ll->end_addr - *region_end) > end_diff ?
172 end_diff : (ll->end_addr - *region_end);
173 }
174 }
175
176 if (start_diff) {
177 msg_cinfo("Region [0x%08x - 0x%08x] is not sector aligned! "
178 "Extending start boundaries by 0x%08x bytes, from 0x%08x -> 0x%08x\n",
179 *region_start, *region_end,
180 start_diff, *region_start, *region_start - start_diff);
181 *region_start = *region_start - start_diff;
182 }
183 if (end_diff) {
184 msg_cinfo("Region [0x%08x - 0x%08x] is not sector aligned! "
185 "Extending end boundaries by 0x%08x bytes, from 0x%08x -> 0x%08x\n",
186 *region_start, *region_end,
187 end_diff, *region_end, *region_end + end_diff);
188 *region_end = *region_end + end_diff;
189 }
190 }
191
192 /* Deselect all the blocks from index_to_deselect and down to the smallest. */
deselect_erase_functions(const struct erase_layout * layout,size_t index_to_deselect,int sub_block_start,const int sub_block_end)193 static void deselect_erase_functions(const struct erase_layout *layout, size_t index_to_deselect,
194 int sub_block_start, const int sub_block_end)
195 {
196 for (int j = sub_block_start; j <= sub_block_end; j++)
197 layout[index_to_deselect].layout_list[j].selected = false;
198
199 int block_start_to_deselect =
200 layout[index_to_deselect].layout_list[sub_block_start].first_sub_block_index;
201 int block_end_to_deselect =
202 layout[index_to_deselect].layout_list[sub_block_end].last_sub_block_index;
203
204 if (index_to_deselect)
205 deselect_erase_functions(layout,
206 index_to_deselect - 1,
207 block_start_to_deselect,
208 block_end_to_deselect);
209 else
210 return; // index_to_deselect has already reached 0, the smallest size of block. we are done.
211 }
212
213 /*
214 * @brief Function to select the list of sectors that need erasing
215 *
216 * @param flashctx flash context
217 * @param layout erase layout
218 * @param findex index of the erase function
219 * @param block_num index of the block to erase according to the erase function index
220 * @param curcontents buffer containg the current contents of the flash
221 * @param newcontents buffer containg the new contents of the flash
222 * @param rstart start address of the region
223 * @rend rend end address of the region
224 */
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)225 static void select_erase_functions(struct flashctx *flashctx, const struct erase_layout *layout,
226 size_t findex, size_t block_num, uint8_t *curcontents, uint8_t *newcontents,
227 chipoff_t rstart, chipoff_t rend)
228 {
229 struct eraseblock_data *ll = &layout[findex].layout_list[block_num];
230 if (!findex) {
231 if (ll->start_addr >= rstart && ll->end_addr <= rend) {
232 chipoff_t start_addr = ll->start_addr;
233 chipoff_t end_addr = ll->end_addr;
234 const chipsize_t erase_len = end_addr - start_addr + 1;
235 const uint8_t erased_value = ERASED_VALUE(flashctx);
236 ll->selected = need_erase(curcontents + start_addr, newcontents + start_addr, erase_len,
237 flashctx->chip->gran, erased_value);
238 }
239 } else {
240 int count = 0;
241 const int sub_block_start = ll->first_sub_block_index;
242 const int sub_block_end = ll->last_sub_block_index;
243
244 for (int j = sub_block_start; j <= sub_block_end; j++) {
245 select_erase_functions(flashctx, layout, findex - 1, j, curcontents, newcontents,
246 rstart, rend);
247 if (layout[findex - 1].layout_list[j].selected)
248 count++;
249 }
250
251 const int total_blocks = sub_block_end - sub_block_start + 1;
252 if (count == total_blocks) {
253 /* We are selecting one large block instead, so send opcode once
254 * instead of sending many smaller ones.
255 */
256 if (ll->start_addr >= rstart && ll->end_addr <= rend) {
257 /* Deselect all smaller blocks covering the same region. */
258 deselect_erase_functions(layout,
259 findex - 1,
260 sub_block_start,
261 sub_block_end);
262
263 /* Select large block. */
264 ll->selected = true;
265 }
266 }
267 }
268 }
269
270 /*
271 * @brief wrapper to use the erase algorithm
272 *
273 * @param flashctx flash context
274 * @param region_start start address of the region
275 * @param region_end end address of the region
276 * @param curcontents buffer containg the current contents of the flash
277 * @param newcontents buffer containg the new contents of the flash
278 * @param erase_layout erase layout
279 * @param all_skipped pointer to the flag to chec if any block was erased
280 */
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)281 int erase_write(struct flashctx *const flashctx, chipoff_t region_start, chipoff_t region_end,
282 uint8_t *curcontents, uint8_t *newcontents,
283 struct erase_layout *erase_layout, bool *all_skipped)
284 {
285 const size_t erasefn_count = count_usable_erasers(flashctx);
286 int ret = 0;
287 size_t i;
288 chipoff_t old_start = region_start, old_end = region_end;
289 align_region(erase_layout, flashctx, ®ion_start, ®ion_end);
290
291 uint8_t *old_start_buf = NULL, *old_end_buf = NULL;
292 const size_t start_buf_len = old_start - region_start;
293 const size_t end_buf_len = region_end - old_end;
294
295 if (start_buf_len) {
296 old_start_buf = (uint8_t *)malloc(start_buf_len);
297 if (!old_start_buf) {
298 msg_cerr("Not enough memory!\n");
299 ret = -1;
300 goto _end;
301 }
302 read_flash(flashctx, curcontents + region_start, region_start, start_buf_len);
303 memcpy(old_start_buf, newcontents + region_start, start_buf_len);
304 memcpy(newcontents + region_start, curcontents + region_start, start_buf_len);
305 }
306 if (end_buf_len) {
307 chipoff_t end_offset = old_end + 1;
308 old_end_buf = (uint8_t *)malloc(end_buf_len);
309 if (!old_end_buf) {
310 msg_cerr("Not enough memory!\n");
311 ret = -1;
312 goto _end;
313 }
314 read_flash(flashctx, curcontents + end_offset, end_offset, end_buf_len);
315 memcpy(old_end_buf, newcontents + end_offset, end_buf_len);
316 memcpy(newcontents + end_offset, curcontents + end_offset, end_buf_len);
317 }
318
319 // select erase functions
320 for (i = 0; i < erase_layout[erasefn_count - 1].block_count; i++) {
321 if (erase_layout[erasefn_count - 1].layout_list[i].start_addr <= region_end &&
322 region_start <= erase_layout[erasefn_count - 1].layout_list[i].end_addr)
323 select_erase_functions(flashctx, erase_layout,
324 erasefn_count - 1, i,
325 curcontents, newcontents,
326 region_start, region_end);
327 }
328
329 for (i = 0; i < erasefn_count; i++) {
330 for (size_t j = 0; j < erase_layout[i].block_count; j++) {
331 if (!erase_layout[i].layout_list[j].selected) continue;
332
333 // erase
334 chipoff_t start_addr = erase_layout[i].layout_list[j].start_addr;
335 unsigned int block_len = erase_layout[i].layout_list[j].end_addr - start_addr + 1;
336 const uint8_t erased_value = ERASED_VALUE(flashctx);
337 // execute erase
338 erasefunc_t *erasefn = lookup_erase_func_ptr(erase_layout[i].eraser);
339
340 if (!flashctx->flags.skip_unwritable_regions) {
341 if (check_for_unwritable_regions(flashctx, start_addr, block_len))
342 goto _end;
343 }
344
345 unsigned int len;
346 for (unsigned int addr = start_addr; addr < start_addr + block_len; addr += len) {
347 struct flash_region region;
348 get_flash_region(flashctx, addr, ®ion);
349
350 len = min(start_addr + block_len, region.end) - addr;
351
352 if (region.write_prot) {
353 msg_gdbg("%s: cannot erase inside %s "
354 "region (%#08"PRIx32"..%#08"PRIx32"), skipping range (%#08x..%#08x).\n",
355 __func__, region.name,
356 region.start, region.end - 1,
357 addr, addr + len - 1);
358 free(region.name);
359 continue;
360 }
361
362 msg_gdbg("%s: %s region (%#08"PRIx32"..%#08"PRIx32") is "
363 "writable, erasing range (%#08x..%#08x).\n",
364 __func__, region.name,
365 region.start, region.end - 1,
366 addr, addr + len - 1);
367 free(region.name);
368
369 if (erasefn(flashctx, addr, len)) {
370 ret = -1;
371 goto _end;
372 }
373 if (check_erased_range(flashctx, addr, len)) {
374 ret = - 1;
375 msg_cerr("ERASE FAILED!\n");
376 goto _end;
377 }
378 }
379
380 // adjust curcontents
381 memset(curcontents+start_addr, erased_value, block_len);
382 // after erase make it unselected again
383 erase_layout[i].layout_list[j].selected = false;
384 msg_cdbg("E(%"PRIx32":%"PRIx32")", start_addr, start_addr + block_len - 1);
385
386 *all_skipped = false;
387 }
388 }
389
390 // write
391 unsigned int start_here = 0, len_here = 0, erase_len = region_end - region_start + 1;
392 while ((len_here = get_next_write(curcontents + region_start + start_here,
393 newcontents + region_start + start_here,
394 erase_len - start_here, &start_here,
395 flashctx->chip->gran))) {
396 // execute write
397 ret = write_flash(flashctx,
398 newcontents + region_start + start_here,
399 region_start + start_here, len_here);
400 if (ret) {
401 msg_cerr("Write failed at %#zx, Abort.\n", i);
402 ret = -1;
403 goto _end;
404 }
405
406 // adjust curcontents
407 memcpy(curcontents + region_start + start_here,
408 newcontents + region_start + start_here, len_here);
409 msg_cdbg("W(%"PRIx32":%"PRIx32")", region_start + start_here, region_start + start_here + len_here - 1);
410
411 *all_skipped = false;
412 }
413
414 _end:
415 if (old_start_buf) {
416 memcpy(newcontents + region_start, old_start_buf, start_buf_len);
417 free(old_start_buf);
418 }
419
420 if (old_end_buf) {
421 memcpy(newcontents + old_end, old_end_buf, end_buf_len);
422 free(old_end_buf);
423 }
424
425 msg_cinfo("Erase/write done from %"PRIx32" to %"PRIx32"\n", region_start, region_end);
426 return ret;
427 }
428