From: ljsebald <ljs...@us...> - 2023-12-15 19:39:25
|
This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "A pseudo Operating System for the Dreamcast.". The branch, master has been updated via 92ef58e0127d75b08ee8cde28166c4c71460cb72 (commit) from a7789e6439b2b1028b5daea78ed33ef93f848752 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit 92ef58e0127d75b08ee8cde28166c4c71460cb72 Author: Falco Girgis <gyr...@gm...> Date: Fri Dec 15 13:38:37 2023 -0600 Timer Cleanup, Precision Enhancement (#345) * Added defines for all timer registers * More timer cleanup * Refactor complete, tests working. * TMUs successfully running at 80ns resolution! 1) added timer_us_gettime() as a mirror of timer_ms_gettime() which is to be used by C and POSIX date/time implementations 2) added timer_ns_gettime() is a mirror of timer_ms_gettime() which is to be used by C and POSIX date/time implementations 3) Added single #define for TIMER_TPSC value to support changing the clock divider used by all timers by changing a single line 4) Added LUTs for timer division ratio and nanoseconds per timer counter tick, indexed by TPSC value 5) Addd a #define for TIMER_PRIO to centralize/allow for changing of timer interrupt priority level (defaulted to 15, or highest level) 6) Renamed the HORRIBLE "timer_ms_counter" to "timer_ms_sec_counter" to make it known that it counts SECONDS, not milliseconds 7) Created static timer_gettime() internal function to unify reading the time into 2 uint32_t pointers for msec, usec, and nsec APIs 8) Modified newlib's _gettimeofday_r() to use the microsecond-resolution timer API, which should beef up gettimeofday(), clock_gettime(), time(), and std::chrono. 9) Modified C11's timespec_get() to use the nanosecond timing API to beef up C11's timer resolution * Formatting + extra comments in sketchy parts * Addressed review feedback + fixed compile issue - Apparently "timer_gettime()" is a reserved POSIX or system symbol... * Addressing review feedback * Fixed incorrect mask in timer_getticks() - Was OR-ing the individual bit positions for TPSC fields to create a mask, which would've missed the high bit. Made a dedicated #define for the mask. * Addressed zcrc's review feedback - Fixed a bug with how the overflow was being checked in timer_ms_gettime64() - Lots of aesthetic changes, including new BIT() macro - New comments to detail the crap out of everything - Modified the classic cpp "simple clock" example to demo increased resolution Removed unneeded include. * Optimization + protection from race condition - zcrc is now in on the PR, and he's helped to optimize timer_ms_getticks(), using some 1337 bitshifting math to replace a slow division - spotted another potential race condition scenario, which requires making multiple counter and underflow flag reads to ensure coherency - race condition fix has allowed us to no longer disable interrupts when querying TMU2 for ms/us/ns time. * Adding zcrc's initials to CHANGELOG update * Addressed Quzar's styling comments - Made the TMU register field defines bit masks instead of bit positions to be consitent with the rest of the drivers. * Began work on updated TMU Doxygen group * Fixed race condition, revamped Doxygen. - Fixed race condition between reading the TMU2 counter and checking its underflow flag which could cause a horrible intermittent mismatch between the seconds and ticks. - Tested for two hours straight using TapamN's timer_test from the DCEmulation forums to ensure we're race-free (we are). - Revamped the Doxygen to clean up grouping and add subgrouping. - Added one more API function for checking whether a timer is currently running or not. - The newfangled/better way to do POSIX date/time, which deprecates gettimeofday() is now at nanosecond resolution. * Move variable declaration before code. ----------------------------------------------------------------------- Summary of changes: doc/CHANGELOG | 1 + examples/dreamcast/cpp/clock/clock.cc | 17 +- kernel/arch/dreamcast/include/arch/timer.h | 316 +++++++++++++++++++++------ kernel/arch/dreamcast/kernel/timer.c | 340 +++++++++++++++++++---------- kernel/libc/c11/timespec_get.c | 7 +- kernel/libc/newlib/newlib_gettimeofday.c | 10 +- kernel/libc/posix/clock_gettime.c | 8 +- 7 files changed, 491 insertions(+), 208 deletions(-) diff --git a/doc/CHANGELOG b/doc/CHANGELOG index fbbaa7b..d0d59b3 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -211,6 +211,7 @@ KallistiOS version 2.1.0 ----------------------------------------------- - DC Added Maple-specific KOS_INIT_FLAGS() which allow for GC-ing unused drivers [FG] - DC Added basic example for micropython KOS port [Aaron Glazer == AG] - DC Improved performance of IRQ context save / restore [PC] +- DC Increased resolution of TMU timers + date/time functions [FG && PC] KallistiOS version 2.0.0 ----------------------------------------------- - DC Broadband Adapter driver fixes [Megan Potter == MP] diff --git a/examples/dreamcast/cpp/clock/clock.cc b/examples/dreamcast/cpp/clock/clock.cc index 1fc6bb8..c403ca0 100644 --- a/examples/dreamcast/cpp/clock/clock.cc +++ b/examples/dreamcast/cpp/clock/clock.cc @@ -2,6 +2,7 @@ // Ported to Dreamcast/KOS by Peter Hatch // read_input () code from KOS examples // Converted to a clock by Megan Potter +// Resolution enhanced by Falco Girgis #include <kos.h> #include <time.h> @@ -17,7 +18,7 @@ const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; -float bg[3]; /* Current bg */ +float bg[3]; /* Current bg */ float bg_delta[3] = { 0.01f }; /* bg per-frame delta */ int bg_cur = 1; float bgarray[][3] = { @@ -55,15 +56,15 @@ void bgframe() { } void drawFrame() { - time_t t; + struct timespec spec; struct tm tm; char tmpbuf[256]; int y = 50; bgframe(); - t = time(NULL); - localtime_r(&t, &tm); + timespec_get(&spec, TIME_UTC); + localtime_r(&spec.tv_sec, &tm); pvr_wait_ready(); pvr_scene_begin(); @@ -77,11 +78,11 @@ void drawFrame() { text->begin(); text->setColor(1, 1, 1); text->start2f(20, y); - text->puts("Simple DC Clock"); + text->puts("(High Res) Simple DC Clock"); text->end(); y += 50; - sprintf(tmpbuf, "Unix Time: %lld", t); + sprintf(tmpbuf, "Unix: %lld", spec.tv_sec); text->begin(); text->setColor(1, 1, 1); @@ -100,8 +101,8 @@ void drawFrame() { text->end(); y += 50; - sprintf(tmpbuf, "%02d:%02d:%02d", - tm.tm_hour, tm.tm_min, tm.tm_sec); + sprintf(tmpbuf, "%02d:%02d:%02d.%lu", + tm.tm_hour, tm.tm_min, tm.tm_sec, spec.tv_nsec); text->begin(); text->setColor(1, 1, 1); diff --git a/kernel/arch/dreamcast/include/arch/timer.h b/kernel/arch/dreamcast/include/arch/timer.h index 683c54c..598cee4 100644 --- a/kernel/arch/dreamcast/include/arch/timer.h +++ b/kernel/arch/dreamcast/include/arch/timer.h @@ -1,12 +1,14 @@ /* KallistiOS ##version## arch/dreamcast/include/timer.h - (c)2000-2001 Megan Potter - + Copyright (c) 2000, 2001 Megan Potter + Copyright (c) 2023 Falco Girgis + */ -/** \file arch/timer.h - \brief Low-level timer functionality. +/** \file arch/timer.h + \brief Low-level timer functionality. + \ingroup timers This file contains functions for interacting with the timer sources on the SH4. Many of these functions may interfere with thread operation or other @@ -14,136 +16,229 @@ functionality that you might use in practice in here in normal programs is the gettime functions. + \sa arch/timer.h + \sa arch/wdt.h + \author Megan Potter + \author Falco Girgis */ #ifndef __ARCH_TIMER_H #define __ARCH_TIMER_H + +#include <stdint.h> #include <sys/cdefs.h> __BEGIN_DECLS -#include <arch/types.h> #include <arch/irq.h> -/* Timer sources -- we get four on the SH4 */ +/** \defgroup timers Timer Unit + \brief SH4 CPU peripheral providing timers and counters + + The Dreamcast's SH4 includes an on-chip Timer Unit (TMU) containing 3 + independent 32-bit channels (TMU0-TMU2). Each channel provides a + down-counter with automatic reload and can be configured to use 1 of + 7 divider circuits for its input clock. By default, KOS uses the fastest + input clock for each TMU channel, providing a maximum internal resolution + of 80ns ticks. + + \warning + Under normal circumstances, all 3 TMU channels are reserved by KOS for + various OS-related purposes. If you need a free general-purpose interval + timer, consider using the Watchdog Timer. + + \note + 90% of the time, you will never have a need to directly interact with this + API, as it's mostly used as a kernel-level driver which backs other APIs. + For example, querying for ticks, fetching the current timestamp, or putting + a thread to sleep is typically done via the standard C, C++, or POSIX APIs. + +*/ + +/** \defgroup tmus Channels + \brief TMU channel constants + \ingroup timers -/** \brief SH4 Timer 0. + The following are the constant `#define` identifiers for the 3 TMU channels. - This timer is used for thread operation, and thus is off limits if you want - that to work properly. + \warning + All three of these channels are typically reserved and are by KOS for + OS-related tasks. + + @{ +*/ + +/** \brief SH4 Timer Channel 0. + + \warning + This timer is used by the kernel's scheduler for thread operation, and thus + is off limits if you want that to work properly. */ #define TMU0 0 -/** \brief SH4 Timer 1. +/** \brief SH4 Timer Channel 1. - This timer is used for the timer_spin_sleep() function. + \warning + This timer channel is used for the timer_spin_sleep() function, which also + backs the kthread, C, C++, and POSIX sleep functions. */ #define TMU1 1 -/** \brief SH4 Timer 2. +/** \brief SH4 Timer Channel 2. - This timer is used by the various gettime functions in this header. + \warning + This timer channel is used by the various gettime functions in this header. + It also backs the standard C, C++, and POSIX date/time and clock functions. */ #define TMU2 2 -/** \brief Which timer does the thread system use? */ +/** @} */ + +/** \cond Which timer does the thread system use? */ #define TIMER_ID TMU0 +/** \endcond */ + +/** \defgroup tmu_direct Direct-Access + \brief Low-level timer driver + \ingroup timers + + This API provides a low-level driver abstraction around the TMU peripheral + and the control, counter, and reload registers of its 3 channels. + + \note + You typically want to use the higher-level APIs associated with the + functionality implemented by each timer channel. +*/ -/** \brief Pre-initialize a timer, but do not start it. +/** \brief Pre-initialize a timer channel, but do not start it. + \ingroup tmu_direct - This function sets up a timer for use, but does not start it. + This function sets up a timer channel for use, but does not start it. - \param which The timer to set up (i.e, \ref TMU0). + \param channel The timer channel to set up (\ref tmus). \param speed The number of ticks per second. \param interrupts Set to 1 to receive interrupts when the timer ticks. \retval 0 On success. */ -int timer_prime(int which, uint32 speed, int interrupts); +int timer_prime(int channel, uint32_t speed, int interrupts); -/** \brief Start a timer. +/** \brief Start a timer channel. + \ingroup tmu_direct - This function starts a timer that has been initialized with timer_prime(), - starting raising interrupts if applicable. + This function starts a timer channel that has been initialized with + timer_prime(), starting raising interrupts if applicable. - \param which The timer to start (i.e, \ref TMU0). + \param channel The timer channel to start (\ref tmus). \retval 0 On success. */ -int timer_start(int which); +int timer_start(int channel); -/** \brief Stop a timer. +/** \brief Stop a timer channel. + \ingroup tmu_direct - This function stops a timer that was started with timer_start(), and as a - result stops interrupts coming in from the timer. + This function stops a timer channel that was started with timer_start(), + and as a result stops interrupts coming in from the timer. - \param which The timer to stop (i.e, \ref TMU0). + \param channel The timer channel to stop (\ref tmus). \retval 0 On success. */ -int timer_stop(int which); +int timer_stop(int channel); -/** \brief Obtain the count of a timer. +/** \brief Checks whether a timer channel is running. + \ingroup tmu_direct - This function simply returns the count of the timer. + This function checks whether the given timer channel is actively counting. - \param which The timer to inspect (i.e, \ref TMU0). - \return The timer's count. + \param channel The timer channel to check (\ref tmus). + \retval 0 The timer channel is stopped. + \retval 1 The timer channel is running. */ -uint32 timer_count(int which); +int timer_running(int channel); -/** \brief Clear the underflow bit of a timer. +/** \brief Obtain the count of a timer channel. + \ingroup tmu_direct - This function clears the underflow bit of a timer if it was set. + This function simply returns the count of the timer channel. - \param which The timer to inspect (i.e, \ref TMU0). - \retval 0 If the underflow bit was clear (prior to calling). - \retval 1 If the underflow bit was set (prior to calling). + \param channel The timer channel to inspect (\ref tmus). + \return The timer's count. */ -int timer_clear(int which); +uint32_t timer_count(int channel); -/** \brief Spin-loop sleep function. +/** \brief Clear the underflow bit of a timer channel. + \ingroup tmu_direct - This function is meant as a very accurate delay function, even if threading - and interrupts are disabled. It uses \ref TMU1 to sleep. + This function clears the underflow bit of a timer channel if it was set. - \param ms The number of milliseconds to sleep. + \param channel The timer channel to clear (\ref tmus). + \retval 0 If the underflow bit was clear (prior to calling). + \retval 1 If the underflow bit was set (prior to calling). */ -void timer_spin_sleep(int ms); +int timer_clear(int channel); -/** \brief Enable high-priority timer interrupts. +/** \brief Enable high-priority timer interrupts. + \ingroup tmu_direct This function enables interrupts on the specified timer. - \param which The timer to enable interrupts on (i.e, \ref TMU0). + \param channel The timer channel to enable interrupts on (\ref tmus). */ -void timer_enable_ints(int which); +void timer_enable_ints(int channel); -/** \brief Disable timer interrupts. +/** \brief Disable timer interrupts. + \ingroup tmu_direct - This function disables interrupts on the specified timer. + This function disables interrupts on the specified timer channel. - \param which The timer to disable interrupts on (i.e, \ref TMU0). + \param channel The timer channel to disable interrupts on + (\ref tmus). */ -void timer_disable_ints(int which); +void timer_disable_ints(int channel); -/** \brief Check whether interrupts are enabled on a timer. +/** \brief Check whether interrupts are enabled on a timer channel. + \ingroup tmu_direct This function checks whether or not interrupts are enabled on the specified - timer. + timer channel. - \param which The timer to inspect (i.e, \ref TMU0). + \param channel The timer channel to inspect (\ref tmus). \retval 0 If interrupts are disabled on the timer. \retval 1 If interrupts are enabled on the timer. */ -int timer_ints_enabled(int which); +int timer_ints_enabled(int channel); + +/** \defgroup tmu_uptime Uptime + \brief Maintaining time since system boot. + \ingroup timers + + This API provides methods for querying the current system boot time or + uptime since KOS started at various resolutions. You can use this timing + for ticks, delta time, or frame deltas for games, profilers, or media + decoding. + + \note + This API is used to back the C, C++, and POSIX standard date/time + APIs. You may wish to favor these for platform independence. + + \warning + This API and its underlying functionality are using \ref TMU2, so any + direct manipulation of it will interfere with the API's proper functioning. + + \note + The highest actual tick resolution of \ref TMU2 is 80ns. +*/ -/** \brief Enable the millisecond timer. +/** \brief Enable the millisecond timer. + \ingroup tmu_uptime This function enables the timer used for the gettime functions. This is on by default. These functions use \ref TMU2 to do their work. */ void timer_ms_enable(void); -/** \brief Disable the millisecond timer. +/** \brief Disable the millisecond timer. + \ingroup tmu_uptime This function disables the timer used for the gettime functions. Generally, you will not want to do this, unless you have some need to use the timer @@ -151,7 +246,8 @@ void timer_ms_enable(void); */ void timer_ms_disable(void); -/** \brief Get the current uptime of the system. +/** \brief Get the current uptime of the system (in secs and millisecs). + \ingroup tmu_uptime This function retrieves the number of seconds and milliseconds since KOS was started. @@ -164,9 +260,10 @@ void timer_ms_disable(void); calculate (*secs * 1000) + *msecs, or use the timer_ms_gettime64() function. */ -void timer_ms_gettime(uint32 *secs, uint32 *msecs); +void timer_ms_gettime(uint32_t *secs, uint32_t *msecs); -/** \brief Get the current uptime of the system (in milliseconds). +/** \brief Get the current uptime of the system (in milliseconds). + \ingroup tmu_uptime This function retrieves the number of milliseconds since KOS was started. It is equivalent to calling timer_ms_gettime() and combining the number of @@ -174,9 +271,26 @@ void timer_ms_gettime(uint32 *secs, uint32 *msecs); \return The number of milliseconds since KOS started. */ -uint64 timer_ms_gettime64(void); +uint64_t timer_ms_gettime64(void); -/** \brief Get the current uptime of the system (in microseconds). +/** \brief Get the current uptime of the system (in secs and microsecs). + \ingroup tmu_uptime + + This function retrieves the number of seconds and microseconds since KOS was + started. + + \param secs A pointer to store the number of seconds since boot + into. + \param usecs A pointer to store the number of microseconds past + a second since boot. + \note To get the total number of microseconds since boot, + calculate (*secs * 1000000) + *usecs, or use the + timer_us_gettime64() function. +*/ +void timer_us_gettime(uint32_t *secs, uint32_t *usecs); + +/** \brief Get the current uptime of the system (in microseconds). + \ingroup tmu_uptime This function retrieves the number of microseconds since KOS was started. It should be more precise, in theory, than timer_ms_gettime64(), but the exact @@ -184,15 +298,76 @@ uint64 timer_ms_gettime64(void); \return The number of microseconds since KOS started. */ -uint64 timer_us_gettime64(void); +uint64_t timer_us_gettime64(void); + +/** \brief Get the current uptime of the system (in secs and nanosecs). + \ingroup tmu_uptime + + This function retrieves the number of seconds and nanoseconds since KOS was + started. + + \param secs A pointer to store the number of seconds since boot + into. + \param nsecs A pointer to store the number of nanoseconds past + a second since boot. + \note To get the total number of nanoseconds since boot, + calculate (*secs * 1000000000) + *nsecs, or use the + timer_ns_gettime64() function. +*/ +void timer_ns_gettime(uint32_t *secs, uint32_t *nsecs); + +/** \defgroup tmu_sleep Sleeping + \brief Low-level thread sleeping + \ingroup timers + + This API provides the low-level functionality used to implement thread + sleeping, used by the KOS, C, C++, and POSIX threading APIs. + + \warning + This API and its underlying functionality are using \ref TMU1, so any + direct manipulation of it will interfere with the API's proper functioning. +*/ + +/** \brief Spin-loop sleep function. + \ingroup tmu_sleep + + This function is meant as a very accurate delay function, even if threading + and interrupts are disabled. It uses \ref TMU1 to sleep. + + \param ms The number of milliseconds to sleep. +*/ +void timer_spin_sleep(int ms); + +/** \defgroup tmu_primary Primary Timer + \brief Primary timer used by the kernel. + \ingroup timers -/** \brief Primary timer callback type. */ + This API provides a callback notification mechanism that can be hooked into + the primary timer (TMU0). It is used by the KOS kernel for threading and + scheduling. + + \warning + This API and its underlying functionality are using \ref TMU0, so any + direct manipulation of it will interfere with the API's proper functioning. +*/ + +/** \brief Primary timer callback type. + \ingroup tmu_primary + + This is the type of function which may be passed to + timer_primary_set_callback() as the function that gets invoked + upon interrupt. +*/ typedef void (*timer_primary_callback_t)(irq_context_t *); ...<truncated>... hooks/post-receive -- A pseudo Operating System for the Dreamcast. |