diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt index 85ff98320dd2..248fee3d7084 100644 --- a/drivers/rtc/CMakeLists.txt +++ b/drivers/rtc/CMakeLists.txt @@ -21,3 +21,4 @@ zephyr_library_sources_ifdef(CONFIG_RTC_SMARTBOND rtc_smartbond.c) zephyr_library_sources_ifdef(CONFIG_RTC_ATMEL_SAM rtc_sam.c) zephyr_library_sources_ifdef(CONFIG_RTC_RPI_PICO rtc_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_RTC_NUMAKER rtc_numaker.c) +zephyr_library_sources_ifdef(CONFIG_RTC_HWCLOCK rtc_hwclock.c) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 46bd2562dd93..5dc763469989 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -53,5 +53,6 @@ source "drivers/rtc/Kconfig.sam" source "drivers/rtc/Kconfig.smartbond" source "drivers/rtc/Kconfig.stm32" source "drivers/rtc/Kconfig.numaker" +source "drivers/rtc/Kconfig.hwclock" endif # RTC diff --git a/drivers/rtc/Kconfig.hwclock b/drivers/rtc/Kconfig.hwclock new file mode 100644 index 000000000000..7270fb8592ca --- /dev/null +++ b/drivers/rtc/Kconfig.hwclock @@ -0,0 +1,25 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +DT_CHOSEN_RTC := zephyr,rtc + +config RTC_HWCLOCK + bool "Hardware clock" + default y + depends on $(dt_chosen_enabled,$(DT_CHOSEN_RTC)) + depends on SYS_REALTIME + help + The hardware clock is used to set the system + real-time clock on boot. + +if RTC_HWCLOCK + +config RTC_HWCLOCK_INIT_PRIORITY + int "Initialization priority of hardware clock" + default 51 + help + The initialization priority of the hardware clock + must be higher than the initialization priority of + the chosen RTC device driver. + +endif # RTC_HWCLOCK diff --git a/drivers/rtc/rtc_hwclock.c b/drivers/rtc/rtc_hwclock.c new file mode 100644 index 000000000000..7688b2c145ba --- /dev/null +++ b/drivers/rtc/rtc_hwclock.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +BUILD_ASSERT( + CONFIG_RTC_HWCLOCK_INIT_PRIORITY > CONFIG_RTC_INIT_PRIORITY, + "Hardware clock init prio must be higher than the RTC device driver" +); + +static const struct device *rtc = DEVICE_DT_GET(DT_CHOSEN(zephyr_rtc)); + +static const struct sys_datetime *rtc_time_to_sys_datetime(const struct rtc_time *timeptr) +{ + return (const struct sys_datetime *)timeptr; +} + +static int rtc_hwclock_init(void) +{ + int ret; + struct rtc_time rtctime; + const struct sys_datetime *datetime; + + if (!device_is_ready(rtc)) { + return -ENODEV; + } + + ret = rtc_get_time(rtc, &rtctime); + if (ret == -ENODATA) { + return 0; + } else if (ret < 0) { + return ret; + } + + datetime = rtc_time_to_sys_datetime(&rtctime); + return sys_realtime_set_datetime(datetime); +} + +SYS_INIT(rtc_hwclock_init, POST_KERNEL, CONFIG_RTC_HWCLOCK_INIT_PRIORITY); diff --git a/drivers/rtc/rtc_shell.c b/drivers/rtc/rtc_shell.c index a87056c6ed70..d624ced7fdfb 100644 --- a/drivers/rtc/rtc_shell.c +++ b/drivers/rtc/rtc_shell.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -227,6 +228,43 @@ static void device_name_get(size_t idx, struct shell_static_entry *entry) entry->subcmd = NULL; } +#if CONFIG_SYS_REALTIME +static int cmd_sync(const struct shell *sh, size_t argc, char **argv) +{ + const struct device *dev = device_get_binding(argv[1]); + + if (!device_is_ready(dev)) { + shell_error(sh, "device %s not %s", argv[1], "ready"); + return -ENODEV; + } + + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + struct rtc_time rtctime; + + int res = rtc_get_time(dev, &rtctime); + + if (res == -ENODATA) { + shell_error(sh, "device %s not %s", argv[1], "set"); + return 0; + } + if (res < 0) { + return res; + } + + struct sys_datetime *datetime = (struct sys_datetime *)(&rtctime); + + res = sys_realtime_set_datetime(datetime); + + if (res < 0) { + shell_error(sh, "failed to set system realtime from %s", argv[1]); + } + + return res; +} +#endif /* CONFIG_SYS_REALTIME */ + #define RTC_GET_HELP \ ("Get current time (UTC)\n" \ "Usage: rtc get ") @@ -235,12 +273,22 @@ static void device_name_get(size_t idx, struct shell_static_entry *entry) ("Set UTC time\n" \ "Usage: rtc set | | ") +#if CONFIG_SYS_REALTIME +#define RTC_SYNC_HELP \ + ("Set system realtime from RTC time\n" \ + "Usage: rtc sync ") +#endif /* CONFIG_SYS_REALTIME */ + SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get); -SHELL_STATIC_SUBCMD_SET_CREATE(sub_rtc, - /* Alphabetically sorted */ - SHELL_CMD_ARG(set, &dsub_device_name, RTC_SET_HELP, cmd_set, 3, 0), - SHELL_CMD_ARG(get, &dsub_device_name, RTC_GET_HELP, cmd_get, 2, 0), - SHELL_SUBCMD_SET_END); +SHELL_STATIC_SUBCMD_SET_CREATE( + sub_rtc, + SHELL_CMD_ARG(set, &dsub_device_name, RTC_SET_HELP, cmd_set, 3, 0), + SHELL_CMD_ARG(get, &dsub_device_name, RTC_GET_HELP, cmd_get, 2, 0), +#if CONFIG_SYS_REALTIME + SHELL_CMD_ARG(sync, &dsub_device_name, RTC_SYNC_HELP, cmd_sync, 2, 0), +#endif /* CONFIG_SYS_REALTIME */ + SHELL_SUBCMD_SET_END +); SHELL_CMD_REGISTER(rtc, &sub_rtc, "RTC commands", NULL); diff --git a/include/zephyr/sys/internal/realtime.h b/include/zephyr/sys/internal/realtime.h new file mode 100644 index 000000000000..c7d0377f23f5 --- /dev/null +++ b/include/zephyr/sys/internal/realtime.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_SYS_INTERNAL_REALTIME_H_ +#define ZEPHYR_INCLUDE_SYS_INTERNAL_REALTIME_H_ + +#include +#include + +/** Validate timestamp */ +bool sys_realtime_validate_timestamp(const int64_t *timestamp_ms); + +/** Validate datetime */ +bool sys_realtime_validate_datetime(const struct sys_datetime *datetime); + +#endif /* ZEPHYR_INCLUDE_SYS_INTERNAL_REALTIME_H_ */ diff --git a/include/zephyr/sys/realtime.h b/include/zephyr/sys/realtime.h new file mode 100644 index 000000000000..f849dfbc8520 --- /dev/null +++ b/include/zephyr/sys/realtime.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_SYS_REALTIME_H_ +#define ZEPHYR_INCLUDE_SYS_REALTIME_H_ + +#include + +struct sys_datetime { + int tm_sec; /**< Seconds [0, 59] */ + int tm_min; /**< Minutes [0, 59] */ + int tm_hour; /**< Hours [0, 23] */ + int tm_mday; /**< Day of the month [1, 31] */ + int tm_mon; /**< Month [0, 11] */ + int tm_year; /**< Year - 1900 */ + int tm_wday; /**< Day of the week [0, 6] (Sunday = 0) (Unknown = -1) */ + int tm_yday; /**< Day of the year [0, 365] (Unknown = -1) */ + int tm_isdst; /**< Daylight saving time flag [-1] (Unknown = -1) */ + int tm_nsec; /**< Nanoseconds [0, 999999999] (Unknown = 0) */ +}; + +/** Get universal coordinated time unix timestamp in milliseconds */ +__syscall int sys_realtime_get_timestamp(int64_t *timestamp_ms); +/** Set universal coordinated time unix timestamp in milliseconds */ +__syscall int sys_realtime_set_timestamp(const int64_t *timestamp_ms); + +/** Get universal coordinated time datetime */ +__syscall int sys_realtime_get_datetime(struct sys_datetime *datetime); +/** Set universal coordinated time datetime */ +__syscall int sys_realtime_set_datetime(const struct sys_datetime *datetime); + +/** Convert universal coordinated time datetime to unix timestamp in milliseconds */ +int sys_realtime_datetime_to_timestamp(int64_t *timestamp_ms, const struct sys_datetime *datetime); +/** Convert universal coordinated time unix timestamp in milliseconds to datetime */ +int sys_realtime_timestamp_to_datetime(struct sys_datetime *datetime, const int64_t *timestamp_ms); + +#include + +#endif /* ZEPHYR_INCLUDE_SYS_REALTIME_H_ */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7a0ee04e4acd..d122e430c178 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(heap) add_subdirectory(mem_blocks) add_subdirectory(os) add_subdirectory(utils) +add_subdirectory(realtime) add_subdirectory_ifdef(CONFIG_SMF smf) add_subdirectory_ifdef(CONFIG_OPENAMP open-amp) add_subdirectory_ifdef(CONFIG_ACPI acpi) diff --git a/lib/Kconfig b/lib/Kconfig index af6717bc22ec..cee3b09fefa3 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -28,4 +28,7 @@ source "lib/acpi/Kconfig" source "lib/runtime/Kconfig" source "lib/utils/Kconfig" + +source "lib/realtime/Kconfig" + endmenu diff --git a/lib/posix/options/CMakeLists.txt b/lib/posix/options/CMakeLists.txt index c67ef4807ace..049f2692da38 100644 --- a/lib/posix/options/CMakeLists.txt +++ b/lib/posix/options/CMakeLists.txt @@ -2,10 +2,6 @@ set(GEN_DIR ${ZEPHYR_BINARY_DIR}/include/generated) -zephyr_syscall_header( - posix_clock.h -) - if(CONFIG_POSIX_API) zephyr_include_directories(${ZEPHYR_BASE}/include/zephyr/posix) endif() diff --git a/lib/posix/options/Kconfig.timer b/lib/posix/options/Kconfig.timer index 42eed40bce68..8f7f0ec5a35c 100644 --- a/lib/posix/options/Kconfig.timer +++ b/lib/posix/options/Kconfig.timer @@ -5,6 +5,7 @@ menuconfig POSIX_TIMERS bool "POSIX timers, clocks, and sleep functions" + select SYS_REALTIME help Select 'y' here and Zephyr will provide implementations of clock_getres(), clock_gettime(), clock_settime(), nanosleep(), timer_create(), timer_delete(), timer_getoverrun(), diff --git a/lib/posix/options/clock.c b/lib/posix/options/clock.c index 5fa09daaae6d..85544337bbfe 100644 --- a/lib/posix/options/clock.c +++ b/lib/posix/options/clock.c @@ -5,97 +5,157 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "posix_clock.h" - #include #include #include #include #include -#include -#include +#include + +static bool __posix_clock_validate_timespec(const struct timespec *ts) +{ + return ts->tv_sec >= 0 && ts->tv_nsec >= 0 && ts->tv_nsec < NSEC_PER_SEC; +} + +static void __posix_clock_k_ticks_to_timespec(struct timespec *ts, int64_t ticks) +{ + uint64_t elapsed_secs = ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC; + uint64_t nremainder = ticks - elapsed_secs * CONFIG_SYS_CLOCK_TICKS_PER_SEC; + + ts->tv_sec = (time_t) elapsed_secs; + /* For ns 32 bit conversion can be used since its smaller than 1sec. */ + ts->tv_nsec = (int32_t) k_ticks_to_ns_floor32(nremainder); +} + +static void __posix_clock_get_monotonic(struct timespec *ts) +{ + __posix_clock_k_ticks_to_timespec(ts, k_uptime_ticks()); +} + +static void __posix_clock_msec_to_timespec(struct timespec *ts, const int64_t *ms) +{ + ts->tv_sec = *ms / MSEC_PER_SEC; + ts->tv_nsec = (*ms % MSEC_PER_SEC) * NSEC_PER_MSEC; +} + +static uint64_t __posix_clock_timespec_to_usec(const struct timespec *ts) +{ + uint64_t usec; + + usec = ts->tv_sec; + usec *= USEC_PER_SEC; + usec += DIV_ROUND_UP(ts->tv_nsec, NSEC_PER_USEC); + return usec; +} + +static uint64_t __posix_clock_timespec_to_msec(const struct timespec *ts) +{ + uint64_t msec; + + msec = ts->tv_sec; + msec *= MSEC_PER_SEC; + msec += DIV_ROUND_UP(ts->tv_nsec, NSEC_PER_MSEC); + return msec; +} + +/* Check if a_ts is less than b_ts (a_ts < b_ts) */ +static bool __posix_clock_timespec_less_than(const struct timespec *a_ts, + const struct timespec *b_ts) +{ + return (a_ts->tv_sec < b_ts->tv_sec) || + (a_ts->tv_sec == b_ts->tv_sec && a_ts->tv_nsec < b_ts->tv_nsec); +} /* - * `k_uptime_get` returns a timestamp based on an always increasing - * value from the system start. To support the `CLOCK_REALTIME` - * clock, this `rt_clock_base` records the time that the system was - * started. This can either be set via 'clock_settime', or could be - * set from a real time clock, if such hardware is present. + * Subtract b_ts from a_ts placing result in res_ts (ret_ts = a_ts - b_ts) + * Presumes a_ts >= b_ts */ -static struct timespec rt_clock_base; -static struct k_spinlock rt_clock_base_lock; +static void __posix_clock_timespec_subtract(struct timespec *res_ts, + const struct timespec *a_ts, + const struct timespec *b_ts) +{ + res_ts->tv_sec = a_ts->tv_sec - b_ts->tv_sec; -/** - * @brief Get clock time specified by clock_id. - * - * See IEEE 1003.1 - */ -int z_impl___posix_clock_get_base(clockid_t clock_id, struct timespec *base) + if (b_ts->tv_nsec <= a_ts->tv_nsec) { + res_ts->tv_nsec = a_ts->tv_nsec - b_ts->tv_nsec; + } else { + res_ts->tv_sec--; + res_ts->tv_nsec = a_ts->tv_nsec + NSEC_PER_SEC - b_ts->tv_nsec; + } +} + +/* Add b_ts to a_ts placing result in res_ts (ret_ts = a_ts + b_ts) */ +static void __posix_clock_timespec_add(struct timespec *res_ts, + const struct timespec *a_ts, + const struct timespec *b_ts) { - switch (clock_id) { - case CLOCK_MONOTONIC: - base->tv_sec = 0; - base->tv_nsec = 0; - break; + res_ts->tv_sec = a_ts->tv_sec + b_ts->tv_sec; + res_ts->tv_nsec = a_ts->tv_nsec + b_ts->tv_nsec; - case CLOCK_REALTIME: - K_SPINLOCK(&rt_clock_base_lock) { - *base = rt_clock_base; - } - break; + if (res_ts->tv_nsec >= NSEC_PER_SEC) { + res_ts->tv_sec++; + res_ts->tv_nsec -= NSEC_PER_SEC; + } +} - default: - errno = EINVAL; - return -1; +static void __posix_clock_timespec_copy(struct timespec *des_ts, const struct timespec *src_ts) +{ + des_ts->tv_sec = src_ts->tv_sec; + des_ts->tv_nsec = src_ts->tv_nsec; +} + +static void __posix_clock_get_realtime(struct timespec *ts) +{ + int res; + int64_t timestamp_ms; + + res = sys_realtime_get_timestamp(×tamp_ms); + if (timestamp_ms < 0) { + /* timespec can't be negative */ + ts->tv_sec = 0; + ts->tv_nsec = 0; } - return 0; + __posix_clock_msec_to_timespec(ts, ×tamp_ms); } -#ifdef CONFIG_USERSPACE -int z_vrfy___posix_clock_get_base(clockid_t clock_id, struct timespec *ts) +static int __posix_clock_set_realtime(const struct timespec *ts) { - K_OOPS(K_SYSCALL_MEMORY_WRITE(ts, sizeof(*ts))); - return z_impl___posix_clock_get_base(clock_id, ts); + int64_t timestamp_ms; + int res; + + timestamp_ms = (int64_t)__posix_clock_timespec_to_msec(ts); + + res = sys_realtime_set_timestamp(×tamp_ms); + if (res < 0) { + errno = EINVAL; + return -1; + } + + return 0; } -#include -#endif int clock_gettime(clockid_t clock_id, struct timespec *ts) { - struct timespec base; + int res; switch (clock_id) { case CLOCK_MONOTONIC: - base.tv_sec = 0; - base.tv_nsec = 0; + __posix_clock_get_monotonic(ts); + res = 0; break; case CLOCK_REALTIME: - (void)__posix_clock_get_base(clock_id, &base); + __posix_clock_get_realtime(ts); + res = 0; break; default: errno = EINVAL; - return -1; + res = -1; } - uint64_t ticks = k_uptime_ticks(); - uint64_t elapsed_secs = ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC; - uint64_t nremainder = ticks - elapsed_secs * CONFIG_SYS_CLOCK_TICKS_PER_SEC; - - ts->tv_sec = (time_t) elapsed_secs; - /* For ns 32 bit conversion can be used since its smaller than 1sec. */ - ts->tv_nsec = (int32_t) k_ticks_to_ns_floor32(nremainder); - - ts->tv_sec += base.tv_sec; - ts->tv_nsec += base.tv_nsec; - if (ts->tv_nsec >= NSEC_PER_SEC) { - ts->tv_sec++; - ts->tv_nsec -= NSEC_PER_SEC; - } - - return 0; + return res; } int clock_getres(clockid_t clock_id, struct timespec *res) @@ -130,31 +190,12 @@ int clock_getres(clockid_t clock_id, struct timespec *res) */ int clock_settime(clockid_t clock_id, const struct timespec *tp) { - struct timespec base; - k_spinlock_key_t key; - - if (clock_id != CLOCK_REALTIME) { + if (clock_id != CLOCK_REALTIME || !__posix_clock_validate_timespec(tp)) { errno = EINVAL; return -1; } - if (tp->tv_nsec < 0 || tp->tv_nsec >= NSEC_PER_SEC) { - errno = EINVAL; - return -1; - } - - uint64_t elapsed_nsecs = k_ticks_to_ns_floor64(k_uptime_ticks()); - int64_t delta = (int64_t)NSEC_PER_SEC * tp->tv_sec + tp->tv_nsec - - elapsed_nsecs; - - base.tv_sec = delta / NSEC_PER_SEC; - base.tv_nsec = delta % NSEC_PER_SEC; - - key = k_spin_lock(&rt_clock_base_lock); - rt_clock_base = base; - k_spin_unlock(&rt_clock_base_lock, key); - - return 0; + return __posix_clock_set_realtime(tp); } /* @@ -195,10 +236,10 @@ int usleep(useconds_t useconds) static int __z_clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp, struct timespec *rmtp) { - uint64_t ns; - uint64_t us; - uint64_t uptime_ns; - k_spinlock_key_t key; + volatile uint64_t usec; + struct timespec clock_ts; + struct timespec rel_ts; + struct timespec abs_ts; const bool update_rmtp = rmtp != NULL; if (!(clock_id == CLOCK_REALTIME || clock_id == CLOCK_MONOTONIC)) { @@ -211,42 +252,35 @@ static int __z_clock_nanosleep(clockid_t clock_id, int flags, const struct times return -1; } - if (rqtp->tv_sec < 0 || rqtp->tv_nsec < 0 || rqtp->tv_nsec >= NSEC_PER_SEC) { + if (!__posix_clock_validate_timespec(rqtp)) { errno = EINVAL; return -1; } - if ((flags & TIMER_ABSTIME) == 0 && - unlikely(rqtp->tv_sec >= ULLONG_MAX / NSEC_PER_SEC)) { - - ns = rqtp->tv_nsec + NSEC_PER_SEC - + k_sleep(K_SECONDS(rqtp->tv_sec - 1)) * NSEC_PER_MSEC; - } else { - ns = rqtp->tv_sec * NSEC_PER_SEC + rqtp->tv_nsec; - } - - uptime_ns = k_cyc_to_ns_ceil64(k_cycle_get_32()); + if ((flags & TIMER_ABSTIME) && clock_id == CLOCK_REALTIME) { + __posix_clock_get_realtime(&clock_ts); - if (flags & TIMER_ABSTIME && clock_id == CLOCK_REALTIME) { - key = k_spin_lock(&rt_clock_base_lock); - ns -= rt_clock_base.tv_sec * NSEC_PER_SEC + rt_clock_base.tv_nsec; - k_spin_unlock(&rt_clock_base_lock, key); - } + if (__posix_clock_timespec_less_than(rqtp, &clock_ts)) { + goto post_sleep; + } - if ((flags & TIMER_ABSTIME) == 0) { - ns += uptime_ns; + __posix_clock_timespec_subtract(&rel_ts, rqtp, &clock_ts); + __posix_clock_get_monotonic(&clock_ts); + __posix_clock_timespec_add(&abs_ts, &rel_ts, &clock_ts); + } else if (flags & TIMER_ABSTIME) { + __posix_clock_timespec_copy(&abs_ts, rqtp); + } else { + __posix_clock_get_monotonic(&clock_ts); + __posix_clock_timespec_add(&abs_ts, rqtp, &clock_ts); } - if (ns <= uptime_ns) { - goto do_rmtp_update; - } + usec = __posix_clock_timespec_to_usec(&abs_ts); - us = DIV_ROUND_UP(ns, NSEC_PER_USEC); do { - us = k_sleep(K_TIMEOUT_ABS_US(us)) * 1000; - } while (us != 0); + usec = k_sleep(K_TIMEOUT_ABS_US(usec)) * USEC_PER_MSEC; + } while (usec != 0); -do_rmtp_update: +post_sleep: if (update_rmtp) { rmtp->tv_sec = 0; rmtp->tv_nsec = 0; @@ -298,23 +332,3 @@ int clock_getcpuclockid(pid_t pid, clockid_t *clock_id) return 0; } - -#ifdef CONFIG_ZTEST -#include -static void reset_clock_base(void) -{ - K_SPINLOCK(&rt_clock_base_lock) { - rt_clock_base = (struct timespec){0}; - } -} - -static void clock_base_reset_rule_after(const struct ztest_unit_test *test, void *data) -{ - ARG_UNUSED(test); - ARG_UNUSED(data); - - reset_clock_base(); -} - -ZTEST_RULE(clock_base_reset_rule, NULL, clock_base_reset_rule_after); -#endif /* CONFIG_ZTEST */ diff --git a/lib/posix/options/posix_clock.h b/lib/posix/options/posix_clock.h deleted file mode 100644 index f505dbc17527..000000000000 --- a/lib/posix/options/posix_clock.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2023, Meta - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef ZEPHYR_LIB_POSIX_POSIX_CLOCK_H_ -#define ZEPHYR_LIB_POSIX_POSIX_CLOCK_H_ - -#include - -#include -#include - -__syscall int __posix_clock_get_base(clockid_t clock_id, struct timespec *ts); - -#include - -#endif diff --git a/lib/realtime/CMakeLists.txt b/lib/realtime/CMakeLists.txt new file mode 100644 index 000000000000..a6e4dd5efcc2 --- /dev/null +++ b/lib/realtime/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +if(CONFIG_SYS_REALTIME) + if(CONFIG_USERSPACE) + zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/sys/realtime.h) + zephyr_sources(handlers.c) + endif() + if(CONFIG_SYS_REALTIME_CONVERT_NATIVE) + zephyr_sources(convert_native.c) + endif() + if(CONFIG_SYS_REALTIME_BASE_CLOCK_SYS_CLOCK) + zephyr_sources(base_clock_sys_clock.c) + endif() +endif() diff --git a/lib/realtime/Kconfig b/lib/realtime/Kconfig new file mode 100644 index 000000000000..d25ce65118f9 --- /dev/null +++ b/lib/realtime/Kconfig @@ -0,0 +1,39 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +menuconfig SYS_REALTIME + bool "System real-time library" + +if SYS_REALTIME + +choice SYS_REALTIME_CONVERT + prompt "System real-time conversion library" + default SYS_REALTIME_CONVERT_NATIVE + +config SYS_REALTIME_CONVERT_NATIVE + bool "Use native real-time conversion library" + +endchoice # SYS_REALTIME_CONVERT + +if SYS_REALTIME_CONVERT_NATIVE + +config SYS_REALTIME_CONVERT_NATIVE_MIN_YEAR + int "Minimum year to support for realtime conversions" + default 1600 + +config SYS_REALTIME_CONVERT_NATIVE_MAX_YEAR + int "Maximum year to support for realtime conversions" + default 2400 + +endif # SYS_REALTIME_CONVERT_NATIVE + +choice SYS_REALTIME_BASE_CLOCK + prompt "System real-time base clock" + default SYS_REALTIME_BASE_CLOCK_SYS_CLOCK + +config SYS_REALTIME_BASE_CLOCK_SYS_CLOCK + bool "Use system clock as base clock for system realtime" + +endchoice # SYS_REALTIME_BASE_CLOCK + +endif # SYS_REALTIME diff --git a/lib/realtime/base_clock_sys_clock.c b/lib/realtime/base_clock_sys_clock.c new file mode 100644 index 000000000000..f40403507eba --- /dev/null +++ b/lib/realtime/base_clock_sys_clock.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +static int64_t realtime_reference_ms; +static struct k_spinlock realtime_lock; + +static void set_realtime_reference(const int64_t *reference_ms) +{ + K_SPINLOCK(&realtime_lock) { + realtime_reference_ms = *reference_ms; + } +} + +static void get_realtime_reference(int64_t *reference_ms) +{ + K_SPINLOCK(&realtime_lock) { + *reference_ms = realtime_reference_ms; + } +} + +int z_impl_sys_realtime_get_timestamp(int64_t *timestamp) +{ + get_realtime_reference(timestamp); + + /* Get uptime just before returning to minimize latency */ + *timestamp += k_uptime_get(); + return 0; +} + +int z_impl_sys_realtime_set_timestamp(const int64_t *timestamp) +{ + int64_t reference_ms; + + /* Get uptime immediately to minimize latency */ + reference_ms = -k_uptime_get(); + + if (!sys_realtime_validate_timestamp(timestamp)) { + return -EINVAL; + } + + reference_ms += *timestamp; + set_realtime_reference(&reference_ms); + return 0; +} + +int z_impl_sys_realtime_get_datetime(struct sys_datetime *datetime) +{ + int64_t timestamp; + int ret; + + ret = sys_realtime_get_timestamp(×tamp); + if (ret < 0) { + return ret; + } + + return sys_realtime_timestamp_to_datetime(datetime, ×tamp); +} + +int z_impl_sys_realtime_set_datetime(const struct sys_datetime *datetime) +{ + int ret; + int64_t timestamp_ms; + + ret = sys_realtime_datetime_to_timestamp(×tamp_ms, datetime); + if (ret < 0) { + return ret; + } + + return sys_realtime_set_timestamp(×tamp_ms); +} diff --git a/lib/realtime/convert_native.c b/lib/realtime/convert_native.c new file mode 100644 index 000000000000..97f65829f3f1 --- /dev/null +++ b/lib/realtime/convert_native.c @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2024 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define MS_IN_SECOND 1000LL +#define MS_IN_MINUTE (MS_IN_SECOND * 60) +#define MS_IN_HOUR (MS_IN_MINUTE * 60) +#define MS_IN_DAY (MS_IN_HOUR * 24) +#define MS_IN_YEAR (MS_IN_DAY * 365) +#define MS_IN_LEAP_YEAR (MS_IN_YEAR + MS_IN_DAY) +#define MS_IN_4_YEARS (MS_IN_YEAR * 4) +#define MS_IN_4_LEAP_YEARS (MS_IN_4_YEARS + MS_IN_DAY) +#define MS_IN_100_YEARS ((MS_IN_YEAR * 76) + (MS_IN_LEAP_YEAR * 24)) +#define MS_IN_100_LEAP_YEARS ((MS_IN_YEAR * 75) + (MS_IN_LEAP_YEAR * 25)) +#define MS_IN_400_YEARS ((MS_IN_YEAR * 303) + (MS_IN_LEAP_YEAR * 97)) +#define NS_IN_MS (1000000LL) + +#define MIN_YEAR CONFIG_SYS_REALTIME_CONVERT_NATIVE_MIN_YEAR +#define MAX_YEAR CONFIG_SYS_REALTIME_CONVERT_NATIVE_MAX_YEAR + +#define TIMESTAMP_MIN ((MIN_YEAR / 400) * MS_IN_400_YEARS) +#define TIMESTAMP_MAX ((MAX_YEAR / 400) * MS_IN_400_YEARS) +#define UTC_REFERENCE (62167219200000LL) +#define UTC_MIN (TIMESTAMP_MIN - UTC_REFERENCE) +#define UTC_MAX (TIMESTAMP_MAX - UTC_REFERENCE) + +BUILD_ASSERT((MIN_YEAR % 400) == 0, "Minimum year must be divisible by 400"); +BUILD_ASSERT((MAX_YEAR % 400) == 0, "Maximum year must be divisible by 400"); +BUILD_ASSERT(MAX_YEAR > MIN_YEAR, "Maximum year must be larger than minimum year"); + +struct year_segment { + int64_t ms; + uint16_t years; + bool repeats; +}; + +static const struct year_segment year_segments[] = { + { + .ms = MS_IN_400_YEARS, + .years = 400, + .repeats = true, + }, + { + .ms = MS_IN_100_LEAP_YEARS, + .years = 100, + .repeats = false, + }, + { + .ms = MS_IN_100_YEARS, + .years = 100, + .repeats = true, + }, + { + .ms = MS_IN_4_YEARS, + .years = 4, + .repeats = false, + }, + { + .ms = MS_IN_4_LEAP_YEARS, + .years = 4, + .repeats = true, + }, + { + .ms = MS_IN_LEAP_YEAR, + .years = 1, + .repeats = false, + }, + { + .ms = MS_IN_YEAR, + .years = 1, + .repeats = true, + }, +}; + +static const uint8_t days_in_month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +static void timestamp_years_from_datetime(int64_t *years, const struct sys_datetime *datetime) +{ + *years = datetime->tm_year + 1900; +} + +static void timestamp_move_minimum_years(int64_t *timestamp_ms, int64_t *years) +{ + *years -= MIN_YEAR; + *timestamp_ms += TIMESTAMP_MIN; +} + +static void timestamp_add_years(int64_t *timestamp_ms, + bool *leap_year, + const struct sys_datetime *datetime) +{ + int64_t difference; + int64_t years; + const struct year_segment *segment; + + *leap_year = false; + + timestamp_years_from_datetime(&years, datetime); + timestamp_move_minimum_years(timestamp_ms, &years); + + for (uint8_t i = 0; i < ARRAY_SIZE(year_segments); i++) { + segment = &year_segments[i]; + + if (segment->years == 1) { + *leap_year = !segment->repeats; + } + + if (years < segment->years) { + continue; + } + + if (segment->repeats) { + difference = years / segment->years; + *timestamp_ms += difference * segment->ms; + years %= segment->years; + } else { + years -= segment->years; + *timestamp_ms += segment->ms; + } + } +} + +static void timestamp_add_months(int64_t *timestamp_ms, + bool leap_year, + const struct sys_datetime *datetime) +{ + uint8_t months; + uint32_t days; + + months = (uint8_t)datetime->tm_mon; + + for (uint8_t i = 0; i < months; i++) { + days = days_in_month[i]; + *timestamp_ms += days * MS_IN_DAY; + } + + if (leap_year && months > 1) { + *timestamp_ms += MS_IN_DAY; + } +} + +static void timestamp_add_days(int64_t *timestamp_ms, const struct sys_datetime *datetime) +{ + uint32_t days; + + days = (uint32_t)(datetime->tm_mday - 1); + *timestamp_ms += days * MS_IN_DAY; +} + +static void timestamp_add_hours(int64_t *timestamp_ms, const struct sys_datetime *datetime) +{ + uint32_t hours; + + hours = (uint32_t)datetime->tm_hour; + *timestamp_ms += hours * MS_IN_HOUR; +} + +static void timestamp_add_minutes(int64_t *timestamp_ms, const struct sys_datetime *datetime) +{ + uint16_t minutes; + + minutes = (uint16_t)datetime->tm_min; + *timestamp_ms += minutes * MS_IN_MINUTE; +} + +static void timestamp_add_seconds(int64_t *timestamp_ms, const struct sys_datetime *datetime) +{ + uint16_t seconds; + + seconds = (uint16_t)datetime->tm_sec; + *timestamp_ms += seconds * MS_IN_SECOND; +} + +static void timestamp_add_ms(int64_t *timestamp_ms, const struct sys_datetime *datetime) +{ + uint32_t ns; + + ns = (uint32_t)datetime->tm_nsec; + *timestamp_ms += ns / NS_IN_MS; +} + +static void timestamp_sub_utc_reference(int64_t *timestamp_ms) +{ + *timestamp_ms -= UTC_REFERENCE; +} + +static void datetime_init(struct sys_datetime *datetime) +{ + datetime->tm_year = -1900; + datetime->tm_wday = -1; + datetime->tm_yday = -1; + datetime->tm_isdst = -1; +} + +static void timestamp_add_utc_reference(int64_t *timestamp_ms) +{ + *timestamp_ms += UTC_REFERENCE; +} + +static void datetime_move_minimum_years(struct sys_datetime *datetime, int64_t *timestamp_ms) +{ + *timestamp_ms -= TIMESTAMP_MIN; + datetime->tm_year += MIN_YEAR; +} + +static void datetime_move_years(struct sys_datetime *datetime, + bool *leap_year, + int64_t *timestamp_ms) +{ + int difference; + const struct year_segment *segment; + + *leap_year = false; + + for (uint8_t i = 0; i < ARRAY_SIZE(year_segments); i++) { + segment = &year_segments[i]; + + if (segment->years == 1) { + *leap_year = !segment->repeats; + } + + if (*timestamp_ms < segment->ms) { + continue; + } + + if (segment->repeats) { + difference = (int)(*timestamp_ms / segment->ms); + datetime->tm_year += difference * segment->years; + *timestamp_ms %= segment->ms; + } else { + datetime->tm_year += segment->years; + *timestamp_ms -= segment->ms; + } + } +} + +static void datetime_move_months(struct sys_datetime *datetime, + bool leap_year, + int64_t *timestamp_ms) +{ + uint32_t days; + uint32_t ms; + + datetime->tm_mon = 0; + + for (uint8_t i = 0; i < ARRAY_SIZE(days_in_month); i++) { + days = days_in_month[i]; + + if (i == 1 && leap_year) { + days++; + } + + ms = days * MS_IN_DAY; + + if (*timestamp_ms < ms) { + return; + } + + datetime->tm_mon++; + *timestamp_ms -= ms; + } +} + +static void datetime_move_days(struct sys_datetime *datetime, int64_t *timestamp_ms) +{ + datetime->tm_mday = (int)(1 + (*timestamp_ms / MS_IN_DAY)); + *timestamp_ms %= MS_IN_DAY; +} + +static void datetime_move_hours(struct sys_datetime *datetime, int64_t *timestamp_ms) +{ + datetime->tm_hour = (int)(*timestamp_ms / MS_IN_HOUR); + *timestamp_ms %= MS_IN_HOUR; +} + +static void datetime_move_minutes(struct sys_datetime *datetime, int64_t *timestamp_ms) +{ + datetime->tm_min = (int)(*timestamp_ms / MS_IN_MINUTE); + *timestamp_ms %= MS_IN_MINUTE; +} + +static void datetime_move_seconds(struct sys_datetime *datetime, int64_t *timestamp_ms) +{ + datetime->tm_sec = (int)(*timestamp_ms / MS_IN_SECOND); + *timestamp_ms %= MS_IN_SECOND; +} + +static void datetime_move_ms(struct sys_datetime *datetime, int64_t *timestamp_ms) +{ + datetime->tm_nsec = (int)(*timestamp_ms * NS_IN_MS); + *timestamp_ms = 0; +} + +bool sys_realtime_validate_timestamp(const int64_t *timestamp_ms) +{ + return *timestamp_ms >= UTC_MIN && *timestamp_ms < UTC_MAX; +} + +bool sys_realtime_validate_datetime(const struct sys_datetime *datetime) +{ + return datetime->tm_year >= (MIN_YEAR - 1900) && + datetime->tm_year <= (MAX_YEAR - 1900) && + datetime->tm_mon <= 11 && + datetime->tm_mday >= 1 && + datetime->tm_mday <= 31 && + datetime->tm_hour >= 0 && + datetime->tm_hour <= 23 && + datetime->tm_min >= 0 && + datetime->tm_min <= 59 && + datetime->tm_sec >= 0 && + datetime->tm_sec <= 59; +} + +int sys_realtime_datetime_to_timestamp(int64_t *timestamp_ms, const struct sys_datetime *datetime) +{ + bool leap_year; + + if (!sys_realtime_validate_datetime(datetime)) { + return -EINVAL; + } + + *timestamp_ms = 0; + timestamp_add_years(timestamp_ms, &leap_year, datetime); + timestamp_add_months(timestamp_ms, leap_year, datetime); + timestamp_add_days(timestamp_ms, datetime); + timestamp_add_hours(timestamp_ms, datetime); + timestamp_add_minutes(timestamp_ms, datetime); + timestamp_add_seconds(timestamp_ms, datetime); + timestamp_add_ms(timestamp_ms, datetime); + timestamp_sub_utc_reference(timestamp_ms); + return 0; +} + +int sys_realtime_timestamp_to_datetime(struct sys_datetime *datetime, const int64_t *timestamp_ms) +{ + bool leap_year; + int64_t remaining_ms; + + if (!sys_realtime_validate_timestamp(timestamp_ms)) { + return -EINVAL; + } + + datetime_init(datetime); + + remaining_ms = *timestamp_ms; + timestamp_add_utc_reference(&remaining_ms); + datetime_move_minimum_years(datetime, &remaining_ms); + datetime_move_years(datetime, &leap_year, &remaining_ms); + datetime_move_months(datetime, leap_year, &remaining_ms); + datetime_move_days(datetime, &remaining_ms); + datetime_move_hours(datetime, &remaining_ms); + datetime_move_minutes(datetime, &remaining_ms); + datetime_move_seconds(datetime, &remaining_ms); + datetime_move_ms(datetime, &remaining_ms); + return 0; +} diff --git a/lib/realtime/handlers.c b/lib/realtime/handlers.c new file mode 100644 index 000000000000..5b993f92db20 --- /dev/null +++ b/lib/realtime/handlers.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static inline int z_vrfy_sys_realtime_get_timestamp(int64_t *timestamp_ms) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(timestamp_ms, sizeof(int64_t))); + return z_impl_sys_realtime_get_timestamp(timestamp_ms); +} +#include + +static inline int z_vrfy_sys_realtime_set_timestamp(const int64_t *timestamp_ms) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(timestamp_ms, sizeof(int64_t))); + return z_impl_sys_realtime_set_timestamp(timestamp_ms); +} +#include + +static inline int z_vrfy_sys_realtime_get_datetime(struct sys_datetime *datetime) +{ + K_OOPS(K_SYSCALL_MEMORY_WRITE(datetime, sizeof(struct sys_datetime))); + return z_impl_sys_realtime_get_datetime(datetime); +} +#include + +static inline int z_vrfy_sys_realtime_set_datetime(const struct sys_datetime *datetime) +{ + K_OOPS(K_SYSCALL_MEMORY_READ(datetime, sizeof(struct sys_datetime))); + return z_impl_sys_realtime_set_datetime(datetime); +} +#include diff --git a/tests/lib/sys_realtime/convert/CMakeLists.txt b/tests/lib/sys_realtime/convert/CMakeLists.txt new file mode 100644 index 000000000000..e26d2720d373 --- /dev/null +++ b/tests/lib/sys_realtime/convert/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(sys_realtime_convert) + +target_sources(app PRIVATE src/test.c) diff --git a/tests/lib/sys_realtime/convert/prj.conf b/tests/lib/sys_realtime/convert/prj.conf new file mode 100644 index 000000000000..e676e83eb8f4 --- /dev/null +++ b/tests/lib/sys_realtime/convert/prj.conf @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SYS_REALTIME=y +CONFIG_SYS_REALTIME_CONVERT_NATIVE=y +CONFIG_SYS_REALTIME_CONVERT_NATIVE_MIN_YEAR=-400 +CONFIG_SYS_REALTIME_CONVERT_NATIVE_MAX_YEAR=2800 +CONFIG_ZTEST=y diff --git a/tests/lib/sys_realtime/convert/src/test.c b/tests/lib/sys_realtime/convert/src/test.c new file mode 100644 index 000000000000..f86a5b221fed --- /dev/null +++ b/tests/lib/sys_realtime/convert/src/test.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2024 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +struct test_sample { + struct sys_datetime datetime; + int64_t timestamp_ms; +}; + +static const struct test_sample samples[] = { + /* UTC timestamp = 0 */ + { + .datetime = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = 70, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = 0, + }, + /* (-0001)-12-31T23:59:59Z */ + { + .datetime = { + .tm_sec = 59, + .tm_min = 59, + .tm_hour = 23, + .tm_mday = 31, + .tm_mon = 11, + .tm_year = -1901, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = -62167219201000, + }, + /* 0000-01-01T00:00:00Z */ + { + .datetime = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = -1900, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = -62167219200000, + }, + /* 0000-01-01T00:00:01Z */ + { + .datetime = { + .tm_sec = 1, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = -1900, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = -62167219199000, + }, + /* 1999-12-31T23:59:59Z */ + { + .datetime = { + .tm_sec = 59, + .tm_min = 59, + .tm_hour = 23, + .tm_mday = 31, + .tm_mon = 11, + .tm_year = 99, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = 946684799000, + }, + /* 2000-01-01T00:00:00Z */ + { + .datetime = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = 100, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = 946684800000, + }, + /* 2000-01-01T00:00:01Z */ + { + .datetime = { + .tm_sec = 1, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = 100, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = 946684801000, + }, + /* 2399-12-31T23:59:59Z */ + { + .datetime = { + .tm_sec = 59, + .tm_min = 59, + .tm_hour = 23, + .tm_mday = 31, + .tm_mon = 11, + .tm_year = 499, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = 13569465599000, + }, + /* 2400-01-01T00:00:00Z */ + { + .datetime = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = 500, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = 13569465600000, + }, + /* 2400-01-01T00:00:01Z */ + { + .datetime = { + .tm_sec = 1, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = 500, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 0, + }, + .timestamp_ms = 13569465601000, + }, + /* 2400-01-01T00:00:01.0001Z */ + { + .datetime = { + .tm_sec = 1, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = 500, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 1000000, + }, + .timestamp_ms = 13569465601001, + }, + /* 2400-01-01T00:00:01.999Z */ + { + .datetime = { + .tm_sec = 1, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = 500, + .tm_wday = -1, + .tm_yday = -1, + .tm_isdst = -1, + .tm_nsec = 999000000, + }, + .timestamp_ms = 13569465601999, + }, +}; + +static bool datetimes_equal(const struct sys_datetime *a, const struct sys_datetime *b) +{ + return memcmp(a, b, sizeof(struct sys_datetime)) == 0; +} + +ZTEST(sys_realtime_convert, test_timestamp_to_datetime) +{ + const struct sys_datetime *datetime; + const int64_t *timestamp_ms; + struct sys_datetime result; + + for (size_t i = 0; i < ARRAY_SIZE(samples); i++) { + datetime = &samples[i].datetime; + timestamp_ms = &samples[i].timestamp_ms; + zassert_ok(sys_realtime_timestamp_to_datetime(&result, timestamp_ms), + "refused to convert %lli", *timestamp_ms); + zassert_true(datetimes_equal(datetime, &result), + "incorrect conversion of %lli", *timestamp_ms); + } +} + +ZTEST(sys_realtime_convert, test_datetime_to_timestamp) +{ + const struct sys_datetime *datetime; + const int64_t *timestamp_ms; + int64_t result; + + for (size_t i = 0; i < ARRAY_SIZE(samples); i++) { + datetime = &samples[i].datetime; + timestamp_ms = &samples[i].timestamp_ms; + zassert_ok(sys_realtime_datetime_to_timestamp(&result, datetime), + "refused to convert %lli", *timestamp_ms); + zassert_equal(result, *timestamp_ms, + "incorrect conversion of %lli", *timestamp_ms); + } +} + +ZTEST_SUITE(sys_realtime_convert, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/lib/sys_realtime/convert/testcase.yaml b/tests/lib/sys_realtime/convert/testcase.yaml new file mode 100644 index 000000000000..13dc879a1376 --- /dev/null +++ b/tests/lib/sys_realtime/convert/testcase.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +tests: + sys_realtime.convert: + tags: sys_realtime diff --git a/tests/lib/sys_realtime/realtime/CMakeLists.txt b/tests/lib/sys_realtime/realtime/CMakeLists.txt new file mode 100644 index 000000000000..00d8c26976a4 --- /dev/null +++ b/tests/lib/sys_realtime/realtime/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(sys_realtime_realtime) + +target_sources(app PRIVATE src/test.c) diff --git a/tests/lib/sys_realtime/realtime/prj.conf b/tests/lib/sys_realtime/realtime/prj.conf new file mode 100644 index 000000000000..17274dd49341 --- /dev/null +++ b/tests/lib/sys_realtime/realtime/prj.conf @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_SYS_REALTIME=y +CONFIG_ZTEST=y +CONFIG_TEST_USERSPACE=y diff --git a/tests/lib/sys_realtime/realtime/src/test.c b/tests/lib/sys_realtime/realtime/src/test.c new file mode 100644 index 000000000000..e4fb1d7b288d --- /dev/null +++ b/tests/lib/sys_realtime/realtime/src/test.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 Bjarki Arge Andreasen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define TEST_START_TIMESTAMP_MS 946684795000 /* 1999-12-31T23:59:55.000Z */ +#define TEST_DURATION_MS 10000 +#define TEST_END_TIMESTAMP_MS TEST_START_TIMESTAMP_MS + TEST_DURATION_MS +#define TEST_SLEEP_MS 100 +#define TEST_THRESHOLD_MS 10 + +static const int64_t test_start_timestamp_ms = TEST_START_TIMESTAMP_MS; +static const int64_t test_end_timestamp_ms = TEST_END_TIMESTAMP_MS; + +static int64_t test_sleep(void) +{ + int64_t uptime_before_ms; + int64_t uptime_after_ms; + + uptime_before_ms = k_uptime_get(); + k_msleep(TEST_SLEEP_MS); + uptime_after_ms = k_uptime_get(); + return uptime_after_ms - uptime_before_ms; +} + +static void test_track_timestamp(void) +{ + int64_t timestamp_before_ms; + int64_t timestamp_after_ms; + int64_t timestamp_delta_ms; + int64_t sleep_ms; + int64_t lower_bound; + int64_t upper_bound; + + do { + zassert_ok(sys_realtime_get_timestamp(×tamp_before_ms)); + sleep_ms = test_sleep(); + zassert_ok(sys_realtime_get_timestamp(×tamp_after_ms)); + timestamp_delta_ms = timestamp_after_ms - timestamp_before_ms; + lower_bound = sleep_ms - TEST_THRESHOLD_MS; + upper_bound = sleep_ms + TEST_THRESHOLD_MS; + zassert_true(timestamp_delta_ms >= lower_bound); + zassert_true(timestamp_delta_ms <= upper_bound); + } while (timestamp_after_ms < test_end_timestamp_ms); +} + +static void test_track_datetime(void) +{ + struct sys_datetime datetime_before; + struct sys_datetime datetime_after; + int64_t timestamp_before_ms; + int64_t timestamp_after_ms; + int64_t timestamp_delta_ms; + int64_t sleep_ms; + int64_t lower_bound; + int64_t upper_bound; + + do { + zassert_ok(sys_realtime_get_datetime(&datetime_before)); + sleep_ms = test_sleep(); + zassert_ok(sys_realtime_get_datetime(&datetime_after)); + zassert_ok(sys_realtime_datetime_to_timestamp(×tamp_before_ms, + &datetime_before)); + zassert_ok(sys_realtime_datetime_to_timestamp(×tamp_after_ms, + &datetime_after)); + timestamp_delta_ms = timestamp_after_ms - timestamp_before_ms; + lower_bound = sleep_ms - TEST_THRESHOLD_MS; + upper_bound = sleep_ms + TEST_THRESHOLD_MS; + zassert_true(timestamp_delta_ms >= lower_bound); + zassert_true(timestamp_delta_ms <= upper_bound); + } while (timestamp_after_ms < test_end_timestamp_ms); +} + +ZTEST_USER(sys_realtime_realtime, test_set_and_track_timestamp) +{ + zassert_ok(sys_realtime_set_timestamp(&test_start_timestamp_ms)); + test_track_timestamp(); + zassert_ok(sys_realtime_set_timestamp(&test_start_timestamp_ms)); + test_track_timestamp(); +} + +ZTEST_USER(sys_realtime_realtime, test_set_and_track_datetime) +{ + struct sys_datetime datetime; + + sys_realtime_timestamp_to_datetime(&datetime, &test_start_timestamp_ms); + zassert_ok(sys_realtime_set_datetime(&datetime)); + test_track_datetime(); + zassert_ok(sys_realtime_set_datetime(&datetime)); + test_track_datetime(); +} + +ZTEST_SUITE(sys_realtime_realtime, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/lib/sys_realtime/realtime/testcase.yaml b/tests/lib/sys_realtime/realtime/testcase.yaml new file mode 100644 index 000000000000..0eb1971713be --- /dev/null +++ b/tests/lib/sys_realtime/realtime/testcase.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2024 Bjarki Arge Andreasen +# SPDX-License-Identifier: Apache-2.0 + +tests: + sys_realtime.realtime: + tags: sys_realtime diff --git a/tests/posix/common/src/nanosleep.c b/tests/posix/common/src/nanosleep.c index e139590bbea0..781e3ee3f285 100644 --- a/tests/posix/common/src/nanosleep.c +++ b/tests/posix/common/src/nanosleep.c @@ -32,47 +32,86 @@ static inline uint64_t cycle_get_64(void) } } +static uint32_t trace_idx; +static uint32_t trace_cycle; + +static void trace_test_reset(void) +{ + trace_idx = 0; + trace_cycle = cycle_get_64(); +} + +static void trace_test(void) +{ + uint64_t cycle; + uint64_t delta_cycle; + + cycle = cycle_get_64(); + delta_cycle = cycle - trace_cycle; + trace_cycle = cycle; + + TC_PRINT("idx: %u, cycle: %llu, delta: %llu\n", trace_idx, cycle, delta_cycle); + trace_idx++; +} + static void common_errors(int selection, clockid_t clock_id, int flags) { struct timespec rem = {}; struct timespec req = {}; + trace_test(); + /* * invalid parameters */ zassert_equal(select_nanosleep(selection, clock_id, flags, NULL, NULL), -1); zassert_equal(errno, EFAULT); + trace_test(); + /* NULL request */ errno = 0; zassert_equal(select_nanosleep(selection, clock_id, flags, NULL, &rem), -1); zassert_equal(errno, EFAULT); + + trace_test(); + /* Expect rem to be the same when function returns */ zassert_equal(rem.tv_sec, 0, "actual: %d expected: %d", rem.tv_sec, 0); zassert_equal(rem.tv_nsec, 0, "actual: %d expected: %d", rem.tv_nsec, 0); + trace_test(); + /* negative times */ errno = 0; req = (struct timespec){.tv_sec = -1, .tv_nsec = 0}; zassert_equal(select_nanosleep(selection, clock_id, flags, &req, NULL), -1); zassert_equal(errno, EINVAL); + trace_test(); + errno = 0; req = (struct timespec){.tv_sec = 0, .tv_nsec = -1}; zassert_equal(select_nanosleep(selection, clock_id, flags, &req, NULL), -1); zassert_equal(errno, EINVAL); + trace_test(); + errno = 0; req = (struct timespec){.tv_sec = -1, .tv_nsec = -1}; zassert_equal(select_nanosleep(selection, clock_id, flags, &req, NULL), -1); zassert_equal(errno, EINVAL); + trace_test(); + /* nanoseconds too high */ errno = 0; req = (struct timespec){.tv_sec = 0, .tv_nsec = 1000000000}; zassert_equal(select_nanosleep(selection, clock_id, flags, &req, NULL), -1); zassert_equal(errno, EINVAL); + trace_test(); + /* * Valid parameters */ @@ -85,12 +124,16 @@ static void common_errors(int selection, clockid_t clock_id, int flags) zassert_equal(req.tv_sec, 1); zassert_equal(req.tv_nsec, 1); + trace_test(); + /* Sleep for 0.0 s. Expect req & rem to be the same when function returns */ zassert_equal(select_nanosleep(selection, clock_id, flags, &req, &rem), 0); zassert_equal(errno, 0); zassert_equal(rem.tv_sec, 0, "actual: %d expected: %d", rem.tv_sec, 0); zassert_equal(rem.tv_nsec, 0, "actual: %d expected: %d", rem.tv_nsec, 0); + trace_test(); + /* * req and rem point to the same timespec * @@ -102,10 +145,13 @@ static void common_errors(int selection, clockid_t clock_id, int flags) zassert_equal(errno, 0); zassert_equal(req.tv_sec, 0, "actual: %d expected: %d", req.tv_sec, 0); zassert_equal(req.tv_nsec, 0, "actual: %d expected: %d", req.tv_nsec, 0); + + trace_test(); } ZTEST(nanosleep, test_nanosleep_errors_errno) { + trace_test_reset(); common_errors(SELECT_NANOSLEEP, CLOCK_REALTIME, 0); } @@ -114,19 +160,26 @@ ZTEST(nanosleep, test_clock_nanosleep_errors_errno) struct timespec rem = {}; struct timespec req = {}; + trace_test_reset(); common_errors(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME); + trace_test(); + /* Absolute timeout in the past. */ clock_gettime(CLOCK_MONOTONIC, &req); zassert_equal(clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, &rem), 0); zassert_equal(rem.tv_sec, 0, "actual: %d expected: %d", rem.tv_sec, 0); zassert_equal(rem.tv_nsec, 0, "actual: %d expected: %d", rem.tv_nsec, 0); + trace_test(); + /* Absolute timeout in the past relative to the realtime clock. */ clock_gettime(CLOCK_REALTIME, &req); zassert_equal(clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &req, &rem), 0); zassert_equal(rem.tv_sec, 0, "actual: %d expected: %d", rem.tv_sec, 0); zassert_equal(rem.tv_nsec, 0, "actual: %d expected: %d", rem.tv_nsec, 0); + + trace_test(); } /** @@ -144,9 +197,12 @@ ZTEST(nanosleep, test_clock_nanosleep_errors_errno) * @param flags Flags to pass to @ref clock_nanosleep * @param s Partial lower bound for yielded time (in seconds) * @param ns Partial lower bound for yielded time (in nanoseconds) + * @param req_s Partial lower bound for requested time (in seconds) + * @param req_ns Partial lower bound for requested time (in nanoseconds) */ -static void common_lower_bound_check(int selection, clockid_t clock_id, int flags, const uint32_t s, - uint32_t ns) +static void common_relative_lower_bound_check(int selection, clockid_t clock_id, int flags, + uint32_t s, uint32_t ns, uint32_t req_s, + uint32_t req_ns) { int r; uint64_t actual_ns; @@ -154,7 +210,7 @@ static void common_lower_bound_check(int selection, clockid_t clock_id, int flag uint64_t now; uint64_t then; struct timespec rem = {0, 0}; - struct timespec req = {s, ns}; + struct timespec req = {req_s, req_ns}; errno = 0; then = cycle_get_64(); @@ -163,8 +219,8 @@ static void common_lower_bound_check(int selection, clockid_t clock_id, int flag zassert_equal(r, 0, "actual: %d expected: %d", r, 0); zassert_equal(errno, 0, "actual: %d expected: %d", errno, 0); - zassert_equal(req.tv_sec, s, "actual: %d expected: %d", req.tv_sec, s); - zassert_equal(req.tv_nsec, ns, "actual: %d expected: %d", req.tv_nsec, ns); + zassert_equal(req.tv_sec, req_s, "actual: %d expected: %d", req.tv_sec, req_s); + zassert_equal(req.tv_nsec, req_ns, "actual: %d expected: %d", req.tv_nsec, req_ns); zassert_equal(rem.tv_sec, 0, "actual: %d expected: %d", rem.tv_sec, 0); zassert_equal(rem.tv_nsec, 0, "actual: %d expected: %d", rem.tv_nsec, 0); @@ -200,6 +256,30 @@ static void common_lower_bound_check(int selection, clockid_t clock_id, int flag /* TODO: Upper bounds check when hr timers are available */ } +/** + * @brief Check that a call to nanosleep has yielded execution for some minimum time. + * + * Check that the actual time slept is >= the total time specified by @p s (in seconds) and + * @p ns (in nanoseconds). + * + * @note The time specified by @p s and @p ns is assumed to be absolute (i.e. a time-point) + * when @p selection is set to @ref SELECT_CLOCK_NANOSLEEP. The time is assumed to be relative + * when @p selection is set to @ref SELECT_NANOSLEEP. + * + * @note This check assumes that the clock is tied 1-1 to k_uptime. + * + * @param selection Either @ref SELECT_CLOCK_NANOSLEEP or @ref SELECT_NANOSLEEP + * @param clock_id The clock to test (e.g. @ref CLOCK_MONOTONIC or @ref CLOCK_REALTIME) + * @param flags Flags to pass to @ref clock_nanosleep + * @param s Partial lower bound for yielded time (in seconds) + * @param ns Partial lower bound for yielded time (in nanoseconds) + */ +static void common_lower_bound_check(int selection, clockid_t clock_id, int flags, const uint32_t s, + uint32_t ns) +{ + common_relative_lower_bound_check(selection, clock_id, flags, s, ns, s, ns); +} + ZTEST(nanosleep, test_nanosleep_execution) { /* sleep for 1ns */ @@ -253,33 +333,35 @@ ZTEST(nanosleep, test_clock_nanosleep_execution) common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_MONOTONIC, TIMER_ABSTIME, ts.tv_sec + 2, 1001); - clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec = 100; + ts.tv_nsec = 0; + clock_settime(CLOCK_REALTIME, &ts); - /* absolute sleeps with the real time clock and adjusted reference time ts */ + /* absolute sleeps with the real time clock set to a time different from monotonic */ /* until 1s + 1ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 1, 1); + common_relative_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + 1, 1, ts.tv_sec + 1, 1); /* until 1s + 1us past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 1, 1000); + common_relative_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + 1, 1000, ts.tv_sec + 1, 1000); /* until 1s + 500000000ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 1, 500000000); + common_relative_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + 1, 500000000, ts.tv_sec + 1, 500000000); /* until 2s past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 2, 0); + common_relative_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + 2, 0, ts.tv_sec + 2, 0); /* until 2s + 1ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 2, 1); + common_relative_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + 2, 1, ts.tv_sec + 2, 1); /* until 2s + 1us + 1ns past the reference time */ - common_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, - ts.tv_sec + 2, 1001); + common_relative_lower_bound_check(SELECT_CLOCK_NANOSLEEP, CLOCK_REALTIME, TIMER_ABSTIME, + 2, 1001, ts.tv_sec + 2, 1001); } ZTEST_SUITE(nanosleep, NULL, NULL, NULL, NULL, NULL);