119ea8026Sopenharmony_ci/*
219ea8026Sopenharmony_ci * Testing block device, wraps filebd and rambd while providing a bunch
319ea8026Sopenharmony_ci * of hooks for testing littlefs in various conditions.
419ea8026Sopenharmony_ci *
519ea8026Sopenharmony_ci * Copyright (c) 2022, The littlefs authors.
619ea8026Sopenharmony_ci * Copyright (c) 2017, Arm Limited. All rights reserved.
719ea8026Sopenharmony_ci * SPDX-License-Identifier: BSD-3-Clause
819ea8026Sopenharmony_ci */
919ea8026Sopenharmony_ci#include "bd/lfs_testbd.h"
1019ea8026Sopenharmony_ci
1119ea8026Sopenharmony_ci#include <stdlib.h>
1219ea8026Sopenharmony_ci
1319ea8026Sopenharmony_ci
1419ea8026Sopenharmony_ciint lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path,
1519ea8026Sopenharmony_ci        const struct lfs_testbd_config *bdcfg) {
1619ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_createcfg(%p {.context=%p, "
1719ea8026Sopenharmony_ci                ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
1819ea8026Sopenharmony_ci                ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
1919ea8026Sopenharmony_ci                ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
2019ea8026Sopenharmony_ci                "\"%s\", "
2119ea8026Sopenharmony_ci                "%p {.erase_value=%"PRId32", .erase_cycles=%"PRIu32", "
2219ea8026Sopenharmony_ci                ".badblock_behavior=%"PRIu8", .power_cycles=%"PRIu32", "
2319ea8026Sopenharmony_ci                ".buffer=%p, .wear_buffer=%p})",
2419ea8026Sopenharmony_ci            (void*)cfg, cfg->context,
2519ea8026Sopenharmony_ci            (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
2619ea8026Sopenharmony_ci            (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
2719ea8026Sopenharmony_ci            cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
2819ea8026Sopenharmony_ci            path, (void*)bdcfg, bdcfg->erase_value, bdcfg->erase_cycles,
2919ea8026Sopenharmony_ci            bdcfg->badblock_behavior, bdcfg->power_cycles,
3019ea8026Sopenharmony_ci            bdcfg->buffer, bdcfg->wear_buffer);
3119ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
3219ea8026Sopenharmony_ci    bd->cfg = bdcfg;
3319ea8026Sopenharmony_ci
3419ea8026Sopenharmony_ci    // setup testing things
3519ea8026Sopenharmony_ci    bd->persist = path;
3619ea8026Sopenharmony_ci    bd->power_cycles = bd->cfg->power_cycles;
3719ea8026Sopenharmony_ci
3819ea8026Sopenharmony_ci    if (bd->cfg->erase_cycles) {
3919ea8026Sopenharmony_ci        if (bd->cfg->wear_buffer) {
4019ea8026Sopenharmony_ci            bd->wear = bd->cfg->wear_buffer;
4119ea8026Sopenharmony_ci        } else {
4219ea8026Sopenharmony_ci            bd->wear = lfs_malloc(sizeof(lfs_testbd_wear_t)*cfg->block_count);
4319ea8026Sopenharmony_ci            if (!bd->wear) {
4419ea8026Sopenharmony_ci                LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", LFS_ERR_NOMEM);
4519ea8026Sopenharmony_ci                return LFS_ERR_NOMEM;
4619ea8026Sopenharmony_ci            }
4719ea8026Sopenharmony_ci        }
4819ea8026Sopenharmony_ci
4919ea8026Sopenharmony_ci        memset(bd->wear, 0, sizeof(lfs_testbd_wear_t) * cfg->block_count);
5019ea8026Sopenharmony_ci    }
5119ea8026Sopenharmony_ci
5219ea8026Sopenharmony_ci    // create underlying block device
5319ea8026Sopenharmony_ci    if (bd->persist) {
5419ea8026Sopenharmony_ci        bd->u.file.cfg = (struct lfs_filebd_config){
5519ea8026Sopenharmony_ci            .erase_value = bd->cfg->erase_value,
5619ea8026Sopenharmony_ci        };
5719ea8026Sopenharmony_ci        int err = lfs_filebd_createcfg(cfg, path, &bd->u.file.cfg);
5819ea8026Sopenharmony_ci        LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err);
5919ea8026Sopenharmony_ci        return err;
6019ea8026Sopenharmony_ci    } else {
6119ea8026Sopenharmony_ci        bd->u.ram.cfg = (struct lfs_rambd_config){
6219ea8026Sopenharmony_ci            .erase_value = bd->cfg->erase_value,
6319ea8026Sopenharmony_ci            .buffer = bd->cfg->buffer,
6419ea8026Sopenharmony_ci        };
6519ea8026Sopenharmony_ci        int err = lfs_rambd_createcfg(cfg, &bd->u.ram.cfg);
6619ea8026Sopenharmony_ci        LFS_TESTBD_TRACE("lfs_testbd_createcfg -> %d", err);
6719ea8026Sopenharmony_ci        return err;
6819ea8026Sopenharmony_ci    }
6919ea8026Sopenharmony_ci}
7019ea8026Sopenharmony_ci
7119ea8026Sopenharmony_ciint lfs_testbd_create(const struct lfs_config *cfg, const char *path) {
7219ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_create(%p {.context=%p, "
7319ea8026Sopenharmony_ci                ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
7419ea8026Sopenharmony_ci                ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
7519ea8026Sopenharmony_ci                ".block_size=%"PRIu32", .block_count=%"PRIu32"}, "
7619ea8026Sopenharmony_ci                "\"%s\")",
7719ea8026Sopenharmony_ci            (void*)cfg, cfg->context,
7819ea8026Sopenharmony_ci            (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
7919ea8026Sopenharmony_ci            (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
8019ea8026Sopenharmony_ci            cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
8119ea8026Sopenharmony_ci            path);
8219ea8026Sopenharmony_ci    static const struct lfs_testbd_config defaults = {.erase_value=-1};
8319ea8026Sopenharmony_ci    int err = lfs_testbd_createcfg(cfg, path, &defaults);
8419ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_create -> %d", err);
8519ea8026Sopenharmony_ci    return err;
8619ea8026Sopenharmony_ci}
8719ea8026Sopenharmony_ci
8819ea8026Sopenharmony_ciint lfs_testbd_destroy(const struct lfs_config *cfg) {
8919ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_destroy(%p)", (void*)cfg);
9019ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
9119ea8026Sopenharmony_ci    if (bd->cfg->erase_cycles && !bd->cfg->wear_buffer) {
9219ea8026Sopenharmony_ci        lfs_free(bd->wear);
9319ea8026Sopenharmony_ci    }
9419ea8026Sopenharmony_ci
9519ea8026Sopenharmony_ci    if (bd->persist) {
9619ea8026Sopenharmony_ci        int err = lfs_filebd_destroy(cfg);
9719ea8026Sopenharmony_ci        LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err);
9819ea8026Sopenharmony_ci        return err;
9919ea8026Sopenharmony_ci    } else {
10019ea8026Sopenharmony_ci        int err = lfs_rambd_destroy(cfg);
10119ea8026Sopenharmony_ci        LFS_TESTBD_TRACE("lfs_testbd_destroy -> %d", err);
10219ea8026Sopenharmony_ci        return err;
10319ea8026Sopenharmony_ci    }
10419ea8026Sopenharmony_ci}
10519ea8026Sopenharmony_ci
10619ea8026Sopenharmony_ci/// Internal mapping to block devices ///
10719ea8026Sopenharmony_cistatic int lfs_testbd_rawread(const struct lfs_config *cfg, lfs_block_t block,
10819ea8026Sopenharmony_ci        lfs_off_t off, void *buffer, lfs_size_t size) {
10919ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
11019ea8026Sopenharmony_ci    if (bd->persist) {
11119ea8026Sopenharmony_ci        return lfs_filebd_read(cfg, block, off, buffer, size);
11219ea8026Sopenharmony_ci    } else {
11319ea8026Sopenharmony_ci        return lfs_rambd_read(cfg, block, off, buffer, size);
11419ea8026Sopenharmony_ci    }
11519ea8026Sopenharmony_ci}
11619ea8026Sopenharmony_ci
11719ea8026Sopenharmony_cistatic int lfs_testbd_rawprog(const struct lfs_config *cfg, lfs_block_t block,
11819ea8026Sopenharmony_ci        lfs_off_t off, const void *buffer, lfs_size_t size) {
11919ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
12019ea8026Sopenharmony_ci    if (bd->persist) {
12119ea8026Sopenharmony_ci        return lfs_filebd_prog(cfg, block, off, buffer, size);
12219ea8026Sopenharmony_ci    } else {
12319ea8026Sopenharmony_ci        return lfs_rambd_prog(cfg, block, off, buffer, size);
12419ea8026Sopenharmony_ci    }
12519ea8026Sopenharmony_ci}
12619ea8026Sopenharmony_ci
12719ea8026Sopenharmony_cistatic int lfs_testbd_rawerase(const struct lfs_config *cfg,
12819ea8026Sopenharmony_ci        lfs_block_t block) {
12919ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
13019ea8026Sopenharmony_ci    if (bd->persist) {
13119ea8026Sopenharmony_ci        return lfs_filebd_erase(cfg, block);
13219ea8026Sopenharmony_ci    } else {
13319ea8026Sopenharmony_ci        return lfs_rambd_erase(cfg, block);
13419ea8026Sopenharmony_ci    }
13519ea8026Sopenharmony_ci}
13619ea8026Sopenharmony_ci
13719ea8026Sopenharmony_cistatic int lfs_testbd_rawsync(const struct lfs_config *cfg) {
13819ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
13919ea8026Sopenharmony_ci    if (bd->persist) {
14019ea8026Sopenharmony_ci        return lfs_filebd_sync(cfg);
14119ea8026Sopenharmony_ci    } else {
14219ea8026Sopenharmony_ci        return lfs_rambd_sync(cfg);
14319ea8026Sopenharmony_ci    }
14419ea8026Sopenharmony_ci}
14519ea8026Sopenharmony_ci
14619ea8026Sopenharmony_ci/// block device API ///
14719ea8026Sopenharmony_ciint lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block,
14819ea8026Sopenharmony_ci        lfs_off_t off, void *buffer, lfs_size_t size) {
14919ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_read(%p, "
15019ea8026Sopenharmony_ci                "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
15119ea8026Sopenharmony_ci            (void*)cfg, block, off, buffer, size);
15219ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
15319ea8026Sopenharmony_ci
15419ea8026Sopenharmony_ci    // check if read is valid
15519ea8026Sopenharmony_ci    LFS_ASSERT(off  % cfg->read_size == 0);
15619ea8026Sopenharmony_ci    LFS_ASSERT(size % cfg->read_size == 0);
15719ea8026Sopenharmony_ci    LFS_ASSERT(block < cfg->block_count);
15819ea8026Sopenharmony_ci
15919ea8026Sopenharmony_ci    // block bad?
16019ea8026Sopenharmony_ci    if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles &&
16119ea8026Sopenharmony_ci            bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) {
16219ea8026Sopenharmony_ci        LFS_TESTBD_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT);
16319ea8026Sopenharmony_ci        return LFS_ERR_CORRUPT;
16419ea8026Sopenharmony_ci    }
16519ea8026Sopenharmony_ci
16619ea8026Sopenharmony_ci    // read
16719ea8026Sopenharmony_ci    int err = lfs_testbd_rawread(cfg, block, off, buffer, size);
16819ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_read -> %d", err);
16919ea8026Sopenharmony_ci    return err;
17019ea8026Sopenharmony_ci}
17119ea8026Sopenharmony_ci
17219ea8026Sopenharmony_ciint lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block,
17319ea8026Sopenharmony_ci        lfs_off_t off, const void *buffer, lfs_size_t size) {
17419ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_prog(%p, "
17519ea8026Sopenharmony_ci                "0x%"PRIx32", %"PRIu32", %p, %"PRIu32")",
17619ea8026Sopenharmony_ci            (void*)cfg, block, off, buffer, size);
17719ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
17819ea8026Sopenharmony_ci
17919ea8026Sopenharmony_ci    // check if write is valid
18019ea8026Sopenharmony_ci    LFS_ASSERT(off  % cfg->prog_size == 0);
18119ea8026Sopenharmony_ci    LFS_ASSERT(size % cfg->prog_size == 0);
18219ea8026Sopenharmony_ci    LFS_ASSERT(block < cfg->block_count);
18319ea8026Sopenharmony_ci
18419ea8026Sopenharmony_ci    // block bad?
18519ea8026Sopenharmony_ci    if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) {
18619ea8026Sopenharmony_ci        if (bd->cfg->badblock_behavior ==
18719ea8026Sopenharmony_ci                LFS_TESTBD_BADBLOCK_PROGERROR) {
18819ea8026Sopenharmony_ci            LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT);
18919ea8026Sopenharmony_ci            return LFS_ERR_CORRUPT;
19019ea8026Sopenharmony_ci        } else if (bd->cfg->badblock_behavior ==
19119ea8026Sopenharmony_ci                LFS_TESTBD_BADBLOCK_PROGNOOP ||
19219ea8026Sopenharmony_ci                bd->cfg->badblock_behavior ==
19319ea8026Sopenharmony_ci                LFS_TESTBD_BADBLOCK_ERASENOOP) {
19419ea8026Sopenharmony_ci            LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
19519ea8026Sopenharmony_ci            return 0;
19619ea8026Sopenharmony_ci        }
19719ea8026Sopenharmony_ci    }
19819ea8026Sopenharmony_ci
19919ea8026Sopenharmony_ci    // prog
20019ea8026Sopenharmony_ci    int err = lfs_testbd_rawprog(cfg, block, off, buffer, size);
20119ea8026Sopenharmony_ci    if (err) {
20219ea8026Sopenharmony_ci        LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", err);
20319ea8026Sopenharmony_ci        return err;
20419ea8026Sopenharmony_ci    }
20519ea8026Sopenharmony_ci
20619ea8026Sopenharmony_ci    // lose power?
20719ea8026Sopenharmony_ci    if (bd->power_cycles > 0) {
20819ea8026Sopenharmony_ci        bd->power_cycles -= 1;
20919ea8026Sopenharmony_ci        if (bd->power_cycles == 0) {
21019ea8026Sopenharmony_ci            // sync to make sure we persist the last changes
21119ea8026Sopenharmony_ci            LFS_ASSERT(lfs_testbd_rawsync(cfg) == 0);
21219ea8026Sopenharmony_ci            // simulate power loss
21319ea8026Sopenharmony_ci            exit(33);
21419ea8026Sopenharmony_ci        }
21519ea8026Sopenharmony_ci    }
21619ea8026Sopenharmony_ci
21719ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
21819ea8026Sopenharmony_ci    return 0;
21919ea8026Sopenharmony_ci}
22019ea8026Sopenharmony_ci
22119ea8026Sopenharmony_ciint lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) {
22219ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_erase(%p, 0x%"PRIx32")", (void*)cfg, block);
22319ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
22419ea8026Sopenharmony_ci
22519ea8026Sopenharmony_ci    // check if erase is valid
22619ea8026Sopenharmony_ci    LFS_ASSERT(block < cfg->block_count);
22719ea8026Sopenharmony_ci
22819ea8026Sopenharmony_ci    // block bad?
22919ea8026Sopenharmony_ci    if (bd->cfg->erase_cycles) {
23019ea8026Sopenharmony_ci        if (bd->wear[block] >= bd->cfg->erase_cycles) {
23119ea8026Sopenharmony_ci            if (bd->cfg->badblock_behavior ==
23219ea8026Sopenharmony_ci                    LFS_TESTBD_BADBLOCK_ERASEERROR) {
23319ea8026Sopenharmony_ci                LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT);
23419ea8026Sopenharmony_ci                return LFS_ERR_CORRUPT;
23519ea8026Sopenharmony_ci            } else if (bd->cfg->badblock_behavior ==
23619ea8026Sopenharmony_ci                    LFS_TESTBD_BADBLOCK_ERASENOOP) {
23719ea8026Sopenharmony_ci                LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", 0);
23819ea8026Sopenharmony_ci                return 0;
23919ea8026Sopenharmony_ci            }
24019ea8026Sopenharmony_ci        } else {
24119ea8026Sopenharmony_ci            // mark wear
24219ea8026Sopenharmony_ci            bd->wear[block] += 1;
24319ea8026Sopenharmony_ci        }
24419ea8026Sopenharmony_ci    }
24519ea8026Sopenharmony_ci
24619ea8026Sopenharmony_ci    // erase
24719ea8026Sopenharmony_ci    int err = lfs_testbd_rawerase(cfg, block);
24819ea8026Sopenharmony_ci    if (err) {
24919ea8026Sopenharmony_ci        LFS_TESTBD_TRACE("lfs_testbd_erase -> %d", err);
25019ea8026Sopenharmony_ci        return err;
25119ea8026Sopenharmony_ci    }
25219ea8026Sopenharmony_ci
25319ea8026Sopenharmony_ci    // lose power?
25419ea8026Sopenharmony_ci    if (bd->power_cycles > 0) {
25519ea8026Sopenharmony_ci        bd->power_cycles -= 1;
25619ea8026Sopenharmony_ci        if (bd->power_cycles == 0) {
25719ea8026Sopenharmony_ci            // sync to make sure we persist the last changes
25819ea8026Sopenharmony_ci            LFS_ASSERT(lfs_testbd_rawsync(cfg) == 0);
25919ea8026Sopenharmony_ci            // simulate power loss
26019ea8026Sopenharmony_ci            exit(33);
26119ea8026Sopenharmony_ci        }
26219ea8026Sopenharmony_ci    }
26319ea8026Sopenharmony_ci
26419ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_prog -> %d", 0);
26519ea8026Sopenharmony_ci    return 0;
26619ea8026Sopenharmony_ci}
26719ea8026Sopenharmony_ci
26819ea8026Sopenharmony_ciint lfs_testbd_sync(const struct lfs_config *cfg) {
26919ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_sync(%p)", (void*)cfg);
27019ea8026Sopenharmony_ci    int err = lfs_testbd_rawsync(cfg);
27119ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_sync -> %d", err);
27219ea8026Sopenharmony_ci    return err;
27319ea8026Sopenharmony_ci}
27419ea8026Sopenharmony_ci
27519ea8026Sopenharmony_ci
27619ea8026Sopenharmony_ci/// simulated wear operations ///
27719ea8026Sopenharmony_cilfs_testbd_swear_t lfs_testbd_getwear(const struct lfs_config *cfg,
27819ea8026Sopenharmony_ci        lfs_block_t block) {
27919ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_getwear(%p, %"PRIu32")", (void*)cfg, block);
28019ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
28119ea8026Sopenharmony_ci
28219ea8026Sopenharmony_ci    // check if block is valid
28319ea8026Sopenharmony_ci    LFS_ASSERT(bd->cfg->erase_cycles);
28419ea8026Sopenharmony_ci    LFS_ASSERT(block < cfg->block_count);
28519ea8026Sopenharmony_ci
28619ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_getwear -> %"PRIu32, bd->wear[block]);
28719ea8026Sopenharmony_ci    return bd->wear[block];
28819ea8026Sopenharmony_ci}
28919ea8026Sopenharmony_ci
29019ea8026Sopenharmony_ciint lfs_testbd_setwear(const struct lfs_config *cfg,
29119ea8026Sopenharmony_ci        lfs_block_t block, lfs_testbd_wear_t wear) {
29219ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_setwear(%p, %"PRIu32")", (void*)cfg, block);
29319ea8026Sopenharmony_ci    lfs_testbd_t *bd = cfg->context;
29419ea8026Sopenharmony_ci
29519ea8026Sopenharmony_ci    // check if block is valid
29619ea8026Sopenharmony_ci    LFS_ASSERT(bd->cfg->erase_cycles);
29719ea8026Sopenharmony_ci    LFS_ASSERT(block < cfg->block_count);
29819ea8026Sopenharmony_ci
29919ea8026Sopenharmony_ci    bd->wear[block] = wear;
30019ea8026Sopenharmony_ci
30119ea8026Sopenharmony_ci    LFS_TESTBD_TRACE("lfs_testbd_setwear -> %d", 0);
30219ea8026Sopenharmony_ci    return 0;
30319ea8026Sopenharmony_ci}
304