diff --git a/components/drivers/rtc/SConscript b/components/drivers/rtc/SConscript index 1dd8dbfb59e7..a8099d13ef64 100644 --- a/components/drivers/rtc/SConscript +++ b/components/drivers/rtc/SConscript @@ -1,3 +1,4 @@ +import os from building import * cwd = GetCurrentDir() @@ -42,4 +43,9 @@ if GetDepend(['RT_RTC_RX8010']): group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_RTC'], CPPPATH = CPPPATH) +list = os.listdir(cwd) +for item in list: + if os.path.isfile(os.path.join(cwd, item, 'SConscript')): + group = group + SConscript(os.path.join(item, 'SConscript')) + Return('group') diff --git a/components/drivers/rtc/utest/Kconfig b/components/drivers/rtc/utest/Kconfig new file mode 100644 index 000000000000..58fff2ec4559 --- /dev/null +++ b/components/drivers/rtc/utest/Kconfig @@ -0,0 +1,40 @@ +menu "RTC Test" + depends on RT_USING_RTC + +config RT_UTEST_RTC + bool "RTC Time Test" + default n + help + Validate RTC timestamp set/get and time advancement. The test changes + the RTC time and restores it after completion. + +config RT_UTEST_RTC_ALARM + bool "RTC Alarm Test" + depends on RT_USING_ALARM + depends on RT_USING_HEAP + default n + help + Validate one-shot alarm delivery using the current RTC time. The test + reapplies the current time to start the RTC counter and changes the + alarm, so no application may use them while the test runs. + +if RT_UTEST_RTC_ALARM + + config RT_RTC_TC_ALARM_DELAY_SEC + int "Alarm Delay in Seconds" + range 3 120 + default 15 + help + Delay between reading the current RTC time and the one-shot alarm. + + config RT_RTC_TC_ALARM_TIMEOUT_SEC + int "Alarm Timeout in Seconds" + range 4 180 + default 25 + help + Maximum time to wait for the alarm callback. Keep this greater + than the configured alarm delay. + +endif + +endmenu diff --git a/components/drivers/rtc/utest/SConscript b/components/drivers/rtc/utest/SConscript new file mode 100644 index 000000000000..041f04a7745b --- /dev/null +++ b/components/drivers/rtc/utest/SConscript @@ -0,0 +1,16 @@ +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = [] +CPPPATH = [cwd, cwd + '/../../include'] + +if GetDepend(['RT_UTEST_RTC']): + src += ['rtc_time_tc.c'] + +if GetDepend(['RT_UTEST_RTC_ALARM']): + src += ['rtc_alarm_tc.c'] + +group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_RTC'], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/drivers/rtc/utest/rtc_alarm_tc.c b/components/drivers/rtc/utest/rtc_alarm_tc.c new file mode 100644 index 000000000000..0f56b88e5b2f --- /dev/null +++ b/components/drivers/rtc/utest/rtc_alarm_tc.c @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2006-2026 RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-25 CYFS add RTC alarm utest + */ + +/** + * Test Case Name: RTC Alarm Test + * + * Test Objectives: + * - Verify that a one-shot alarm reaches the RT-Thread alarm callback. + * - Verify the callback timestamp and one-shot state. + * + * Dependencies: + * - Requires `RT_UTEST_RTC_ALARM`, `RT_USING_RTC`, and `RT_USING_ALARM`. + * - The RTC device must be registered as `rtc` and support hardware alarms. + * - No application may use the RTC or alarm service during the test. + * + * Expected Results: + * - The one-shot alarm callback arrives before the configured timeout. + * - The alarm is stopped and deleted after the test. + */ + +#include +#include +#include +#include +#include "utest.h" + +#ifdef RT_UTEST_RTC_ALARM + +#define RTC_ALARM_TC_LATE_TOLERANCE_SEC 5 +#define RTC_ALARM_TC_SUITE_TIMEOUT (RT_RTC_TC_ALARM_TIMEOUT_SEC + 10) + +static struct rt_semaphore rtc_alarm_tc_sem; +static rt_bool_t rtc_alarm_tc_sem_inited; +static rt_device_t rtc_alarm_tc_device; +static rt_bool_t rtc_alarm_tc_device_opened; +static rt_alarm_t rtc_alarm_tc_alarm; +static volatile rt_uint32_t rtc_alarm_tc_count; +static volatile time_t rtc_alarm_tc_timestamp; + +static void rtc_alarm_tc_record_cleanup_error(rt_err_t *result, rt_err_t operation_result) +{ + if ((*result == RT_EOK) && (operation_result != RT_EOK)) + { + *result = operation_result; + } +} + +static rt_err_t rtc_alarm_tc_timestamp_to_tm(time_t timestamp, struct tm *time) +{ +#ifdef RT_ALARM_USING_LOCAL_TIME + return localtime_r(×tamp, time) != RT_NULL ? RT_EOK : -RT_ERROR; +#else + return gmtime_r(×tamp, time) != RT_NULL ? RT_EOK : -RT_ERROR; +#endif +} + +static time_t rtc_alarm_tc_tm_to_timestamp(const struct tm *time) +{ + struct tm alarm_time; + + alarm_time = *time; +#ifdef RT_ALARM_USING_LOCAL_TIME + return mktime(&alarm_time); +#else + return timegm(&alarm_time); +#endif +} + +static void rtc_alarm_tc_callback(rt_alarm_t alarm, time_t timestamp) +{ + RT_UNUSED(alarm); + + rtc_alarm_tc_timestamp = timestamp; + rtc_alarm_tc_count++; + rt_sem_release(&rtc_alarm_tc_sem); +} + +static void rtc_oneshot_alarm_test(void) +{ + rt_err_t result; + rt_tick_t timeout; + time_t current_timestamp; + time_t requested_timestamp; + time_t effective_timestamp; + time_t timeout_timestamp; + struct rt_alarm_setup setup; + + rtc_alarm_tc_count = 0; + rtc_alarm_tc_timestamp = 0; + + result = get_timestamp(¤t_timestamp); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + + /* First open may initialize the RTC clock source with its counter stopped. */ + result = set_timestamp(current_timestamp); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + + result = get_timestamp(¤t_timestamp); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + + rt_memset(&setup, 0, sizeof(setup)); + setup.flag = RT_ALARM_ONESHOT; + requested_timestamp = current_timestamp + RT_RTC_TC_ALARM_DELAY_SEC; + result = rtc_alarm_tc_timestamp_to_tm(requested_timestamp, &setup.wktime); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + + rtc_alarm_tc_alarm = rt_alarm_create(rtc_alarm_tc_callback, &setup); + uassert_not_null(rtc_alarm_tc_alarm); + if (rtc_alarm_tc_alarm == RT_NULL) + { + return; + } + + result = rt_alarm_start(rtc_alarm_tc_alarm); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + + effective_timestamp = rtc_alarm_tc_tm_to_timestamp(&rtc_alarm_tc_alarm->wktime); + uassert_true(effective_timestamp != (time_t)-1); + if (effective_timestamp == (time_t)-1) + { + return; + } + + LOG_I("RTC alarm current=%ld requested=%ld effective=%ld", + (long)current_timestamp, + (long)requested_timestamp, + (long)effective_timestamp); + + timeout = rt_tick_from_millisecond(RT_RTC_TC_ALARM_TIMEOUT_SEC * 1000); + if (timeout == 0) + { + timeout = 1; + } + result = rt_sem_take(&rtc_alarm_tc_sem, timeout); + if (result != RT_EOK) + { + if (get_timestamp(&timeout_timestamp) == RT_EOK) + { + LOG_E("RTC alarm timeout: result=%d current=%ld count=%u", + result, + (long)timeout_timestamp, + rtc_alarm_tc_count); + } + else + { + LOG_E("RTC alarm timeout: result=%d, current time unavailable, count=%u", + result, + rtc_alarm_tc_count); + } + } + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + + uassert_int_equal((int)rtc_alarm_tc_count, 1); + uassert_true(rtc_alarm_tc_timestamp >= effective_timestamp); + uassert_true(rtc_alarm_tc_timestamp <= effective_timestamp + RTC_ALARM_TC_LATE_TOLERANCE_SEC); + uassert_true((rtc_alarm_tc_alarm->flag & RT_ALARM_STATE_START) == 0); +} + +static rt_err_t utest_tc_init(void) +{ + rt_err_t result; + rt_device_t rtc_device; + + rtc_alarm_tc_sem_inited = RT_FALSE; + rtc_alarm_tc_device = RT_NULL; + rtc_alarm_tc_device_opened = RT_FALSE; + rtc_alarm_tc_alarm = RT_NULL; + + if (RT_RTC_TC_ALARM_TIMEOUT_SEC <= RT_RTC_TC_ALARM_DELAY_SEC) + { + LOG_E("RTC alarm timeout must be greater than alarm delay"); + return -RT_EINVAL; + } + + rtc_device = rt_device_find("rtc"); + if (rtc_device == RT_NULL) + { + LOG_E("RTC device rtc is not registered"); + return -RT_ERROR; + } + if (rtc_device->type != RT_Device_Class_RTC) + { + LOG_E("Device rtc is not an RTC device"); + return -RT_ERROR; + } + + result = rt_device_open(rtc_device, RT_DEVICE_OFLAG_RDWR); + if (result != RT_EOK) + { + return result; + } + rtc_alarm_tc_device = rtc_device; + rtc_alarm_tc_device_opened = RT_TRUE; + + result = rt_sem_init(&rtc_alarm_tc_sem, "rtctc", 0, RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_device_close(rtc_alarm_tc_device); + rtc_alarm_tc_device = RT_NULL; + rtc_alarm_tc_device_opened = RT_FALSE; + return result; + } + rtc_alarm_tc_sem_inited = RT_TRUE; + + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + rt_err_t result; + rt_err_t operation_result; + + result = RT_EOK; + if (rtc_alarm_tc_alarm != RT_NULL) + { + operation_result = rt_alarm_stop(rtc_alarm_tc_alarm); + rtc_alarm_tc_record_cleanup_error(&result, operation_result); + operation_result = rt_alarm_delete(rtc_alarm_tc_alarm); + rtc_alarm_tc_record_cleanup_error(&result, operation_result); + rtc_alarm_tc_alarm = RT_NULL; + } + + if (rtc_alarm_tc_sem_inited) + { + operation_result = rt_sem_detach(&rtc_alarm_tc_sem); + rtc_alarm_tc_record_cleanup_error(&result, operation_result); + rtc_alarm_tc_sem_inited = RT_FALSE; + } + + if (rtc_alarm_tc_device_opened) + { + operation_result = rt_device_close(rtc_alarm_tc_device); + rtc_alarm_tc_record_cleanup_error(&result, operation_result); + rtc_alarm_tc_device = RT_NULL; + rtc_alarm_tc_device_opened = RT_FALSE; + } + + return result; +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(rtc_oneshot_alarm_test); +} + +UTEST_TC_EXPORT(testcase, + "components.drivers.rtc.alarm", + utest_tc_init, + utest_tc_cleanup, + RTC_ALARM_TC_SUITE_TIMEOUT); + +#endif /* RT_UTEST_RTC_ALARM */ diff --git a/components/drivers/rtc/utest/rtc_time_tc.c b/components/drivers/rtc/utest/rtc_time_tc.c new file mode 100644 index 000000000000..1e0a3090a490 --- /dev/null +++ b/components/drivers/rtc/utest/rtc_time_tc.c @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2006-2026 RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-25 CYFS add RTC time utest + */ + +/** + * Test Case Name: RTC Time Test + * + * Test Objectives: + * - Verify that the RTC timestamp can be set and read back. + * - Verify that the RTC advances across a second boundary. + * + * Dependencies: + * - Requires `RT_UTEST_RTC` and `RT_USING_RTC`. + * - The RTC device must be registered as `rtc`. + * - No application may change the RTC during the test. + * + * Expected Results: + * - Timestamp set/get and advancement checks pass. + * - The original RTC time is restored with elapsed test time added. + */ + +#include +#include +#include +#include "utest.h" + +#ifdef RT_UTEST_RTC + +/* 2025-01-15 12:00:50 UTC, close enough to cover minute rollover. */ +#define RTC_TC_BASE_TIMESTAMP ((time_t)1736942450) +#define RTC_TC_ADVANCE_WAIT_MS 3000 +#define RTC_TC_READBACK_TOLERANCE_SEC 2 +#define RTC_TC_ADVANCE_TOLERANCE_SEC 5 +#define RTC_TC_SUITE_TIMEOUT 10 + +static time_t rtc_tc_original_timestamp; +static rt_tick_t rtc_tc_original_tick; +static rt_bool_t rtc_tc_timestamp_saved; +static rt_device_t rtc_tc_device; +static rt_bool_t rtc_tc_device_opened; + +static void rtc_timestamp_set_get_test(void) +{ + rt_err_t result; + time_t first_timestamp; + time_t second_timestamp; + + result = set_timestamp(RTC_TC_BASE_TIMESTAMP); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + + result = get_timestamp(&first_timestamp); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + uassert_true(first_timestamp >= RTC_TC_BASE_TIMESTAMP); + uassert_true(first_timestamp <= RTC_TC_BASE_TIMESTAMP + RTC_TC_READBACK_TOLERANCE_SEC); + + rt_thread_mdelay(RTC_TC_ADVANCE_WAIT_MS); + + result = get_timestamp(&second_timestamp); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return; + } + LOG_I("RTC time first=%ld second=%ld", + (long)first_timestamp, + (long)second_timestamp); + uassert_true(second_timestamp > first_timestamp); + uassert_true(second_timestamp <= first_timestamp + RTC_TC_ADVANCE_TOLERANCE_SEC); +} + +static rt_err_t utest_tc_init(void) +{ + rt_err_t result; + rt_device_t rtc_device; + + rtc_tc_timestamp_saved = RT_FALSE; + rtc_tc_device = RT_NULL; + rtc_tc_device_opened = RT_FALSE; + + rtc_device = rt_device_find("rtc"); + if (rtc_device == RT_NULL) + { + LOG_E("RTC device rtc is not registered"); + return -RT_ERROR; + } + if (rtc_device->type != RT_Device_Class_RTC) + { + LOG_E("Device rtc is not an RTC device"); + return -RT_ERROR; + } + + result = rt_device_open(rtc_device, RT_DEVICE_OFLAG_RDWR); + if (result != RT_EOK) + { + return result; + } + rtc_tc_device = rtc_device; + rtc_tc_device_opened = RT_TRUE; + + result = get_timestamp(&rtc_tc_original_timestamp); + if (result != RT_EOK) + { + rt_device_close(rtc_tc_device); + rtc_tc_device = RT_NULL; + rtc_tc_device_opened = RT_FALSE; + return result; + } + rtc_tc_original_tick = rt_tick_get(); + rtc_tc_timestamp_saved = RT_TRUE; + + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + rt_err_t result; + rt_err_t operation_result; + time_t restore_timestamp; + rt_tick_t elapsed_tick; + + result = RT_EOK; + if (rtc_tc_timestamp_saved) + { + elapsed_tick = rt_tick_get_delta(rtc_tc_original_tick); + restore_timestamp = rtc_tc_original_timestamp + elapsed_tick / RT_TICK_PER_SECOND; + operation_result = set_timestamp(restore_timestamp); + if (operation_result != RT_EOK) + { + result = operation_result; + } + rtc_tc_timestamp_saved = RT_FALSE; + } + + if (rtc_tc_device_opened) + { + operation_result = rt_device_close(rtc_tc_device); + if ((result == RT_EOK) && (operation_result != RT_EOK)) + { + result = operation_result; + } + rtc_tc_device = RT_NULL; + rtc_tc_device_opened = RT_FALSE; + } + + return result; +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(rtc_timestamp_set_get_test); +} + +UTEST_TC_EXPORT(testcase, + "components.drivers.rtc.time", + utest_tc_init, + utest_tc_cleanup, + RTC_TC_SUITE_TIMEOUT); + +#endif /* RT_UTEST_RTC */ diff --git a/components/utilities/utest/Kconfig b/components/utilities/utest/Kconfig index af23e9e78995..3df60c059673 100644 --- a/components/utilities/utest/Kconfig +++ b/components/utilities/utest/Kconfig @@ -17,6 +17,7 @@ menu "RT-Thread Utestcases" rsource "../../../components/drivers/audio/utest/Kconfig" rsource "../../../components/drivers/ipc/utest/Kconfig" rsource "../../../components/drivers/pin/utest/Kconfig" + rsource "../../../components/drivers/rtc/utest/Kconfig" rsource "../../../components/drivers/serial/utest/Kconfig" rsource "../../../components/drivers/smp_call/utest/Kconfig" rsource "../../../components/drivers/spi/utest/Kconfig"