blob: f37c4acce6cbdb705d1ff4b9426b539284a1f13c [file] [log] [blame]
Albert Chaulk534723a2013-03-20 14:46:50 -07001/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6/* *** THIS CODE HAS NOT BEEN SECURITY REVIEWED ***
7 * It lives in the firmware directory because that's where it needs to go
8 * eventually, but at the moment it is used only by usermode tools.
9 * Security review must be completed before this code is used in the
10 * firmware.
11 * See issue 246680
12 */
13
14#include "flash_ts.h"
15
Alex Deymof5109732014-08-25 16:34:52 -070016#include <errno.h>
17#include <stdio.h>
18#include <string.h>
19
Albert Chaulk534723a2013-03-20 14:46:50 -070020#include "utility.h"
21
22// These match the linux driver
23#define FLASH_TS_MAGIC 0x53542a46
24
Albert Chaulk92f22e72013-04-02 13:20:52 -070025#define FLASH_TS_HEADER_SIZE 16
Albert Chaulk534723a2013-03-20 14:46:50 -070026#define FLASH_TS_MAX_SIZE 16384
27#define FLASH_TS_MAX_ELEMENT_SIZE (FLASH_TS_MAX_SIZE - FLASH_TS_HEADER_SIZE)
28
29typedef struct {
30 uint32_t magic;
31 uint32_t crc;
32 uint32_t length;
33 uint32_t version;
34 char data[FLASH_TS_MAX_ELEMENT_SIZE];
35} __attribute__((packed)) flash_ts;
36
37typedef struct {
38 size_t start_block; // Partition start offset (in erase blocks)
39 size_t end_block; // Partition end offset (in erase blocks)
40 size_t chunk_size; // Minimum element size
41 size_t pages_per_block, chunks_per_block, pages_per_chunk;
42 nand_geom nand;
43
44 size_t cached_block;
45 size_t current_block;
46
47 flash_ts current;
48 flash_ts temp;
49} flash_ts_state;
50
51
52static flash_ts_state state;
53
54size_t pow2(size_t x) {
55 size_t v = 1;
56 while (v < x)
57 v <<= 1;
58 return v;
59}
60
61static inline uint32_t flash_ts_crc(const flash_ts *cache)
62{
63 const unsigned char *p;
64 uint32_t crc = 0;
65 size_t len;
66
67 /* skip magic and crc fields */
68 len = cache->length + 2 * sizeof(uint32_t);
69 p = (const unsigned char*)&cache->length;
70
71 while (len--) {
72 int i;
73
74 crc ^= *p++;
75 for (i = 0; i < 8; i++)
76 crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
77 }
78 return crc ^ ~0;
79}
80
81static inline int flash_ts_check_crc(const flash_ts *ts) {
82 return ts->crc == flash_ts_crc(ts);
83}
84
85static int is_blank(const void *ptr, size_t sz) {
86 const unsigned char *p = (const unsigned char*)ptr;
87 const unsigned char *end = p + sz;
88 while (p < end)
89 if (*p++ != 0xff)
90 return 0;
91 return 1;
92}
93
94static int is_pow2(size_t v) {
95 return v && (v & (v - 1)) == 0;
96}
97
98/* Scan the entire partition to find the latest version */
99static void flash_ts_scan_partition(flash_ts_state *ts) {
100 size_t block;
101
102 for (block = ts->start_block; block < ts->end_block; block++) {
103 if (!nand_is_bad_block(&ts->nand, block)) {
104 size_t chunk;
105 size_t page_base = block * ts->pages_per_block;
106
107 for (chunk = 0; chunk < ts->chunks_per_block;
108 chunk++, page_base += ts->pages_per_chunk) {
109 if (nand_read_page(&ts->nand, page_base,
110 &ts->temp, sizeof(ts->temp))) {
111 continue;
112 }
113 if (ts->temp.magic != FLASH_TS_MAGIC ||
114 ts->temp.version <= ts->current.version ||
115 !flash_ts_check_crc(&ts->temp)) {
116 if (is_blank(&ts->temp, sizeof(ts->temp))) {
117 // Since we only write sequentially, a blank chunk means no more
118 // data in this block.
119 break;
120 }
121 continue;
122 }
123
124 // It's good & newer than our current version
Albert Chaulk92f22e72013-04-02 13:20:52 -0700125 VBDEBUG(("Found good version %d\n", ts->temp.version));
Albert Chaulk534723a2013-03-20 14:46:50 -0700126 ts->current_block = block;
127 Memcpy(&ts->current, &ts->temp, sizeof(ts->current));
128 }
129 }
130 }
131}
132
133static char *flash_ts_search(flash_ts *ts, const char *key) {
134 char *str = &ts->data[0];
135 size_t keylen = strlen(key);
136
137 while(*str && str + keylen < &ts->data[ts->length]) {
138 // Format: name=value\0name2=value2\0 ... keyn=valuen\0\0
139 if (!Memcmp(str, key, keylen) && str[keylen] == '=') {
140 return &str[keylen + 1];
141 } else {
142 str += strlen(str) + 1; // Skip to next key
143 }
144 }
145 return NULL;
146}
147
148static int flash_ts_find_writeable_chunk(flash_ts_state *ts, uint32_t block) {
149 uint32_t page_base = block * ts->pages_per_block;
150 uint32_t page_end = (block + 1) * ts->pages_per_block;
151
152 for(; page_base < page_end; page_base += ts->pages_per_chunk) {
153 if(!nand_read_page(&ts->nand, page_base,
154 &ts->temp, sizeof(ts->temp))) {
155 if (is_blank(&ts->temp, sizeof(ts->temp)))
156 return page_base;
157 }
158 }
159
160 return -1;
161}
162
163static int in_range(const flash_ts_state *ts, uint32_t block) {
164 return block >= ts->start_block && block < ts->end_block;
165}
166
167static int flash_try_write(flash_ts_state *ts, uint32_t page) {
168 return nand_write_page(&ts->nand, page, &ts->current, sizeof(ts->current)) ||
169 nand_read_page(&ts->nand, page, &ts->temp, sizeof(ts->temp)) ||
170 Memcmp(&ts->current, &ts->temp, sizeof(ts->current));
171}
172
173
174static int flash_ts_find_writeable_spot(flash_ts_state *ts,
175 uint32_t *page_ofs) {
176 uint32_t block;
177 if (in_range(ts, ts->cached_block)) {
178 // We have a starting position to scan from
179 block = ts->cached_block;
180 } else {
181 block = ts->start_block;
182 VBDEBUG(("Cached block not in range - starting from %u\n", block));
183 }
184 for (; block < ts->end_block; block++) {
185 int chunk;
186 if (nand_is_bad_block(&ts->nand, block)) {
187 VBDEBUG(("Skipping bad block %u\n", block));
188 continue;
189 }
190
191 chunk = flash_ts_find_writeable_chunk(ts, block);
192 if (chunk < 0) {
193 VBDEBUG(("No free chunks in block %u\n", block));
194 continue;
195 }
196
197 VBDEBUG(("Free chunk %d in block %u\n", chunk, block));
198 *page_ofs = chunk;
199 ts->cached_block = block;
200 return 0;
201 }
202 return -1;
203}
204
205static int flash_try_erase(flash_ts_state *ts, int block) {
206 return nand_is_bad_block(&ts->nand, block) ||
207 nand_erase_block(&ts->nand, block);
208}
209
210static int flash_erase_any_block(flash_ts_state *ts, uint32_t hint) {
211 uint32_t block;
212 for (block = hint; block < ts->end_block; block++) {
213 if (!flash_try_erase(ts, block)) {
214 ts->cached_block = block;
215 VBDEBUG(("Erased block %u\n", block));
216 return 0;
217 }
218 }
219
220 if (hint > ts->end_block)
221 hint = ts->end_block;
222
223 for (block = ts->start_block; block < hint; block++) {
224 if (!flash_try_erase(ts, block)) {
225 ts->cached_block = block;
226 VBDEBUG(("Erased block %u\n", block));
227 return 0;
228 }
229 }
230 return -1;
231}
232
233static int flash_ts_write(flash_ts_state *ts) {
234 int passes = 3;
235 uint32_t page;
236
237
238 ts->cached_block = ts->current_block;
239 ts->current.version++;
240 ts->current.crc = flash_ts_crc(&ts->current);
241 VBDEBUG(("flash_ts_write() - %u bytes, crc %08X\n",
242 ts->current.length, ts->current.crc));
243
244 while(passes--) {
245 if (flash_ts_find_writeable_spot(ts, &page)) {
246 if (ts->cached_block == ts->end_block) {
247 uint32_t block;
248
249 // Partition full!
250 // Erase a block to get some space
251 if (in_range(ts, ts->current_block) &&
252 ts->current_block != ts->end_block - 1) {
253 // We don't want to overwrite our good copy if we can avoid it.
254 block = ts->current_block + 1;
255 } else {
256 block = ts->start_block;
257 }
258 VBDEBUG(("Partition full - begin erasing from block %u\n", block));
259
260 // Erase block, and try again.
261 if (flash_erase_any_block(ts, block)) {
262 // Failed to erase anything, so abort.
263 VBDEBUG(("All erases failed, aborting\n"));
264 return -ENOMEM;
265 }
266 continue;
267 } else {
268 // Try again, re-scan everything.
269 ts->cached_block = ts->end_block;
270 continue;
271 }
272 }
273
274 if (flash_try_write(ts, page)) {
275 // Write failure, or read-back failure, try again with the next block.
276 VBDEBUG(("Write failure, retry\n"));
277 ts->cached_block++;
278 continue;
279 }
280
281 VBDEBUG(("Successfully written v%u @ %u\n", ts->current.version, page));
282 ts->current_block = ts->cached_block;
283 return 0;
284 }
285
286 VBDEBUG(("Out of tries\n"));
287 return -EAGAIN;
288}
289
290// Set value, returns 0 on success
291int flash_ts_set(const char *key, const char *value) {
292 flash_ts *ts = &state.current;
293 char *at;
294 size_t keylen = strlen(key);
295 size_t value_len = strlen(value);
296
297 if (keylen == 0) {
298 VBDEBUG(("0-length key - illegal\n"));
299 return -1;
300 }
301
302 if (strchr(key, '=')) {
303 VBDEBUG(("key contains '=' - illegal\n"));
304 return -1;
305 }
306
307 Memcpy(&state.temp, &state.current, sizeof(state.temp));
308
309 at = flash_ts_search(ts, key);
310 if (at) {
311 size_t old_value_len;
312
313 // Already exists
314 if (!strcmp(at, value)) {
315 // No change
316 VBDEBUG(("Values are the same, not writing\n"));
317 return 0;
318 }
319
320 old_value_len = strlen(at);
321 if (value_len == old_value_len) {
322 // Overwrite it
323 Memcpy(at, value, value_len);
324 VBDEBUG(("Values are the same length, overwrite\n"));
325 } else {
326 // Remove it
327 // if value_len == 0, then we're done
328 // if value_len != old_value_len, then we do the append below
329 char *src = at - (keylen + 1);
330 char *end = &ts->data[ts->length];
331 char *from = at + old_value_len + 1;
332
333 VBDEBUG(("Delete old value\n"));
334 memmove(src, from, end - from);
335 ts->length -= (from-src);
336 ts->data[ts->length - 1] = '\0';
337 at = NULL; // Enter the append branch below
338 }
339 } else if (value_len == 0) {
340 // Removing non-existent entry
341 return 0;
342 }
343
344 if (!at && value_len > 0) {
345 // Append it
346
347 if (ts->length + keylen + 1 + value_len + 1 > FLASH_TS_MAX_ELEMENT_SIZE) {
348 // Not enough space, restore previous
Albert Chaulk92f22e72013-04-02 13:20:52 -0700349 VBDEBUG(("Not enough space to write %d data bytes\n", (int)value_len));
Albert Chaulk534723a2013-03-20 14:46:50 -0700350 Memcpy(&state.current, &state.temp, sizeof(state.temp));
351 return -1;
352 }
353
354 VBDEBUG(("Append new value\n"));
355 at = &ts->data[ts->length - 1];
356 strcpy(at, key);
357 at[keylen] = '=';
358 strcpy(at + keylen + 1, value);
359 ts->length += keylen + 1 + value_len + 1;
360 ts->data[ts->length-1] = '\0';
361 }
362
363 return flash_ts_write(&state);
364}
365
366void flash_ts_get(const char *key, char *value, unsigned int size) {
367 flash_ts_state *ts = &state;
368 const char *at;
369
370 at = flash_ts_search(&ts->current, key);
371 if (at) {
372 strncpy(value, at, size);
373 } else {
374 *value = '\0';
375 }
376}
377
378int flash_ts_init(unsigned int start_block, unsigned int blocks,
379 unsigned int szofpg, unsigned int szofblk,
380 unsigned int szofsector, void *user) {
381 flash_ts_state *ts = &state;
382
383 if (!is_pow2(szofpg) || !is_pow2(szofblk) || !is_pow2(szofsector) ||
384 szofsector > szofpg || szofpg > szofblk || blocks == 0)
385 return -ENODEV;
386
387 Memset(ts, 0, sizeof(*ts));
388
389 // Page <= chunk <= block
390 // Page is minimum writable unit
391 // Chunk is actual write unit
392 // Block is erase unit
393 ts->start_block = start_block;
394 ts->end_block = start_block + blocks;
395 ts->pages_per_block = szofblk / szofpg;
396
397 ts->nand.user = user;
398 ts->nand.szofpg = szofpg;
399 ts->nand.szofblk = szofblk;
400 ts->nand.szofsector = szofsector;
401
402 // Calculate our write size, this mirrors the linux driver's logic
403 ts->chunk_size = pow2((sizeof(flash_ts) + szofpg - 1) & ~(szofpg - 1));
404 if (!is_pow2(ts->chunk_size))
405 return -ENODEV;
406
407 ts->pages_per_chunk = ts->chunk_size / szofpg;
408 if (ts->pages_per_chunk == 0 || ts->chunk_size > szofblk)
409 return -ENODEV;
410
411 ts->chunks_per_block = szofblk / ts->chunk_size;
412
413 ts->current.version = 0;
414 ts->current.length = 1;
415 ts->current.magic = FLASH_TS_MAGIC;
416 ts->current.crc = flash_ts_crc(&ts->current);
417 ts->current.data[0] = '\0';
418 ts->current_block = ts->end_block;
419
420 flash_ts_scan_partition(ts);
421
422 return 0;
423}