1 /*
2 * Testing block device, wraps filebd and rambd while providing a bunch
3 * of hooks for testing littlefs in various conditions.
4 *
5 * Copyright (c) 2022, The littlefs authors.
6 * Copyright (c) 2017, Arm Limited. All rights reserved.
7 * SPDX-License-Identifier: BSD-3-Clause
8 */
9 #include "bd/lfs_testbd.h"
10
11 #include <stdlib.h>
12
13
lfs_testbd_createcfg(const struct lfs_config * cfg,const char * path,const struct lfs_testbd_config * bdcfg)14 int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
15 const struct lfs_testbd_config *bdcfg) {
16 LFS_TESTBD_TRACE("lfs_testbd_createcfg(%p {.context=%p, "
17 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
18 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
19 ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
20 "\"%s\", "
21 "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", "
22 ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", "
23 ".buffer=%p, .wear_buffer=%p})",
24 (void*)cfg, cfg->context,
25 (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
26 (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
27 cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
28 path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles,
29 bdcfg->badblock_behavior, bdcfg->power_cycles,
30 bdcfg->buffer, bdcfg->wear_buffer);
31 lfs_testbd_t *bd = cfg->context;
32 bd->cfg = bdcfg;
33
34 // setup testing things
35 bd->persist = path;
36 bd->power_cycles = bd->cfg->power_cycles;
37
38 if (bd->cfg->erase_cycles) {
39 if (bd->cfg->wear_buffer) {
40 bd->wear = bd->cfg->wear_buffer;
41 } else {
42 bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t)*cfg->block_count);
43 if (!bd->wear) {
44 LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM);
45 return LFS_ERR_NOMEM;
46 }
47 }
48
49 memset(bd->wear, 0, sizeof(lfs_testbd_wear_t) * cfg->block_count);
50 }
51
52 // create underlying block device
53 if (bd->persist) {
54 bd->u.file.cfg = (struct lfs_filebd_config){
55 .erase_value = bd->cfg->erase_value,
56 };
57 int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg);
58 LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err);
59 return err;
60 } else {
61 bd->u.ram.cfg = (struct lfs_rambd_config){
62 .erase_value = bd->cfg->erase_value,
63 .buffer = bd->cfg->buffer,
64 };
65 int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg);
66 LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err);
67 return err;
68 }
69 }
70
lfs_testbd_create(const struct lfs_config * cfg,const char * path)71 int lfs_testbd_create(const struct lfs_config *cfg, const char *path) {
72 LFS_TESTBD_TRACE("lfs_testbd_create(%p {.context=%p, "
73 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
74 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
75 ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
76 "\"%s\")",
77 (void*)cfg, cfg->context,
78 (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
79 (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
80 cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
81 path);
82 static const struct lfs_testbd_config defaults = {.erase_value=-1};
83 int err = lfs_testbd_createcfg(cfg, path, &defaults);
84 LFS_TESTBD_TRACE("lfs_testbd_create -> %d", err);
85 return err;
86 }
87
lfs_testbd_destroy(const struct lfs_config * cfg)88 int lfs_testbd_destroy(const struct lfs_config *cfg) {
89 LFS_TESTBD_TRACE("lfs_testbd_destroy(%p)", (void*)cfg);
90 lfs_testbd_t *bd = cfg->context;
91 if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) {
92 lfs_free(bd->wear);
93 }
94
95 if (bd->persist) {
96 int err = lfs_filebd_destroy(cfg);
97 LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err);
98 return err;
99 } else {
100 int err = lfs_rambd_destroy(cfg);
101 LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err);
102 return err;
103 }
104 }
105
106 /// Internal mapping to block devices ///
lfs_testbd_rawread(const struct lfs_config * cfg,lfs_block_t block,lfs_off_t off,void * buffer,lfs_size_t size)107 static int lfs_testbd_rawread(const struct lfs_config *cfg, lfs_block_t block,
108 lfs_off_t off, void *buffer, lfs_size_t size) {
109 lfs_testbd_t *bd = cfg->context;
110 if (bd->persist) {
111 return lfs_filebd_read(cfg, block, off, buffer, size);
112 } else {
113 return lfs_rambd_read(cfg, block, off, buffer, size);
114 }
115 }
116
lfs_testbd_rawprog(const struct lfs_config * cfg,lfs_block_t block,lfs_off_t off,const void * buffer,lfs_size_t size)117 static int lfs_testbd_rawprog(const struct lfs_config *cfg, lfs_block_t block,
118 lfs_off_t off, const void *buffer, lfs_size_t size) {
119 lfs_testbd_t *bd = cfg->context;
120 if (bd->persist) {
121 return lfs_filebd_prog(cfg, block, off, buffer, size);
122 } else {
123 return lfs_rambd_prog(cfg, block, off, buffer, size);
124 }
125 }
126
lfs_testbd_rawerase(const struct lfs_config * cfg,lfs_block_t block)127 static int lfs_testbd_rawerase(const struct lfs_config *cfg,
128 lfs_block_t block) {
129 lfs_testbd_t *bd = cfg->context;
130 if (bd->persist) {
131 return lfs_filebd_erase(cfg, block);
132 } else {
133 return lfs_rambd_erase(cfg, block);
134 }
135 }
136
lfs_testbd_rawsync(const struct lfs_config * cfg)137 static int lfs_testbd_rawsync(const struct lfs_config *cfg) {
138 lfs_testbd_t *bd = cfg->context;
139 if (bd->persist) {
140 return lfs_filebd_sync(cfg);
141 } else {
142 return lfs_rambd_sync(cfg);
143 }
144 }
145
146 /// block device API ///
lfs_testbd_read(const struct lfs_config * cfg,lfs_block_t block,lfs_off_t off,void * buffer,lfs_size_t size)147 int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
148 lfs_off_t off, void *buffer, lfs_size_t size) {
149 LFS_TESTBD_TRACE("lfs_testbd_read(%p, "
150 "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
151 (void*)cfg, block, off, buffer, size);
152 lfs_testbd_t *bd = cfg->context;
153
154 // check if read is valid
155 LFS_ASSERT(off % cfg->read_size == 0);
156 LFS_ASSERT(size % cfg->read_size == 0);
157 LFS_ASSERT(block < cfg->block_count);
158
159 // block bad?
160 if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles &&
161 bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) {
162 LFS_TESTBD_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT);
163 return LFS_ERR_CORRUPT;
164 }
165
166 // read
167 int err = lfs_testbd_rawread(cfg, block, off, buffer, size);
168 LFS_TESTBD_TRACE("lfs_testbd_read -> %d", err);
169 return err;
170 }
171
lfs_testbd_prog(const struct lfs_config * cfg,lfs_block_t block,lfs_off_t off,const void * buffer,lfs_size_t size)172 int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
173 lfs_off_t off, const void *buffer, lfs_size_t size) {
174 LFS_TESTBD_TRACE("lfs_testbd_prog(%p, "
175 "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
176 (void*)cfg, block, off, buffer, size);
177 lfs_testbd_t *bd = cfg->context;
178
179 // check if write is valid
180 LFS_ASSERT(off % cfg->prog_size == 0);
181 LFS_ASSERT(size % cfg->prog_size == 0);
182 LFS_ASSERT(block < cfg->block_count);
183
184 // block bad?
185 if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) {
186 if (bd->cfg->badblock_behavior ==
187 LFS_TESTBD_BADBLOCK_PROGERROR) {
188 LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT);
189 return LFS_ERR_CORRUPT;
190 } else if (bd->cfg->badblock_behavior ==
191 LFS_TESTBD_BADBLOCK_PROGNOOP ||
192 bd->cfg->badblock_behavior ==
193 LFS_TESTBD_BADBLOCK_ERASENOOP) {
194 LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
195 return 0;
196 }
197 }
198
199 // prog
200 int err = lfs_testbd_rawprog(cfg, block, off, buffer, size);
201 if (err) {
202 LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err);
203 return err;
204 }
205
206 // lose power?
207 if (bd->power_cycles > 0) {
208 bd->power_cycles -= 1;
209 if (bd->power_cycles == 0) {
210 // sync to make sure we persist the last changes
211 LFS_ASSERT(lfs_testbd_rawsync(cfg) == 0);
212 // simulate power loss
213 exit(33);
214 }
215 }
216
217 LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
218 return 0;
219 }
220
lfs_testbd_erase(const struct lfs_config * cfg,lfs_block_t block)221 int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
222 LFS_TESTBD_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
223 lfs_testbd_t *bd = cfg->context;
224
225 // check if erase is valid
226 LFS_ASSERT(block < cfg->block_count);
227
228 // block bad?
229 if (bd->cfg->erase_cycles) {
230 if (bd->wear[block] >= bd->cfg->erase_cycles) {
231 if (bd->cfg->badblock_behavior ==
232 LFS_TESTBD_BADBLOCK_ERASEERROR) {
233 LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT);
234 return LFS_ERR_CORRUPT;
235 } else if (bd->cfg->badblock_behavior ==
236 LFS_TESTBD_BADBLOCK_ERASENOOP) {
237 LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", 0);
238 return 0;
239 }
240 } else {
241 // mark wear
242 bd->wear[block] += 1;
243 }
244 }
245
246 // erase
247 int err = lfs_testbd_rawerase(cfg, block);
248 if (err) {
249 LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", err);
250 return err;
251 }
252
253 // lose power?
254 if (bd->power_cycles > 0) {
255 bd->power_cycles -= 1;
256 if (bd->power_cycles == 0) {
257 // sync to make sure we persist the last changes
258 LFS_ASSERT(lfs_testbd_rawsync(cfg) == 0);
259 // simulate power loss
260 exit(33);
261 }
262 }
263
264 LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
265 return 0;
266 }
267
lfs_testbd_sync(const struct lfs_config * cfg)268 int lfs_testbd_sync(const struct lfs_config *cfg) {
269 LFS_TESTBD_TRACE("lfs_testbd_sync(%p)", (void*)cfg);
270 int err = lfs_testbd_rawsync(cfg);
271 LFS_TESTBD_TRACE("lfs_testbd_sync -> %d", err);
272 return err;
273 }
274
275
276 /// simulated wear operations ///
lfs_testbd_getwear(const struct lfs_config * cfg,lfs_block_t block)277 lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg,
278 lfs_block_t block) {
279 LFS_TESTBD_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block);
280 lfs_testbd_t *bd = cfg->context;
281
282 // check if block is valid
283 LFS_ASSERT(bd->cfg->erase_cycles);
284 LFS_ASSERT(block < cfg->block_count);
285
286 LFS_TESTBD_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]);
287 return bd->wear[block];
288 }
289
lfs_testbd_setwear(const struct lfs_config * cfg,lfs_block_t block,lfs_testbd_wear_t wear)290 int lfs_testbd_setwear(const struct lfs_config *cfg,
291 lfs_block_t block, lfs_testbd_wear_t wear) {
292 LFS_TESTBD_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block);
293 lfs_testbd_t *bd = cfg->context;
294
295 // check if block is valid
296 LFS_ASSERT(bd->cfg->erase_cycles);
297 LFS_ASSERT(block < cfg->block_count);
298
299 bd->wear[block] = wear;
300
301 LFS_TESTBD_TRACE("lfs_testbd_setwear -> %d", 0);
302 return 0;
303 }
304