blob: dcfc663496a86fccd63435db6f97395b13b1f6e5 [file] [log] [blame]
Angel Pons118a9c72020-04-02 23:48:34 +02001/* SPDX-License-Identifier: GPL-2.0-only */
2/* This file is part of the coreboot project. */
Aaron Durbincd0bc982016-11-19 12:36:09 -06003
4#include <commonlib/helpers.h>
5#include <console/console.h>
6#include <region_file.h>
7#include <string.h>
8
9/*
10 * A region file provides generic support for appending new data
11 * within a storage region. The book keeping is tracked in metadata
12 * blocks where an offset pointer points to the last byte of a newly
13 * allocated byte sequence. Thus, by taking 2 block offets one can
14 * determine start and size of the latest update. The data does not
15 * have to be the same consistent size, but the data size has be small
16 * enough to fit a metadata block and one data write within the region.
17 *
18 * The granularity of the block offsets are 16 bytes. By using 16-bit
19 * block offsets a region's total size can be no larger than 1MiB.
20 * However, the last 32 bytes cannot be used in the 1MiB maximum region
21 * because one needs to put a block offset indicating last byte written.
22 * An unused block offset is the value 0xffff or 0xffff0 bytes. The last
23 * block offset that can be written is 0xfffe or 0xfffe0 byte offset.
24 *
25 * The goal of this library is to provide a simple mechanism for
26 * allocating blocks of data for updates. The metadata is written first
27 * followed by the data. That means a power event between the block offset
28 * write and the data write results in blocks being allocated but not
29 * entirely written. It's up to the user of the library to sanity check
30 * data stored.
31 */
32
33#define REGF_BLOCK_SHIFT 4
34#define REGF_BLOCK_GRANULARITY (1 << REGF_BLOCK_SHIFT)
35#define REGF_METADATA_BLOCK_SIZE REGF_BLOCK_GRANULARITY
36#define REGF_UNALLOCATED_BLOCK 0xffff
37#define REGF_UPDATES_PER_METADATA_BLOCK \
38 (REGF_METADATA_BLOCK_SIZE / sizeof(uint16_t))
39
40enum {
41 RF_ONLY_METADATA = 0,
42 RF_EMPTY = -1,
43 RF_NEED_TO_EMPTY = -2,
44 RF_FATAL = -3,
45};
46
47struct metadata_block {
48 uint16_t blocks[REGF_UPDATES_PER_METADATA_BLOCK];
49};
50
51static size_t block_to_bytes(uint16_t offset)
52{
53 return (size_t)offset << REGF_BLOCK_SHIFT;
54}
55
56static size_t bytes_to_block(size_t bytes)
57{
58 return bytes >> REGF_BLOCK_SHIFT;
59}
60
61static inline int block_offset_unallocated(uint16_t offset)
62{
63 return offset == REGF_UNALLOCATED_BLOCK;
64}
65
66static inline size_t region_file_data_begin(const struct region_file *f)
67{
68 return f->data_blocks[0];
69}
70
71static inline size_t region_file_data_end(const struct region_file *f)
72{
73 return f->data_blocks[1];
74}
75
76static int all_block_offsets_unallocated(const struct metadata_block *mb)
77{
78 size_t i;
79
80 for (i = 0; i < ARRAY_SIZE(mb->blocks); i++) {
81 if (!block_offset_unallocated(mb->blocks[i]))
82 return 0;
83 }
84
85 return 1;
86}
87
88/* Read metadata block at block i. */
89static int read_mb(size_t i, struct metadata_block *mb,
90 const struct region_file *f)
91{
92 size_t offset = block_to_bytes(i);
93
94 if (rdev_readat(&f->metadata, mb, offset, sizeof(*mb)) < 0)
95 return -1;
96
97 return 0;
98}
99
100/* Locate metadata block with the latest update */
101static int find_latest_mb(struct metadata_block *mb, size_t num_mb_blocks,
102 struct region_file *f)
103{
104 size_t l = 0;
105 size_t r = num_mb_blocks;
106
107 while (l + 1 < r) {
108 size_t mid = (l + r) / 2;
109
110 if (read_mb(mid, mb, f) < 0)
111 return -1;
112 if (all_block_offsets_unallocated(mb))
113 r = mid;
114 else
115 l = mid;
116 }
117
118 /* Set the base block slot. */
119 f->slot = l * REGF_UPDATES_PER_METADATA_BLOCK;
120
121 /* Re-read metadata block with the latest update. */
122 if (read_mb(l, mb, f) < 0)
123 return -1;
124
125 return 0;
126}
127
128static void find_latest_slot(struct metadata_block *mb, struct region_file *f)
129{
130 size_t i;
131
132 for (i = REGF_UPDATES_PER_METADATA_BLOCK - 1; i > 0; i--) {
133 if (!block_offset_unallocated(mb->blocks[i]))
134 break;
135 }
136
137 f->slot += i;
138}
139
140static int fill_data_boundaries(struct region_file *f)
141{
142 struct region_device slots;
143 size_t offset;
144 size_t size = sizeof(f->data_blocks);
145
146 if (f->slot == RF_ONLY_METADATA) {
147 size_t start = bytes_to_block(region_device_sz(&f->metadata));
148 f->data_blocks[0] = start;
149 f->data_blocks[1] = start;
150 return 0;
151 }
152
153 /* Sanity check the 2 slot sequence to read. If it's out of the
154 * metadata blocks' bounds then one needs to empty it. This is done
155 * to uniquely identify I/O vs data errors in the readat() below. */
156 offset = (f->slot - 1) * sizeof(f->data_blocks[0]);
157 if (rdev_chain(&slots, &f->metadata, offset, size)) {
158 f->slot = RF_NEED_TO_EMPTY;
159 return 0;
160 }
161
162 if (rdev_readat(&slots, &f->data_blocks, 0, size) < 0) {
163 printk(BIOS_ERR, "REGF failed to read data boundaries.\n");
164 return -1;
165 }
166
167 /* All used blocks should be incrementing from previous write. */
168 if (region_file_data_begin(f) >= region_file_data_end(f)) {
169 printk(BIOS_ERR, "REGF data boundaries wrong. [%zd,%zd) Need to empty.\n",
170 region_file_data_begin(f), region_file_data_end(f));
171 f->slot = RF_NEED_TO_EMPTY;
172 return 0;
173 }
174
175 /* Ensure data doesn't exceed the region. */
176 if (region_file_data_end(f) >
177 bytes_to_block(region_device_sz(&f->rdev))) {
178 printk(BIOS_ERR, "REGF data exceeds region %zd > %zd\n",
179 region_file_data_end(f),
180 bytes_to_block(region_device_sz(&f->rdev)));
181 f->slot = RF_NEED_TO_EMPTY;
182 }
183
184 return 0;
185}
186
187int region_file_init(struct region_file *f, const struct region_device *p)
188{
189 struct metadata_block mb;
190
191 /* Total number of metadata blocks is found by reading the first
192 * block offset as the metadata is allocated first. At least one
193 * metadata block is available. */
194
195 memset(f, 0, sizeof(*f));
196 f->slot = RF_FATAL;
197
198 /* Keep parent around for accessing data later. */
Aaron Durbinb1ea53d2019-11-08 09:51:15 -0700199 if (rdev_chain_full(&f->rdev, p))
Aaron Durbincd0bc982016-11-19 12:36:09 -0600200 return -1;
201
202 if (rdev_readat(p, &mb, 0, sizeof(mb)) < 0) {
203 printk(BIOS_ERR, "REGF fail reading first metadata block.\n");
204 return -1;
205 }
206
207 /* No metadata has been allocated. Assume region is empty. */
208 if (block_offset_unallocated(mb.blocks[0])) {
209 f->slot = RF_EMPTY;
210 return 0;
211 }
212
213 /* If metadata block is 0 in size then need to empty. */
214 if (mb.blocks[0] == 0) {
215 f->slot = RF_NEED_TO_EMPTY;
216 return 0;
217 }
218
219 /* The region needs to be emptied as the metadata is broken. */
220 if (rdev_chain(&f->metadata, p, 0, block_to_bytes(mb.blocks[0]))) {
221 f->slot = RF_NEED_TO_EMPTY;
222 return 0;
223 }
224
225 /* Locate latest metadata block with latest update. */
226 if (find_latest_mb(&mb, mb.blocks[0], f)) {
227 printk(BIOS_ERR, "REGF fail locating latest metadata block.\n");
228 f->slot = RF_FATAL;
229 return -1;
230 }
231
232 find_latest_slot(&mb, f);
233
234 /* Fill in the data blocks marking the latest update. */
235 if (fill_data_boundaries(f)) {
236 printk(BIOS_ERR, "REGF fail locating data boundaries.\n");
237 f->slot = RF_FATAL;
238 return -1;
239 }
240
241 return 0;
242}
243
244int region_file_data(const struct region_file *f, struct region_device *rdev)
245{
246
247 size_t offset;
248 size_t size;
249
250 /* Slot indicates if any data is available. */
251 if (f->slot <= RF_ONLY_METADATA)
252 return -1;
253
254 offset = block_to_bytes(region_file_data_begin(f));
255 size = block_to_bytes(region_file_data_end(f)) - offset;
256
257 return rdev_chain(rdev, &f->rdev, offset, size);
258}
259
260/*
261 * Allocate enough metadata blocks to maximize data updates. Do this in
262 * terms of blocks. To solve the balance of metadata vs data, 2 linear
263 * equations are solved in terms of blocks where 'x' is number of
264 * data updates and 'y' is number of metadata blocks:
265 *
266 * x = number of data updates
267 * y = number of metadata blocks
268 * T = total blocks in region
269 * D = data size in blocks
270 * M = metadata size in blocks
271 * A = updates accounted for in each metadata block
272 *
273 * T = D * x + M * y
274 * y = x / A
275 * -----------------
276 * T = D * x + M * x / A = x * (D + M / A)
277 * T * A = x * (D * A + M)
278 * x = T * A / (D * A + M)
279 */
280static int allocate_metadata(struct region_file *f, size_t data_blks)
281{
282 size_t t, m;
283 size_t x, y;
284 uint16_t tot_metadata;
285 const size_t a = REGF_UPDATES_PER_METADATA_BLOCK;
286 const size_t d = data_blks;
287
288 t = bytes_to_block(ALIGN_DOWN(region_device_sz(&f->rdev),
289 REGF_BLOCK_GRANULARITY));
290 m = bytes_to_block(ALIGN_UP(REGF_METADATA_BLOCK_SIZE,
291 REGF_BLOCK_GRANULARITY));
292
293 /* Ensure at least one data update can fit with 1 metadata block
294 * within the region. */
295 if (d > t - m)
296 return -1;
297
298 /* Maximize number of updates by aligning up to the number updates in
299 * a metadata block. May not really be able to achieve the number of
300 * updates in practice, but it ensures enough metadata blocks are
301 * allocated. */
302 x = ALIGN_UP(t * a / (d * a + m), a);
303
304 /* One data block has to fit. */
305 if (x == 0)
306 x = 1;
307
308 /* Now calculate how many metadata blocks are needed. */
309 y = ALIGN_UP(x, a) / a;
310
311 /* Need to commit the metadata allocation. */
312 tot_metadata = m * y;
313 if (rdev_writeat(&f->rdev, &tot_metadata, 0, sizeof(tot_metadata)) < 0)
314 return -1;
315
316 if (rdev_chain(&f->metadata, &f->rdev, 0,
317 block_to_bytes(tot_metadata)))
318 return -1;
319
320 /* Initialize a 0 data block to start appending from. */
321 f->data_blocks[0] = tot_metadata;
322 f->data_blocks[1] = tot_metadata;
323
324 return 0;
325}
326
327static int update_can_fit(const struct region_file *f, size_t data_blks)
328{
329 size_t metadata_slots;
330 size_t end_blk;
331
332 metadata_slots = region_device_sz(&f->metadata) / sizeof(uint16_t);
333
334 /* No more slots. */
335 if ((size_t)f->slot + 1 >= metadata_slots)
336 return 0;
337
338 /* See where the last block lies from the current one. */
339 end_blk = data_blks + region_file_data_end(f);
340
341 /* Update would have exceeded block addressing. */
342 if (end_blk >= REGF_UNALLOCATED_BLOCK)
343 return 0;
344
345 /* End block exceeds size of region. */
346 if (end_blk > bytes_to_block(region_device_sz(&f->rdev)))
347 return 0;
348
349 return 1;
350}
351
352static int commit_data_allocation(struct region_file *f, size_t data_blks)
353{
354 size_t offset;
355
356 f->slot++;
357
358 offset = f->slot * sizeof(uint16_t);
359 f->data_blocks[0] = region_file_data_end(f);
360 f->data_blocks[1] = region_file_data_begin(f) + data_blks;
361
362 if (rdev_writeat(&f->metadata, &f->data_blocks[1], offset,
363 sizeof(f->data_blocks[1])) < 0)
364 return -1;
365
366 return 0;
367}
368
369static int commit_data(const struct region_file *f, const void *buf,
370 size_t size)
371{
372 size_t offset = block_to_bytes(region_file_data_begin(f));
373 if (rdev_writeat(&f->rdev, buf, offset, size) < 0)
374 return -1;
375 return 0;
376}
377
378static int handle_empty(struct region_file *f, size_t data_blks)
379{
380 if (allocate_metadata(f, data_blks)) {
381 printk(BIOS_ERR, "REGF metadata allocation failed: %zd data blocks %zd total blocks\n",
382 data_blks, bytes_to_block(region_device_sz(&f->rdev)));
383 return -1;
384 }
385
386 f->slot = RF_ONLY_METADATA;
387
388 return 0;
389}
390
391static int handle_need_to_empty(struct region_file *f)
392{
393 if (rdev_eraseat(&f->rdev, 0, region_device_sz(&f->rdev)) < 0) {
394 printk(BIOS_ERR, "REGF empty failed.\n");
395 return -1;
396 }
397
398 f->slot = RF_EMPTY;
399
400 return 0;
401}
402
403static int handle_update(struct region_file *f, size_t blocks, const void *buf,
404 size_t size)
405{
406 if (!update_can_fit(f, blocks)) {
407 printk(BIOS_INFO, "REGF update can't fit. Will empty.\n");
408 f->slot = RF_NEED_TO_EMPTY;
409 return 0;
410 }
411
412 if (commit_data_allocation(f, blocks)) {
413 printk(BIOS_ERR, "REGF failed to commit data allocation.\n");
414 return -1;
415 }
416
417 if (commit_data(f, buf, size)) {
418 printk(BIOS_ERR, "REGF failed to commit data.\n");
419 return -1;
420 }
421
422 return 0;
423}
424
425int region_file_update_data(struct region_file *f, const void *buf, size_t size)
426{
427 int ret;
428 size_t blocks;
429
430 blocks = bytes_to_block(ALIGN_UP(size, REGF_BLOCK_GRANULARITY));
431
432 while (1) {
433 int prev_slot = f->slot;
434
435 switch (f->slot) {
436 case RF_EMPTY:
437 ret = handle_empty(f, blocks);
438 break;
439 case RF_NEED_TO_EMPTY:
440 ret = handle_need_to_empty(f);
441 break;
442 case RF_FATAL:
443 ret = -1;
444 break;
445 default:
446 ret = handle_update(f, blocks, buf, size);
447 break;
448 }
449
450 /* Failing case. No more updates allowed to be attempted. */
451 if (ret) {
452 f->slot = RF_FATAL;
453 break;
454 }
455
Elyes HAOUAS1ec76442018-08-07 12:20:04 +0200456 /* No more state changes and data committed. */
Aaron Durbincd0bc982016-11-19 12:36:09 -0600457 if (f->slot > RF_ONLY_METADATA && prev_slot != f->slot)
458 break;
459 }
460
461 return ret;
462}