diff --git a/docs/unix/quickref.rst b/docs/unix/quickref.rst index ec5312a535..2eac1edc79 100644 --- a/docs/unix/quickref.rst +++ b/docs/unix/quickref.rst @@ -73,6 +73,8 @@ General options: - ``-X heapsize=[w][K|M]`` sets the heap size for the garbage collector. The suffix ``w`` means words instead of bytes. ``K`` means x1024 and ``M`` means x1024x1024. + - ``-X realtime`` sets thread priority to realtime. This can be used to + improve timer precision. Only available on macOS. diff --git a/ports/unix/main.c b/ports/unix/main.c index 8d7fcf4843..0e00db7fa2 100644 --- a/ports/unix/main.c +++ b/ports/unix/main.c @@ -328,6 +328,10 @@ STATIC void print_help(char **argv) { , heap_size); impl_opts_cnt++; #endif + #if defined(__APPLE__) + printf(" realtime -- set thread priority to realtime\n"); + impl_opts_cnt++; + #endif if (impl_opts_cnt == 0) { printf(" (none)\n"); @@ -399,6 +403,15 @@ STATIC void pre_process_options(int argc, char **argv) { goto invalid_arg; } #endif + #if defined(__APPLE__) + } else if (strcmp(argv[a + 1], "realtime") == 0) { + #if MICROPY_PY_THREAD + mp_thread_is_realtime_enabled = true; + #endif + // main thread was already intialized before the option + // was parsed, so we have to enable realtime here. + mp_thread_set_realtime(); + #endif } else { invalid_arg: exit(invalid_args()); diff --git a/ports/unix/mpthreadport.c b/ports/unix/mpthreadport.c index 2ec7e65d3b..6a267e7236 100644 --- a/ports/unix/mpthreadport.c +++ b/ports/unix/mpthreadport.c @@ -192,6 +192,13 @@ void mp_thread_set_state(mp_state_thread_t *state) { } void mp_thread_start(void) { + // enable realtime priority if `-X realtime` command line parameter was set + #if defined(__APPLE__) + if (mp_thread_is_realtime_enabled) { + mp_thread_set_realtime(); + } + #endif + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); mp_thread_unix_begin_atomic_section(); for (mp_thread_t *th = thread; th != NULL; th = th->next) { @@ -310,3 +317,39 @@ void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { } #endif // MICROPY_PY_THREAD + +// this is used even when MICROPY_PY_THREAD is disabled + +#if defined(__APPLE__) +#include +#include +#include +#include + +bool mp_thread_is_realtime_enabled; + +// based on https://developer.apple.com/library/archive/technotes/tn2169/_index.html +void mp_thread_set_realtime(void) { + mach_timebase_info_data_t timebase_info; + + mach_timebase_info(&timebase_info); + + const uint64_t NANOS_PER_MSEC = 1000000ULL; + double clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC; + + thread_time_constraint_policy_data_t policy; + policy.period = 0; + policy.computation = (uint32_t)(5 * clock2abs); // 5 ms of work + policy.constraint = (uint32_t)(10 * clock2abs); + policy.preemptible = FALSE; + + int kr = thread_policy_set(pthread_mach_thread_np(pthread_self()), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t)&policy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + + if (kr != KERN_SUCCESS) { + mach_error("thread_policy_set:", kr); + } +} +#endif diff --git a/ports/unix/mpthreadport.h b/ports/unix/mpthreadport.h index a7dbe08c49..b365f200ed 100644 --- a/ports/unix/mpthreadport.h +++ b/ports/unix/mpthreadport.h @@ -25,6 +25,7 @@ */ #include +#include typedef pthread_mutex_t mp_thread_mutex_t; @@ -36,3 +37,9 @@ void mp_thread_gc_others(void); // Functions as a port-global lock for any code that must be serialised. void mp_thread_unix_begin_atomic_section(void); void mp_thread_unix_end_atomic_section(void); + +// for `-X realtime` command line option +#if defined(__APPLE__) +extern bool mp_thread_is_realtime_enabled; +void mp_thread_set_realtime(void); +#endif