wolfBoot/tools/unit-tests/unit-sdhci-wait-busy.c

175 lines
5.0 KiB
C

/* unit-sdhci-wait-busy.c
*
* Regression test: sdhci_wait_busy() had no timeout in either the DAT0
* or the CMD13 loop, so a stuck or removed card spun forever.
*
* The timer stub advances by a settable step, so the shipped
* SDHCI_WAIT_BUSY_TIMEOUT_MS is exercised as built rather than against
* a shrunken stand-in, and it counts sdhci_platform_wdt_pet() calls: a
* wait this long must service the watchdog or it becomes a reset
* instead of the clean I/O error it is meant to be.
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfBoot.
*
* wolfBoot is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfBoot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
#include <check.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "sdhci.h"
#include "disk.h"
/* Host register file for the platform-provided register accessors. */
static uint32_t g_sdhci_regs[0x400 / sizeof(uint32_t)];
uint32_t sdhci_reg_read(uint32_t offset)
{
return g_sdhci_regs[offset / sizeof(uint32_t)];
}
void sdhci_reg_write(uint32_t offset, uint32_t val)
{
g_sdhci_regs[offset / sizeof(uint32_t)] = val;
}
/* Timer: advance by g_timer_step microseconds on every read, so a test
* can reach the shipped timeout in a sane number of iterations. */
static uint64_t g_timer_us;
static uint64_t g_timer_step = 1;
uint64_t hal_get_timer_us(void)
{
g_timer_us += g_timer_step;
return g_timer_us;
}
/* Counts watchdog services performed inside the busy loops. */
static unsigned int g_wdt_pets;
void sdhci_platform_wdt_pet(void)
{
g_wdt_pets++;
}
/* Platform hooks the SD init path touches; irrelevant here. */
void sdhci_platform_init(void)
{
}
void sdhci_platform_irq_init(void)
{
}
void sdhci_platform_set_bus_mode(int is_emmc)
{
(void)is_emmc;
}
/* The real sdhci_read() is renamed to sdhci_read_hw() in the generated
* copy; the data path is not under test, so this stub just satisfies
* the disk_read()/disk_write() references. */
int sdhci_read(uint32_t cmd_index, uint32_t block_addr, uint32_t *dst,
uint32_t sz)
{
(void)cmd_index; (void)block_addr; (void)dst; (void)sz;
return -1;
}
/* The real driver (see the Makefile for the transforms). */
#include "sdhci_host.c"
static void setup(void)
{
memset(g_sdhci_regs, 0, sizeof(g_sdhci_regs));
g_timer_us = 0;
g_wdt_pets = 0;
/* 1 ms per timer read: the shipped 30 s budget is reached in ~30k
* iterations, so the real value is what gets exercised. */
g_timer_step = 1000;
}
static void teardown(void)
{
}
/* A card stuck with DATA0 low must time out instead of hanging. */
START_TEST(test_wait_busy_dat0_timeout)
{
/* DAT0 low: the card asserts the data line (programming). */
g_sdhci_regs[SDHCI_SRS09 / 4] &= ~SDHCI_SRS09_DAT0_LVL;
ck_assert_int_eq(sdhci_wait_busy(1), -1);
/* the wait ran to the shipped budget, so it must have petted */
ck_assert_uint_gt(g_wdt_pets, 0);
}
END_TEST
/* A card whose CMD13 keeps reporting DEVICE_BUSY must time out. */
START_TEST(test_wait_busy_cmd13_timeout)
{
/* DAT0 high (not busy), so only the CMD13 loop runs. The command
* always completes; the response READY_FOR_DATA bit stays clear,
* so sdhci_cmd() returns DEVICE_BUSY every time. */
g_sdhci_regs[SDHCI_SRS09 / 4] |= SDHCI_SRS09_DAT0_LVL;
g_sdhci_regs[SDHCI_SRS12 / 4] = SDHCI_SRS12_CC;
g_sdhci_regs[SDHCI_SRS04 / 4] = 0;
ck_assert_int_eq(sdhci_wait_busy(0), -1);
ck_assert_uint_gt(g_wdt_pets, 0);
}
END_TEST
/* A ready card returns success promptly. */
START_TEST(test_wait_busy_ready)
{
/* DAT0 high; the first CMD13 response has READY_FOR_DATA set, so
* sdhci_cmd() returns 0 and the loop exits. */
g_sdhci_regs[SDHCI_SRS09 / 4] |= SDHCI_SRS09_DAT0_LVL;
g_sdhci_regs[SDHCI_SRS12 / 4] = SDHCI_SRS12_CC;
g_sdhci_regs[SDHCI_SRS04 / 4] = 1U << 8;
ck_assert_int_eq(sdhci_wait_busy(1), 0);
}
END_TEST
Suite *sdhci_wait_busy_suite(void)
{
Suite *s = suite_create("sdhci-wait-busy");
TCase *tc = tcase_create("sdhci-wait-busy");
tcase_add_checked_fixture(tc, setup, teardown);
tcase_add_test(tc, test_wait_busy_dat0_timeout);
tcase_add_test(tc, test_wait_busy_cmd13_timeout);
tcase_add_test(tc, test_wait_busy_ready);
suite_add_tcase(s, tc);
return s;
}
int main(void)
{
int fails;
Suite *s = sdhci_wait_busy_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return fails;
}