mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	Also finished migrating tests with test_relocations and test_exhaustion. The issue I was running into when migrating these tests was a lack of flexibility with what you could do with the block devices. It was possible to hack in some hooks for things like bad blocks and power loss, but it wasn't clean or easily extendable. The solution here was to just put all of these test extensions into a third block device, testbd, that uses the other two example block devices internally. testbd has several useful features for testing. Note this makes it a pretty terrible block device _example_ since these hooks look more complicated than a block device needs to be. - testbd can simulate different erase values, supporting 1s, 0s, other byte patterns, or no erases at all (which can cause surprising bugs). This actually depends on the simulated erase values in ramdb and filebd. I did try to move this out of rambd/filebd, but it's not possible to simulate erases in testbd without buffering entire blocks and creating an excessive amount of extra write operations. - testbd also helps simulate power-loss by containing a "power cycles" counter that is decremented every write operation until it calls exit. This is notably faster than the previous gdb approach, which is valuable since the reentrant tests tend to take a while to resolve. - testbd also tracks wear, which can be manually set and read. This is very useful for testing things like bad block handling, wear leveling, or even changing the effective size of the block device at runtime.
		
			
				
	
	
		
			290 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Testing block device, wraps filebd and rambd while providing a bunch
 | |
|  * of hooks for testing littlefs in various conditions.
 | |
|  *
 | |
|  * Copyright (c) 2017, Arm Limited. All rights reserved.
 | |
|  * SPDX-License-Identifier: BSD-3-Clause
 | |
|  */
 | |
| #include "testbd/lfs_testbd.h"
 | |
| 
 | |
| #include <stdlib.h>
 | |
| 
 | |
| 
 | |
| int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
 | |
|         const struct lfs_testbd_config *bdcfg) {
 | |
|     LFS_TRACE("lfs_testbd_createcfg(%p {.context=%p, "
 | |
|                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
 | |
|                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
 | |
|                 ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
 | |
|                 "\"%s\", "
 | |
|                 "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", "
 | |
|                 ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", "
 | |
|                 ".buffer=%p, .wear_buffer=%p})",
 | |
|             (void*)cfg, cfg->context,
 | |
|             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
 | |
|             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
 | |
|             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
 | |
|             path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles,
 | |
|             bdcfg->badblock_behavior, bdcfg->power_cycles,
 | |
|             bdcfg->buffer, bdcfg->wear_buffer);
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
|     bd->cfg = bdcfg;
 | |
| 
 | |
|     // setup testing things
 | |
|     bd->persist = path;
 | |
|     bd->power_cycles = bd->cfg->power_cycles;
 | |
| 
 | |
|     if (bd->cfg->erase_cycles) {
 | |
|         if (bd->cfg->wear_buffer) {
 | |
|             bd->wear = bd->cfg->wear_buffer;
 | |
|         } else {
 | |
|             bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t) * cfg->block_count);
 | |
|             if (!bd->wear) {
 | |
|                 LFS_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM);
 | |
|                 return LFS_ERR_NOMEM;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         memset(bd->wear, 0, sizeof(lfs_testbd_wear_t) * cfg->block_count);
 | |
|     }
 | |
|     
 | |
|     // create underlying block device
 | |
|     if (bd->persist) {
 | |
|         bd->u.file.cfg = (struct lfs_filebd_config){
 | |
|             .erase_value = bd->cfg->erase_value,
 | |
|         };
 | |
|         int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg);
 | |
|         LFS_TRACE("lfs_testbd_createcfg -> %d", err);
 | |
|         return err;
 | |
|     } else {
 | |
|         bd->u.ram.cfg = (struct lfs_rambd_config){
 | |
|             .erase_value = bd->cfg->erase_value,
 | |
|             .buffer = bd->cfg->buffer,
 | |
|         };
 | |
|         int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg);
 | |
|         LFS_TRACE("lfs_testbd_createcfg -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| }
 | |
| 
 | |
| int lfs_testbd_create(const struct lfs_config *cfg, const char *path) {
 | |
|     LFS_TRACE("lfs_testbd_create(%p {.context=%p, "
 | |
|                 ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
 | |
|                 ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
 | |
|                 ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
 | |
|                 "\"%s\")",
 | |
|             (void*)cfg, cfg->context,
 | |
|             (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
 | |
|             (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
 | |
|             cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
 | |
|             path);
 | |
|     static const struct lfs_testbd_config defaults = {.erase_value=-1};
 | |
|     int err = lfs_testbd_createcfg(cfg, path, &defaults);
 | |
|     LFS_TRACE("lfs_testbd_create -> %d", err);
 | |
|     return err;
 | |
| }
 | |
| 
 | |
| int lfs_testbd_destroy(const struct lfs_config *cfg) {
 | |
|     LFS_TRACE("lfs_testbd_destroy(%p)", (void*)cfg);
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
|     if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) {
 | |
|         lfs_free(bd->wear);
 | |
|     }
 | |
| 
 | |
|     if (bd->persist) {
 | |
|         int err = lfs_filebd_destroy(cfg);
 | |
|         LFS_TRACE("lfs_testbd_destroy -> %d", err);
 | |
|         return err;
 | |
|     } else {
 | |
|         int err = lfs_rambd_destroy(cfg);
 | |
|         LFS_TRACE("lfs_testbd_destroy -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Internal mapping to block devices ///
 | |
| static int lfs_testbd_rawread(const struct lfs_config *cfg, lfs_block_t block,
 | |
|         lfs_off_t off, void *buffer, lfs_size_t size) {
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
|     if (bd->persist) {
 | |
|         return lfs_filebd_read(cfg, block, off, buffer, size);
 | |
|     } else {
 | |
|         return lfs_rambd_read(cfg, block, off, buffer, size);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int lfs_testbd_rawprog(const struct lfs_config *cfg, lfs_block_t block,
 | |
|         lfs_off_t off, const void *buffer, lfs_size_t size) {
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
|     if (bd->persist) {
 | |
|         return lfs_filebd_prog(cfg, block, off, buffer, size);
 | |
|     } else {
 | |
|         return lfs_rambd_prog(cfg, block, off, buffer, size);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int lfs_testbd_rawerase(const struct lfs_config *cfg,
 | |
|         lfs_block_t block) {
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
|     if (bd->persist) {
 | |
|         return lfs_filebd_erase(cfg, block);
 | |
|     } else {
 | |
|         return lfs_rambd_erase(cfg, block);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int lfs_testbd_rawsync(const struct lfs_config *cfg) {
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
|     if (bd->persist) {
 | |
|         return lfs_filebd_sync(cfg);
 | |
|     } else {
 | |
|         return lfs_rambd_sync(cfg);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// block device API ///
 | |
| int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
 | |
|         lfs_off_t off, void *buffer, lfs_size_t size) {
 | |
|     LFS_TRACE("lfs_testbd_read(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
 | |
|             (void*)cfg, block, off, buffer, size);
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
| 
 | |
|     // check if read is valid
 | |
|     LFS_ASSERT(off  % cfg->read_size == 0);
 | |
|     LFS_ASSERT(size % cfg->read_size == 0);
 | |
|     LFS_ASSERT(block < cfg->block_count);
 | |
| 
 | |
|     // block bad?
 | |
|     if (bd->cfg->erase_cycles &&
 | |
|             bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOREAD &&
 | |
|             bd->wear[block] >= bd->cfg->erase_cycles) {
 | |
|         LFS_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT);
 | |
|         return LFS_ERR_CORRUPT;
 | |
|     }
 | |
| 
 | |
|     // read
 | |
|     int err = lfs_testbd_rawread(cfg, block, off, buffer, size);
 | |
|     LFS_TRACE("lfs_testbd_read -> %d", err);
 | |
|     return err;
 | |
| }
 | |
| 
 | |
| int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
 | |
|         lfs_off_t off, const void *buffer, lfs_size_t size) {
 | |
|     LFS_TRACE("lfs_testbd_prog(%p, 0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
 | |
|             (void*)cfg, block, off, buffer, size);
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
| 
 | |
|     // check if write is valid
 | |
|     LFS_ASSERT(off  % cfg->prog_size == 0);
 | |
|     LFS_ASSERT(size % cfg->prog_size == 0);
 | |
|     LFS_ASSERT(block < cfg->block_count);
 | |
| 
 | |
|     // block bad?
 | |
|     if (bd->cfg->erase_cycles &&
 | |
|             bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOPROG &&
 | |
|             bd->wear[block] >= bd->cfg->erase_cycles) {
 | |
|         LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT);
 | |
|         return LFS_ERR_CORRUPT;
 | |
|     }
 | |
| 
 | |
|     // prog
 | |
|     int err = lfs_testbd_rawprog(cfg, block, off, buffer, size);
 | |
|     if (err) {
 | |
|         LFS_TRACE("lfs_testbd_prog -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| 
 | |
|     // lose power?
 | |
|     if (bd->power_cycles > 0) {
 | |
|         bd->power_cycles -= 1;
 | |
|         if (bd->power_cycles == 0) {
 | |
|             // sync to make sure we persist the last changes
 | |
|             lfs_testbd_rawsync(cfg);
 | |
|             // simulate power loss
 | |
|             exit(33);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     LFS_TRACE("lfs_testbd_prog -> %d", 0);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
 | |
|     LFS_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
| 
 | |
|     // check if erase is valid
 | |
|     LFS_ASSERT(block < cfg->block_count);
 | |
| 
 | |
|     // block bad?
 | |
|     if (bd->cfg->erase_cycles) {
 | |
|         if (bd->wear[block] >= bd->cfg->erase_cycles) {
 | |
|             if (bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOERASE) {
 | |
|                 LFS_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT);
 | |
|                 return LFS_ERR_CORRUPT;
 | |
|             }
 | |
|         } else {
 | |
|             // mark wear
 | |
|             bd->wear[block] += 1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // erase
 | |
|     int err = lfs_testbd_rawerase(cfg, block);
 | |
|     if (err) {
 | |
|         LFS_TRACE("lfs_testbd_erase -> %d", err);
 | |
|         return err;
 | |
|     }
 | |
| 
 | |
|     // lose power?
 | |
|     if (bd->power_cycles > 0) {
 | |
|         bd->power_cycles -= 1;
 | |
|         if (bd->power_cycles == 0) {
 | |
|             // sync to make sure we persist the last changes
 | |
|             lfs_testbd_rawsync(cfg);
 | |
|             // simulate power loss
 | |
|             exit(33);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     LFS_TRACE("lfs_testbd_prog -> %d", 0);
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int lfs_testbd_sync(const struct lfs_config *cfg) {
 | |
|     LFS_TRACE("lfs_testbd_sync(%p)", (void*)cfg);
 | |
|     int err = lfs_testbd_rawsync(cfg);
 | |
|     LFS_TRACE("lfs_testbd_sync -> %d", err);
 | |
|     return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| /// simulated wear operations ///
 | |
| lfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg,
 | |
|         lfs_block_t block) {
 | |
|     LFS_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block);
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
| 
 | |
|     // check if block is valid
 | |
|     LFS_ASSERT(bd->cfg->erase_cycles);
 | |
|     LFS_ASSERT(block < cfg->block_count);
 | |
| 
 | |
|     LFS_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]);
 | |
|     return bd->wear[block];
 | |
| }
 | |
| 
 | |
| int lfs_testbd_setwear(const struct lfs_config *cfg,
 | |
|         lfs_block_t block, lfs_testbd_wear_t wear) {
 | |
|     LFS_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block);
 | |
|     lfs_testbd_t *bd = cfg->context;
 | |
| 
 | |
|     // check if block is valid
 | |
|     LFS_ASSERT(bd->cfg->erase_cycles);
 | |
|     LFS_ASSERT(block < cfg->block_count);
 | |
| 
 | |
|     bd->wear[block] = wear;
 | |
| 
 | |
|     LFS_TRACE("lfs_testbd_setwear -> %d", 0);
 | |
|     return 0;
 | |
| }
 |