跳到正文
Qiita · Unity 开发社区· 来源· hk816·· 3 小时前AI 评分1

基于 Unity 框架的 SSD ECC 多段错误恢复测试设计

SSDのデータを死守せよ!「ECC多段エラーリカバリ」のテスト設計(Unity編)

正文 · AI 翻译

0

转到点赞用户列表

0

在 X (Twitter) 上分享

在 Facebook 上分享

添加到 Hatena Bookmark

删除文章

已删除的文章无法恢复。

本文的草稿也将被删除。

您确定要删除这篇文章吗?

🚀 内存控制器的错误纠正与数据可靠性提升(ECC)功能

在嵌入式软件中,用软件来弥补硬件限制的“错误恢复”代码是最容易隐藏bug、也最难测试的领域之一。
特别是SSD的ECC(错误纠正)功能,伴随着诸如“起初无法读取,但改变读取电压后就能读取了”、“接近纠正极限,因此迁移到另一个块”等复杂的状态转换。

在实际设备上故意制造这种“临界错误状态”极其困难,但通过结合使用Unity(单元测试框架)和Mock对象,可以在PC上100%模拟硬件的极限状态。

本次,我们将公开用于证明上次“ECC·多级错误恢复”C语言实现的企业级质量的测试代码!


💡 测试设计的要点

在错误恢复的测试中,除了正常情况外,还需要证明固件在硬件出现"最坏行为"时能够如何坚持(故障安全)。

  1. 阈值的边界值测试(证明Scrubbing触发)
    即使纠错(CORRECTED)成功,如果纠正的位数"低于阈值(例如:39位)"则正常结束,而如果是"刚好达到阈值(40位)",则作为寿命信号触发预防性维护(Scrubbing),对此进行严格验证。
  2. Read Retry的状态转换及副作用排除(正常/异常)
    向mock注入"首次UNC(不可纠正),第二次重试成功"的场景。此时,不仅要正确退出循环,还要从调用历史中证明"为确保不影响下一次读取,硬件电压(Vth)设置是否已可靠地重置为0(默认值)"。
  3. 最终防线(RAIN)与故障安全的验证(异常)
    验证重试上限(5次)全部失败并回退到NAND间奇偶校验(RAIN)的场景。如果通过RAIN恢复成功,则证明"GBB(后续不良)注册"被可靠执行;如果连RAIN也失败,则证明不会挂起(无限循环),而是安全地返回 false 并结束。

💻 测试代码

#include "unity.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

/* テスト対象のCファイルを直接インクルード(ホワイトボックステスト用) */
#include "ftl_ecc_recovery.c"

/* --- モック用制御・状態記録変数 --- */
#define MOCK_MAX_READS 10U
static ECC_Result_t g_mock_ecc_seq[MOCK_MAX_READS];
static uint32_t     g_mock_ecc_idx = 0U;

static uint32_t g_call_read = 0U;
static uint32_t g_call_set_retry = 0U;
static uint32_t g_last_retry_level = 0xFFFFFFFFU; /* 初期値(無効値) */

static uint32_t g_call_scrub = 0U;
static uint32_t g_call_gbb = 0U;
static uint32_t g_call_rain = 0U;
static bool     g_mock_rain_success = true;

/* --- 外部HAL/FW関数のダミー実装 (モック) --- */
ECC_Result_t HAL_NandReadPageWithEcc(uint32_t pba, uint8_t* p_buffer) {
    g_call_read++;
    ECC_Result_t res = g_mock_ecc_seq[g_mock_ecc_idx];
    if (g_mock_ecc_idx < (MOCK_MAX_READS - 1U)) {
        g_mock_ecc_idx++;
    }
    return res;
}

void HAL_SetReadRetryLevel(uint32_t retry_index) {
    g_call_set_retry++;
    g_last_retry_level = retry_index;
}

void FTL_TriggerScrubbing(uint32_t pba) {
    g_call_scrub++;
}

bool FTL_RecoverByRain(uint32_t pba, uint8_t* p_buffer) {
    g_call_rain++;
    return g_mock_rain_success;
}

void FTL_MarkBlockAsGbb(uint32_t pba) {
    g_call_gbb++;
}

/* --- テストのセットアップ --- */
void setUp(void) {
    g_mock_ecc_idx = 0U;
    g_call_read = 0U;
    g_call_set_retry = 0U;
    g_last_retry_level = 0xFFFFFFFFU;
    g_call_scrub = 0U;
    g_call_gbb = 0U;
    g_call_rain = 0U;
    g_mock_rain_success = true;
    
    memset(g_mock_ecc_seq, 0, sizeof(g_mock_ecc_seq));
}
void tearDown(void) {}

/* =========================================================
 *  テストケース
 * ========================================================= */

/* 1. 異常系: NULLポインタ入力によるガード検証 */
void test_FTL_RobustReadWithEcc_NullPointer_ShouldReturnFalse(void) {
    bool result = FTL_RobustReadWithEcc(100U, NULL);
    TEST_ASSERT_FALSE_MESSAGE(result, "バッファポインタがNULLの場合はfalseを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_read, "HWへのReadアクセスを行わないこと");
}

/* 2. 正常系: エラーなし(SUCCESS)の一発読み出し */
void test_FTL_RobustReadWithEcc_Success_ShouldReturnTrue(void) {
    uint8_t buffer[4096];
    g_mock_ecc_seq[0].status = FTL_ECC_SUCCESS;
    
    bool result = FTL_RobustReadWithEcc(100U, buffer);
    
    TEST_ASSERT_TRUE_MESSAGE(result, "一発成功時はtrueを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_read, "Readは1度だけ呼ばれること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_scrub, "Scrubbing(退避)は発動しないこと");
}

/* 3. 境界値: 訂正ビット数が閾値未満 (Scrub発動せず) */
void test_FTL_RobustReadWithEcc_CorrectedBelowThreshold_ShouldNotScrub(void) {
    uint8_t buffer[4096];
    g_mock_ecc_seq[0].status = FTL_ECC_CORRECTED;
    g_mock_ecc_seq[0].corrected_bits = FTL_SCRUB_THRESHOLD_BITS - 1U; /* 39ビット(閾値未満) */
    
    bool result = FTL_RobustReadWithEcc(100U, buffer);
    
    TEST_ASSERT_TRUE_MESSAGE(result, "訂正成功なのでtrueを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_scrub, "閾値未満の劣化ではまだデータ退避(Scrub)を行わないこと");
}

/* 4. 境界値: 訂正ビット数が閾値到達 (Scrub発動の証明) */
void test_FTL_RobustReadWithEcc_CorrectedAtThreshold_ShouldTriggerScrub(void) {
    uint8_t buffer[4096];
    g_mock_ecc_seq[0].status = FTL_ECC_CORRECTED;
    g_mock_ecc_seq[0].corrected_bits = FTL_SCRUB_THRESHOLD_BITS; /* 40ビット(閾値到達) */
    
    bool result = FTL_RobustReadWithEcc(100U, buffer);
    
    TEST_ASSERT_TRUE_MESSAGE(result, "訂正成功なのでtrueを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_scrub, "閾値到達時は寿命のサインと見なし、Scrubbingを発動すること");
}

/* 5. 状態遷移: Read Retry 成功と副作用(Vthリセット)の検証 */
void test_FTL_RobustReadWithEcc_RetrySuccess_ShouldScrubAndResetVth(void) {
    uint8_t buffer[4096];
    
    /* 1回目(通常)、2回目(Retry=1)はUNC、3回目(Retry=2)でSUCCESSするシナリオ */
    g_mock_ecc_seq[0].status = FTL_ECC_UNCORRECTABLE;
    g_mock_ecc_seq[1].status = FTL_ECC_UNCORRECTABLE;
    g_mock_ecc_seq[2].status = FTL_ECC_SUCCESS;
    
    bool result = FTL_RobustReadWithEcc(100U, buffer);
    
    TEST_ASSERT_TRUE_MESSAGE(result, "Retryで救出できた場合はtrueを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(3U, g_call_read, "Read関数が計3回呼ばれること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_scrub, "Retryが発動したブロックは劣化が確定しているため、必ずScrubすること");
    
    /* 【セキュリティ・副作用検証】 */
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_last_retry_level, "Retryループを抜けた後、次のReadを壊さないようVthが必ず0(リセット)されること");
}

/* 6. 異常系: RAINによる最終復元と不良ブロック(GBB)登録 */
void test_FTL_RobustReadWithEcc_RetryExhausted_RainSuccess_ShouldMarkGbb(void) {
    uint8_t buffer[4096];
    
    /* 全てのRead(初回 + リトライ最大5回 = 計6回)がUNCになるシナリオ */
    for (uint32_t i = 0U; i <= FTL_MAX_READ_RETRIES; i++) {
        g_mock_ecc_seq[i].status = FTL_ECC_UNCORRECTABLE;
    }
    g_mock_rain_success = true; /* RAIN(パリティ復元)は成功する */
    
    bool result = FTL_RobustReadWithEcc(100U, buffer);
    
    TEST_ASSERT_TRUE_MESSAGE(result, "RAINで救出できた場合はtrueを返すこと");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U + FTL_MAX_READ_RETRIES, g_call_read, "最大リトライ回数まで全て試行されること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_rain, "Retry限界到達後にRAINが発動すること");
    
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_scrub, "RAIN復旧ブロックのデータは即時退避すること");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(1U, g_call_gbb, "RAINまで発動したブロックは完全破損と見なし、GBB登録すること");
}

/* 7. 異常系: RAINすら失敗する完全破損(Unrecovered Error)時のフェイルソフト検証 */
void test_FTL_RobustReadWithEcc_AllFails_ShouldReturnFalse(void) {
    uint8_t buffer[4096];
    
    for (uint32_t i = 0U; i <= FTL_MAX_READ_RETRIES; i++) {
        g_mock_ecc_seq[i].status = FTL_ECC_UNCORRECTABLE;
    }
    g_mock_rain_success = false; /* RAINも失敗(論理的にも復元不可能) */
    
    bool result = FTL_RobustReadWithEcc(100U, buffer);
    
    TEST_ASSERT_FALSE_MESSAGE(result, "全てのリカバリ手段が失敗した場合は、ハングアップせず安全にfalseを返すこと(フェイルソフト)");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_scrub, "復元に失敗したゴミデータを退避(Scrub)してはならない");
    TEST_ASSERT_EQUAL_UINT32_MESSAGE(0U, g_call_gbb, "データが失われたため、通常のGBB登録シーケンスではなく上位エラーハンドラへ委譲すること");
}

0

查看点赞用户列表

0

注册新用户并更便捷地使用 Qiita

  1. 获取符合您需求的文章
  2. 高效回顾有用信息
  3. 支持深色主题

注册后可享受的功能

0

转到喜欢该文章的用户列表

0

删除文章

已删除的文章无法恢复。

本文的草稿也将被删除。

确定要删除此文章吗?

来源:Qiita · Unity 开发社区 · qiita.com