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