From 1c0c2c33892e1cdc22fc4c65be6bfc3cee2ff208 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Thu, 9 Apr 2026 22:27:53 +0200 Subject: [PATCH 01/43] Reapply "Rtapi cleanup" This reverts commit a24f17354668b1b73245f2c0ba3933154a1c2b17. --- src/rtapi/Submakefile | 12 +- src/rtapi/rtapi_pci.cc | 2 +- src/rtapi/uspace_common.h | 3 +- src/rtapi/uspace_posix.cc | 253 +++ src/rtapi/uspace_rtai.cc | 95 +- src/rtapi/uspace_rtapi_app.cc | 1364 ++--------------- .../{rtapi_uspace.hh => uspace_rtapi_app.hh} | 61 +- src/rtapi/uspace_rtapi_main.cc | 952 ++++++++++++ src/rtapi/uspace_rtapi_parport.cc | 2 +- src/rtapi/uspace_xenomai.cc | 125 +- src/rtapi/uspace_xenomai_evl.cc | 136 +- 11 files changed, 1575 insertions(+), 1430 deletions(-) create mode 100644 src/rtapi/uspace_posix.cc rename src/rtapi/{rtapi_uspace.hh => uspace_rtapi_app.hh} (76%) create mode 100644 src/rtapi/uspace_rtapi_main.cc diff --git a/src/rtapi/Submakefile b/src/rtapi/Submakefile index a51231dc1af..883f9486fc8 100644 --- a/src/rtapi/Submakefile +++ b/src/rtapi/Submakefile @@ -32,6 +32,7 @@ $(patsubst ./rtapi/%,../include/%,$(RTAPIINCS)): ../include/%.h: ./rtapi/%.h ifeq ($(BUILD_SYS),uspace) RTAPI_APP_SRCS := \ + rtapi/uspace_rtapi_main.cc \ rtapi/uspace_rtapi_app.cc \ rtapi/uspace_rtapi_parport.cc \ rtapi/uspace_rtapi_string.c \ @@ -43,10 +44,19 @@ $(call TOOBJSDEPS, $(RTAPI_APP_SRCS)): EXTRAFLAGS += -DSIM \ -UULAPI -DRTAPI -pthread ../bin/rtapi_app: $(call TOOBJS, $(RTAPI_APP_SRCS)) $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt $(LIBUDEV_LIBS) -ldl $(LDFLAGS) + $(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt -lfmt $(LIBUDEV_LIBS) -ldl $(LDFLAGS) TARGETS += ../bin/rtapi_app endif +USPACE_POSIX_SRCS := rtapi/uspace_posix.cc +USERSRCS += $(USPACE_POSIX_SRCS) +$(call TOOBJSDEPS, $(USPACE_POSIX_SRCS)): EXTRAFLAGS += -pthread -fPIC +../lib/libuspace-posix.so.0: $(call TOOBJS, $(USPACE_POSIX_SRCS)) + $(ECHO) Linking $(notdir $@) + $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ -Wl,-soname,$(notdir $@) +TARGETS += ../lib/libuspace-posix.so.0 +TARGETS += ../lib/libuspace-posix.so + ifeq ($(CONFIG_USPACE_RTAI),y) USPACE_RTAI_SRCS := rtapi/uspace_rtai.cc USERSRCS += $(USPACE_RTAI_SRCS) diff --git a/src/rtapi/rtapi_pci.cc b/src/rtapi/rtapi_pci.cc index f3e71844515..cfbf05ca885 100644 --- a/src/rtapi/rtapi_pci.cc +++ b/src/rtapi/rtapi_pci.cc @@ -34,7 +34,7 @@ #include #include #include -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #include #include diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index 9c82c3a397e..ffc7376b9a9 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -39,7 +40,7 @@ static msg_level_t msg_level = RTAPI_MSG_ERR; /* message printing level */ #include "config.h" #ifdef RTAPI -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #endif typedef struct { diff --git a/src/rtapi/uspace_posix.cc b/src/rtapi/uspace_posix.cc new file mode 100644 index 00000000000..3cb48ff0e50 --- /dev/null +++ b/src/rtapi/uspace_posix.cc @@ -0,0 +1,253 @@ +/* Copyright (C) 2006-2014 Jeff Epler + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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-1301 USA. + */ + +#include "config.h" +#include "rtapi.h" +#include "uspace_rtapi_app.hh" +#include +#include +#include +#include +#ifdef HAVE_SYS_IO_H +#include +#endif + +namespace { +struct PosixTask : rtapi_task { + PosixTask() : rtapi_task{}, thr{} { + } + + pthread_t thr; /* thread's context */ +}; + +struct PosixApp : RtapiApp { + PosixApp(int policy = SCHED_FIFO) : RtapiApp(policy), do_thread_lock(policy != SCHED_FIFO) { + pthread_once(&key_once, init_key); + if (do_thread_lock) { + pthread_once(&lock_once, init_lock); + } + } + + struct rtapi_task *do_task_new() { + return new PosixTask; + } + + int task_delete(int id) { + auto task = ::rtapi_get_task(id); + if (!task) + return -EINVAL; + + pthread_cancel(task->thr); + pthread_join(task->thr, 0); + task->magic = 0; + task_array[id] = 0; + delete task; + return 0; + } + + int task_start(int task_id, unsigned long period_nsec) { + auto task = ::rtapi_get_task(task_id); + if (!task) + return -EINVAL; + + task->period = period_nsec; + struct sched_param param; + memset(¶m, 0, sizeof(param)); + param.sched_priority = task->prio; + + // limit PLL correction values to +/-1% of cycle time + task->pll_correction_limit = period_nsec / 100; + task->pll_correction = 0; + + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); + + pthread_attr_t attr; + int ret; + if ((ret = pthread_attr_init(&attr)) != 0) + return -ret; + if ((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) + return -ret; + if ((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) + return -ret; + if ((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) + return -ret; + if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + return -ret; + if (nprocs > 1) { + const static int rt_cpu_number = find_rt_cpu_number(); + rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); + if (rt_cpu_number != -1) { +#ifdef __FreeBSD__ + cpuset_t cpuset; +#else + cpu_set_t cpuset; +#endif + CPU_ZERO(&cpuset); + CPU_SET(rt_cpu_number, &cpuset); + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + return -ret; + } + } + if (do_thread_lock) + pthread_mutex_lock(&thread_lock); + if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) + return -ret; + + return 0; + } + + static void *wrapper(void *arg) { + auto task = reinterpret_cast(arg); + + pthread_setspecific(key, arg); + set_namef("rtapi_app:T#%d", task->id); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + rtapi_timespec_advance(task->nextstart, now, task->period + task->pll_correction); + + /* call the task function with the task argument */ + (task->taskcode)(task->arg); + + rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); + return NULL; + } + + int task_pause(int task_id) { + (void)task_id; + return -ENOSYS; + } + + int task_resume(int task_id) { + (void)task_id; + return -ENOSYS; + } + + long long task_pll_get_reference(void) { + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return 0; + return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; + } + + int task_pll_set_correction(long value) { + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; + if (value > task->pll_correction_limit) + value = task->pll_correction_limit; + if (value < -(task->pll_correction_limit)) + value = -(task->pll_correction_limit); + task->pll_correction = value; + return 0; + } + + void wait() { + if (do_thread_lock) + pthread_mutex_unlock(&thread_lock); + pthread_testcancel(); + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (rtapi_timespec_less(task->nextstart, now)) { + if (policy == SCHED_FIFO) + unexpected_realtime_delay(task); + } else { + int res = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &task->nextstart, nullptr); + if (res < 0) + perror("clock_nanosleep"); + } + if (do_thread_lock) + pthread_mutex_lock(&thread_lock); + } + + unsigned char do_inb(unsigned int port) { +#ifdef HAVE_SYS_IO_H + return inb(port); +#else + (void)port; + return 0; +#endif + } + + void do_outb(unsigned char val, unsigned int port) { +#ifdef HAVE_SYS_IO_H + return outb(val, port); +#else + (void)val; + (void)port; +#endif + } + + int run_threads(int fd, int (*callback)(int fd)) { + while (callback(fd)) { + /* nothing */ + } + return 0; + } + + int task_self() { + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; + return task->id; + } + + bool do_thread_lock; + + static pthread_once_t key_once; + static pthread_key_t key; + static void init_key(void) { + pthread_key_create(&key, NULL); + } + + static pthread_once_t lock_once; + static pthread_mutex_t thread_lock; + static void init_lock(void) { + pthread_mutex_init(&thread_lock, NULL); + } + + long long do_get_time() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000000000LL + ts.tv_nsec; + } + + void do_delay(long ns) { + struct timespec ts = {0, ns}; + clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, nullptr); + } +}; + +pthread_once_t PosixApp::key_once = PTHREAD_ONCE_INIT; +pthread_once_t PosixApp::lock_once = PTHREAD_ONCE_INIT; +pthread_key_t PosixApp::key; +pthread_mutex_t PosixApp::thread_lock; + +} // namespace + +extern "C" RtapiApp *make(int policy); + +RtapiApp *make(int policy) { + if (policy == SCHED_OTHER) { + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX non-realtime\n"); + } else { + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX realtime\n"); + } + return new PosixApp(policy); +} diff --git a/src/rtapi/uspace_rtai.cc b/src/rtapi/uspace_rtai.cc index dd6549e8441..14218b382b7 100644 --- a/src/rtapi/uspace_rtai.cc +++ b/src/rtapi/uspace_rtai.cc @@ -1,28 +1,44 @@ +/* Copyright (C) 2016 Jeff Epler + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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-1301 USA. + */ #include "config.h" #include "rtapi.h" -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnarrowing" #include #pragma GCC diagnostic pop +#include #ifdef HAVE_SYS_IO_H #include #endif -namespace -{ +namespace { RtapiApp *app; struct RtaiTask : rtapi_task { - RtaiTask() : rtapi_task{}, cancel{}, rt_task{}, thr{} {} + RtaiTask() : rtapi_task{}, cancel{}, rt_task{}, thr{} { + } std::atomic_int cancel; RT_TASK *rt_task; pthread_t thr; }; -template -T *get_task(int task_id) { - return static_cast(RtapiApp::get_task(task_id)); +template T *get_task(int task_id) { + return static_cast(RtapiApp::get_task(task_id)); } @@ -31,13 +47,14 @@ struct RtaiApp : RtapiApp { pthread_once(&key_once, init_key); } - RtaiTask *do_task_new() { + struct rtapi_task *do_task_new() { return new RtaiTask; } int task_delete(int id) { auto task = ::get_task(id); - if(!task) return -EINVAL; + if (!task) + return -EINVAL; task->cancel = 1; pthread_join(task->thr, nullptr); @@ -49,7 +66,8 @@ struct RtaiApp : RtapiApp { int task_start(int task_id, unsigned long period_nsec) { auto task = ::get_task(task_id); - if(!task) return -EINVAL; + if (!task) + return -EINVAL; task->period = period_nsec; struct sched_param param; @@ -62,30 +80,30 @@ struct RtaiApp : RtapiApp { int ret; pthread_attr_t attr; - if((ret = pthread_attr_init(&attr)) != 0) + if ((ret = pthread_attr_init(&attr)) != 0) return -ret; - if((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) + if ((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) return -ret; - if((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) + if ((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) return -ret; - if((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) + if ((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) return -ret; - if((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) + if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) return -ret; return 0; } static void *wrapper(void *arg) { - auto task = reinterpret_cast(arg); + auto task = reinterpret_cast(arg); pthread_setspecific(key, arg); - - int nprocs = sysconf( _SC_NPROCESSORS_ONLN ); - int cpus_allowed = 1 << (nprocs-1); //Use last CPU as default + + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); + int cpus_allowed = 1 << (nprocs - 1); //Use last CPU as default const static int rt_cpu_number = find_rt_cpu_number(); - if(rt_cpu_number != -1) { + if (rt_cpu_number != -1) { rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); cpus_allowed = 1 << rt_cpu_number; } @@ -96,7 +114,7 @@ struct RtaiApp : RtapiApp { rt_task_use_fpu(task->rt_task, 1); rt_make_hard_real_time(); rt_task_make_periodic_relative_ns(task->rt_task, task->period, task->period); - (task->taskcode) (task->arg); + (task->taskcode)(task->arg); rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); rt_make_soft_real_time(); @@ -105,13 +123,15 @@ struct RtaiApp : RtapiApp { int task_pause(int task_id) { auto task = ::get_task(task_id); - if(!task) return -EINVAL; + if (!task) + return -EINVAL; return rt_task_suspend(task->rt_task); } int task_resume(int task_id) { auto task = ::get_task(task_id); - if(!task) return -EINVAL; + if (!task) + return -EINVAL; return rt_task_resume(task->rt_task); } @@ -129,11 +149,12 @@ struct RtaiApp : RtapiApp { void wait() { int task_id = task_self(); auto task = ::get_task(task_id); - if(task->cancel) { + if (task->cancel) { rt_make_soft_real_time(); pthread_exit(nullptr); } - if(rt_task_wait_period() < 0) unexpected_realtime_delay(task); + if (rt_task_wait_period() < 0) + unexpected_realtime_delay(task); } unsigned char do_inb(unsigned int port) { @@ -149,19 +170,22 @@ struct RtaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return outb(val, port); #else + (void)val; (void)port; - return 0; #endif } int run_threads(int fd, int (*callback)(int fd)) { - while(callback(fd)) { /* nothing */ } + while (callback(fd)) { + /* nothing */ + } return 0; } int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; return task->id; } @@ -190,11 +214,14 @@ struct RtaiApp : RtapiApp { pthread_once_t RtaiApp::key_once; pthread_key_t RtaiApp::key; -} +} // namespace -extern "C" RtapiApp *make(); +extern "C" RtapiApp *make(int policy); -RtapiApp *make() { +RtapiApp *make(int policy) { + if (policy != SCHED_FIFO) { + throw std::invalid_argument("Only SCHED_FIFO allowed"); + } rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using LXRT realtime\n"); - return app = new RtaiApp; + return app = new RtaiApp(); } diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index bda630254d0..78aa962b596 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -15,56 +15,18 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "config.h" -#include +#include "rtapi.h" +#include "uspace_rtapi_app.hh" -#ifdef __linux__ -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#ifdef HAVE_SYS_IO_H -#include -#endif -#include -#include -#ifdef __linux__ -#include -#include -#endif -#ifdef __FreeBSD__ -#include -#endif - -#include - -#include "rtapi.h" -#include -#include "hal/hal_priv.h" -#include "rtapi_uspace.hh" +#include +#include std::atomic_int WithRoot::level; -static uid_t euid, ruid; - -#include "rtapi/uspace_common.h" +uid_t euid, ruid; WithRoot::WithRoot() { - if(!level++) { + if (!level++) { #ifdef __linux__ setfsuid(euid); #endif @@ -72,848 +34,39 @@ WithRoot::WithRoot() { } WithRoot::~WithRoot() { - if(!--level) { + if (!--level) { #ifdef __linux__ setfsuid(ruid); #endif } } -namespace -{ -RtapiApp &App(); - -struct message_t { - msg_level_t level; - char msg[1024-sizeof(level)]; -}; - -boost::lockfree::queue> -rtapi_msg_queue; - -static void set_namef(const char *fmt, ...) { - char *buf = NULL; - va_list ap; - - va_start(ap, fmt); - if (vasprintf(&buf, fmt, ap) < 0) { - va_end(ap); - return; - } - va_end(ap); - - int res = pthread_setname_np(pthread_self(), buf); - if (res) { - fprintf(stderr, "pthread_setname_np() failed for %s: %d\n", buf, res); - } - free(buf); -} - -pthread_t queue_thread; -void *queue_function(void * /*arg*/) { - set_namef("rtapi_app:mesg"); - // note: can't use anything in this function that requires App() to exist - // but it's OK to use functions that aren't safe for realtime (that's the - // point of running this in a thread) - while(1) { - pthread_testcancel(); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr); - rtapi_msg_queue.consume_all([](const message_t &m) { - fputs(m.msg, m.level == RTAPI_MSG_ALL ? stdout : stderr); - }); - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); - struct timespec ts = {0, 10000000}; - rtapi_clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL, NULL); - } - return nullptr; -} -} - -static int sim_rtapi_run_threads(int fd, int (*callback)(int fd)); - -using namespace std; - -template T DLSYM(void *handle, const string &name) { - return (T)(dlsym(handle, name.c_str())); -} - -template T DLSYM(void *handle, const char *name) { - return (T)(dlsym(handle, name)); -} - -static std::map modules; - -static int instance_count = 0; -static int force_exit = 0; - -static int do_newinst_cmd(const string& type, const string& name, const string& arg) { - void *module = modules["hal_lib"]; - if(!module) { - rtapi_print_msg(RTAPI_MSG_ERR, - "newinst: hal_lib is required, but not loaded\n"); - return -1; - } - - hal_comp_t *(*find_comp_by_name)(char*) = - DLSYM(module, "halpr_find_comp_by_name"); - if(!find_comp_by_name) { - rtapi_print_msg(RTAPI_MSG_ERR, - "newinst: halpr_find_comp_by_name not found\n"); - return -1; - } - - hal_comp_t *comp = find_comp_by_name((char*)type.c_str()); - if(!comp) { - rtapi_print_msg(RTAPI_MSG_ERR, - "newinst: component %s not found\n", type.c_str()); - return -1; - } - - return comp->make((char*)name.c_str(), (char*)arg.c_str()); -} - -static int do_one_item(char item_type_char, const string ¶m_name, const string ¶m_value, void *vitem, int idx=0) { - char *endp; - switch(item_type_char) { - case 'l': { - long *litem = *(long**) vitem; - litem[idx] = strtol(param_value.c_str(), &endp, 0); - if(*endp) { - rtapi_print_msg(RTAPI_MSG_ERR, - "`%s' invalid for parameter `%s'", - param_value.c_str(), param_name.c_str()); - return -1; - } - return 0; - } - case 'i': { - int *iitem = *(int**) vitem; - iitem[idx] = strtol(param_value.c_str(), &endp, 0); - if(*endp) { - rtapi_print_msg(RTAPI_MSG_ERR, - "`%s' invalid for parameter `%s'", - param_value.c_str(), param_name.c_str()); - return -1; - } - return 0; - } - case 's': { - char **sitem = *(char***) vitem; - sitem[idx] = strdup(param_value.c_str()); - return 0; - } - default: - rtapi_print_msg(RTAPI_MSG_ERR, - "%s: Invalid type character `%c'\n", - param_name.c_str(), item_type_char); - return -1; - } - return 0; -} - -void remove_quotes(string &s) { - s.erase(remove_copy(s.begin(), s.end(), s.begin(), '"'), s.end()); -} - -static int do_comp_args(void *module, vector args) { - for(unsigned i=1; i < args.size(); i++) { - string &s = args[i]; - remove_quotes(s); - size_t idx = s.find('='); - if(idx == string::npos) { - rtapi_print_msg(RTAPI_MSG_ERR, "Invalid parameter `%s'\n", - s.c_str()); - return -1; - } - string param_name(s, 0, idx); - string param_value(s, idx+1); - void *item=DLSYM(module, "rtapi_info_address_" + param_name); - if(!item) { - rtapi_print_msg(RTAPI_MSG_ERR, - "Unknown parameter `%s'\n", s.c_str()); - return -1; - } - char **item_type=DLSYM(module, "rtapi_info_type_" + param_name); - if(!item_type || !*item_type) { - rtapi_print_msg(RTAPI_MSG_ERR, - "Unknown parameter `%s' (type information missing)\n", - s.c_str()); - return -1; - } - - int*max_size_ptr=DLSYM(module, "rtapi_info_size_" + param_name); - - char item_type_char = **item_type; - if(max_size_ptr) { - int max_size = *max_size_ptr; - size_t idx = 0; - int i = 0; - while(idx != string::npos) { - if(i == max_size) { - rtapi_print_msg(RTAPI_MSG_ERR, - "%s: can only take %d arguments\n", - s.c_str(), max_size); - return -1; - } - size_t idx1 = param_value.find(",", idx); - string substr(param_value, idx, idx1 - idx); - int result = do_one_item(item_type_char, s, substr, item, i); - if(result != 0) return result; - i++; - idx = idx1 == string::npos ? idx1 : idx1 + 1; - } - } else { - int result = do_one_item(item_type_char, s, param_value, item); - if(result != 0) return result; - } - } - return 0; -} - -static int do_load_cmd(const string& name, const vector& args) { - void *w = modules[name]; - if(w == NULL) { - char what[LINELEN+1]; - snprintf(what, LINELEN, "%s/%s.so", EMC2_RTLIB_DIR, name.c_str()); - void *module = modules[name] = dlopen(what, RTLD_GLOBAL | RTLD_NOW); - if(!module) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlopen: %s\n", name.c_str(), dlerror()); - modules.erase(name); - return -1; - } - /// XXX handle arguments - int (*start)(void) = DLSYM(module, "rtapi_app_main"); - if(!start) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlsym: %s\n", name.c_str(), dlerror()); - dlclose(module); - modules.erase(name); - return -1; - } - if(!DLSYM(module, "rtapi_app_exit")) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: component is missing rtapi_app_exit\n", name.c_str()); - dlclose(module); - modules.erase(name); - return -1; - } - int result; - - result = do_comp_args(module, args); - if(result < 0) { - dlclose(module); - modules.erase(name); - return -1; - } - - if ((result=start()) < 0) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: rtapi_app_main: %s (%d)\n", - name.c_str(), strerror(-result), result); - dlclose(module); - modules.erase(name); - return result; - } else { - instance_count ++; - return 0; - } - } else { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: already exists\n", name.c_str()); - return -1; - } -} - -static int do_unload_cmd(const string& name) { - void *w = modules[name]; - if(w == NULL) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: not loaded\n", name.c_str()); - return -1; - } else { - void (*stop)(void) = DLSYM(w, "rtapi_app_exit"); - if(stop) stop(); - modules.erase(modules.find(name)); - dlclose(w); - instance_count --; - } - return 0; -} - -static int do_debug_cmd(const string& value) { - try{ - int new_level = stoi(value); - if (new_level < 0 || new_level > 5){ - rtapi_print_msg(RTAPI_MSG_ERR, "Debug level must be >=0 and <= 5\n"); - return -EINVAL; - } - return rtapi_set_msg_level(new_level); - }catch(invalid_argument &e){ - //stoi will throw an exception if parsing is not possible - rtapi_print_msg(RTAPI_MSG_ERR, "Debug level is not a number\n"); - return -EINVAL; - } -} - -struct ReadError : std::exception {}; -struct WriteError : std::exception {}; - -static int read_number(int fd) { - int r = 0, neg=1; - char ch; - - while(1) { - int res = read(fd, &ch, 1); - if(res != 1) return -1; - if(ch == '-') neg = -1; - else if(ch == ' ') return r * neg; - else r = 10 * r + ch - '0'; - } -} - -static string read_string(int fd) { - int len = read_number(fd); - if(len < 0) - throw ReadError(); - if(!len) - return string(); - string str(len, 0); - if(read(fd, str.data(), len) != len) - throw ReadError(); - return str; -} - -static vector read_strings(int fd) { - vector result; - int count = read_number(fd); - if(count < 0) - return result; - for(int i=0; i& strings) { - string buf; - write_number(buf, strings.size()); - for(unsigned int i=0; i args) { - if(args.size() == 0) { return 0; } - if(args.size() == 1 && args[0] == "exit") { - force_exit = 1; - return 0; - } else if(args.size() >= 2 && args[0] == "load") { - string name = args[1]; - args.erase(args.begin()); - return do_load_cmd(name, args); - } else if(args.size() == 2 && args[0] == "unload") { - return do_unload_cmd(args[1]); - } else if(args.size() == 3 && args[0] == "newinst") { - return do_newinst_cmd(args[1], args[2], ""); - } else if(args.size() == 4 && args[0] == "newinst") { - return do_newinst_cmd(args[1], args[2], args[3]); - } else if(args.size() == 2 && args[0] == "debug") { - return do_debug_cmd(args[1]); - } else { - rtapi_print_msg(RTAPI_MSG_ERR, - "Unrecognized command starting with %s\n", - args[0].c_str()); - return -1; - } -} - -static int slave(int fd, const vector& args) { - try { - write_strings(fd, args); - } - catch (WriteError &e) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: failed to write to master: %s\n", strerror(errno)); - } - - int result = read_number(fd); - return result; -} - -static int callback(int fd) -{ - struct sockaddr_un client_addr; - memset(&client_addr, 0, sizeof(client_addr)); - socklen_t len = sizeof(client_addr); - int fd1 = accept(fd, (sockaddr*)&client_addr, &len); - if(fd1 < 0) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: failed to accept connection from slave: %s\n", strerror(errno)); - return -1; - } else { - int result; - try { - result = handle_command(read_strings(fd1)); - } catch (ReadError &e) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: failed to read from slave: %s\n", strerror(errno)); - close(fd1); - return -1; - } - string buf; - write_number(buf, result); - if(write(fd1, buf.data(), buf.size()) != (ssize_t)buf.size()) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: failed to write to slave: %s\n", strerror(errno)); - }; - close(fd1); - } - return !force_exit && instance_count > 0; -} - -static pthread_t main_thread{}; - -static int master(int fd, const vector& args) { - main_thread = pthread_self(); - int result; - if((result = pthread_create(&queue_thread, nullptr, &queue_function, nullptr)) != 0) { - errno = result; - perror("pthread_create (queue function)"); - return -1; - } - do_load_cmd("hal_lib", vector()); - instance_count = 0; - App(); // force rtapi_app to be created - if(args.size()) { - result = handle_command(args); - if(result != 0) goto out; - if(force_exit || instance_count == 0) goto out; - } - sim_rtapi_run_threads(fd, callback); -out: - pthread_cancel(queue_thread); - pthread_join(queue_thread, nullptr); - rtapi_msg_queue.consume_all([](const message_t &m) { - fputs(m.msg, m.level == RTAPI_MSG_ALL ? stdout : stderr); - }); - return result; -} - -static std::string -_get_fifo_path() { - std::string s; - if(getenv("RTAPI_FIFO_PATH")) - s = getenv("RTAPI_FIFO_PATH"); - else if(getenv("HOME")) - s = std::string(getenv("HOME")) + "/.rtapi_fifo"; - else { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: RTAPI_FIFO_PATH and HOME are unset. rtapi fifo creation is unsafe."); - return NULL; - } - if(s.size() + 1 > sizeof(sockaddr_un::sun_path)) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: rtapi fifo path is too long (arch limit %zd): %s", - sizeof(sockaddr_un::sun_path), s.c_str()); - return NULL; - } - return s; -} - -static const char * -get_fifo_path() { - static std::string path = _get_fifo_path(); - return path.c_str(); -} - -static int -get_fifo_path(char *buf, size_t bufsize) { - int len; - const char *s = get_fifo_path(); - if(!s) return -1; - len=snprintf(buf+1, bufsize-1, "%s", s); - return len; -} - -int main(int argc, char **argv) { - if(getuid() == 0) { - char *fallback_uid_str = getenv("RTAPI_UID"); - int fallback_uid = fallback_uid_str ? atoi(fallback_uid_str) : 0; - if(fallback_uid == 0) - { - // Cppcheck cannot see EMC2_BIN_DIR when RTAPI is defined, but that - // doesn't happen in uspace. - fprintf(stderr, - "Refusing to run as root without fallback UID specified\n" - "To run under a debugger with I/O, use e.g.,\n" - // cppcheck-suppress unknownMacro - " sudo env RTAPI_UID=`id -u` RTAPI_FIFO_PATH=$HOME/.rtapi_fifo gdb " EMC2_BIN_DIR "/rtapi_app\n"); - exit(1); - } - if (setreuid(fallback_uid, 0) != 0) { perror("setreuid"); abort(); } - fprintf(stderr, - "Running with fallback_uid. getuid()=%d geteuid()=%d\n", - getuid(), geteuid()); - } - ruid = getuid(); - euid = geteuid(); - if (setresuid(euid, euid, ruid) != 0) { perror("setresuid"); abort(); } -#ifdef __linux__ - setfsuid(ruid); -#endif - vector args; - for(int i=1; i(malloc(PRE_ALLOC_SIZE)); - if (buf == NULL) { - rtapi_print_msg(RTAPI_MSG_WARN, "malloc(PRE_ALLOC_SIZE) failed\n"); - return; - } - long pagesize = sysconf(_SC_PAGESIZE); - /* Touch each page in this piece of memory to get it mapped into RAM */ - for (size_t i = 0; i < PRE_ALLOC_SIZE; i += pagesize) { - /* Each write to this buffer will generate a pagefault. - * Once the pagefault is handled a page will be locked in - * memory and never given back to the system. */ - buf[i] = 0; - } - free((void *)buf); -} - -static int harden_rt() -{ - if(!rtapi_is_realtime()) return -EINVAL; - - WITH_ROOT; -#if defined(__linux__) && (defined(__x86_64__) || defined(__i386__)) - if (iopl(3) < 0) { - rtapi_print_msg(RTAPI_MSG_ERR, - "iopl() failed: %s\n" - "cannot gain I/O privileges - " - "forgot 'sudo make setuid' or using secure boot? -" - "parallel port access is not allowed\n", - strerror(errno)); - } -#endif - - struct sigaction sig_act = {}; -#ifdef __linux__ - // enable realtime - if (setrlimit(RLIMIT_RTPRIO, &unlimited) < 0) - { - rtapi_print_msg(RTAPI_MSG_WARN, - "setrlimit(RTLIMIT_RTPRIO): %s\n", - strerror(errno)); - return -errno; - } - - // enable core dumps - if (setrlimit(RLIMIT_CORE, &unlimited) < 0) - rtapi_print_msg(RTAPI_MSG_WARN, - "setrlimit: %s - core dumps may be truncated or non-existent\n", - strerror(errno)); - - // even when setuid root - if (prctl(PR_SET_DUMPABLE, 1) < 0) - rtapi_print_msg(RTAPI_MSG_WARN, - "prctl(PR_SET_DUMPABLE) failed: no core dumps will be created - %d - %s\n", - errno, strerror(errno)); -#endif /* __linux__ */ - - configure_memory(); - - sigemptyset( &sig_act.sa_mask ); - sig_act.sa_handler = SIG_IGN; - sig_act.sa_sigaction = NULL; - - // prevent stopping of RT threads by ^Z - sigaction(SIGTSTP, &sig_act, (struct sigaction *) NULL); - - sig_act.sa_sigaction = signal_handler; - sig_act.sa_flags = SA_SIGINFO; - - sigaction(SIGSEGV, &sig_act, (struct sigaction *) NULL); - sigaction(SIGILL, &sig_act, (struct sigaction *) NULL); - sigaction(SIGFPE, &sig_act, (struct sigaction *) NULL); - sigaction(SIGTERM, &sig_act, (struct sigaction *) NULL); - sigaction(SIGINT, &sig_act, (struct sigaction *) NULL); - -#ifdef __linux__ - int fd = open("/dev/cpu_dma_latency", O_WRONLY | O_CLOEXEC); - if (fd < 0) { - rtapi_print_msg(RTAPI_MSG_WARN, "failed to open /dev/cpu_dma_latency: %s\n", strerror(errno)); - } else { - int r; - r = write(fd, "\0\0\0\0", 4); - if (r != 4) { - rtapi_print_msg(RTAPI_MSG_WARN, "failed to write to /dev/cpu_dma_latency: %s\n", strerror(errno)); - } - // deliberately leak fd until program exit - } -#endif /* __linux__ */ - return 0; -} - - -static RtapiApp *makeApp() -{ - if(euid != 0 || harden_rt() < 0) - { - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX non-realtime\n"); - return new Posix(SCHED_OTHER); - } - WithRoot r; - void *dll = nullptr; - if(detect_xenomai_evl()) { - dll = dlopen(EMC2_HOME "/lib/libuspace-xenomai-evl.so.0", RTLD_NOW); - if(!dll) fprintf(stderr, "dlopen: %s\n", dlerror()); - }else if(detect_xenomai()) { - dll = dlopen(EMC2_HOME "/lib/libuspace-xenomai.so.0", RTLD_NOW); - if(!dll) fprintf(stderr, "dlopen: %s\n", dlerror()); - } else if(detect_rtai()) { - dll = dlopen(EMC2_HOME "/lib/libuspace-rtai.so.0", RTLD_NOW); - if(!dll) fprintf(stderr, "dlopen: %s\n", dlerror()); - } - if(dll) - { - auto fn = reinterpret_cast(dlsym(dll, "make")); - if(!fn) fprintf(stderr, "dlopen: %s\n", dlerror()); - auto result = fn ? fn() : nullptr; - if(result) { - return result; - } - } - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX realtime\n"); - return new Posix(SCHED_FIFO); -} -RtapiApp &App() -{ - static RtapiApp *app = makeApp(); - return *app; -} - -} -/* data for all tasks */ -struct rtapi_task *task_array[MAX_TASKS]; - /* Priority functions. Uspace uses POSIX task priorities. */ -int RtapiApp::prio_highest() const -{ +int RtapiApp::prio_highest() const { return sched_get_priority_max(policy); } -int RtapiApp::prio_lowest() const -{ - return sched_get_priority_min(policy); +int RtapiApp::prio_lowest() const { + return sched_get_priority_min(policy); } int RtapiApp::prio_higher_delta() const { - if(rtapi_prio_highest() > rtapi_prio_lowest()) { + if (rtapi_prio_highest() > rtapi_prio_lowest()) { return 1; } return -1; } int RtapiApp::prio_bound(int prio) const { - if(rtapi_prio_highest() > rtapi_prio_lowest()) { + if (rtapi_prio_highest() > rtapi_prio_lowest()) { if (prio >= rtapi_prio_highest()) return rtapi_prio_highest(); if (prio < rtapi_prio_lowest()) @@ -928,77 +81,81 @@ int RtapiApp::prio_bound(int prio) const { } bool RtapiApp::prio_check(int prio) const { - if(rtapi_prio_highest() > rtapi_prio_lowest()) { + if (rtapi_prio_highest() > rtapi_prio_lowest()) { return (prio <= rtapi_prio_highest()) && (prio >= rtapi_prio_lowest()); } else { return (prio <= rtapi_prio_lowest()) && (prio >= rtapi_prio_highest()); } } -int RtapiApp::prio_next_higher(int prio) const -{ +int RtapiApp::prio_next_higher(int prio) const { prio = prio_bound(prio); - if(prio != rtapi_prio_highest()) + if (prio != rtapi_prio_highest()) return prio + prio_higher_delta(); return prio; } -int RtapiApp::prio_next_lower(int prio) const -{ +int RtapiApp::prio_next_lower(int prio) const { prio = prio_bound(prio); - if(prio != rtapi_prio_lowest()) + if (prio != rtapi_prio_lowest()) return prio - prio_higher_delta(); return prio; } -int RtapiApp::allocate_task_id() -{ - for(int n=0; nid = n; - task->owner = owner; - /* uses_fp is deprecated and ignored; always save FPU state */ - task->uses_fp = 1; - task->arg = arg; - task->stacksize = stacksize; - task->taskcode = taskcode; - task->prio = prio; - task->magic = TASK_MAGIC; - task_array[n] = task; + struct rtapi_task *task = do_task_new(); + if (stacksize < (1024 * 1024)) + stacksize = (1024 * 1024); + task->id = n; + task->owner = owner; + /* uses_fp is deprecated and ignored; always save FPU state */ + task->uses_fp = 1; + task->arg = arg; + task->stacksize = stacksize; + task->taskcode = taskcode; + task->prio = prio; + task->magic = TASK_MAGIC; + task_array[n] = task; - /* and return handle to the caller */ + /* and return handle to the caller */ - return n; + return n; } rtapi_task *RtapiApp::get_task(int task_id) { - if(task_id < 0 || task_id >= MAX_TASKS) return NULL; + if (task_id < 0 || task_id >= MAX_TASKS) + return NULL; /* validate task handle */ rtapi_task *task = task_array[task_id]; - if(!task || task == TASK_MAGIC_INIT || task->magic != TASK_MAGIC) + if (!task || task == TASK_MAGIC_INIT || task->magic != TASK_MAGIC) return NULL; return task; @@ -1006,34 +163,33 @@ rtapi_task *RtapiApp::get_task(int task_id) { void RtapiApp::unexpected_realtime_delay(rtapi_task *task, int /*nperiod*/) { static int printed = 0; - if(!printed) - { - rtapi_print_msg(RTAPI_MSG_ERR, - "Unexpected realtime delay on task %d with period %ld\n" - "This Message will only display once per session.\n" - "Run the Latency Test and resolve before continuing.\n", - task->id, task->period); + if (!printed) { + rtapi_print_msg( + RTAPI_MSG_ERR, + "Unexpected realtime delay on task %d with period %ld\n" + "This Message will only display once per session.\n" + "Run the Latency Test and resolve before continuing.\n", + task->id, + task->period + ); printed = 1; } } -int Posix::task_delete(int id) -{ - auto task = ::rtapi_get_task(id); - if(!task) return -EINVAL; - - pthread_cancel(task->thr); - pthread_join(task->thr, 0); - task->magic = 0; - task_array[id] = 0; - delete task; - return 0; +long RtapiApp::clock_set_period(long nsecs) { + if (nsecs == 0) + return period; + if (period != 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "attempt to set period twice\n"); + return -EINVAL; + } + period = nsecs; + return period; } //parse_cpu_list from https://gitlab.com/Xenomai/xenomai4/libevl/-/blob/11e6a1fb183a315ae861762e7650fd5e10d83ff5/tests/helpers.c //License: MIT -static void parse_cpu_list(const char *path, cpu_set_t *cpuset) -{ +static void parse_cpu_list(const char *path, cpu_set_t *cpuset) { char *p, *range, *range_p = NULL, *id, *id_r; int start, end, cpu; char buf[BUFSIZ]; @@ -1072,29 +228,33 @@ static void parse_cpu_list(const char *path, cpu_set_t *cpuset) } int find_rt_cpu_number() { - if(getenv("RTAPI_CPU_NUMBER")) return atoi(getenv("RTAPI_CPU_NUMBER")); + if (getenv("RTAPI_CPU_NUMBER")) + return atoi(getenv("RTAPI_CPU_NUMBER")); #ifdef __linux__ - const char* isolated_file="/sys/devices/system/cpu/isolated"; + const char *isolated_file = "/sys/devices/system/cpu/isolated"; cpu_set_t cpuset; parse_cpu_list(isolated_file, &cpuset); //Print list rtapi_print_msg(RTAPI_MSG_INFO, "cpuset isolated "); - for(int i=0; i(task_id); - if(!task) return -EINVAL; - - if(period_nsec < (unsigned long)period) period_nsec = (unsigned long)period; - task->period = period_nsec; - task->ratio = period_nsec / period; - - struct sched_param param; - memset(¶m, 0, sizeof(param)); - param.sched_priority = task->prio; - - // limit PLL correction values to +/-1% of cycle time - task->pll_correction_limit = period_nsec / 100; - task->pll_correction = 0; - - int nprocs = sysconf( _SC_NPROCESSORS_ONLN ); - - pthread_attr_t attr; - int ret; - if((ret = pthread_attr_init(&attr)) != 0) - return -ret; - if((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) - return -ret; - if((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) - return -ret; - if((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) - return -ret; - if((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) - return -ret; - if(nprocs > 1) { - const static int rt_cpu_number = find_rt_cpu_number(); - rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if(rt_cpu_number != -1) { -#ifdef __FreeBSD__ - cpuset_t cpuset; -#else - cpu_set_t cpuset; -#endif - CPU_ZERO(&cpuset); - CPU_SET(rt_cpu_number, &cpuset); - if((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) - return -ret; - } - } - if((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) - return -ret; - - return 0; -} - -#define RTAPI_CLOCK (CLOCK_MONOTONIC) - -pthread_once_t Posix::key_once = PTHREAD_ONCE_INIT; -pthread_once_t Posix::lock_once = PTHREAD_ONCE_INIT; -pthread_key_t Posix::key; -pthread_mutex_t Posix::thread_lock; - -void *Posix::wrapper(void *arg) -{ - struct rtapi_task *task; - - /* use the argument to point to the task data */ - task = (struct rtapi_task*)arg; - long int period = App().period; - if(task->period < period) task->period = period; - task->ratio = task->period / period; - task->period = task->ratio * period; - rtapi_print_msg(RTAPI_MSG_INFO, "task %p period = %lu ratio=%u\n", - task, task->period, task->ratio); - - pthread_setspecific(key, arg); - set_namef("rtapi_app:T#%d", task->id); - - Posix &papp = reinterpret_cast(App()); - if(papp.do_thread_lock) - pthread_mutex_lock(&papp.thread_lock); - - struct timespec now; - clock_gettime(RTAPI_CLOCK, &now); - rtapi_timespec_advance(task->nextstart, now, task->period + task->pll_correction); - - /* call the task function with the task argument */ - (task->taskcode) (task->arg); - - rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); - return NULL; -} - -long long Posix::task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return 0; - return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; -} - -int Posix::task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; - if (value > task->pll_correction_limit) value = task->pll_correction_limit; - if (value < -(task->pll_correction_limit)) value = -(task->pll_correction_limit); - task->pll_correction = value; - return 0; -} - -int Posix::task_pause(int) { - return -ENOSYS; -} - -int Posix::task_resume(int) { - return -ENOSYS; -} - -int Posix::task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; - return task->id; -} - -void Posix::wait() { - if(do_thread_lock) - pthread_mutex_unlock(&thread_lock); - pthread_testcancel(); - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); - struct timespec now; - clock_gettime(RTAPI_CLOCK, &now); - if(rtapi_timespec_less(task->nextstart, now)) - { - if(policy == SCHED_FIFO) - unexpected_realtime_delay(task); - } - else - { - int res = rtapi_clock_nanosleep(RTAPI_CLOCK, TIMER_ABSTIME, &task->nextstart, nullptr, &now); - if(res < 0) perror("clock_nanosleep"); - } - if(do_thread_lock) - pthread_mutex_lock(&thread_lock); -} - -unsigned char Posix::do_inb(unsigned int port) -{ -#ifdef HAVE_SYS_IO_H - return inb(port); -#else - (void)port; - return 0; -#endif -} - -void Posix::do_outb(unsigned char val, unsigned int port) -{ -#ifdef HAVE_SYS_IO_H - return outb(val, port); -#else - (void)val; - (void)port; -#endif -} - -void Posix::do_delay(long ns) { - struct timespec ts = {0, ns}; - rtapi_clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL, NULL); -} -int rtapi_prio_highest(void) -{ - return App().prio_highest(); -} - -int rtapi_prio_lowest(void) -{ - return App().prio_lowest(); -} - -int rtapi_prio_next_higher(int prio) -{ - return App().prio_next_higher(prio); -} - -int rtapi_prio_next_lower(int prio) -{ - return App().prio_next_lower(prio); -} - -long rtapi_clock_set_period(long nsecs) -{ - return App().clock_set_period(nsecs); -} - -long RtapiApp::clock_set_period(long nsecs) -{ - if(nsecs == 0) return period; - if(period != 0) { - rtapi_print_msg(RTAPI_MSG_ERR, "attempt to set period twice\n"); - return -EINVAL; - } - period = nsecs; - return period; -} - - -int rtapi_task_new(void (*taskcode) (void*), void *arg, - int prio, int owner, unsigned long int stacksize, int uses_fp) { - return App().task_new(taskcode, arg, prio, owner, stacksize, uses_fp); -} - -int rtapi_task_delete(int id) { - return App().task_delete(id); -} - -int rtapi_task_start(int task_id, unsigned long period_nsec) -{ - int ret = App().task_start(task_id, period_nsec); - if(ret != 0) { - errno = -ret; - perror("rtapi_task_start()"); - } - return ret; -} - -int rtapi_task_pause(int task_id) -{ - return App().task_pause(task_id); -} - -int rtapi_task_resume(int task_id) -{ - return App().task_resume(task_id); -} - -int rtapi_task_self() -{ - return App().task_self(); -} - -long long rtapi_task_pll_get_reference(void) -{ - return App().task_pll_get_reference(); -} - -int rtapi_task_pll_set_correction(long value) -{ - return App().task_pll_set_correction(value); -} - -void rtapi_wait(void) -{ - App().wait(); -} - -void rtapi_outb(unsigned char byte, unsigned int port) -{ - App().do_outb(byte, port); -} - -unsigned char rtapi_inb(unsigned int port) -{ - return App().do_inb(port); -} - -long int simple_strtol(const char *nptr, char **endptr, int base) { - return strtol(nptr, endptr, base); -} - -int Posix::run_threads(int fd, int(*callback)(int fd)) { - while(callback(fd)) { /* nothing */ } - return 0; -} - -int sim_rtapi_run_threads(int fd, int (*callback)(int fd)) { - return App().run_threads(fd, callback); -} - -long long rtapi_get_time() { - return App().do_get_time(); -} +void set_namef(const char *fmt, ...) { + char *buf = NULL; + va_list ap; -void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { - if(main_thread && pthread_self() != main_thread) { - message_t m; - m.level = level; - vsnprintf(m.msg, sizeof(m.msg), fmt, ap); - rtapi_msg_queue.push(m); - } else { - vfprintf(level == RTAPI_MSG_ALL ? stdout : stderr, fmt, ap); + va_start(ap, fmt); + if (vasprintf(&buf, fmt, ap) < 0) { + va_end(ap); + return; } -} - -long int rtapi_delay_max() { return 10000; } - -void rtapi_delay(long ns) { - if(ns > rtapi_delay_max()) ns = rtapi_delay_max(); - App().do_delay(ns); -} + va_end(ap); -const unsigned long ONE_SEC_IN_NS = 1000000000; -void rtapi_timespec_advance(struct timespec &result, const struct timespec &src, unsigned long nsec) -{ - time_t sec = src.tv_sec; - while(nsec >= ONE_SEC_IN_NS) - { - ++sec; - nsec -= ONE_SEC_IN_NS; - } - nsec += src.tv_nsec; - if(nsec >= ONE_SEC_IN_NS) - { - ++sec; - nsec -= ONE_SEC_IN_NS; + int res = pthread_setname_np(pthread_self(), buf); + if (res) { + fprintf(stderr, "pthread_setname_np() failed for %s: %d\n", buf, res); } - result.tv_sec = sec; - result.tv_nsec = nsec; -} - -int rtapi_open_as_root(const char *filename, int mode) { - WITH_ROOT; - int r = open(filename, mode); - if(r < 0) return -errno; - return r; -} - -int rtapi_spawn_as_root(pid_t *pid, const char *path, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *attrp, - char *const argv[], char *const envp[]) -{ - return posix_spawn(pid, path, file_actions, attrp, argv, envp); -} - -int rtapi_spawnp_as_root(pid_t *pid, const char *path, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *attrp, - char *const argv[], char *const envp[]) -{ - return posix_spawnp(pid, path, file_actions, attrp, argv, envp); -} + free(buf); +} \ No newline at end of file diff --git a/src/rtapi/rtapi_uspace.hh b/src/rtapi/uspace_rtapi_app.hh similarity index 76% rename from src/rtapi/rtapi_uspace.hh rename to src/rtapi/uspace_rtapi_app.hh index bc589c12536..880b2937781 100644 --- a/src/rtapi/rtapi_uspace.hh +++ b/src/rtapi/uspace_rtapi_app.hh @@ -34,42 +34,42 @@ static inline void rtapi_timespec_add(timespec &result, const timespec &ta, cons } static inline bool rtapi_timespec_less(const struct timespec &ta, const struct timespec &tb) { - if(ta.tv_sec < tb.tv_sec) return 1; - if(ta.tv_sec > tb.tv_sec) return 0; + if (ta.tv_sec < tb.tv_sec) + return 1; + if (ta.tv_sec > tb.tv_sec) + return 0; return ta.tv_nsec < tb.tv_nsec; } void rtapi_timespec_advance(struct timespec &result, const struct timespec &src, unsigned long nsec); -struct WithRoot -{ +struct WithRoot { WithRoot(); ~WithRoot(); static std::atomic_int level; }; struct rtapi_task { - rtapi_task(); + rtapi_task(); - int magic; /* to check for valid handle */ - int id; - int owner; - int uses_fp; - size_t stacksize; - int prio; - long period; - struct timespec nextstart; - unsigned ratio; - long pll_correction; - long pll_correction_limit; - void *arg; - void (*taskcode) (void*); /* pointer to task function */ + int magic; /* to check for valid handle */ + int id; + int owner; + int uses_fp; + size_t stacksize; + int prio; + long period; + struct timespec nextstart; + long pll_correction; + long pll_correction_limit; + void *arg; + void (*taskcode)(void *); /* pointer to task function */ }; -struct RtapiApp -{ +struct RtapiApp { - RtapiApp(int policy = SCHED_OTHER) : policy(policy), period(0) {} + RtapiApp(int policy = SCHED_OTHER) : policy(policy), period(0) { + } virtual int prio_highest() const; virtual int prio_lowest() const; @@ -79,12 +79,11 @@ struct RtapiApp int prio_next_higher(int prio) const; int prio_next_lower(int prio) const; long clock_set_period(long int period_nsec); - int task_new(void (*taskcode)(void*), void *arg, - int prio, int owner, unsigned long int stacksize, int uses_fp); + int task_new(void (*taskcode)(void *), void *arg, int prio, int owner, unsigned long int stacksize, int uses_fp); virtual rtapi_task *do_task_new() = 0; static int allocate_task_id(); static struct rtapi_task *get_task(int task_id); - void unexpected_realtime_delay(rtapi_task *task, int nperiod=1); + void unexpected_realtime_delay(rtapi_task *task, int nperiod = 1); virtual int task_delete(int id) = 0; virtual int task_start(int task_id, unsigned long period_nsec) = 0; virtual int task_pause(int task_id) = 0; @@ -102,18 +101,20 @@ struct RtapiApp long period; }; -template -T *rtapi_get_task(int task_id) { - return static_cast(RtapiApp::get_task(task_id)); +template T *rtapi_get_task(int task_id) { + return static_cast(RtapiApp::get_task(task_id)); } int find_rt_cpu_number(); +void set_namef(const char *fmt, ...); -#define MAX_TASKS 64 -#define TASK_MAGIC 21979 /* random numbers used as signatures */ -#define TASK_MAGIC_INIT ((rtapi_task*)(-1)) +#define MAX_TASKS 64 +#define TASK_MAGIC 21979 /* random numbers used as signatures */ +#define TASK_MAGIC_INIT ((rtapi_task *)(-1)) extern struct rtapi_task *task_array[MAX_TASKS]; +extern uid_t euid, ruid; //ToDo: Improve + #define WITH_ROOT WithRoot root #endif diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc new file mode 100644 index 00000000000..f796f0a6110 --- /dev/null +++ b/src/rtapi/uspace_rtapi_main.cc @@ -0,0 +1,952 @@ +/* Copyright (C) 2006-2014 Jeff Epler + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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-1301 USA. + */ + +#include "config.h" +#include + +#ifdef __linux__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_IO_H +#include +#endif +#include +#include +#ifdef __linux__ +#include +#include +#endif +#ifdef __FreeBSD__ +#include +#endif + +#include +#include + +#include "rtapi.h" +#include +#include "hal/hal_priv.h" +#include "uspace_common.h" + +RtapiApp &App(); + +struct message_t { + msg_level_t level; + char msg[1024 - sizeof(level)]; +}; + +boost::lockfree::queue> rtapi_msg_queue; + +pthread_t queue_thread; +void *queue_function(void * /*arg*/) { + set_namef("rtapi_app:mesg"); + // note: can't use anything in this function that requires App() to exist + // but it's OK to use functions that aren't safe for realtime (that's the + // point of running this in a thread) + while (1) { + pthread_testcancel(); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr); + rtapi_msg_queue.consume_all([](const message_t &m) { + fputs(m.msg, m.level == RTAPI_MSG_ALL ? stdout : stderr); + }); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); + struct timespec ts = {0, 10000000}; + rtapi_clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL, NULL); + } + return nullptr; +} + +static int sim_rtapi_run_threads(int fd, int (*callback)(int fd)); + +template T DLSYM(void *handle, const std::string &name) { + return (T)(dlsym(handle, name.c_str())); +} + +template T DLSYM(void *handle, const char *name) { + return (T)(dlsym(handle, name)); +} + +static std::map modules; + +static int instance_count = 0; +static int force_exit = 0; + +static int do_newinst_cmd(const std::string &type, const std::string &name, const std::string &arg) { + void *module = modules["hal_lib"]; + if (!module) { + rtapi_print_msg(RTAPI_MSG_ERR, "newinst: hal_lib is required, but not loaded\n"); + return -1; + } + + hal_comp_t *(*find_comp_by_name)(char *) = DLSYM(module, "halpr_find_comp_by_name"); + if (!find_comp_by_name) { + rtapi_print_msg(RTAPI_MSG_ERR, "newinst: halpr_find_comp_by_name not found\n"); + return -1; + } + + hal_comp_t *comp = find_comp_by_name((char *)type.c_str()); + if (!comp) { + rtapi_print_msg(RTAPI_MSG_ERR, "newinst: component %s not found\n", type.c_str()); + return -1; + } + + return comp->make((char *)name.c_str(), (char *)arg.c_str()); +} + +static int do_one_item( + char item_type_char, const std::string ¶m_name, const std::string ¶m_value, void *vitem, int idx = 0 +) { + char *endp; + switch (item_type_char) { + case 'l': { + long *litem = *(long **)vitem; + litem[idx] = strtol(param_value.c_str(), &endp, 0); + if (*endp) { + rtapi_print_msg( + RTAPI_MSG_ERR, "`%s' invalid for parameter `%s'\n", param_value.c_str(), param_name.c_str() + ); + return -1; + } + return 0; + } + case 'i': { + int *iitem = *(int **)vitem; + iitem[idx] = strtol(param_value.c_str(), &endp, 0); + if (*endp) { + rtapi_print_msg( + RTAPI_MSG_ERR, "`%s' invalid for parameter `%s'\n", param_value.c_str(), param_name.c_str() + ); + return -1; + } + return 0; + } + case 's': { + char **sitem = *(char ***)vitem; + sitem[idx] = strdup(param_value.c_str()); + return 0; + } + default: + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Invalid type character `%c'\n", param_name.c_str(), item_type_char); + return -1; + } + return 0; +} + +void remove_quotes(std::string &s) { + s.erase(remove_copy(s.begin(), s.end(), s.begin(), '"'), s.end()); +} + +static int do_comp_args(void *module, std::vector args) { + for (unsigned i = 1; i < args.size(); i++) { + std::string &s = args[i]; + remove_quotes(s); + size_t idx = s.find('='); + if (idx == std::string::npos) { + rtapi_print_msg(RTAPI_MSG_ERR, "Invalid parameter `%s'\n", s.c_str()); + return -1; + } + std::string param_name(s, 0, idx); + std::string param_value(s, idx + 1); + void *item = DLSYM(module, "rtapi_info_address_" + param_name); + if (!item) { + rtapi_print_msg(RTAPI_MSG_ERR, "Unknown parameter `%s'\n", s.c_str()); + return -1; + } + char **item_type = DLSYM(module, "rtapi_info_type_" + param_name); + if (!item_type || !*item_type) { + rtapi_print_msg(RTAPI_MSG_ERR, "Unknown parameter `%s' (type information missing)\n", s.c_str()); + return -1; + } + + int *max_size_ptr = DLSYM(module, "rtapi_info_size_" + param_name); + + char item_type_char = **item_type; + if (max_size_ptr) { + int max_size = *max_size_ptr; + size_t idx = 0; + int i = 0; + while (idx != std::string::npos) { + if (i == max_size) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: can only take %d arguments\n", s.c_str(), max_size); + return -1; + } + size_t idx1 = param_value.find(",", idx); + std::string substr(param_value, idx, idx1 - idx); + int result = do_one_item(item_type_char, s, substr, item, i); + if (result != 0) + return result; + i++; + idx = idx1 == std::string::npos ? idx1 : idx1 + 1; + } + } else { + int result = do_one_item(item_type_char, s, param_value, item); + if (result != 0) + return result; + } + } + return 0; +} + +static int do_load_cmd(const std::string &name, const std::vector &args) { + void *w = modules[name]; + if (w == NULL) { + std::string what; + what = fmt::format("{}/{}.so", EMC2_RTLIB_DIR, name); + void *module = modules[name] = dlopen(what.c_str(), RTLD_GLOBAL | RTLD_NOW); + if (!module) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlopen: %s\n", name.c_str(), dlerror()); + modules.erase(name); + return -1; + } + /// XXX handle arguments + int (*start)(void) = DLSYM(module, "rtapi_app_main"); + if (!start) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlsym: %s\n", name.c_str(), dlerror()); + dlclose(module); + modules.erase(name); + return -1; + } + if(!DLSYM(module, "rtapi_app_exit")) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: component is missing rtapi_app_exit\n", name.c_str()); + dlclose(module); + modules.erase(name); + return -1; + } + int result; + + result = do_comp_args(module, args); + if (result < 0) { + dlclose(module); + modules.erase(name); + return -1; + } + + if ((result = start()) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: rtapi_app_main: %s (%d)\n", name.c_str(), strerror(-result), result); + dlclose(module); + modules.erase(name); + return result; + } else { + instance_count++; + return 0; + } + } else { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: already exists\n", name.c_str()); + return -1; + } +} + +static int do_unload_cmd(const std::string &name) { + void *w = modules[name]; + if (w == NULL) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: not loaded\n", name.c_str()); + return -1; + } else { + void (*stop)(void) = DLSYM(w, "rtapi_app_exit"); + if (stop) + stop(); + modules.erase(modules.find(name)); + dlclose(w); + instance_count--; + } + return 0; +} + +static int do_debug_cmd(const std::string &value) { + try { + int new_level = stoi(value); + if (new_level < 0 || new_level > 5) { + rtapi_print_msg(RTAPI_MSG_ERR, "Debug level must be >=0 and <= 5\n"); + return -EINVAL; + } + return rtapi_set_msg_level(new_level); + } catch (std::invalid_argument &e) { + //stoi will throw an exception if parsing is not possible + rtapi_print_msg(RTAPI_MSG_ERR, "Debug level is not a number\n"); + return -EINVAL; + } +} + +struct ReadError : std::exception {}; +struct WriteError : std::exception {}; + +static int read_number(int fd) { + int r = 0, neg = 1; + char ch; + + while (1) { + int res = read(fd, &ch, 1); + if (res != 1) + return -1; + if (ch == '-') + neg = -1; + else if (ch == ' ') + return r * neg; + else + r = 10 * r + ch - '0'; + } +} + +static std::string read_string(int fd) { + int len = read_number(fd); + if (len < 0) + throw ReadError(); + if (!len) + return std::string(); + std::string str(len, 0); + if (read(fd, str.data(), len) != len) + throw ReadError(); + return str; +} + +static std::vector read_strings(int fd) { + std::vector result; + int count = read_number(fd); + if (count < 0) + return result; + for (int i = 0; i < count; i++) { + result.push_back(read_string(fd)); + } + return result; +} + +static void write_number(std::string &buf, int num) { + buf = buf + fmt::format("{} ", num); +} + +static void write_string(std::string &buf, const std::string &s) { + write_number(buf, s.size()); + buf += s; +} + +static void write_strings(int fd, const std::vector &strings) { + std::string buf; + write_number(buf, strings.size()); + for (unsigned int i = 0; i < strings.size(); i++) { + write_string(buf, strings[i]); + } + if (write(fd, buf.data(), buf.size()) != (ssize_t)buf.size()) + throw WriteError(); +} + +static int handle_command(std::vector args) { + if (args.size() == 0) { + return 0; + } + if (args.size() == 1 && args[0] == "exit") { + force_exit = 1; + return 0; + } else if (args.size() >= 2 && args[0] == "load") { + std::string name = args[1]; + args.erase(args.begin()); + return do_load_cmd(name, args); + } else if (args.size() == 2 && args[0] == "unload") { + return do_unload_cmd(args[1]); + } else if (args.size() == 3 && args[0] == "newinst") { + return do_newinst_cmd(args[1], args[2], ""); + } else if (args.size() == 4 && args[0] == "newinst") { + return do_newinst_cmd(args[1], args[2], args[3]); + } else if (args.size() == 2 && args[0] == "debug") { + return do_debug_cmd(args[1]); + } else { + rtapi_print_msg(RTAPI_MSG_ERR, "Unrecognized command starting with %s\n", args[0].c_str()); + return -1; + } +} + +static int slave(int fd, const std::vector &args) { + try { + write_strings(fd, args); + } catch (WriteError &e) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to master: %s\n", strerror(errno)); + } + + int result = read_number(fd); + return result; +} + +static int callback(int fd) { + struct sockaddr_un client_addr; + memset(&client_addr, 0, sizeof(client_addr)); + socklen_t len = sizeof(client_addr); + int fd1 = accept(fd, (sockaddr *)&client_addr, &len); + if (fd1 < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to accept connection from slave: %s\n", strerror(errno)); + return -1; + } else { + int result; + try { + result = handle_command(read_strings(fd1)); + } catch (ReadError &e) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from slave: %s\n", strerror(errno)); + close(fd1); + return -1; + } + std::string buf; + write_number(buf, result); + if (write(fd1, buf.data(), buf.size()) != (ssize_t)buf.size()) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to slave: %s\n", strerror(errno)); + }; + close(fd1); + } + return !force_exit && instance_count > 0; +} + +static pthread_t main_thread{}; + +static int master(int fd, const std::vector &args) { + main_thread = pthread_self(); + int result; + if ((result = pthread_create(&queue_thread, nullptr, &queue_function, nullptr)) != 0) { + errno = result; + perror("pthread_create (queue function)"); + return -1; + } + do_load_cmd("hal_lib", std::vector()); + instance_count = 0; + App(); // force rtapi_app to be created + if (args.size()) { + result = handle_command(args); + if (result != 0) + goto out; + if (force_exit || instance_count == 0) + goto out; + } + sim_rtapi_run_threads(fd, callback); +out: + pthread_cancel(queue_thread); + pthread_join(queue_thread, nullptr); + rtapi_msg_queue.consume_all([](const message_t &m) { + fputs(m.msg, m.level == RTAPI_MSG_ALL ? stdout : stderr); + }); + return result; +} + +static std::string get_fifo_path() { + std::string s; + if (getenv("RTAPI_FIFO_PATH")) { + s = getenv("RTAPI_FIFO_PATH"); + } else if (getenv("HOME")) { + s = std::string(getenv("HOME")) + "/.rtapi_fifo"; + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: RTAPI_FIFO_PATH and HOME are unset. rtapi fifo creation is unsafe.\n" + ); + } + return s; +} + +static int get_fifo_path(char *buf, size_t bufsize) { + int len; + const std::string s = get_fifo_path(); + if (s.empty()) { + return -1; + } + if (s.size() + 1 > sizeof(sockaddr_un::sun_path)) { + rtapi_print_msg( + RTAPI_MSG_ERR, + "rtapi_app: rtapi fifo path is too long (arch limit %zd): %s\n", + sizeof(sockaddr_un::sun_path), + s.c_str() + ); + return -1; + } + len = snprintf(buf + 1, bufsize - 1, "%s", s.c_str()); + return len; +} + +int main(int argc, char **argv) { + if (getuid() == 0) { + char *fallback_uid_str = getenv("RTAPI_UID"); + int fallback_uid = fallback_uid_str ? atoi(fallback_uid_str) : 0; + if (fallback_uid == 0) { + // Cppcheck cannot see EMC2_BIN_DIR when RTAPI is defined, but that + // doesn't happen in uspace. + fprintf( + stderr, + "Refusing to run as root without fallback UID specified\n" + "To run under a debugger with I/O, use e.g.,\n" + // cppcheck-suppress unknownMacro + " sudo env RTAPI_UID=`id -u` RTAPI_FIFO_PATH=$HOME/.rtapi_fifo gdb " EMC2_BIN_DIR "/rtapi_app\n" + ); + exit(1); + } + if (setreuid(fallback_uid, 0) != 0) { + perror("setreuid"); + abort(); + } + fprintf(stderr, "Running with fallback_uid. getuid()=%d geteuid()=%d\n", getuid(), geteuid()); + } + ruid = getuid(); + euid = geteuid(); + if (setresuid(euid, euid, ruid) != 0) { + perror("setresuid"); + abort(); + } +#ifdef __linux__ + setfsuid(ruid); +#endif + std::vector args; + for (int i = 1; i < argc; i++) { + args.push_back(std::string(argv[i])); + } + +become_master: + int len = 0; + int fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + perror("socket"); + exit(1); + } + + int enable = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + struct sockaddr_un addr; + memset(&addr, 0x0, sizeof(addr)); + addr.sun_family = AF_UNIX; + if ((len = get_fifo_path(addr.sun_path, sizeof(addr.sun_path))) < 0) + exit(1); + + // plus one because we use the abstract namespace, it will show up in + // /proc/net/unix prefixed with an @ + int result = ::bind(fd, (sockaddr *)&addr, len + sizeof(addr.sun_family) + 1); + + if (result == 0) { + //If called in master mode with exit command, no need to start master + //and exit again + if (args.size() == 1 && args[0] == "exit") { + return 0; + } + int result = listen(fd, 10); + if (result != 0) { + perror("listen"); + exit(1); + } + setsid(); // create a new session if we can... + result = master(fd, args); + return result; + } else if (errno == EADDRINUSE) { + struct timeval t0, t1; + gettimeofday(&t0, NULL); + gettimeofday(&t1, NULL); + for (int i = 0; i < 3 || (t1.tv_sec < 3 + t0.tv_sec); i++) { + result = connect(fd, (sockaddr *)&addr, len + sizeof(addr.sun_family) + 1); + if (result == 0) + break; + if (i == 0) + srand48(t0.tv_sec ^ t0.tv_usec); + usleep(lrand48() % 100000); + gettimeofday(&t1, NULL); + } + if (result < 0 && errno == ECONNREFUSED) { + fprintf(stderr, "Waited 3 seconds for master. giving up.\n"); + close(fd); + goto become_master; + } + if (result < 0) { + fprintf(stderr, "connect %s: %s", addr.sun_path, strerror(errno)); + exit(1); + } + return slave(fd, args); + } else { + perror("bind"); + exit(1); + } +} + + +/* These structs hold data associated with objects like tasks, etc. */ +/* Task handles are pointers to these structs. */ + +struct rtapi_module { + int magic; +}; + +#define MODULE_MAGIC 30812 +#define SHMEM_MAGIC 25453 + +#define MAX_MODULES 64 +#define MODULE_OFFSET 32768 + +static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { + switch (sig) { + case SIGXCPU: + // should not happen - must be handled in RTAPI if enabled + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: BUG: SIGXCPU received - exiting\n"); + exit(0); + break; + + case SIGTERM: + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: SIGTERM - shutting down\n"); + exit(0); + break; + + default: // pretty bad + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: caught signal %d - dumping core\n", sig); + sleep(1); // let syslog drain + signal(sig, SIG_DFL); + // for reasons unknown raise(sig); doesn't lead to core dump file + // but this will + kill(getpid(), sig); + break; + } + exit(1); +} + +const size_t PRE_ALLOC_SIZE = 1024 * 1024 * 32; +const static struct rlimit unlimited = {RLIM_INFINITY, RLIM_INFINITY}; +static void configure_memory() { + int res = setrlimit(RLIMIT_MEMLOCK, &unlimited); + if (res < 0) + perror("setrlimit"); + + res = mlockall(MCL_CURRENT | MCL_FUTURE); + if (res < 0) + perror("mlockall"); + +#ifdef __linux__ + /* Turn off malloc trimming.*/ + if (!mallopt(M_TRIM_THRESHOLD, -1)) { + rtapi_print_msg(RTAPI_MSG_WARN, "mallopt(M_TRIM_THRESHOLD, -1) failed\n"); + } + /* Turn off mmap usage. */ + if (!mallopt(M_MMAP_MAX, 0)) { + rtapi_print_msg(RTAPI_MSG_WARN, "mallopt(M_MMAP_MAX, -1) failed\n"); + } +#endif + /* + * The following code seems pointless, but there is a non-observable effect + * in the allocation and loop. + * + * The malloc() is forced to set brk() because mmap() allocation is + * disabled in a call to mallopt() above. All touched pages become resident + * and locked in the loop because of above mlockall() call (see notes in + * mlockall(2)). The mallopt() trim setting prevents the brk() from being + * reduced after free(), effectively creating an open space for future + * allocations that will not generate page faults. + * + * The qualifier 'volatile' on the buffer pointer is required because newer + * clang would remove the malloc(), for()-loop and free() completely. + * Marking 'buf' volatile ensures that the code will remain in place. + */ + volatile char *buf = static_cast(malloc(PRE_ALLOC_SIZE)); + if (buf == NULL) { + rtapi_print_msg(RTAPI_MSG_WARN, "malloc(PRE_ALLOC_SIZE) failed\n"); + return; + } + long pagesize = sysconf(_SC_PAGESIZE); + /* Touch each page in this piece of memory to get it mapped into RAM */ + for (size_t i = 0; i < PRE_ALLOC_SIZE; i += pagesize) { + /* Each write to this buffer will generate a pagefault. + * Once the pagefault is handled a page will be locked in + * memory and never given back to the system. */ + buf[i] = 0; + } + free((void *)buf); +} + +static int harden_rt() { + if (!rtapi_is_realtime()) + return -EINVAL; + + WITH_ROOT; +#if defined(__linux__) && (defined(__x86_64__) || defined(__i386__)) + if (iopl(3) < 0) { + rtapi_print_msg( + RTAPI_MSG_ERR, + "iopl() failed: %s\n" + "cannot gain I/O privileges - " + "forgot 'sudo make setuid' or using secure boot? -" + "parallel port access is not allowed\n", + strerror(errno) + ); + } +#endif + + struct sigaction sig_act = {}; +#ifdef __linux__ + // enable realtime + if (setrlimit(RLIMIT_RTPRIO, &unlimited) < 0) { + rtapi_print_msg(RTAPI_MSG_WARN, "setrlimit(RTLIMIT_RTPRIO): %s\n", strerror(errno)); + return -errno; + } + + // enable core dumps + if (setrlimit(RLIMIT_CORE, &unlimited) < 0) + rtapi_print_msg( + RTAPI_MSG_WARN, "setrlimit: %s - core dumps may be truncated or non-existent\n", strerror(errno) + ); + + // even when setuid root + if (prctl(PR_SET_DUMPABLE, 1) < 0) + rtapi_print_msg( + RTAPI_MSG_WARN, + "prctl(PR_SET_DUMPABLE) failed: no core dumps will be created - %d - %s\n", + errno, + strerror(errno) + ); +#endif /* __linux__ */ + + configure_memory(); + + sigemptyset(&sig_act.sa_mask); + sig_act.sa_handler = SIG_IGN; + sig_act.sa_sigaction = NULL; + + // prevent stopping of RT threads by ^Z + sigaction(SIGTSTP, &sig_act, (struct sigaction *)NULL); + + sig_act.sa_sigaction = signal_handler; + sig_act.sa_flags = SA_SIGINFO; + + sigaction(SIGSEGV, &sig_act, (struct sigaction *)NULL); + sigaction(SIGILL, &sig_act, (struct sigaction *)NULL); + sigaction(SIGFPE, &sig_act, (struct sigaction *)NULL); + sigaction(SIGTERM, &sig_act, (struct sigaction *)NULL); + sigaction(SIGINT, &sig_act, (struct sigaction *)NULL); + +#ifdef __linux__ + int fd = open("/dev/cpu_dma_latency", O_WRONLY | O_CLOEXEC); + if (fd < 0) { + rtapi_print_msg(RTAPI_MSG_WARN, "failed to open /dev/cpu_dma_latency: %s\n", strerror(errno)); + } else { + int r; + r = write(fd, "\0\0\0\0", 4); + if (r != 4) { + rtapi_print_msg(RTAPI_MSG_WARN, "failed to write to /dev/cpu_dma_latency: %s\n", strerror(errno)); + } + // deliberately leak fd until program exit + } +#endif /* __linux__ */ + return 0; +} + +static RtapiApp *makeDllApp(std::string dllName, int policy) { + void *dll = nullptr; + dll = dlopen(dllName.c_str(), RTLD_NOW); + if (!dll) { + fprintf(stderr, "dlopen: %s\n", dlerror()); + return nullptr; + } + auto fn = reinterpret_cast(dlsym(dll, "make")); + if (!fn) { + fprintf(stderr, "dlsym: %s\n", dlerror()); + return nullptr; + } + auto result = fn(policy); + if (!result) { + fprintf(stderr, "dlsym: %s\n", dlerror()); + return nullptr; + } + return result; +} + +static RtapiApp *makeApp() { + RtapiApp *app; + if (euid != 0 || harden_rt() < 0) { + app = makeDllApp(EMC2_HOME "/lib/libuspace-posix.so.0", SCHED_OTHER); + } else { + WithRoot r; + if (detect_xenomai_evl()) { + app = makeDllApp(EMC2_HOME "/lib/libuspace-xenomai-evl.so.0", SCHED_FIFO); + } else if (detect_xenomai()) { + app = makeDllApp(EMC2_HOME "/lib/libuspace-xenomai.so.0", SCHED_FIFO); + } else if (detect_rtai()) { + app = makeDllApp(EMC2_HOME "/lib/libuspace-rtai.so.0", SCHED_FIFO); + } else { + app = makeDllApp(EMC2_HOME "/lib/libuspace-posix.so.0", SCHED_FIFO); + } + } + + if (!app) { + throw std::invalid_argument("Could not load rtapi dll"); + } else { + return app; + } +} +RtapiApp &App() { + static RtapiApp *app = makeApp(); + return *app; +} + +/* data for all tasks */ +struct rtapi_task *task_array[MAX_TASKS]; + +int rtapi_prio_highest(void) { + return App().prio_highest(); +} + +int rtapi_prio_lowest(void) { + return App().prio_lowest(); +} + +int rtapi_prio_next_higher(int prio) { + return App().prio_next_higher(prio); +} + +int rtapi_prio_next_lower(int prio) { + return App().prio_next_lower(prio); +} + +long rtapi_clock_set_period(long nsecs) { + return App().clock_set_period(nsecs); +} + +int rtapi_task_new(void (*taskcode)(void *), void *arg, int prio, int owner, unsigned long int stacksize, int uses_fp) { + return App().task_new(taskcode, arg, prio, owner, stacksize, uses_fp); +} + +int rtapi_task_delete(int id) { + return App().task_delete(id); +} + +int rtapi_task_start(int task_id, unsigned long period_nsec) { + int ret = App().task_start(task_id, period_nsec); + if (ret != 0) { + errno = -ret; + perror("rtapi_task_start()"); + } + return ret; +} + +int rtapi_task_pause(int task_id) { + return App().task_pause(task_id); +} + +int rtapi_task_resume(int task_id) { + return App().task_resume(task_id); +} + +int rtapi_task_self() { + return App().task_self(); +} + +long long rtapi_task_pll_get_reference(void) { + return App().task_pll_get_reference(); +} + +int rtapi_task_pll_set_correction(long value) { + return App().task_pll_set_correction(value); +} + +void rtapi_wait(void) { + App().wait(); +} + +void rtapi_outb(unsigned char byte, unsigned int port) { + App().do_outb(byte, port); +} + +unsigned char rtapi_inb(unsigned int port) { + return App().do_inb(port); +} + +long int simple_strtol(const char *nptr, char **endptr, int base) { + return strtol(nptr, endptr, base); +} + +int sim_rtapi_run_threads(int fd, int (*callback)(int fd)) { + return App().run_threads(fd, callback); +} + +long long rtapi_get_time() { + return App().do_get_time(); +} + +void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { + if (main_thread && pthread_self() != main_thread) { + message_t m; + m.level = level; + vsnprintf(m.msg, sizeof(m.msg), fmt, ap); + rtapi_msg_queue.push(m); + } else { + vfprintf(level == RTAPI_MSG_ALL ? stdout : stderr, fmt, ap); + } +} + +long int rtapi_delay_max() { + return 10000; +} + +void rtapi_delay(long ns) { + if (ns > rtapi_delay_max()) + ns = rtapi_delay_max(); + App().do_delay(ns); +} + +const unsigned long ONE_SEC_IN_NS = 1000000000; +void rtapi_timespec_advance(struct timespec &result, const struct timespec &src, unsigned long nsec) { + time_t sec = src.tv_sec; + while (nsec >= ONE_SEC_IN_NS) { + ++sec; + nsec -= ONE_SEC_IN_NS; + } + nsec += src.tv_nsec; + if (nsec >= ONE_SEC_IN_NS) { + ++sec; + nsec -= ONE_SEC_IN_NS; + } + result.tv_sec = sec; + result.tv_nsec = nsec; +} + +int rtapi_open_as_root(const char *filename, int mode) { + WITH_ROOT; + int r = open(filename, mode); + if (r < 0) + return -errno; + return r; +} + +int rtapi_spawn_as_root( + pid_t *pid, + const char *path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], + char *const envp[] +) { + return posix_spawn(pid, path, file_actions, attrp, argv, envp); +} + +int rtapi_spawnp_as_root( + pid_t *pid, + const char *path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], + char *const envp[] +) { + return posix_spawnp(pid, path, file_actions, attrp, argv, envp); +} diff --git a/src/rtapi/uspace_rtapi_parport.cc b/src/rtapi/uspace_rtapi_parport.cc index f396837c71f..f208181fa97 100644 --- a/src/rtapi/uspace_rtapi_parport.cc +++ b/src/rtapi/uspace_rtapi_parport.cc @@ -19,7 +19,7 @@ #include #include #include -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #include #include #include diff --git a/src/rtapi/uspace_xenomai.cc b/src/rtapi/uspace_xenomai.cc index c3c6e432ea5..0b9ccd8540c 100644 --- a/src/rtapi/uspace_xenomai.cc +++ b/src/rtapi/uspace_xenomai.cc @@ -1,18 +1,35 @@ +/* Copyright (C) 2016 Jeff Epler + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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-1301 USA. + */ #include "config.h" #include "rtapi.h" -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #include -#include +#include #include #include +#include #ifdef HAVE_SYS_IO_H #include #endif -namespace -{ -struct RtaiTask : rtapi_task { - RtaiTask() : rtapi_task{}, cancel{}, thr{} {} +namespace { +struct XenomaiTask : rtapi_task { + XenomaiTask() : rtapi_task{}, cancel{}, thr{} { + } std::atomic_int cancel; pthread_t thr; }; @@ -23,13 +40,14 @@ struct XenomaiApp : RtapiApp { pthread_once(&key_once, init_key); } - RtaiTask *do_task_new() { - return new RtaiTask; + struct rtapi_task *do_task_new() { + return new XenomaiTask; } int task_delete(int id) { - auto task = ::rtapi_get_task(id); - if(!task) return -EINVAL; + auto task = ::rtapi_get_task(id); + if (!task) + return -EINVAL; task->cancel = 1; pthread_join(task->thr, nullptr); @@ -40,8 +58,9 @@ struct XenomaiApp : RtapiApp { } int task_start(int task_id, unsigned long period_nsec) { - auto task = ::rtapi_get_task(task_id); - if(!task) return -EINVAL; + auto task = ::rtapi_get_task(task_id); + if (!task) + return -EINVAL; task->period = period_nsec; struct sched_param param; @@ -52,39 +71,39 @@ struct XenomaiApp : RtapiApp { task->pll_correction_limit = period_nsec / 100; task->pll_correction = 0; - int nprocs = sysconf( _SC_NPROCESSORS_ONLN ); + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - int ret; pthread_attr_t attr; - if((ret = pthread_attr_init(&attr)) != 0) + int ret; + if ((ret = pthread_attr_init(&attr)) != 0) return -ret; - if((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) + if ((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) return -ret; - if((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) + if ((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) return -ret; - if((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) + if ((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) return -ret; - if((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if(nprocs > 1){ + if (nprocs > 1) { const static int rt_cpu_number = find_rt_cpu_number(); rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if(rt_cpu_number != -1) { + if (rt_cpu_number != -1) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(rt_cpu_number, &cpuset); - if((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) return -ret; } } - if((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) + if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) return -ret; return 0; } static void *wrapper(void *arg) { - auto task = reinterpret_cast(arg); + auto task = reinterpret_cast(arg); pthread_setspecific(key, arg); struct timespec now; @@ -97,7 +116,7 @@ struct XenomaiApp : RtapiApp { // encountered on: 3.18.20-xenomai-2.6.5 with a 2-thread SMP system rtapi_timespec_advance(task->nextstart, now, task->period + task->pll_correction); - (task->taskcode) (task->arg); + (task->taskcode)(task->arg); rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); return nullptr; @@ -114,38 +133,40 @@ struct XenomaiApp : RtapiApp { } long long task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return 0; + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return 0; return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; } int task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; - if (value > task->pll_correction_limit) value = task->pll_correction_limit; - if (value < -(task->pll_correction_limit)) value = -(task->pll_correction_limit); + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; + if (value > task->pll_correction_limit) + value = task->pll_correction_limit; + if (value < -(task->pll_correction_limit)) + value = -(task->pll_correction_limit); task->pll_correction = value; return 0; } void wait() { int task_id = task_self(); - auto task = ::rtapi_get_task(task_id); - if(task->cancel) { + auto task = ::rtapi_get_task(task_id); + if (task->cancel) { pthread_exit(nullptr); } rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - if(rtapi_timespec_less(task->nextstart, now)) - { - if(policy == SCHED_FIFO) + if (rtapi_timespec_less(task->nextstart, now)) { + if (policy == SCHED_FIFO) unexpected_realtime_delay(task); - } - else - { + } else { int res = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &task->nextstart, nullptr); - if(res < 0) perror("clock_nanosleep"); + if (res < 0) + perror("clock_nanosleep"); } } @@ -153,6 +174,7 @@ struct XenomaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return inb(port); #else + (void)port; return 0; #endif } @@ -161,18 +183,22 @@ struct XenomaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return outb(val, port); #else - return 0; + (void)val; + (void)port; #endif } int run_threads(int fd, int (*callback)(int fd)) { - while(callback(fd)) { /* nothing */ } + while (callback(fd)) { + /* nothing */ + } return 0; } int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; return task->id; } @@ -196,11 +222,14 @@ struct XenomaiApp : RtapiApp { pthread_once_t XenomaiApp::key_once; pthread_key_t XenomaiApp::key; -} +} // namespace -extern "C" RtapiApp *make(); +extern "C" RtapiApp *make(int policy); -RtapiApp *make() { +RtapiApp *make(int policy) { + if (policy != SCHED_FIFO) { + throw std::invalid_argument("Only SCHED_FIFO allowed"); + } rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI (posix-skin) realtime\n"); - return new XenomaiApp; + return new XenomaiApp(); } diff --git a/src/rtapi/uspace_xenomai_evl.cc b/src/rtapi/uspace_xenomai_evl.cc index b4f65f2f4c2..3482a3d3ee3 100644 --- a/src/rtapi/uspace_xenomai_evl.cc +++ b/src/rtapi/uspace_xenomai_evl.cc @@ -1,6 +1,25 @@ +/* Copyright (C) 2016 Jeff Epler + * Copyright (C) 2026 Hannes Diethelm + * Copy uspace_xenomai.cc and adapted to Xenomai4 EVL + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program 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-1301 USA. + */ + #include "config.h" #include "rtapi.h" -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #include @@ -13,31 +32,33 @@ #include #include #include +#include #ifdef HAVE_SYS_IO_H #include #endif -namespace -{ -struct RtaiTask : rtapi_task { - RtaiTask() : rtapi_task{}, cancel{}, thr{} {} +namespace { +struct EvlTask : rtapi_task { + EvlTask() : rtapi_task{}, cancel{}, thr{} { + } std::atomic_int cancel; pthread_t thr; }; -struct XenomaiApp : RtapiApp { - XenomaiApp() : RtapiApp(SCHED_FIFO) { +struct EvlApp : RtapiApp { + EvlApp() : RtapiApp(SCHED_FIFO) { pthread_once(&key_once, init_key); } - RtaiTask *do_task_new() { - return new RtaiTask; + struct rtapi_task *do_task_new() { + return new EvlTask; } int task_delete(int id) { - auto task = ::rtapi_get_task(id); - if(!task) return -EINVAL; + auto task = ::rtapi_get_task(id); + if (!task) + return -EINVAL; task->cancel = 1; pthread_join(task->thr, nullptr); @@ -48,8 +69,9 @@ struct XenomaiApp : RtapiApp { } int task_start(int task_id, unsigned long period_nsec) { - auto task = ::rtapi_get_task(task_id); - if(!task) return -EINVAL; + auto task = ::rtapi_get_task(task_id); + if (!task) + return -EINVAL; task->period = period_nsec; struct sched_param param; @@ -60,39 +82,39 @@ struct XenomaiApp : RtapiApp { task->pll_correction_limit = period_nsec / 100; task->pll_correction = 0; - int nprocs = sysconf( _SC_NPROCESSORS_ONLN ); + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - int ret; pthread_attr_t attr; - if((ret = pthread_attr_init(&attr)) != 0) + int ret; + if ((ret = pthread_attr_init(&attr)) != 0) return -ret; - if((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) + if ((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) return -ret; - if((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) + if ((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) return -ret; - if((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) + if ((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) return -ret; - if((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if(nprocs > 1){ + if (nprocs > 1) { const static int rt_cpu_number = find_rt_cpu_number(); rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if(rt_cpu_number != -1) { + if (rt_cpu_number != -1) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(rt_cpu_number, &cpuset); - if((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) return -ret; } } - if((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) + if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) return -ret; return 0; } static void *wrapper(void *arg) { - auto task = reinterpret_cast(arg); + auto task = reinterpret_cast(arg); pthread_setspecific(key, arg); { @@ -100,7 +122,7 @@ struct XenomaiApp : RtapiApp { /* Attach to the core. */ rtapi_print("linuxcnc-task:%d\n", gettid()); int tfd = evl_attach_self("linuxcnc-thread:%d", gettid()); - if (tfd < 0){ + if (tfd < 0) { rtapi_print("evl_attach_self() failed ret %i errno %i\n", tfd, errno); } } @@ -115,7 +137,7 @@ struct XenomaiApp : RtapiApp { // encountered on: 3.18.20-xenomai-2.6.5 with a 2-thread SMP system rtapi_timespec_advance(task->nextstart, now, task->period + task->pll_correction); - (task->taskcode) (task->arg); + (task->taskcode)(task->arg); rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); return nullptr; @@ -132,38 +154,40 @@ struct XenomaiApp : RtapiApp { } long long task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return 0; + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return 0; return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; } int task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; - if (value > task->pll_correction_limit) value = task->pll_correction_limit; - if (value < -(task->pll_correction_limit)) value = -(task->pll_correction_limit); + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; + if (value > task->pll_correction_limit) + value = task->pll_correction_limit; + if (value < -(task->pll_correction_limit)) + value = -(task->pll_correction_limit); task->pll_correction = value; return 0; } void wait() { int task_id = task_self(); - auto task = ::rtapi_get_task(task_id); - if(task->cancel) { + auto task = ::rtapi_get_task(task_id); + if (task->cancel) { pthread_exit(nullptr); } rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); struct timespec now; evl_read_clock(EVL_CLOCK_MONOTONIC, &now); - if(rtapi_timespec_less(task->nextstart, now)) - { - if(policy == SCHED_FIFO) + if (rtapi_timespec_less(task->nextstart, now)) { + if (policy == SCHED_FIFO) unexpected_realtime_delay(task); - } - else - { + } else { int res = evl_sleep_until(EVL_CLOCK_MONOTONIC, &task->nextstart); - if(res < 0) perror("evl_sleep_until"); + if (res < 0) + perror("evl_sleep_until"); } } @@ -171,6 +195,7 @@ struct XenomaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return inb(port); #else + (void)port; return 0; #endif } @@ -179,18 +204,22 @@ struct XenomaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return outb(val, port); #else - return 0; + (void)val; + (void)port; #endif } int run_threads(int fd, int (*callback)(int fd)) { - while(callback(fd)) { /* nothing */ } + while (callback(fd)) { + /* nothing */ + } return 0; } int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; + struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; return task->id; } @@ -214,13 +243,16 @@ struct XenomaiApp : RtapiApp { } }; -pthread_once_t XenomaiApp::key_once; -pthread_key_t XenomaiApp::key; -} +pthread_once_t EvlApp::key_once; +pthread_key_t EvlApp::key; +} // namespace -extern "C" RtapiApp *make(); +extern "C" RtapiApp *make(int policy); -RtapiApp *make() { +RtapiApp *make(int policy) { + if (policy != SCHED_FIFO) { + throw std::invalid_argument("Only SCHED_FIFO allowed"); + } rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI4 EVL realtime\n"); - return new XenomaiApp; + return new EvlApp(); } From 15dba0c5ae6b10fd5ef24ba960be287e4d3b0666 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Thu, 9 Apr 2026 21:06:49 +0200 Subject: [PATCH 02/43] Cleanup: Library doesn't need a hardcoded path --- src/rtapi/uspace_rtapi_main.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index f796f0a6110..1015cc8b15b 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -775,17 +775,17 @@ static RtapiApp *makeDllApp(std::string dllName, int policy) { static RtapiApp *makeApp() { RtapiApp *app; if (euid != 0 || harden_rt() < 0) { - app = makeDllApp(EMC2_HOME "/lib/libuspace-posix.so.0", SCHED_OTHER); + app = makeDllApp("libuspace-posix.so.0", SCHED_OTHER); } else { WithRoot r; if (detect_xenomai_evl()) { - app = makeDllApp(EMC2_HOME "/lib/libuspace-xenomai-evl.so.0", SCHED_FIFO); + app = makeDllApp("libuspace-xenomai-evl.so.0", SCHED_FIFO); } else if (detect_xenomai()) { - app = makeDllApp(EMC2_HOME "/lib/libuspace-xenomai.so.0", SCHED_FIFO); + app = makeDllApp("libuspace-xenomai.so.0", SCHED_FIFO); } else if (detect_rtai()) { - app = makeDllApp(EMC2_HOME "/lib/libuspace-rtai.so.0", SCHED_FIFO); + app = makeDllApp("libuspace-rtai.so.0", SCHED_FIFO); } else { - app = makeDllApp(EMC2_HOME "/lib/libuspace-posix.so.0", SCHED_FIFO); + app = makeDllApp("libuspace-posix.so.0", SCHED_FIFO); } } From c9664ce3d5e578359ce61a2c50f07a277e6ea824 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 11 Apr 2026 11:02:32 +0200 Subject: [PATCH 03/43] Cleanup: Get rid of global task_array and add static where possible --- src/rtapi/uspace_rtapi_app.cc | 2 ++ src/rtapi/uspace_rtapi_app.hh | 11 +++++------ src/rtapi/uspace_rtapi_main.cc | 15 ++++++--------- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index 78aa962b596..2612aac3681 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -48,6 +48,8 @@ rtapi_task::rtapi_task() { } +struct rtapi_task *RtapiApp::task_array[MAX_TASKS]; + /* Priority functions. Uspace uses POSIX task priorities. */ int RtapiApp::prio_highest() const { diff --git a/src/rtapi/uspace_rtapi_app.hh b/src/rtapi/uspace_rtapi_app.hh index 880b2937781..209a5a954a2 100644 --- a/src/rtapi/uspace_rtapi_app.hh +++ b/src/rtapi/uspace_rtapi_app.hh @@ -66,6 +66,10 @@ struct rtapi_task { void (*taskcode)(void *); /* pointer to task function */ }; +#define MAX_TASKS 64 +#define TASK_MAGIC 21979 /* random numbers used as signatures */ +#define TASK_MAGIC_INIT ((rtapi_task *)(-1)) + struct RtapiApp { RtapiApp(int policy = SCHED_OTHER) : policy(policy), period(0) { @@ -99,6 +103,7 @@ struct RtapiApp { virtual void do_delay(long ns) = 0; int policy; long period; + static struct rtapi_task *task_array[MAX_TASKS]; }; template T *rtapi_get_task(int task_id) { @@ -108,12 +113,6 @@ template T *rtapi_get_task(int task_id) { int find_rt_cpu_number(); void set_namef(const char *fmt, ...); -#define MAX_TASKS 64 -#define TASK_MAGIC 21979 /* random numbers used as signatures */ -#define TASK_MAGIC_INIT ((rtapi_task *)(-1)) - -extern struct rtapi_task *task_array[MAX_TASKS]; - extern uid_t euid, ruid; //ToDo: Improve #define WITH_ROOT WithRoot root diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 1015cc8b15b..a6437a96adb 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -59,17 +59,17 @@ #include "hal/hal_priv.h" #include "uspace_common.h" -RtapiApp &App(); +static RtapiApp &App(); struct message_t { msg_level_t level; char msg[1024 - sizeof(level)]; }; -boost::lockfree::queue> rtapi_msg_queue; +static boost::lockfree::queue> rtapi_msg_queue; -pthread_t queue_thread; -void *queue_function(void * /*arg*/) { +static pthread_t queue_thread; +static void *queue_function(void * /*arg*/) { set_namef("rtapi_app:mesg"); // note: can't use anything in this function that requires App() to exist // but it's OK to use functions that aren't safe for realtime (that's the @@ -163,7 +163,7 @@ static int do_one_item( return 0; } -void remove_quotes(std::string &s) { +static void remove_quotes(std::string &s) { s.erase(remove_copy(s.begin(), s.end(), s.begin(), '"'), s.end()); } @@ -624,7 +624,7 @@ static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { exit(1); } -const size_t PRE_ALLOC_SIZE = 1024 * 1024 * 32; +const static size_t PRE_ALLOC_SIZE = 1024 * 1024 * 32; const static struct rlimit unlimited = {RLIM_INFINITY, RLIM_INFINITY}; static void configure_memory() { int res = setrlimit(RLIMIT_MEMLOCK, &unlimited); @@ -800,9 +800,6 @@ RtapiApp &App() { return *app; } -/* data for all tasks */ -struct rtapi_task *task_array[MAX_TASKS]; - int rtapi_prio_highest(void) { return App().prio_highest(); } From 176b63ae6c16427c7af537ff2926c1611bff1e95 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 11 Apr 2026 11:36:10 +0200 Subject: [PATCH 04/43] Cleanup: Get rid of globals ruid/euid --- src/rtapi/uspace_common.h | 2 +- src/rtapi/uspace_rtapi_app.cc | 7 ++++++- src/rtapi/uspace_rtapi_app.hh | 11 +++++++++-- src/rtapi/uspace_rtapi_main.cc | 7 ++++--- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index ffc7376b9a9..5b625ff03bc 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -116,7 +116,7 @@ int rtapi_shmem_new(int key, int module_id, unsigned long int size) */ /* ensure the segment is owned by user, not root */ if(geteuid() == 0) { - stat.shm_perm.uid = ruid; + stat.shm_perm.uid = WithRoot::getRuid(); res = shmctl(shmem->id, IPC_SET, &stat); if(res < 0) perror("shmctl IPC_SET"); } diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index 2612aac3681..f04aee76fb3 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -23,7 +23,7 @@ #include std::atomic_int WithRoot::level; -uid_t euid, ruid; +uid_t WithRoot::ruid, WithRoot::euid; WithRoot::WithRoot() { if (!level++) { @@ -41,6 +41,11 @@ WithRoot::~WithRoot() { } } +void WithRoot::init(uid_t ruid_ini, uid_t euid_ini){ + ruid=ruid_ini; + euid=euid_ini; +} + rtapi_task::rtapi_task() : magic{}, id{}, owner{}, uses_fp{}, stacksize{}, prio{}, period{}, nextstart{}, pll_correction{}, pll_correction_limit{}, arg{}, taskcode{} diff --git a/src/rtapi/uspace_rtapi_app.hh b/src/rtapi/uspace_rtapi_app.hh index 209a5a954a2..3c503f14f5e 100644 --- a/src/rtapi/uspace_rtapi_app.hh +++ b/src/rtapi/uspace_rtapi_app.hh @@ -46,7 +46,16 @@ void rtapi_timespec_advance(struct timespec &result, const struct timespec &src, struct WithRoot { WithRoot(); ~WithRoot(); + static void init(uid_t ruid_ini, uid_t euid_ini); + static uid_t getRuid(){ + return ruid; + } + static uid_t getEuid(){ + return euid; + } +private: static std::atomic_int level; + static uid_t ruid, euid; }; struct rtapi_task { @@ -113,7 +122,5 @@ template T *rtapi_get_task(int task_id) { int find_rt_cpu_number(); void set_namef(const char *fmt, ...); -extern uid_t euid, ruid; //ToDo: Improve - #define WITH_ROOT WithRoot root #endif diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index a6437a96adb..83eec0b8f21 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -508,8 +508,9 @@ int main(int argc, char **argv) { } fprintf(stderr, "Running with fallback_uid. getuid()=%d geteuid()=%d\n", getuid(), geteuid()); } - ruid = getuid(); - euid = geteuid(); + uid_t ruid = getuid(); + uid_t euid = geteuid(); + WithRoot::init(ruid, euid); if (setresuid(euid, euid, ruid) != 0) { perror("setresuid"); abort(); @@ -774,7 +775,7 @@ static RtapiApp *makeDllApp(std::string dllName, int policy) { static RtapiApp *makeApp() { RtapiApp *app; - if (euid != 0 || harden_rt() < 0) { + if (WithRoot::getEuid() != 0 || harden_rt() < 0) { app = makeDllApp("libuspace-posix.so.0", SCHED_OTHER); } else { WithRoot r; From 2dc87940191e00473d7b87d589ec511ea5614d3d Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 11 Apr 2026 12:10:16 +0200 Subject: [PATCH 05/43] Cleanup: Rename libs From libuspace-* to liblinuxcnc-uspace-* --- src/rtapi/Submakefile | 24 ++++++++++++------------ src/rtapi/uspace_rtapi_main.cc | 10 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/rtapi/Submakefile b/src/rtapi/Submakefile index 883f9486fc8..531536aa8f1 100644 --- a/src/rtapi/Submakefile +++ b/src/rtapi/Submakefile @@ -51,41 +51,41 @@ endif USPACE_POSIX_SRCS := rtapi/uspace_posix.cc USERSRCS += $(USPACE_POSIX_SRCS) $(call TOOBJSDEPS, $(USPACE_POSIX_SRCS)): EXTRAFLAGS += -pthread -fPIC -../lib/libuspace-posix.so.0: $(call TOOBJS, $(USPACE_POSIX_SRCS)) +../lib/liblinuxcnc-uspace-posix.so.0: $(call TOOBJS, $(USPACE_POSIX_SRCS)) $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ -Wl,-soname,$(notdir $@) -TARGETS += ../lib/libuspace-posix.so.0 -TARGETS += ../lib/libuspace-posix.so +TARGETS += ../lib/liblinuxcnc-uspace-posix.so.0 +TARGETS += ../lib/liblinuxcnc-uspace-posix.so ifeq ($(CONFIG_USPACE_RTAI),y) USPACE_RTAI_SRCS := rtapi/uspace_rtai.cc USERSRCS += $(USPACE_RTAI_SRCS) $(call TOOBJSDEPS, $(USPACE_RTAI_SRCS)): EXTRAFLAGS += -pthread -fPIC $(filter-out -Wstrict-prototypes,$(RTAI_LXRT_CFLAGS)) -../lib/libuspace-rtai.so.0: $(call TOOBJS, $(USPACE_RTAI_SRCS)) +../lib/liblinuxcnc-uspace-rtai.so.0: $(call TOOBJS, $(USPACE_RTAI_SRCS)) $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ $(RTAI_LXRT_LDFLAGS) -Wl,-soname,$(notdir $@) -TARGETS += ../lib/libuspace-rtai.so.0 -TARGETS += ../lib/libuspace-rtai.so +TARGETS += ../lib/liblinuxcnc-uspace-rtai.so.0 +TARGETS += ../lib/liblinuxcnc-uspace-rtai.so endif ifeq ($(CONFIG_USPACE_XENOMAI),y) USPACE_XENOMAI_SRCS := rtapi/uspace_xenomai.cc USERSRCS += $(USPACE_XENOMAI_SRCS) $(call TOOBJSDEPS, $(USPACE_XENOMAI_SRCS)): EXTRAFLAGS += -fPIC $(XENOMAI_CFLAGS) -../lib/libuspace-xenomai.so.0: $(call TOOBJS, $(USPACE_XENOMAI_SRCS)) +../lib/liblinuxcnc-uspace-xenomai.so.0: $(call TOOBJS, $(USPACE_XENOMAI_SRCS)) $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ $(XENOMAI_LDFLAGS) -Wl,-soname,$(notdir $@) -TARGETS += ../lib/libuspace-xenomai.so.0 -TARGETS += ../lib/libuspace-xenomai.so +TARGETS += ../lib/liblinuxcnc-uspace-xenomai.so.0 +TARGETS += ../lib/liblinuxcnc-uspace-xenomai.so endif ifeq ($(CONFIG_USPACE_XENOMAI_EVL),y) USPACE_XENOMAI_EVL_SRCS := rtapi/uspace_xenomai_evl.cc USERSRCS += $(USPACE_XENOMAI_EVL_SRCS) $(call TOOBJSDEPS, $(USPACE_XENOMAI_EVL_SRCS)): EXTRAFLAGS += -fPIC $(XENOMAI_EVL_CFLAGS) -../lib/libuspace-xenomai-evl.so.0: $(call TOOBJS, $(USPACE_XENOMAI_EVL_SRCS)) +../lib/liblinuxcnc-uspace-xenomai-evl.so.0: $(call TOOBJS, $(USPACE_XENOMAI_EVL_SRCS)) $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ $(XENOMAI_EVL_LDFLAGS) -Wl,-soname,$(notdir $@) -TARGETS += ../lib/libuspace-xenomai-evl.so.0 -TARGETS += ../lib/libuspace-xenomai-evl.so +TARGETS += ../lib/liblinuxcnc-uspace-xenomai-evl.so.0 +TARGETS += ../lib/liblinuxcnc-uspace-xenomai-evl.so endif diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 83eec0b8f21..4a394e08905 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -776,17 +776,17 @@ static RtapiApp *makeDllApp(std::string dllName, int policy) { static RtapiApp *makeApp() { RtapiApp *app; if (WithRoot::getEuid() != 0 || harden_rt() < 0) { - app = makeDllApp("libuspace-posix.so.0", SCHED_OTHER); + app = makeDllApp("liblinuxcnc-uspace-posix.so.0", SCHED_OTHER); } else { WithRoot r; if (detect_xenomai_evl()) { - app = makeDllApp("libuspace-xenomai-evl.so.0", SCHED_FIFO); + app = makeDllApp("liblinuxcnc-uspace-xenomai-evl.so.0", SCHED_FIFO); } else if (detect_xenomai()) { - app = makeDllApp("libuspace-xenomai.so.0", SCHED_FIFO); + app = makeDllApp("liblinuxcnc-uspace-xenomai.so.0", SCHED_FIFO); } else if (detect_rtai()) { - app = makeDllApp("libuspace-rtai.so.0", SCHED_FIFO); + app = makeDllApp("liblinuxcnc-uspace-rtai.so.0", SCHED_FIFO); } else { - app = makeDllApp("libuspace-posix.so.0", SCHED_FIFO); + app = makeDllApp("liblinuxcnc-uspace-posix.so.0", SCHED_FIFO); } } From 7442887e4732b1b6a6d4becb2b8a07108c5c5985 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 11 Apr 2026 12:41:44 +0200 Subject: [PATCH 06/43] Cleanup: Get rid of globals find_rt_cpu_number / set_namef --- src/rtapi/uspace_rtapi_app.cc | 4 ++-- src/rtapi/uspace_rtapi_app.hh | 5 ++--- src/rtapi/uspace_rtapi_main.cc | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index f04aee76fb3..d9e94b1637b 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -234,7 +234,7 @@ static void parse_cpu_list(const char *path, cpu_set_t *cpuset) { fclose(fp); } -int find_rt_cpu_number() { +int RtapiApp::find_rt_cpu_number() { if (getenv("RTAPI_CPU_NUMBER")) return atoi(getenv("RTAPI_CPU_NUMBER")); @@ -269,7 +269,7 @@ int find_rt_cpu_number() { #endif } -void set_namef(const char *fmt, ...) { +void RtapiApp::set_namef(const char *fmt, ...) { char *buf = NULL; va_list ap; diff --git a/src/rtapi/uspace_rtapi_app.hh b/src/rtapi/uspace_rtapi_app.hh index 3c503f14f5e..a464a9b7984 100644 --- a/src/rtapi/uspace_rtapi_app.hh +++ b/src/rtapi/uspace_rtapi_app.hh @@ -110,6 +110,8 @@ struct RtapiApp { virtual int run_threads(int fd, int (*callback)(int fd)) = 0; virtual long long do_get_time(void) = 0; virtual void do_delay(long ns) = 0; + static int find_rt_cpu_number(); + static void set_namef(const char *fmt, ...); int policy; long period; static struct rtapi_task *task_array[MAX_TASKS]; @@ -119,8 +121,5 @@ template T *rtapi_get_task(int task_id) { return static_cast(RtapiApp::get_task(task_id)); } -int find_rt_cpu_number(); -void set_namef(const char *fmt, ...); - #define WITH_ROOT WithRoot root #endif diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 4a394e08905..6ecec7c489d 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -70,7 +70,7 @@ static boost::lockfree::queue> rtapi_m static pthread_t queue_thread; static void *queue_function(void * /*arg*/) { - set_namef("rtapi_app:mesg"); + RtapiApp::set_namef("rtapi_app:mesg"); // note: can't use anything in this function that requires App() to exist // but it's OK to use functions that aren't safe for realtime (that's the // point of running this in a thread) From b407ddea1ef6b31a8ca248244d2485828f01b073 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sun, 12 Apr 2026 22:22:20 +0200 Subject: [PATCH 07/43] Cleanup: Review: const std::string & --- src/rtapi/uspace_rtapi_main.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 6ecec7c489d..2fc83a8cec7 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -753,7 +753,7 @@ static int harden_rt() { return 0; } -static RtapiApp *makeDllApp(std::string dllName, int policy) { +static RtapiApp *makeDllApp(const std::string &dllName, int policy) { void *dll = nullptr; dll = dlopen(dllName.c_str(), RTLD_NOW); if (!dll) { From f86dae0aa14c8d1f6ca6dc007aa56c5a87bba10f Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sun, 12 Apr 2026 22:54:56 +0200 Subject: [PATCH 08/43] Cleanup: Correct sizeof() --- src/rtapi/uspace_rtapi_main.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 2fc83a8cec7..1bd790a70e9 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -541,7 +541,7 @@ int main(int argc, char **argv) { // plus one because we use the abstract namespace, it will show up in // /proc/net/unix prefixed with an @ - int result = ::bind(fd, (sockaddr *)&addr, len + sizeof(addr.sun_family) + 1); + int result = ::bind(fd, (sockaddr *)&addr, sizeof(addr)); if (result == 0) { //If called in master mode with exit command, no need to start master @@ -562,7 +562,7 @@ int main(int argc, char **argv) { gettimeofday(&t0, NULL); gettimeofday(&t1, NULL); for (int i = 0; i < 3 || (t1.tv_sec < 3 + t0.tv_sec); i++) { - result = connect(fd, (sockaddr *)&addr, len + sizeof(addr.sun_family) + 1); + result = connect(fd, (sockaddr *)&addr, sizeof(addr)); if (result == 0) break; if (i == 0) From 85729d00fbc8d4ece95c6d61919fe96048d7a1d4 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sun, 12 Apr 2026 22:59:19 +0200 Subject: [PATCH 09/43] Cleanup: Correct _exit --- src/rtapi/uspace_rtapi_main.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 1bd790a70e9..95cc71201e9 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -605,12 +605,12 @@ static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { case SIGXCPU: // should not happen - must be handled in RTAPI if enabled rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: BUG: SIGXCPU received - exiting\n"); - exit(0); + _exit(0); break; case SIGTERM: rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: SIGTERM - shutting down\n"); - exit(0); + _exit(0); break; default: // pretty bad @@ -622,7 +622,7 @@ static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { kill(getpid(), sig); break; } - exit(1); + _exit(1); } const static size_t PRE_ALLOC_SIZE = 1024 * 1024 * 32; From a276f7c8eb5a9f6ed0f03efd51a0db6120cec971 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 13 Apr 2026 22:24:56 +0200 Subject: [PATCH 10/43] Cleanup: Review: get_fifo_path nicer --- src/rtapi/uspace_rtapi_main.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 95cc71201e9..8651dc90c7f 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -467,13 +467,13 @@ static std::string get_fifo_path() { return s; } -static int get_fifo_path(char *buf, size_t bufsize) { +static int get_fifo_path_to_addr(struct sockaddr_un *addr) { int len; const std::string s = get_fifo_path(); if (s.empty()) { return -1; } - if (s.size() + 1 > sizeof(sockaddr_un::sun_path)) { + if (s.size() + 1 > sizeof(addr->sun_path)) { rtapi_print_msg( RTAPI_MSG_ERR, "rtapi_app: rtapi fifo path is too long (arch limit %zd): %s\n", @@ -482,7 +482,10 @@ static int get_fifo_path(char *buf, size_t bufsize) { ); return -1; } - len = snprintf(buf + 1, bufsize - 1, "%s", s.c_str()); + //See: https://www.man7.org/linux/man-pages/man7/unix.7.html abstract + //sun_path[0] is a null byte ('\0') + addr->sun_path[0]=0; + len = snprintf(addr->sun_path + 1, sizeof(addr->sun_path) - 1, "%s", s.c_str()); return len; } @@ -536,7 +539,7 @@ int main(int argc, char **argv) { struct sockaddr_un addr; memset(&addr, 0x0, sizeof(addr)); addr.sun_family = AF_UNIX; - if ((len = get_fifo_path(addr.sun_path, sizeof(addr.sun_path))) < 0) + if ((len = get_fifo_path_to_addr(&addr)) < 0) exit(1); // plus one because we use the abstract namespace, it will show up in From 70e2cbb8355b9d192e22fdb1a942d47b3b4e6523 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 13 Apr 2026 23:09:20 +0200 Subject: [PATCH 11/43] Cleanup: Review: Fix timeout --- src/rtapi/uspace_rtapi_main.cc | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 8651dc90c7f..f60e2f40c0b 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -489,6 +489,10 @@ static int get_fifo_path_to_addr(struct sockaddr_un *addr) { return len; } +static double diff_timespec(const struct timespec *time1, const struct timespec *time0) { + return (time1->tv_sec - time0->tv_sec) + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0; +} + int main(int argc, char **argv) { if (getuid() == 0) { char *fallback_uid_str = getenv("RTAPI_UID"); @@ -561,17 +565,17 @@ int main(int argc, char **argv) { result = master(fd, args); return result; } else if (errno == EADDRINUSE) { - struct timeval t0, t1; - gettimeofday(&t0, NULL); - gettimeofday(&t1, NULL); - for (int i = 0; i < 3 || (t1.tv_sec < 3 + t0.tv_sec); i++) { + struct timespec start, now; + clock_gettime(CLOCK_MONOTONIC, &start); + clock_gettime(CLOCK_MONOTONIC, &now); + srand48(start.tv_sec ^ start.tv_nsec); + while(diff_timespec(&now, &start) < 3.0) { result = connect(fd, (sockaddr *)&addr, sizeof(addr)); if (result == 0) break; - if (i == 0) - srand48(t0.tv_sec ^ t0.tv_usec); - usleep(lrand48() % 100000); - gettimeofday(&t1, NULL); + + usleep(lrand48() % 100000 + 100); //Random sleep min 100us max 100100us + clock_gettime(CLOCK_MONOTONIC, &now); } if (result < 0 && errno == ECONNREFUSED) { fprintf(stderr, "Waited 3 seconds for master. giving up.\n"); From 7f7677a43da1bfe5bd6c6717cfa1c9a49116258f Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Tue, 14 Apr 2026 21:19:23 +0200 Subject: [PATCH 12/43] Cleanup: Improve get_fifo_path_to_addr Changed size check to -2 due to 1 byte is needed at the beginning and 1 for \0 at the end --- src/rtapi/uspace_rtapi_main.cc | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index f60e2f40c0b..4cdb7c20d04 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -467,26 +467,25 @@ static std::string get_fifo_path() { return s; } -static int get_fifo_path_to_addr(struct sockaddr_un *addr) { - int len; +static bool get_fifo_path_to_addr(struct sockaddr_un *addr) { const std::string s = get_fifo_path(); if (s.empty()) { - return -1; + return false; } - if (s.size() + 1 > sizeof(addr->sun_path)) { + if (s.size() + 2 > sizeof(addr->sun_path)) { rtapi_print_msg( RTAPI_MSG_ERR, "rtapi_app: rtapi fifo path is too long (arch limit %zd): %s\n", sizeof(sockaddr_un::sun_path), s.c_str() ); - return -1; + return false; } //See: https://www.man7.org/linux/man-pages/man7/unix.7.html abstract //sun_path[0] is a null byte ('\0') addr->sun_path[0]=0; - len = snprintf(addr->sun_path + 1, sizeof(addr->sun_path) - 1, "%s", s.c_str()); - return len; + strncpy(addr->sun_path + 1, s.c_str(), sizeof(addr->sun_path) - 2); + return true; } static double diff_timespec(const struct timespec *time1, const struct timespec *time0) { @@ -531,7 +530,6 @@ int main(int argc, char **argv) { } become_master: - int len = 0; int fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd == -1) { perror("socket"); @@ -543,7 +541,7 @@ int main(int argc, char **argv) { struct sockaddr_un addr; memset(&addr, 0x0, sizeof(addr)); addr.sun_family = AF_UNIX; - if ((len = get_fifo_path_to_addr(&addr)) < 0) + if (!get_fifo_path_to_addr(&addr)) exit(1); // plus one because we use the abstract namespace, it will show up in From e379b7804f7d279176cf963f2e6a0903cd3866fe Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Tue, 14 Apr 2026 22:35:55 +0200 Subject: [PATCH 13/43] Cleanup: Rewrite socket protocol --- src/rtapi/uspace_rtapi_main.cc | 162 ++++++++++++++++++++------------- 1 file changed, 99 insertions(+), 63 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 4cdb7c20d04..7bc7bb18796 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -298,66 +298,98 @@ static int do_debug_cmd(const std::string &value) { } } -struct ReadError : std::exception {}; -struct WriteError : std::exception {}; +static bool send_result(int fd, int result) { + ssize_t res = send(fd, &result, sizeof(int), 0); + return res == sizeof(int); +} -static int read_number(int fd) { - int r = 0, neg = 1; - char ch; +static bool recv_result(int fd, int *result) { + ssize_t res = recv(fd, result, sizeof(int), 0); + return res == sizeof(int); +} - while (1) { - int res = read(fd, &ch, 1); - if (res != 1) - return -1; - if (ch == '-') - neg = -1; - else if (ch == ' ') - return r * neg; - else - r = 10 * r + ch - '0'; - } -} - -static std::string read_string(int fd) { - int len = read_number(fd); - if (len < 0) - throw ReadError(); - if (!len) - return std::string(); - std::string str(len, 0); - if (read(fd, str.data(), len) != len) - throw ReadError(); - return str; -} - -static std::vector read_strings(int fd) { - std::vector result; - int count = read_number(fd); - if (count < 0) - return result; - for (int i = 0; i < count; i++) { - result.push_back(read_string(fd)); - } - return result; +static void set_uint16(std::vector &buf, uint16_t value, size_t idx) { + buf[idx] = 0xff & (value >> 0); + buf[idx + 1] = 0xff & (value >> 8); } -static void write_number(std::string &buf, int num) { - buf = buf + fmt::format("{} ", num); +static uint16_t get_uint16(std::vector &buf, size_t idx) { + return ((uint16_t)buf[idx] << 0) | ((uint16_t)buf[idx + 1] << 8); } -static void write_string(std::string &buf, const std::string &s) { - write_number(buf, s.size()); - buf += s; +static bool recv_args(int fd, std::vector &args) { + //Get size + uint16_t tmp; + ssize_t res = recv(fd, &tmp, sizeof(uint16_t), 0); + if (res != sizeof(uint16_t)) { + return false; + } + size_t buff_size = tmp; + + //Get data + std::vector buf(buff_size); + res = recv(fd, buf.data(), buff_size, 0); + if (res != (ssize_t)buff_size) { + return false; + } + + //Deserialize + size_t idx = 0; + size_t n_args = get_uint16(buf, idx); + idx += sizeof(uint16_t); + for (size_t i = 0; i < n_args; i++) { + size_t arg_size = get_uint16(buf, idx); + idx += sizeof(uint16_t); + args.push_back(std::string(buf.begin() + idx, buf.begin() + idx + arg_size)); + idx += arg_size; + } + if (idx + sizeof(uint16_t) != buff_size) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug recv_args: idx %li != buff_size %li\n", idx, buff_size); + return false; + } + + return true; } -static void write_strings(int fd, const std::vector &strings) { - std::string buf; - write_number(buf, strings.size()); - for (unsigned int i = 0; i < strings.size(); i++) { - write_string(buf, strings[i]); +static bool send_args(int fd, const std::vector &args) { + //Calculate size + size_t buff_size = 0; + buff_size += 2 * sizeof(uint16_t); + for (size_t i = 0; i < args.size(); i++) { + buff_size += sizeof(uint16_t); + buff_size += args[i].size(); } - if (write(fd, buf.data(), buf.size()) != (ssize_t)buf.size()) - throw WriteError(); + + //This is the largest value set by set_int16() + if (buff_size > std::numeric_limits::max()) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug send_args: args to big, size = %li!\n", buff_size); + return false; + } + + //Serialize + std::vector buf(buff_size); + size_t idx = 0; + set_uint16(buf, buff_size, idx); + idx += sizeof(uint16_t); + set_uint16(buf, args.size(), idx); + idx += sizeof(uint16_t); + for (size_t i = 0; i < args.size(); i++) { + set_uint16(buf, args[i].size(), idx); + idx += sizeof(uint16_t); + buf.insert(buf.begin() + idx, args[i].begin(), args[i].end()); + idx += args[i].size(); + } + if (idx != buff_size) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug send_args: idx %li != buff_size %li\n", idx, buff_size); + return false; + } + + //Send + ssize_t res = send(fd, buf.data(), buf.size(), 0); + if (res != (ssize_t)buf.size()) { + return false; + } + return true; } static int handle_command(std::vector args) { @@ -386,14 +418,18 @@ static int handle_command(std::vector args) { } static int slave(int fd, const std::vector &args) { - try { - write_strings(fd, args); - } catch (WriteError &e) { + if (!send_args(fd, args)) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to master: %s\n", strerror(errno)); + return -1; } - int result = read_number(fd); - return result; + int result = -1; + if (!recv_result(fd, &result)) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from master: %s\n", strerror(errno)); + return -1; + } else { + return result; + } } static int callback(int fd) { @@ -406,18 +442,18 @@ static int callback(int fd) { return -1; } else { int result; - try { - result = handle_command(read_strings(fd1)); - } catch (ReadError &e) { + std::vector args; + if (!recv_args(fd1, args)) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from slave: %s\n", strerror(errno)); close(fd1); return -1; } - std::string buf; - write_number(buf, result); - if (write(fd1, buf.data(), buf.size()) != (ssize_t)buf.size()) { + + result = handle_command(args); + + if (!send_result(fd1, result)) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to slave: %s\n", strerror(errno)); - }; + } close(fd1); } return !force_exit && instance_count > 0; From 76eef576f1dbdf2817768056a98fea0c47f734ad Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Tue, 14 Apr 2026 23:24:38 +0200 Subject: [PATCH 14/43] Cleanup: Run clang-format --- src/rtapi/uspace_rtapi_app.cc | 6 +++--- src/rtapi/uspace_rtapi_app.hh | 7 ++++--- src/rtapi/uspace_rtapi_main.cc | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index d9e94b1637b..6bcf850da4e 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -41,9 +41,9 @@ WithRoot::~WithRoot() { } } -void WithRoot::init(uid_t ruid_ini, uid_t euid_ini){ - ruid=ruid_ini; - euid=euid_ini; +void WithRoot::init(uid_t ruid_ini, uid_t euid_ini) { + ruid = ruid_ini; + euid = euid_ini; } rtapi_task::rtapi_task() diff --git a/src/rtapi/uspace_rtapi_app.hh b/src/rtapi/uspace_rtapi_app.hh index a464a9b7984..fa25237f138 100644 --- a/src/rtapi/uspace_rtapi_app.hh +++ b/src/rtapi/uspace_rtapi_app.hh @@ -47,13 +47,14 @@ struct WithRoot { WithRoot(); ~WithRoot(); static void init(uid_t ruid_ini, uid_t euid_ini); - static uid_t getRuid(){ + static uid_t getRuid() { return ruid; } - static uid_t getEuid(){ + static uid_t getEuid() { return euid; } -private: + + private: static std::atomic_int level; static uid_t ruid, euid; }; diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 7bc7bb18796..11e6117dfba 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -519,13 +519,13 @@ static bool get_fifo_path_to_addr(struct sockaddr_un *addr) { } //See: https://www.man7.org/linux/man-pages/man7/unix.7.html abstract //sun_path[0] is a null byte ('\0') - addr->sun_path[0]=0; + addr->sun_path[0] = 0; strncpy(addr->sun_path + 1, s.c_str(), sizeof(addr->sun_path) - 2); return true; } static double diff_timespec(const struct timespec *time1, const struct timespec *time0) { - return (time1->tv_sec - time0->tv_sec) + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0; + return (time1->tv_sec - time0->tv_sec) + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0; } int main(int argc, char **argv) { @@ -603,11 +603,11 @@ int main(int argc, char **argv) { clock_gettime(CLOCK_MONOTONIC, &start); clock_gettime(CLOCK_MONOTONIC, &now); srand48(start.tv_sec ^ start.tv_nsec); - while(diff_timespec(&now, &start) < 3.0) { + while (diff_timespec(&now, &start) < 3.0) { result = connect(fd, (sockaddr *)&addr, sizeof(addr)); if (result == 0) break; - + usleep(lrand48() % 100000 + 100); //Random sleep min 100us max 100100us clock_gettime(CLOCK_MONOTONIC, &now); } From ee79b7b2f97acb693425ac86646afc642e29f7a7 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Wed, 15 Apr 2026 01:12:01 +0200 Subject: [PATCH 15/43] Cleanup: Signal handler: Use only allowed functions Core dump did not work any way --- src/rtapi/uspace_rtapi_main.cc | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 11e6117dfba..b77c19fd5e3 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -641,28 +641,33 @@ struct rtapi_module { #define MAX_MODULES 64 #define MODULE_OFFSET 32768 +#define WRITE_STDERR_STR(str) ((void)!write(STDERR_FILENO, str, strlen(str))) static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { + //Read: https://www.man7.org/linux/man-pages/man7/signal-safety.7.html switch (sig) { case SIGXCPU: - // should not happen - must be handled in RTAPI if enabled - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: BUG: SIGXCPU received - exiting\n"); - _exit(0); + WRITE_STDERR_STR("rtapi_app: SIGXCPU - shutting down\n"); + break; + case SIGSEGV: + WRITE_STDERR_STR("rtapi_app: SIGSEGV - shutting down\n"); + break; + case SIGILL: + WRITE_STDERR_STR("rtapi_app: SIGILL - shutting down\n"); + break; + case SIGFPE: + WRITE_STDERR_STR("rtapi_app: SIGFPE - shutting down\n"); break; - case SIGTERM: - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: SIGTERM - shutting down\n"); - _exit(0); + WRITE_STDERR_STR("rtapi_app: SIGTERM - shutting down\n"); break; - - default: // pretty bad - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: caught signal %d - dumping core\n", sig); - sleep(1); // let syslog drain - signal(sig, SIG_DFL); - // for reasons unknown raise(sig); doesn't lead to core dump file - // but this will - kill(getpid(), sig); + case SIGINT: + WRITE_STDERR_STR("rtapi_app: SIGINT - shutting down\n"); + break; + default: + WRITE_STDERR_STR("rtapi_app: UNKNOWN - shutting down\n"); break; } + _exit(1); } @@ -772,6 +777,7 @@ static int harden_rt() { sig_act.sa_sigaction = signal_handler; sig_act.sa_flags = SA_SIGINFO; + sigaction(SIGXCPU, &sig_act, (struct sigaction *)NULL); sigaction(SIGSEGV, &sig_act, (struct sigaction *)NULL); sigaction(SIGILL, &sig_act, (struct sigaction *)NULL); sigaction(SIGFPE, &sig_act, (struct sigaction *)NULL); From 125f16f5f88adcbefe279357ebd5912fe32568e9 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Wed, 15 Apr 2026 12:16:39 +0200 Subject: [PATCH 16/43] Cleanup: Signal handler: Fix coredump This concept uses abort to always create a coredump, indepednent if the original signal has core as a default action. Write out remaining messages: This should be signal safe. Unshure: consume_all() could use free internaly. But due to the queue is fixed size, it would be strange. --- src/rtapi/uspace_rtapi_main.cc | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index b77c19fd5e3..384f37c5f10 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -644,31 +644,47 @@ struct rtapi_module { #define WRITE_STDERR_STR(str) ((void)!write(STDERR_FILENO, str, strlen(str))) static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { //Read: https://www.man7.org/linux/man-pages/man7/signal-safety.7.html + bool doAbort = true; switch (sig) { case SIGXCPU: - WRITE_STDERR_STR("rtapi_app: SIGXCPU - shutting down\n"); + WRITE_STDERR_STR("rtapi_app: SIGXCPU - aborting\n"); break; case SIGSEGV: - WRITE_STDERR_STR("rtapi_app: SIGSEGV - shutting down\n"); + WRITE_STDERR_STR("rtapi_app: SIGSEGV - aborting\n"); break; case SIGILL: - WRITE_STDERR_STR("rtapi_app: SIGILL - shutting down\n"); + WRITE_STDERR_STR("rtapi_app: SIGILL - aborting\n"); break; case SIGFPE: - WRITE_STDERR_STR("rtapi_app: SIGFPE - shutting down\n"); + WRITE_STDERR_STR("rtapi_app: SIGFPE - aborting\n"); break; case SIGTERM: WRITE_STDERR_STR("rtapi_app: SIGTERM - shutting down\n"); + doAbort = false; //TERM is a user signal, no need for a coredump break; case SIGINT: WRITE_STDERR_STR("rtapi_app: SIGINT - shutting down\n"); + doAbort = false; //INT is a user signal, no need for a coredump break; default: - WRITE_STDERR_STR("rtapi_app: UNKNOWN - shutting down\n"); + WRITE_STDERR_STR("rtapi_app: UNKNOWN - aborting\n"); break; } - _exit(1); + //Write remaining messages + rtapi_msg_queue.consume_all([](const message_t &m) { + WRITE_STDERR_STR(m.msg); + }); + + if(doAbort){ + //Call abort to generate a coredump if enabled + //To enable coredumps for setuid applications: + //echo 1 | sudo tee /proc/sys/fs/suid_dumpable #rtapi_app is setuid + //In general: + //ulimit -c unlimited or coredumpctl + abort(); + } + _exit(128+sig); //128+n: Fatal error signal "n" } const static size_t PRE_ALLOC_SIZE = 1024 * 1024 * 32; From 4617389fe29442a7f2c3f6ff1072ff29b2ab72d6 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Wed, 15 Apr 2026 14:02:39 +0200 Subject: [PATCH 17/43] Cleanup: Review: Doc protocol / const --- src/rtapi/uspace_rtapi_main.cc | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 384f37c5f10..48f2d06f64e 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -298,6 +298,27 @@ static int do_debug_cmd(const std::string &value) { } } +/* + * Protocol: + * + * client->master: std::vector args + * master processes the args and returns result + * master->client: int result + * + * Packing: + * args are serialized as: + * uint16_t size (full package size including the size field) + * uint16_t n_args + * n_args times: + * { + * uint16_t arg_size + * char[arg_size] argument + * } + * + * result is serialized as: + * int + */ + static bool send_result(int fd, int result) { ssize_t res = send(fd, &result, sizeof(int), 0); return res == sizeof(int); @@ -313,7 +334,7 @@ static void set_uint16(std::vector &buf, uint16_t value, size_t idx) { buf[idx + 1] = 0xff & (value >> 8); } -static uint16_t get_uint16(std::vector &buf, size_t idx) { +static uint16_t get_uint16(const std::vector &buf, size_t idx) { return ((uint16_t)buf[idx] << 0) | ((uint16_t)buf[idx + 1] << 8); } From 7f585bcd8840c80fa41b8d077f0f862994782af1 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Wed, 15 Apr 2026 14:06:59 +0200 Subject: [PATCH 18/43] Cleanup: recv_args: No need for push_back: more efficient --- src/rtapi/uspace_rtapi_main.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 48f2d06f64e..17bf27d7f2f 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -357,11 +357,12 @@ static bool recv_args(int fd, std::vector &args) { //Deserialize size_t idx = 0; size_t n_args = get_uint16(buf, idx); + args.resize(n_args); idx += sizeof(uint16_t); for (size_t i = 0; i < n_args; i++) { size_t arg_size = get_uint16(buf, idx); idx += sizeof(uint16_t); - args.push_back(std::string(buf.begin() + idx, buf.begin() + idx + arg_size)); + args[i]=std::string(buf.begin() + idx, buf.begin() + idx + arg_size); idx += arg_size; } if (idx + sizeof(uint16_t) != buff_size) { From c5c81275a2a54daf3f86d5bb8cfcd98d0c23abec Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 18 Apr 2026 10:02:12 +0200 Subject: [PATCH 19/43] Cleanup: signal_handler: Replace define by function --- src/rtapi/uspace_rtapi_main.cc | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 17bf27d7f2f..d6b0afb3a9e 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -663,42 +663,45 @@ struct rtapi_module { #define MAX_MODULES 64 #define MODULE_OFFSET 32768 -#define WRITE_STDERR_STR(str) ((void)!write(STDERR_FILENO, str, strlen(str))) +static inline void write_string(int fd, const char *str) { + (void)!write(fd, str, strlen(str)); +} + static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { //Read: https://www.man7.org/linux/man-pages/man7/signal-safety.7.html bool doAbort = true; switch (sig) { case SIGXCPU: - WRITE_STDERR_STR("rtapi_app: SIGXCPU - aborting\n"); + write_string(STDERR_FILENO, "rtapi_app: SIGXCPU - aborting\n"); break; case SIGSEGV: - WRITE_STDERR_STR("rtapi_app: SIGSEGV - aborting\n"); + write_string(STDERR_FILENO, "rtapi_app: SIGSEGV - aborting\n"); break; case SIGILL: - WRITE_STDERR_STR("rtapi_app: SIGILL - aborting\n"); + write_string(STDERR_FILENO, "rtapi_app: SIGILL - aborting\n"); break; case SIGFPE: - WRITE_STDERR_STR("rtapi_app: SIGFPE - aborting\n"); + write_string(STDERR_FILENO, "rtapi_app: SIGFPE - aborting\n"); break; case SIGTERM: - WRITE_STDERR_STR("rtapi_app: SIGTERM - shutting down\n"); + write_string(STDERR_FILENO, "rtapi_app: SIGTERM - shutting down\n"); doAbort = false; //TERM is a user signal, no need for a coredump break; case SIGINT: - WRITE_STDERR_STR("rtapi_app: SIGINT - shutting down\n"); + write_string(STDERR_FILENO, "rtapi_app: SIGINT - shutting down\n"); doAbort = false; //INT is a user signal, no need for a coredump break; default: - WRITE_STDERR_STR("rtapi_app: UNKNOWN - aborting\n"); + write_string(STDERR_FILENO, "rtapi_app: UNKNOWN - aborting\n"); break; } //Write remaining messages rtapi_msg_queue.consume_all([](const message_t &m) { - WRITE_STDERR_STR(m.msg); + write_string(STDERR_FILENO, m.msg); }); - if(doAbort){ + if (doAbort) { //Call abort to generate a coredump if enabled //To enable coredumps for setuid applications: //echo 1 | sudo tee /proc/sys/fs/suid_dumpable #rtapi_app is setuid @@ -706,7 +709,7 @@ static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { //ulimit -c unlimited or coredumpctl abort(); } - _exit(128+sig); //128+n: Fatal error signal "n" + _exit(128 + sig); //128+n: Fatal error signal "n" } const static size_t PRE_ALLOC_SIZE = 1024 * 1024 * 32; From a729270e300556cc95921a0da03b82656f6e006b Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 18 Apr 2026 12:54:06 +0200 Subject: [PATCH 20/43] Cleanup: remove run_threads There is nothing usefull you can do in run_threads due to callback() blocks on accept and only returns on a slave command. Return -1 did not break the while loop. It looks like a mistake. Never the less, this behavour is kept due to exiting all threads just because a socket error happened is most probably not desired. --- src/rtapi/uspace_posix.cc | 7 ------- src/rtapi/uspace_rtai.cc | 7 ------- src/rtapi/uspace_rtapi_app.hh | 1 - src/rtapi/uspace_rtapi_main.cc | 19 +++++++++---------- src/rtapi/uspace_xenomai.cc | 7 ------- src/rtapi/uspace_xenomai_evl.cc | 7 ------- 6 files changed, 9 insertions(+), 39 deletions(-) diff --git a/src/rtapi/uspace_posix.cc b/src/rtapi/uspace_posix.cc index 3cb48ff0e50..3cac2ae0466 100644 --- a/src/rtapi/uspace_posix.cc +++ b/src/rtapi/uspace_posix.cc @@ -194,13 +194,6 @@ struct PosixApp : RtapiApp { #endif } - int run_threads(int fd, int (*callback)(int fd)) { - while (callback(fd)) { - /* nothing */ - } - return 0; - } - int task_self() { struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); if (!task) diff --git a/src/rtapi/uspace_rtai.cc b/src/rtapi/uspace_rtai.cc index 14218b382b7..945a79e80bf 100644 --- a/src/rtapi/uspace_rtai.cc +++ b/src/rtapi/uspace_rtai.cc @@ -175,13 +175,6 @@ struct RtaiApp : RtapiApp { #endif } - int run_threads(int fd, int (*callback)(int fd)) { - while (callback(fd)) { - /* nothing */ - } - return 0; - } - int task_self() { struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); if (!task) diff --git a/src/rtapi/uspace_rtapi_app.hh b/src/rtapi/uspace_rtapi_app.hh index fa25237f138..a9c73e7a589 100644 --- a/src/rtapi/uspace_rtapi_app.hh +++ b/src/rtapi/uspace_rtapi_app.hh @@ -108,7 +108,6 @@ struct RtapiApp { virtual void wait() = 0; virtual unsigned char do_inb(unsigned int port) = 0; virtual void do_outb(unsigned char value, unsigned int port) = 0; - virtual int run_threads(int fd, int (*callback)(int fd)) = 0; virtual long long do_get_time(void) = 0; virtual void do_delay(long ns) = 0; static int find_rt_cpu_number(); diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index d6b0afb3a9e..6ae6480285e 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -87,8 +87,6 @@ static void *queue_function(void * /*arg*/) { return nullptr; } -static int sim_rtapi_run_threads(int fd, int (*callback)(int fd)); - template T DLSYM(void *handle, const std::string &name) { return (T)(dlsym(handle, name.c_str())); } @@ -454,21 +452,25 @@ static int slave(int fd, const std::vector &args) { } } -static int callback(int fd) { +//Processes incoming command on socket +//This function blocks on accept until a client connects +//return: true if master should continue +// false if master should exit +static bool master_process_socket_command(int fd) { struct sockaddr_un client_addr; memset(&client_addr, 0, sizeof(client_addr)); socklen_t len = sizeof(client_addr); int fd1 = accept(fd, (sockaddr *)&client_addr, &len); if (fd1 < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to accept connection from slave: %s\n", strerror(errno)); - return -1; + return true; //If there is a socket error, just continue } else { int result; std::vector args; if (!recv_args(fd1, args)) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from slave: %s\n", strerror(errno)); close(fd1); - return -1; + return true; //If there is a socket error, just continue } result = handle_command(args); @@ -501,7 +503,8 @@ static int master(int fd, const std::vector &args) { if (force_exit || instance_count == 0) goto out; } - sim_rtapi_run_threads(fd, callback); + //Process commands as long as master should not exit + while(master_process_socket_command(fd)); out: pthread_cancel(queue_thread); pthread_join(queue_thread, nullptr); @@ -962,10 +965,6 @@ long int simple_strtol(const char *nptr, char **endptr, int base) { return strtol(nptr, endptr, base); } -int sim_rtapi_run_threads(int fd, int (*callback)(int fd)) { - return App().run_threads(fd, callback); -} - long long rtapi_get_time() { return App().do_get_time(); } diff --git a/src/rtapi/uspace_xenomai.cc b/src/rtapi/uspace_xenomai.cc index 0b9ccd8540c..d10988660ff 100644 --- a/src/rtapi/uspace_xenomai.cc +++ b/src/rtapi/uspace_xenomai.cc @@ -188,13 +188,6 @@ struct XenomaiApp : RtapiApp { #endif } - int run_threads(int fd, int (*callback)(int fd)) { - while (callback(fd)) { - /* nothing */ - } - return 0; - } - int task_self() { struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); if (!task) diff --git a/src/rtapi/uspace_xenomai_evl.cc b/src/rtapi/uspace_xenomai_evl.cc index 3482a3d3ee3..ad46e2c457e 100644 --- a/src/rtapi/uspace_xenomai_evl.cc +++ b/src/rtapi/uspace_xenomai_evl.cc @@ -209,13 +209,6 @@ struct EvlApp : RtapiApp { #endif } - int run_threads(int fd, int (*callback)(int fd)) { - while (callback(fd)) { - /* nothing */ - } - return 0; - } - int task_self() { struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); if (!task) From 29e8841bb9e9eb4be4924e14918194faf78972fd Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 20 Apr 2026 08:34:40 +0200 Subject: [PATCH 21/43] Cleanup: socket protocol: fully checked send/recv --- src/rtapi/uspace_rtapi_main.cc | 64 +++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 6ae6480285e..9755cf56adf 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -296,6 +296,54 @@ static int do_debug_cmd(const std::string &value) { } } +/* + * Fully checked send/recv + * Will retry on EINTR, so to abort a send_data/recv_data on a + * signal, change to something like while(remaining > 0 && !exit_flag) + * and set exit_flag in signal handler + */ +static int send_data(int fd, const void *buf, size_t n, int flags) { + const uint8_t *ptr = (const uint8_t *)buf; + size_t n_rem = n; + while (n_rem > 0) { + ssize_t n_ret = send(fd, ptr, n_rem, flags); + if (n_ret == -1) { + if (errno == EINTR) { + // Retry + } else { + return -1; // Other error, fail + } + } else if (n_ret == 0) { + return (n - n_rem); // No more data + } else { + ptr += n_ret; + n_rem -= n_ret; + } + } + return n; // All sent +} + +static int recv_data(int fd, void *buf, size_t n, int flags) { + uint8_t *ptr = (uint8_t *)buf; + size_t n_rem = n; + while (n_rem > 0) { + ssize_t n_ret = recv(fd, ptr, n_rem, flags); + if (n_ret == -1) { + if (errno == EINTR) { + // Retry + } else { + return -1; // Other error, fail + } + } else if (n_ret == 0) { + return (n - n_rem); // No more data + } else { + ptr += n_ret; + n_rem -= n_ret; + } + } + return n; // All read +} + /* * Protocol: * @@ -318,12 +366,12 @@ static int do_debug_cmd(const std::string &value) { */ static bool send_result(int fd, int result) { - ssize_t res = send(fd, &result, sizeof(int), 0); + ssize_t res = send_data(fd, &result, sizeof(int), 0); return res == sizeof(int); } static bool recv_result(int fd, int *result) { - ssize_t res = recv(fd, result, sizeof(int), 0); + ssize_t res = recv_data(fd, result, sizeof(int), 0); return res == sizeof(int); } @@ -339,7 +387,7 @@ static uint16_t get_uint16(const std::vector &buf, size_t idx) { static bool recv_args(int fd, std::vector &args) { //Get size uint16_t tmp; - ssize_t res = recv(fd, &tmp, sizeof(uint16_t), 0); + ssize_t res = recv_data(fd, &tmp, sizeof(uint16_t), 0); if (res != sizeof(uint16_t)) { return false; } @@ -347,7 +395,7 @@ static bool recv_args(int fd, std::vector &args) { //Get data std::vector buf(buff_size); - res = recv(fd, buf.data(), buff_size, 0); + res = recv_data(fd, buf.data(), buff_size, 0); if (res != (ssize_t)buff_size) { return false; } @@ -360,7 +408,7 @@ static bool recv_args(int fd, std::vector &args) { for (size_t i = 0; i < n_args; i++) { size_t arg_size = get_uint16(buf, idx); idx += sizeof(uint16_t); - args[i]=std::string(buf.begin() + idx, buf.begin() + idx + arg_size); + args[i] = std::string(buf.begin() + idx, buf.begin() + idx + arg_size); idx += arg_size; } if (idx + sizeof(uint16_t) != buff_size) { @@ -405,7 +453,7 @@ static bool send_args(int fd, const std::vector &args) { } //Send - ssize_t res = send(fd, buf.data(), buf.size(), 0); + ssize_t res = send_data(fd, buf.data(), buf.size(), 0); if (res != (ssize_t)buf.size()) { return false; } @@ -463,7 +511,7 @@ static bool master_process_socket_command(int fd) { int fd1 = accept(fd, (sockaddr *)&client_addr, &len); if (fd1 < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to accept connection from slave: %s\n", strerror(errno)); - return true; //If there is a socket error, just continue + return true; //If there is a socket error, just continue, no need to check errno } else { int result; std::vector args; @@ -607,7 +655,7 @@ int main(int argc, char **argv) { // plus one because we use the abstract namespace, it will show up in // /proc/net/unix prefixed with an @ - int result = ::bind(fd, (sockaddr *)&addr, sizeof(addr)); + int result = bind(fd, (sockaddr *)&addr, sizeof(addr)); if (result == 0) { //If called in master mode with exit command, no need to start master From 8e065050cf171dc0cdf4cb7f181ace943f1db4e3 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 20 Apr 2026 08:48:23 +0200 Subject: [PATCH 22/43] Cleanup: socket protocol: improve error messages Errno was always printed, even if there was an other issue --- src/rtapi/uspace_rtapi_main.cc | 55 ++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 9755cf56adf..550e4f27681 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -367,12 +367,34 @@ static int recv_data(int fd, void *buf, size_t n, int flags) { static bool send_result(int fd, int result) { ssize_t res = send_data(fd, &result, sizeof(int), 0); - return res == sizeof(int); + if (res != sizeof(int)) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: send_result failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: send_result failed, send only %li of %li bytes\n", res, sizeof(int) + ); + } + return false; + } else { + return true; + } } static bool recv_result(int fd, int *result) { ssize_t res = recv_data(fd, result, sizeof(int), 0); - return res == sizeof(int); + if (res != sizeof(int)) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: recv_result failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: recv_result failed, recv only %li of %li bytes\n", res, sizeof(int) + ); + } + return false; + } else { + return true; + } } static void set_uint16(std::vector &buf, uint16_t value, size_t idx) { @@ -389,6 +411,13 @@ static bool recv_args(int fd, std::vector &args) { uint16_t tmp; ssize_t res = recv_data(fd, &tmp, sizeof(uint16_t), 0); if (res != sizeof(uint16_t)) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: recv_args 1 failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: recv_args 1 failed, recv only %li of %li bytes\n", res, sizeof(uint16_t) + ); + } return false; } size_t buff_size = tmp; @@ -397,6 +426,13 @@ static bool recv_args(int fd, std::vector &args) { std::vector buf(buff_size); res = recv_data(fd, buf.data(), buff_size, 0); if (res != (ssize_t)buff_size) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: recv_args 2 failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: recv_args 2 failed, recv only %li of %li bytes\n", res, buff_size + ); + } return false; } @@ -455,6 +491,13 @@ static bool send_args(int fd, const std::vector &args) { //Send ssize_t res = send_data(fd, buf.data(), buf.size(), 0); if (res != (ssize_t)buf.size()) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: send_args failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: send_args failed, sent only %li of %li bytes\n", res, buf.size() + ); + } return false; } return true; @@ -487,13 +530,13 @@ static int handle_command(std::vector args) { static int slave(int fd, const std::vector &args) { if (!send_args(fd, args)) { - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to master: %s\n", strerror(errno)); + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to master\n"); return -1; } int result = -1; if (!recv_result(fd, &result)) { - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from master: %s\n", strerror(errno)); + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from master\n"); return -1; } else { return result; @@ -516,7 +559,7 @@ static bool master_process_socket_command(int fd) { int result; std::vector args; if (!recv_args(fd1, args)) { - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from slave: %s\n", strerror(errno)); + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from slave\n"); close(fd1); return true; //If there is a socket error, just continue } @@ -524,7 +567,7 @@ static bool master_process_socket_command(int fd) { result = handle_command(args); if (!send_result(fd1, result)) { - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to slave: %s\n", strerror(errno)); + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to slave\n"); } close(fd1); } From 3ece0a3f5e39d7cf11b0b0a63c701f004b0e9b7b Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 20 Apr 2026 09:28:49 +0200 Subject: [PATCH 23/43] Cleanup: socket protocol: fix send_args vector.insert increases size, so to much data was sent. Change to reserve() and push_back() to avoid allocation. --- src/rtapi/uspace_rtapi_main.cc | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 550e4f27681..d0947b5a8cb 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -397,9 +397,9 @@ static bool recv_result(int fd, int *result) { } } -static void set_uint16(std::vector &buf, uint16_t value, size_t idx) { - buf[idx] = 0xff & (value >> 0); - buf[idx + 1] = 0xff & (value >> 8); +static void push_uint16(std::vector &buf, uint16_t value) { + buf.push_back(0xff & (value >> 0)); + buf.push_back(0xff & (value >> 8)); } static uint16_t get_uint16(const std::vector &buf, size_t idx) { @@ -420,7 +420,7 @@ static bool recv_args(int fd, std::vector &args) { } return false; } - size_t buff_size = tmp; + size_t buff_size = tmp - sizeof(uint16_t); //Size already consumed //Get data std::vector buf(buff_size); @@ -447,7 +447,7 @@ static bool recv_args(int fd, std::vector &args) { args[i] = std::string(buf.begin() + idx, buf.begin() + idx + arg_size); idx += arg_size; } - if (idx + sizeof(uint16_t) != buff_size) { + if (idx != buff_size) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug recv_args: idx %li != buff_size %li\n", idx, buff_size); return false; } @@ -471,20 +471,18 @@ static bool send_args(int fd, const std::vector &args) { } //Serialize - std::vector buf(buff_size); - size_t idx = 0; - set_uint16(buf, buff_size, idx); - idx += sizeof(uint16_t); - set_uint16(buf, args.size(), idx); - idx += sizeof(uint16_t); + std::vector buf; + buf.reserve(buff_size); + push_uint16(buf, buff_size); + push_uint16(buf, args.size()); for (size_t i = 0; i < args.size(); i++) { - set_uint16(buf, args[i].size(), idx); - idx += sizeof(uint16_t); - buf.insert(buf.begin() + idx, args[i].begin(), args[i].end()); - idx += args[i].size(); + push_uint16(buf, args[i].size()); + buf.insert(buf.end(), args[i].begin(), args[i].end()); } - if (idx != buff_size) { - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug send_args: idx %li != buff_size %li\n", idx, buff_size); + if (buf.size() != buff_size) { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: Bug send_args: buf.size() %li != buff_size %li\n", buf.size(), buff_size + ); return false; } From 715c427a1df5b08e05f058fc4be6b530e1b9e795 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 20 Apr 2026 09:46:41 +0200 Subject: [PATCH 24/43] Cleanup: socket protocol: Set recive timeout in master Otherwhise, master will hang forever if client doesn't send enough data --- src/rtapi/uspace_rtapi_main.cc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index d0947b5a8cb..a7971d0d0ef 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -556,6 +556,17 @@ static bool master_process_socket_command(int fd) { } else { int result; std::vector args; + + //Set timeout, so master doesn't hang forever + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + if (setsockopt(fd1, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0){ + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: setsockopt timeout failed: %s\n", strerror(errno)); + close(fd1); + return true; //If there is a socket error, just continue + } + if (!recv_args(fd1, args)) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from slave\n"); close(fd1); From 408708336133ca706bb0cb1d763ad2193cbd128e Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 20 Apr 2026 10:12:09 +0200 Subject: [PATCH 25/43] Cleanup: socket protocol: args to big is not a bug --- src/rtapi/uspace_rtapi_main.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index a7971d0d0ef..6832c1ed026 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -466,7 +466,7 @@ static bool send_args(int fd, const std::vector &args) { //This is the largest value set by set_int16() if (buff_size > std::numeric_limits::max()) { - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug send_args: args to big, size = %li!\n", buff_size); + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: send_args: args to big, size = %li!\n", buff_size); return false; } From 55849034eb824b213f7ec26a667a7fb9ca453194 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 20 Apr 2026 12:10:26 +0200 Subject: [PATCH 26/43] Cleanup: socket protocol: bounds checking --- src/rtapi/uspace_rtapi_main.cc | 38 ++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 6832c1ed026..d5b0d7324c0 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -403,7 +403,8 @@ static void push_uint16(std::vector &buf, uint16_t value) { } static uint16_t get_uint16(const std::vector &buf, size_t idx) { - return ((uint16_t)buf[idx] << 0) | ((uint16_t)buf[idx + 1] << 8); + //at() will check index and throw std::out_of_range + return ((uint16_t)buf.at(idx) << 0) | ((uint16_t)buf.at(idx + 1) << 8); } static bool recv_args(int fd, std::vector &args) { @@ -437,18 +438,29 @@ static bool recv_args(int fd, std::vector &args) { } //Deserialize - size_t idx = 0; - size_t n_args = get_uint16(buf, idx); - args.resize(n_args); - idx += sizeof(uint16_t); - for (size_t i = 0; i < n_args; i++) { - size_t arg_size = get_uint16(buf, idx); + try { + size_t idx = 0; + size_t n_args = get_uint16(buf, idx); + args.resize(n_args); idx += sizeof(uint16_t); - args[i] = std::string(buf.begin() + idx, buf.begin() + idx + arg_size); - idx += arg_size; - } - if (idx != buff_size) { - rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug recv_args: idx %li != buff_size %li\n", idx, buff_size); + for (size_t i = 0; i < n_args; i++) { + size_t arg_size = get_uint16(buf, idx); + idx += sizeof(uint16_t); + //Bound checked, unpack argument + auto start = buf.begin() + idx; + auto end = start + arg_size; + if (end > buf.end()) { + throw std::out_of_range("recv_args: arg size not in buffer range"); + } + args[i] = std::string(start, end); + idx += arg_size; + } + if (idx != buff_size) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug recv_args: idx %li != buff_size %li\n", idx, buff_size); + return false; + } + } catch (std::out_of_range &e) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug recv_args: %s\n", e.what()); return false; } @@ -561,7 +573,7 @@ static bool master_process_socket_command(int fd) { struct timeval timeout; timeout.tv_sec = 10; timeout.tv_usec = 0; - if (setsockopt(fd1, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0){ + if (setsockopt(fd1, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: setsockopt timeout failed: %s\n", strerror(errno)); close(fd1); return true; //If there is a socket error, just continue From 994ee45ddde3f203ff546c5bef4e6c72a43d9208 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Tue, 21 Apr 2026 20:16:36 +0200 Subject: [PATCH 27/43] Cleanup: Remove unused code No references where found in this file --- src/rtapi/uspace_rtapi_main.cc | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index d5b0d7324c0..14e24c76ade 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -764,20 +764,6 @@ int main(int argc, char **argv) { } } - -/* These structs hold data associated with objects like tasks, etc. */ -/* Task handles are pointers to these structs. */ - -struct rtapi_module { - int magic; -}; - -#define MODULE_MAGIC 30812 -#define SHMEM_MAGIC 25453 - -#define MAX_MODULES 64 -#define MODULE_OFFSET 32768 - static inline void write_string(int fd, const char *str) { (void)!write(fd, str, strlen(str)); } From ab79dee26e2cebf82b769314b791261550a6f5d9 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Tue, 21 Apr 2026 20:20:13 +0200 Subject: [PATCH 28/43] Cleanup: Naming and remove out of place struct keyword Struct for C++ instance is unusual --- src/rtapi/uspace_posix.cc | 14 +++++++------- src/rtapi/uspace_rtai.cc | 10 +++++----- src/rtapi/uspace_rtapi_app.cc | 16 ++++++++-------- src/rtapi/uspace_rtapi_app.hh | 16 ++++++++-------- src/rtapi/uspace_xenomai.cc | 12 ++++++------ src/rtapi/uspace_xenomai_evl.cc | 12 ++++++------ 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/rtapi/uspace_posix.cc b/src/rtapi/uspace_posix.cc index 3cac2ae0466..a6490fdcf22 100644 --- a/src/rtapi/uspace_posix.cc +++ b/src/rtapi/uspace_posix.cc @@ -27,8 +27,8 @@ #endif namespace { -struct PosixTask : rtapi_task { - PosixTask() : rtapi_task{}, thr{} { +struct PosixTask : RtapiTask { + PosixTask() : RtapiTask{}, thr{} { } pthread_t thr; /* thread's context */ @@ -42,7 +42,7 @@ struct PosixApp : RtapiApp { } } - struct rtapi_task *do_task_new() { + RtapiTask *do_task_new() { return new PosixTask; } @@ -138,14 +138,14 @@ struct PosixApp : RtapiApp { } long long task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return 0; return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; } int task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return -EINVAL; if (value > task->pll_correction_limit) @@ -160,7 +160,7 @@ struct PosixApp : RtapiApp { if (do_thread_lock) pthread_mutex_unlock(&thread_lock); pthread_testcancel(); - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); @@ -195,7 +195,7 @@ struct PosixApp : RtapiApp { } int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return -EINVAL; return task->id; diff --git a/src/rtapi/uspace_rtai.cc b/src/rtapi/uspace_rtai.cc index 945a79e80bf..ad3adf2dd21 100644 --- a/src/rtapi/uspace_rtai.cc +++ b/src/rtapi/uspace_rtai.cc @@ -29,15 +29,15 @@ namespace { RtapiApp *app; -struct RtaiTask : rtapi_task { - RtaiTask() : rtapi_task{}, cancel{}, rt_task{}, thr{} { +struct RtaiTask : RtapiTask { + RtaiTask() : RtapiTask{}, cancel{}, rt_task{}, thr{} { } std::atomic_int cancel; RT_TASK *rt_task; pthread_t thr; }; -template T *get_task(int task_id) { +template T *get_task(int task_id) { return static_cast(RtapiApp::get_task(task_id)); } @@ -47,7 +47,7 @@ struct RtaiApp : RtapiApp { pthread_once(&key_once, init_key); } - struct rtapi_task *do_task_new() { + RtapiTask *do_task_new() { return new RtaiTask; } @@ -176,7 +176,7 @@ struct RtaiApp : RtapiApp { } int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return -EINVAL; return task->id; diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index 6bcf850da4e..8e3746e0af8 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -46,14 +46,14 @@ void WithRoot::init(uid_t ruid_ini, uid_t euid_ini) { euid = euid_ini; } -rtapi_task::rtapi_task() +RtapiTask::RtapiTask() : magic{}, id{}, owner{}, uses_fp{}, stacksize{}, prio{}, period{}, nextstart{}, pll_correction{}, pll_correction_limit{}, arg{}, taskcode{} { } -struct rtapi_task *RtapiApp::task_array[MAX_TASKS]; +RtapiTask *RtapiApp::task_array[MAX_TASKS]; /* Priority functions. Uspace uses POSIX task priorities. */ @@ -111,8 +111,8 @@ int RtapiApp::prio_next_lower(int prio) const { int RtapiApp::allocate_task_id() { for (int n = 0; n < MAX_TASKS; n++) { - rtapi_task **taskptr = &(task_array[n]); - if (__sync_bool_compare_and_swap(taskptr, (rtapi_task *)0, TASK_MAGIC_INIT)) + RtapiTask **taskptr = &(task_array[n]); + if (__sync_bool_compare_and_swap(taskptr, (RtapiTask *)0, TASK_MAGIC_INIT)) return n; } return -ENOSPC; @@ -138,7 +138,7 @@ int RtapiApp::task_new( if (n < 0) return n; - struct rtapi_task *task = do_task_new(); + RtapiTask *task = do_task_new(); if (stacksize < (1024 * 1024)) stacksize = (1024 * 1024); task->id = n; @@ -157,18 +157,18 @@ int RtapiApp::task_new( return n; } -rtapi_task *RtapiApp::get_task(int task_id) { +RtapiTask *RtapiApp::get_task(int task_id) { if (task_id < 0 || task_id >= MAX_TASKS) return NULL; /* validate task handle */ - rtapi_task *task = task_array[task_id]; + RtapiTask *task = task_array[task_id]; if (!task || task == TASK_MAGIC_INIT || task->magic != TASK_MAGIC) return NULL; return task; } -void RtapiApp::unexpected_realtime_delay(rtapi_task *task, int /*nperiod*/) { +void RtapiApp::unexpected_realtime_delay(RtapiTask *task, int /*nperiod*/) { static int printed = 0; if (!printed) { rtapi_print_msg( diff --git a/src/rtapi/uspace_rtapi_app.hh b/src/rtapi/uspace_rtapi_app.hh index a9c73e7a589..5a33423a689 100644 --- a/src/rtapi/uspace_rtapi_app.hh +++ b/src/rtapi/uspace_rtapi_app.hh @@ -59,8 +59,8 @@ struct WithRoot { static uid_t ruid, euid; }; -struct rtapi_task { - rtapi_task(); +struct RtapiTask { + RtapiTask(); int magic; /* to check for valid handle */ int id; @@ -78,7 +78,7 @@ struct rtapi_task { #define MAX_TASKS 64 #define TASK_MAGIC 21979 /* random numbers used as signatures */ -#define TASK_MAGIC_INIT ((rtapi_task *)(-1)) +#define TASK_MAGIC_INIT ((RtapiTask *)(-1)) struct RtapiApp { @@ -94,10 +94,10 @@ struct RtapiApp { int prio_next_lower(int prio) const; long clock_set_period(long int period_nsec); int task_new(void (*taskcode)(void *), void *arg, int prio, int owner, unsigned long int stacksize, int uses_fp); - virtual rtapi_task *do_task_new() = 0; + virtual RtapiTask *do_task_new() = 0; static int allocate_task_id(); - static struct rtapi_task *get_task(int task_id); - void unexpected_realtime_delay(rtapi_task *task, int nperiod = 1); + static RtapiTask *get_task(int task_id); + void unexpected_realtime_delay(RtapiTask *task, int nperiod = 1); virtual int task_delete(int id) = 0; virtual int task_start(int task_id, unsigned long period_nsec) = 0; virtual int task_pause(int task_id) = 0; @@ -114,10 +114,10 @@ struct RtapiApp { static void set_namef(const char *fmt, ...); int policy; long period; - static struct rtapi_task *task_array[MAX_TASKS]; + static RtapiTask *task_array[MAX_TASKS]; }; -template T *rtapi_get_task(int task_id) { +template T *rtapi_get_task(int task_id) { return static_cast(RtapiApp::get_task(task_id)); } diff --git a/src/rtapi/uspace_xenomai.cc b/src/rtapi/uspace_xenomai.cc index d10988660ff..4c7ff05c5a2 100644 --- a/src/rtapi/uspace_xenomai.cc +++ b/src/rtapi/uspace_xenomai.cc @@ -27,8 +27,8 @@ #endif namespace { -struct XenomaiTask : rtapi_task { - XenomaiTask() : rtapi_task{}, cancel{}, thr{} { +struct XenomaiTask : RtapiTask { + XenomaiTask() : RtapiTask{}, cancel{}, thr{} { } std::atomic_int cancel; pthread_t thr; @@ -40,7 +40,7 @@ struct XenomaiApp : RtapiApp { pthread_once(&key_once, init_key); } - struct rtapi_task *do_task_new() { + RtapiTask *do_task_new() { return new XenomaiTask; } @@ -133,14 +133,14 @@ struct XenomaiApp : RtapiApp { } long long task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return 0; return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; } int task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return -EINVAL; if (value > task->pll_correction_limit) @@ -189,7 +189,7 @@ struct XenomaiApp : RtapiApp { } int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return -EINVAL; return task->id; diff --git a/src/rtapi/uspace_xenomai_evl.cc b/src/rtapi/uspace_xenomai_evl.cc index ad46e2c457e..6c2d46b6142 100644 --- a/src/rtapi/uspace_xenomai_evl.cc +++ b/src/rtapi/uspace_xenomai_evl.cc @@ -38,8 +38,8 @@ #endif namespace { -struct EvlTask : rtapi_task { - EvlTask() : rtapi_task{}, cancel{}, thr{} { +struct EvlTask : RtapiTask { + EvlTask() : RtapiTask{}, cancel{}, thr{} { } std::atomic_int cancel; pthread_t thr; @@ -51,7 +51,7 @@ struct EvlApp : RtapiApp { pthread_once(&key_once, init_key); } - struct rtapi_task *do_task_new() { + RtapiTask *do_task_new() { return new EvlTask; } @@ -154,14 +154,14 @@ struct EvlApp : RtapiApp { } long long task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return 0; return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; } int task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return -EINVAL; if (value > task->pll_correction_limit) @@ -210,7 +210,7 @@ struct EvlApp : RtapiApp { } int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); if (!task) return -EINVAL; return task->id; From e0543693cd4317f1422a6ae16a87a38b4076a174 Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Fri, 24 Apr 2026 19:48:38 +0800 Subject: [PATCH 29/43] Makefile: add setcap target for rootless uspace builds New 'setcap' target grants rtapi_app the kernel privileges it needs via file capabilities instead of setuid root: cap_ipc_lock, cap_net_admin, cap_sys_rawio, cap_sys_nice Clears the setuid bit first so a prior 'make setuid' run cannot silently combine with caps. Post-build warning at the end of 'make build-software' now treats either setuid-root OR any file capabilities as "ready to run", and suggests both 'sudo make setuid' and 'sudo make setcap' when neither is set. --- src/Makefile | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 15c9000d13c..4507ed239c5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -57,7 +57,7 @@ endif ifeq ($(MAKECMDGOALS),) TRIVIAL_BUILD=no else -ifeq ($(filter-out docclean clean setuid install tags swish,$(MAKECMDGOALS)),) +ifeq ($(filter-out docclean clean setuid setcap install tags swish,$(MAKECMDGOALS)),) TRIVIAL_BUILD=yes else TRIVIAL_BUILD=no @@ -140,7 +140,13 @@ ifeq ($(RUN_IN_PLACE),yes) ifneq ($(BUILD_SYS),uspace) @if [ -f ../bin/linuxcnc_module_helper ]; then if ! [ `id -u` = 0 -a -O ../bin/linuxcnc_module_helper -a -u ../bin/linuxcnc_module_helper ]; then $(VECHO) "You now need to run 'sudo make setuid' in order to run in place."; fi; fi else - @if [ -f ../bin/rtapi_app ]; then if ! [ `id -u` = 0 -a -O ../bin/rtapi_app -a -u ../bin/rtapi_app ]; then $(VECHO) "You now need to run 'sudo make setuid' in order to run in place with access to hardware."; fi; fi + @if [ -f ../bin/rtapi_app ]; then \ + if [ `id -u` = 0 -a -O ../bin/rtapi_app -a -u ../bin/rtapi_app ]; then :; \ + elif PATH="/sbin:/usr/sbin:$$PATH" command -v getcap >/dev/null 2>&1 \ + && [ -n "`PATH=/sbin:/usr/sbin:$$PATH getcap ../bin/rtapi_app 2>/dev/null`" ]; then :; \ + else $(VECHO) "You now need to run 'sudo make setuid' or 'sudo make setcap' in order to run in place with access to hardware."; \ + fi; \ + fi endif endif @@ -568,6 +574,22 @@ endif chown root ../bin/linuxcnc_module_helper chmod 4750 ../bin/linuxcnc_module_helper +# File capabilities alternative to setuid (uspace only). +# Grants rtapi_app the kernel privileges it needs without running as root: +# cap_ipc_lock - mlock() for realtime memory +# cap_net_admin - raw socket access for hm2_eth / iptables management +# cap_sys_rawio - iopl() and /dev/mem for parallel port and PCI I/O +# cap_sys_nice - SCHED_FIFO scheduling and CPU affinity +# Clears any setuid bit left by a prior 'make setuid' so the two paths don't +# silently stack. +setcap: +ifeq ($(BUILD_SYS),uspace) + chmod u-s ../bin/rtapi_app + setcap cap_ipc_lock,cap_net_admin,cap_sys_rawio,cap_sys_nice+ep ../bin/rtapi_app +else + @echo "setcap target is only supported for uspace builds" >&2; exit 1 +endif + # These rules allows a header file from this directory to be installed into # ../include. A pair of rules like these will exist in the Submakefile # of each file that contains headers. From 8efa29c55a941f1eca3652332c45e9db8cf746e2 Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Fri, 24 Apr 2026 20:43:02 +0800 Subject: [PATCH 30/43] hm2_eth: skip iptables setup when unprivileged Running rtapi_app with file capabilities (via 'make setcap') rather than setuid root means iptables commands can no longer be exec'd: Linux capabilities do not propagate across exec() into the iptables binary, so CAP_NET_ADMIN on rtapi_app is dropped before iptables starts. Two complementary changes handle this cleanly: 1. Auto-detect at module load. A read-only 'iptables -L INPUT' probe runs once the first time use_iptables() is consulted. If it fails (missing binary, permission denial, no shell access) we log a single informational message and mark iptables as unavailable for the rest of the session. No more noisy "ERROR: Failed to create iptables chain" on every rootless module load. 2. Explicit override. New module parameter 'no_iptables=1' disables all iptables interaction even when the probe would succeed, for administrators managing the firewall externally (nftables, firewalld, systemd units). install_iptables_perinterface() gains a use_iptables() gate that was previously missing, bringing it in line with the other iptables call sites. Manpage updated to document the new parameter and the auto-detect behaviour. --- docs/src/man/man9/hm2_eth.9.adoc | 13 ++++++++++++- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 22 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/src/man/man9/hm2_eth.9.adoc b/docs/src/man/man9/hm2_eth.9.adoc index 7cf1b3015a9..8627786a93c 100644 --- a/docs/src/man/man9/hm2_eth.9.adoc +++ b/docs/src/man/man9/hm2_eth.9.adoc @@ -7,7 +7,7 @@ IO boards, with HostMot2 firmware. == SYNOPSIS -*loadrt hm2_eth* [**config=**"__str__[,__str__...]"] [**board_ip=**__ip__[,__ip__...] ] [**board_mac=**__mac__[,__mac__...] ] +*loadrt hm2_eth* [**config=**"__str__[,__str__...]"] [**board_ip=**__ip__[,__ip__...] ] [**board_mac=**__mac__[,__mac__...] ] [**no_iptables=**__0|1__] ____ *config* [default: ""]:: @@ -15,6 +15,17 @@ ____ *board_ip* [default: ""]:: The IP address of the board(s), separated by commas. As shipped, the board address is 192.168.1.121. +*no_iptables* [default: 0]:: + Explicit override that disables all iptables interaction. By default + hm2_eth probes iptables at load time with a read-only listing and + silently skips rule installation if the probe fails (for example + when rtapi_app is running unprivileged via file capabilities rather + than setuid root, since Linux capabilities are not inherited across + exec() into the iptables binary). Set *no_iptables=1* when iptables + is reachable but you prefer to manage the firewall externally + (nftables, firewalld, systemd units). In both cases the + interface-isolation rules must be provided by the administrator; see + the NOTES section below. ____ == DESCRIPTION diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 8cb23fe40b7..914df11cd2d 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -97,6 +97,9 @@ RTAPI_MP_ARRAY_STRING(config, MAX_ETH_BOARDS, "config string for the AnyIO board int debug = 0; RTAPI_MP_INT(debug, "Developer/debug use only! Enable debug logging."); +static int no_iptables = 0; +RTAPI_MP_INT(no_iptables, "Skip automatic iptables rule installation; firewall must be configured externally."); + static int boards_count = 0; int comm_active = 0; @@ -475,6 +478,22 @@ static bool chain_exists() { static int iptables_state = -1; static bool use_iptables() { if(iptables_state == -1) { + if(no_iptables) { + LL_PRINT("Skipping iptables setup (no_iptables=1); configure firewall externally.\n"); + return (iptables_state = 0); + } + // Pre-flight probe: capabilities held by rtapi_app do not propagate + // across exec(), so when we run unprivileged (file-caps instead of + // setuid root) iptables cannot touch the kernel tables. A read-only + // list of a built-in chain is the cheapest way to find out without + // leaving side effects. + if(shell(IPTABLES " -n -L INPUT > /dev/null 2>&1") != EXIT_SUCCESS) { + LL_PRINT("iptables is not available to this process " + "(running unprivileged?); skipping automatic rule " + "installation. Configure firewall externally to " + "isolate the hm2-eth interface.\n"); + return (iptables_state = 0); + } if(!chain_exists()) { int res = shell(IPTABLES " -N " CHAIN); if(res != EXIT_SUCCESS) { @@ -1595,7 +1614,8 @@ int rtapi_app_main(void) { if(!added) goto error; if(*added) continue; - install_iptables_perinterface(ifptr); + if(use_iptables()) + install_iptables_perinterface(ifptr); *added = 1; } From 95c4ecec47d73569fd7e697464c08347e13d011d Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Fri, 24 Apr 2026 20:21:23 +0800 Subject: [PATCH 31/43] rtapi: make rtapi_is_realtime() a SCHED_FIFO success probe Issue #3928 reported three bugs in rtapi_is_realtime(): 1. It required a setuid bit on EMC2_BIN_DIR/rtapi_app, ignoring file capabilities that grant the same kernel privileges. 2. It stat()ed a fixed path instead of the running binary, so wrapper-based installs (NixOS /run/wrappers and similar) never saw the check succeed. 3. It ran the setuid test before LINUXCNC_FORCE_REALTIME, silently discarding the environment variable. PR #918 replaced the whole function with 'return 1', which breaks the sim-vs-rt distinction that GUIs read via hal.is_sim / hal.is_rt and removes the makeApp() sim fallback. Rework rtapi_is_realtime() as a runtime capability probe: briefly set SCHED_FIFO on the calling thread and restore the previous policy, cache the result. LINUXCNC_FORCE_REALTIME short-circuits before the probe, and the RTAI / Xenomai backend detectors still force-true when those environments are present. This matches the convention used by comparable userspace-realtime projects (JACK's jack_is_realtime, PipeWire, rtkit, Xenomai, Klipper): surface observed capability rather than kernel metadata, and let callers who need EPERM visibility use the action API directly. makeApp() drops its own probe and calls rtapi_is_realtime() to choose between SCHED_FIFO and SCHED_OTHER, falling back to POSIX non-realtime with a warning that points at 'make setcap' or 'make setuid'. detect_preempt_rt() is removed -- the probe subsumes it and also works on kernels that no longer expose /sys/kernel/realtime (6.12+). --- src/rtapi/uspace_common.h | 71 +++++++++++++++++++--------------- src/rtapi/uspace_rtapi_main.cc | 10 ++++- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index 5b625ff03bc..0e76d7e39ec 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include @@ -351,25 +353,6 @@ int rtapi_exit(int module_id) } int rtapi_is_kernelspace() { return 0; } -static int _rtapi_is_realtime = -1; -#ifdef __linux__ -static int detect_preempt_rt() { - struct utsname u; - int crit1 = 0; - - uname(&u); - crit1 = strcasestr (u.version, "PREEMPT RT") != 0; - - //"PREEMPT_RT" is used in the version string instead of "PREEMPT RT" starting with kernel version 5.4 - crit1 = crit1 || (strcasestr(u.version, "PREEMPT_RT") != 0); - - return crit1; -} -#else -static int detect_preempt_rt() { - return 0; -} -#endif #ifdef USPACE_RTAI static int detect_rtai() { struct utsname u; @@ -404,22 +387,48 @@ static int detect_xenomai_evl() { } #endif -static int detect_env_override() { - char *p = getenv("LINUXCNC_FORCE_REALTIME"); - return p != NULL && atoi(p) != 0; -} - -static int detect_realtime() { - struct stat st; - if ((stat(EMC2_BIN_DIR "/rtapi_app", &st) < 0) - || st.st_uid != 0 || !(st.st_mode & S_ISUID)) +// Success-probe for realtime scheduling: briefly try to set SCHED_FIFO on +// the calling thread and restore the previous policy. Succeeds when the +// process holds CAP_SYS_NICE (file caps or setuid root) or has a matching +// RLIMIT_RTPRIO. Works on any kernel, so the probe also covers the +// PREEMPT_RT-vs-stock distinction implicitly: if we can actually get +// SCHED_FIFO, the platform can deliver realtime, regardless of how. +static int can_set_sched_fifo(void) { + struct sched_param old_param, probe_param; + int old_policy = sched_getscheduler(0); + if(old_policy < 0) return 0; + if(sched_getparam(0, &old_param) < 0) return 0; + + memset(&probe_param, 0, sizeof(probe_param)); + probe_param.sched_priority = sched_get_priority_min(SCHED_FIFO); + if(sched_setscheduler(0, SCHED_FIFO, &probe_param) < 0) return 0; - return detect_env_override() || detect_preempt_rt() || detect_rtai() || detect_xenomai() || detect_xenomai_evl(); + + // Best-effort restore; if this fails we are still on SCHED_FIFO at + // minimum priority, which is no worse than where we started. + sched_setscheduler(0, old_policy, &old_param); + return 1; } +// rtapi_is_realtime() reports whether this process can actually run +// realtime code. This matches the convention used by JACK, PipeWire, +// rtkit, Xenomai, and Klipper: surface the observed capability, not +// kernel metadata. The old setuid-root stat check has been removed; it +// stat()ed EMC2_BIN_DIR/rtapi_app rather than the running binary (breaking +// wrapper-based installs like NixOS /run/wrappers) and silently masked +// LINUXCNC_FORCE_REALTIME (see issue #3928). int rtapi_is_realtime() { - if(_rtapi_is_realtime == -1) _rtapi_is_realtime = detect_realtime(); - return _rtapi_is_realtime; + static int cached = -1; + if(cached != -1) return cached; + + const char *force = getenv("LINUXCNC_FORCE_REALTIME"); + if(force != NULL && atoi(force) != 0) + return (cached = 1); + + if(detect_rtai() || detect_xenomai() || detect_xenomai_evl()) + return (cached = 1); + + return (cached = can_set_sched_fifo()); } /* Like clock_nanosleep, except that an optional 'estimate of now' parameter may diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 14e24c76ade..915c2787727 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -964,7 +964,15 @@ static RtapiApp *makeDllApp(const std::string &dllName, int policy) { static RtapiApp *makeApp() { RtapiApp *app; - if (WithRoot::getEuid() != 0 || harden_rt() < 0) { + bool rt_ok = rtapi_is_realtime(); + if (!rt_ok) { + rtapi_print_msg(RTAPI_MSG_ERR, + "Note: SCHED_FIFO not permitted for this process, " + "falling back to POSIX non-realtime. " + "Run 'sudo make setcap' (preferred) or 'sudo make setuid' " + "on rtapi_app to enable realtime scheduling.\n"); + } + if (!rt_ok || harden_rt() < 0) { app = makeDllApp("liblinuxcnc-uspace-posix.so.0", SCHED_OTHER); } else { WithRoot r; From 7c0df4d0339beee3876e61953c09e7bd3e9efb52 Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Fri, 24 Apr 2026 20:26:51 +0800 Subject: [PATCH 32/43] udev: rules for rootless Mesa HostMot2 PCI and /dev/cpu_dma_latency Two new rule files installed into /usr/lib/udev/rules.d via debian/extras. Needed only when rtapi_app runs under file capabilities (from 'make setcap'); setuid-root builds bypass DAC regardless. 99-linuxcnc-hm2-pci.rules -- chgrp+chmod PCI config space and resource BARs to the plugdev group for direct-Mesa (vendor 0x2718) cards and for the PLX-bridged Mesa board family (0x10B5 + Mesa subsystem_device ids drawn from hm2_pci.h). 99-linuxcnc-realtime.rules -- chmod /dev/cpu_dma_latency to mode 0660, group plugdev, so harden_rt() can pin the CPU idle state without CAP_DAC_OVERRIDE. plugdev is already the default hotplug group on Debian-derived distros and matches the convention used by the existing Shuttle/XHC rule files. --- .../udev/rules.d/99-linuxcnc-hm2-pci.rules | 51 +++++++++++++++++++ .../udev/rules.d/99-linuxcnc-realtime.rules | 8 +++ 2 files changed, 59 insertions(+) create mode 100644 debian/extras/lib/udev/rules.d/99-linuxcnc-hm2-pci.rules create mode 100644 debian/extras/lib/udev/rules.d/99-linuxcnc-realtime.rules diff --git a/debian/extras/lib/udev/rules.d/99-linuxcnc-hm2-pci.rules b/debian/extras/lib/udev/rules.d/99-linuxcnc-hm2-pci.rules new file mode 100644 index 00000000000..5bf292b4beb --- /dev/null +++ b/debian/extras/lib/udev/rules.d/99-linuxcnc-hm2-pci.rules @@ -0,0 +1,51 @@ +# LinuxCNC - grant Mesa HostMot2 PCI cards to the plugdev group so +# rtapi_app can map their config space and BARs without CAP_DAC_OVERRIDE. +# Needed only for rootless (file-capabilities) builds; setuid-root builds +# bypass DAC regardless. +# +# Add or remove Mesa subsystem IDs below to match new boards. SSIDs come +# from src/hal/drivers/mesa-hostmot2/hm2_pci.h. + +ACTION!="add|change", GOTO="linuxcnc_hm2_end" +SUBSYSTEM!="pci", GOTO="linuxcnc_hm2_end" + +# Direct-Mesa cards (5i24, 5i25, 5i25T, 6i25, 6i25T): single vendor match. +ATTR{vendor}=="0x2718", GOTO="linuxcnc_hm2_chmod" + +# PLX-bridged Mesa cards share vendor 0x10B5 with generic PLX bridges, +# so match Mesa subsystem_device ids one at a time. +ATTR{vendor}!="0x10b5", GOTO="linuxcnc_hm2_end" +# 5i20 +ATTRS{subsystem_device}=="0x3131", GOTO="linuxcnc_hm2_chmod" +# 4i65 +ATTRS{subsystem_device}=="0x3132", GOTO="linuxcnc_hm2_chmod" +# 4i68 (old SSID) +ATTRS{subsystem_device}=="0x3133", GOTO="linuxcnc_hm2_chmod" +# 4i68 (new SSID) +ATTRS{subsystem_device}=="0x3311", GOTO="linuxcnc_hm2_chmod" +# 5i21 +ATTRS{subsystem_device}=="0x3312", GOTO="linuxcnc_hm2_chmod" +# 5i22-1.5M +ATTRS{subsystem_device}=="0x3313", GOTO="linuxcnc_hm2_chmod" +# 5i22-1.0M +ATTRS{subsystem_device}=="0x3314", GOTO="linuxcnc_hm2_chmod" +# 5i23 +ATTRS{subsystem_device}=="0x3315", GOTO="linuxcnc_hm2_chmod" +# 3x20-10 +ATTRS{subsystem_device}=="0x3427", GOTO="linuxcnc_hm2_chmod" +# 3x20-15 +ATTRS{subsystem_device}=="0x3428", GOTO="linuxcnc_hm2_chmod" +# 3x20-20 +ATTRS{subsystem_device}=="0x3429", GOTO="linuxcnc_hm2_chmod" +# 4i69-16 +ATTRS{subsystem_device}=="0x3472", GOTO="linuxcnc_hm2_chmod" +# 4i69-25 +ATTRS{subsystem_device}=="0x3473", GOTO="linuxcnc_hm2_chmod" +GOTO="linuxcnc_hm2_end" + +LABEL="linuxcnc_hm2_chmod" +# Fork a helper; sysfs files may not exist until the device is fully +# sized, so the chmod failures are ignored. +RUN+="/bin/sh -c 'chgrp plugdev /sys%p/config /sys%p/resource* 2>/dev/null; chmod g+rw /sys%p/config /sys%p/resource* 2>/dev/null; exit 0'" + +LABEL="linuxcnc_hm2_end" diff --git a/debian/extras/lib/udev/rules.d/99-linuxcnc-realtime.rules b/debian/extras/lib/udev/rules.d/99-linuxcnc-realtime.rules new file mode 100644 index 00000000000..fa3b057e192 --- /dev/null +++ b/debian/extras/lib/udev/rules.d/99-linuxcnc-realtime.rules @@ -0,0 +1,8 @@ +# LinuxCNC - expose realtime tuning knobs to the plugdev group so that +# rtapi_app can tune latency without CAP_DAC_OVERRIDE when running under +# file capabilities. setuid-root builds do not need this rule. + +# /dev/cpu_dma_latency: harden_rt() opens this to pin CPU idle states at +# C0, cutting wake-up jitter on AC-powered machines. Default is 0600 +# root:root, so an unprivileged rtapi_app would fail to open it. +KERNEL=="cpu_dma_latency", MODE="0660", GROUP="plugdev" From 86d4f72a0fbbf75e5206b94d1f3edeb02c4fc4d3 Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Fri, 24 Apr 2026 21:44:39 +0800 Subject: [PATCH 33/43] rtapi: always attempt mlockall, do not gate on rlim_max PR #918's SCHED_FIFO unification commit replaced setrlimit(RLIMIT_MEMLOCK, unlimited) + unconditional mlockall() with getrlimit + mlockall only when rlim_max >= 2 * PRE_ALLOC_SIZE. This silently skips mlockall inside Debian-packaging CI containers where the default RLIMIT_MEMLOCK is 64 KiB, because 64 KiB is less than the 64 MiB threshold. Without locked pages, thread scheduling jitter causes tests/threads.0 to miss counter increments. Seen as 'line 3097: got 0, expected 10 or 1' on amd64 trixie and sid. Always call mlockall and log failures. A best-effort setrlimit is still attempted (to raise the soft cap to the hard cap without requiring CAP_SYS_RESOURCE), but the mlockall call itself succeeds on any kernel as long as the process has CAP_IPC_LOCK, regardless of the rlimit. --- src/rtapi/uspace_rtapi_main.cc | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 915c2787727..d817d96ecfa 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -816,13 +816,23 @@ static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { const static size_t PRE_ALLOC_SIZE = 1024 * 1024 * 32; const static struct rlimit unlimited = {RLIM_INFINITY, RLIM_INFINITY}; static void configure_memory() { - int res = setrlimit(RLIMIT_MEMLOCK, &unlimited); - if (res < 0) - perror("setrlimit"); + // Best-effort raise of the soft cap to the hard cap. Fails on + // unprivileged processes without CAP_SYS_RESOURCE or without a + // matching setrlimit; CAP_IPC_LOCK alone lets mlockall succeed + // regardless of the rlimit, so ignoring the failure is safe. + struct rlimit limit; + if (getrlimit(RLIMIT_MEMLOCK, &limit) == 0) { + limit.rlim_cur = limit.rlim_max; + if (setrlimit(RLIMIT_MEMLOCK, &limit) < 0) + rtapi_print_msg(RTAPI_MSG_DBG, + "setrlimit(RLIMIT_MEMLOCK) failed: %s\n", strerror(errno)); + } - res = mlockall(MCL_CURRENT | MCL_FUTURE); + int res = mlockall(MCL_CURRENT | MCL_FUTURE); if (res < 0) - perror("mlockall"); + rtapi_print_msg(RTAPI_MSG_WARN, + "mlockall failed: %s. Realtime latency may suffer.\n", + strerror(errno)); #ifdef __linux__ /* Turn off malloc trimming.*/ From 564cf43ba6e2521d284246992480dcd3a34c926d Mon Sep 17 00:00:00 2001 From: Damian Wrobel Date: Sat, 23 May 2020 12:21:46 +0200 Subject: [PATCH 34/43] Use TEMP_FAILURE_RETRY() to handle EINTR properly Signed-off-by: Damian Wrobel --- src/rtapi/uspace_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index 0e76d7e39ec..796fae0dfd5 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -442,7 +442,7 @@ static int rtapi_clock_nanosleep(clockid_t clock_id, int flags, { (void)pnow; #if defined(HAVE_CLOCK_NANOSLEEP) - return clock_nanosleep(clock_id, flags, prequest, remain); + return TEMP_FAILURE_RETRY(clock_nanosleep(clock_id, flags, prequest, remain)); #else if(flags == 0) return nanosleep(prequest, remain); From bc2f9a89eb19a9f382d72d827c4ca1d8f17de7cd Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Sat, 25 Apr 2026 11:44:03 +0800 Subject: [PATCH 35/43] rtapi: make harden_rt() rootless-safe setrlimit(RLIMIT_RTPRIO, RLIM_INFINITY) requires CAP_SYS_RESOURCE, which neither setuid root nor 'make setcap' (CAP_IPC_LOCK, CAP_NET_ADMIN, CAP_SYS_RAWIO, CAP_SYS_NICE) grants by default. Under rootless the call returns EPERM, which the previous code treated as fatal: harden_rt() returned -errno, makeApp() fell back to SCHED_OTHER, and the SCHED_FIFO probe in rtapi_is_realtime() became a lie. Soften both setrlimit calls to best-effort. SCHED_FIFO scheduling itself only needs CAP_SYS_NICE, which the cap set does grant; the rlimit just bounds the achievable priority. Distros that want unlimited RT priority can ship a /etc/security/limits.d entry, or the operator can grant CAP_SYS_RESOURCE explicitly. Also update the iopl() error message: 'sudo make setuid' is no longer the only path, and the diagnostic should name the missing capability (CAP_SYS_RAWIO). Derived from Damian Wrobel's 2020 'Unify FIFO_SCHED between root and non-root user' commit, ported onto hdiethelm's rtapi cleanup v2 structure. --- src/rtapi/uspace_rtapi_main.cc | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index d817d96ecfa..ec0d0e4c3b7 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -886,7 +886,7 @@ static int harden_rt() { RTAPI_MSG_ERR, "iopl() failed: %s\n" "cannot gain I/O privileges - " - "forgot 'sudo make setuid' or using secure boot? -" + "missing CAP_SYS_RAWIO or using secure boot? - " "parallel port access is not allowed\n", strerror(errno) ); @@ -895,19 +895,21 @@ static int harden_rt() { struct sigaction sig_act = {}; #ifdef __linux__ - // enable realtime - if (setrlimit(RLIMIT_RTPRIO, &unlimited) < 0) { - rtapi_print_msg(RTAPI_MSG_WARN, "setrlimit(RTLIMIT_RTPRIO): %s\n", strerror(errno)); - return -errno; - } + // Best-effort raise of RTPRIO/CORE soft caps. Setting these to + // RLIM_INFINITY requires CAP_SYS_RESOURCE, which neither setuid root + // nor file capabilities grant by default. Without it, threads still + // get SCHED_FIFO via CAP_SYS_NICE; the rlimit just gates how high + // they can go. Don't fail harden_rt() when it can't be raised. + if (setrlimit(RLIMIT_RTPRIO, &unlimited) < 0) + rtapi_print_msg(RTAPI_MSG_DBG, + "setrlimit(RLIMIT_RTPRIO): %s\n", strerror(errno)); - // enable core dumps if (setrlimit(RLIMIT_CORE, &unlimited) < 0) rtapi_print_msg( RTAPI_MSG_WARN, "setrlimit: %s - core dumps may be truncated or non-existent\n", strerror(errno) ); - // even when setuid root + // even when running with elevated capabilities if (prctl(PR_SET_DUMPABLE, 1) < 0) rtapi_print_msg( RTAPI_MSG_WARN, From bf9107187c135b423ae087f93c9606444ce68b56 Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Sat, 25 Apr 2026 19:23:49 +0800 Subject: [PATCH 36/43] rtapi: warn on stock kernel, gate Xenomai/RTAI on setuid Address two review concerns from @hdiethelm on PR #3964: 1. SCHED_FIFO probe was too generous on PREEMPT_DYNAMIC stock kernels. The probe correctly reports that SCHED_FIFO is achievable, but the resulting latency on a non-PREEMPT_RT kernel can be tens of milliseconds, surprising users who read 'POSIX realtime' and expect bounded scheduling. Restore detect_preempt_rt() (uname-based) and emit a one-shot warning at makeApp() when the SCHED_FIFO path is chosen but the kernel lacks PREEMPT_RT and there is no Xenomai/RTAI backend. Behavior is unchanged: SCHED_FIFO on stock is still strictly better than SCHED_OTHER, the warning just makes the tradeoff visible. 2. Xenomai/RTAI backends still need root for iopl() (RTAI) or RTDM device access (Xenomai/EVL) and were being selected for unprivileged users on a Xenomai kernel, leading to 'iopl() failed: Operation not permitted'. Gate detect_rtai/detect_xenomai/detect_xenomai_evl on geteuid()==0 so unprivileged callers fall through to the SCHED_FIFO probe and a clean POSIX path. This is a band-aid pending proper capability/group support (udev rules + 'xenomai'/'evl' group membership, the approach Xenomai's own docs recommend); marked with a FIXME pointing at @hdiethelm's planned follow-up. The probe-based rtapi_is_realtime() itself is unchanged: it remains a pure capability check, matching the convention in JACK, PipeWire, rtkit, and Klipper. Kernel quality is reported as a separate diagnostic, not folded into the boolean. --- src/rtapi/uspace_common.h | 40 ++++++++++++++++++++++++++++++++++ src/rtapi/uspace_rtapi_main.cc | 11 ++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index 796fae0dfd5..8ee75734841 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -353,8 +353,46 @@ int rtapi_exit(int module_id) } int rtapi_is_kernelspace() { return 0; } + +#ifdef __linux__ +// detect_preempt_rt() inspects uname for the PREEMPT_RT marker. Used only +// for diagnostic warning at startup; callers must not gate behavior on +// the kernel string, since SCHED_FIFO on a PREEMPT_DYNAMIC kernel is still +// useful (better than SCHED_OTHER, worse than PREEMPT_RT). +// Marked unused because uspace_common.h is also included from ULAPI TUs +// that do not reference it. +__attribute__((unused)) +static int detect_preempt_rt() { + struct utsname u; + if(uname(&u) < 0) return 0; + return strcasestr(u.version, "PREEMPT RT") != 0 + || strcasestr(u.version, "PREEMPT_RT") != 0; +} +#else +__attribute__((unused)) +static int detect_preempt_rt() { + return 0; +} +#endif + +// FIXME: detect_rtai/detect_xenomai/detect_xenomai_evl currently gate on +// setuid because the RTAI/Xenomai backends still need root for iopl() +// (RTAI) or RTDM device access (Xenomai/EVL). Long-term these should +// probe the actual capability the way can_set_sched_fifo() does, paired +// with udev rules + a 'xenomai'/'evl' group; @hdiethelm has a follow-up +// planned. Until then, an unprivileged user on a Xenomai kernel cannot +// claim the Xenomai backend, and falls back to the SCHED_FIFO probe. +// Marked unused because the helper is called only when one of the +// USPACE_RTAI / USPACE_XENOMAI / USPACE_XENOMAI_EVL macros is defined, +// and the header is included from ULAPI TUs that define none of them. +__attribute__((unused)) +static int has_setuid_root() { + return geteuid() == 0; +} + #ifdef USPACE_RTAI static int detect_rtai() { + if(!has_setuid_root()) return 0; struct utsname u; uname(&u); return strcasestr (u.release, "-rtai") != 0; @@ -366,6 +404,7 @@ static int detect_rtai() { #endif #ifdef USPACE_XENOMAI static int detect_xenomai() { + if(!has_setuid_root()) return 0; struct stat sb; //Running xenomai has /proc/xenomai return stat("/proc/xenomai", &sb) == 0; @@ -377,6 +416,7 @@ static int detect_xenomai() { #endif #ifdef USPACE_XENOMAI_EVL static int detect_xenomai_evl() { + if(!has_setuid_root()) return 0; struct stat sb; //Running xenomai evl has /dev/evl but no /proc/xenomai return stat("/dev/evl", &sb) == 0; diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index ec0d0e4c3b7..6d7b6036046 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -995,6 +995,17 @@ static RtapiApp *makeApp() { } else if (detect_rtai()) { app = makeDllApp("liblinuxcnc-uspace-rtai.so.0", SCHED_FIFO); } else { + // SCHED_FIFO available but no Xenomai/RTAI backend. Warn if the + // kernel is not PREEMPT_RT: SCHED_FIFO still beats SCHED_OTHER, + // but latency on a PREEMPT_DYNAMIC stock kernel can be tens of + // milliseconds, which will surprise users who expect the same + // bounds as a PREEMPT_RT or Xenomai setup. + if (!detect_preempt_rt()) { + rtapi_print_msg(RTAPI_MSG_ERR, + "Note: SCHED_FIFO available but kernel is not PREEMPT_RT. " + "Latency may be unbounded; install a PREEMPT_RT kernel " + "for hard realtime guarantees.\n"); + } app = makeDllApp("liblinuxcnc-uspace-posix.so.0", SCHED_FIFO); } } From bfee183f0cfd897e458db12a2a03e2210efd667b Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sat, 25 Apr 2026 16:19:14 +0200 Subject: [PATCH 37/43] hm2_eth: Fully cleanup iptables --- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 914df11cd2d..b3aa6a4ec43 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -516,6 +516,12 @@ static void clear_iptables() { shell(IPTABLES" -F "CHAIN" > /dev/null 2>&1"); } +static void cleanup_iptables() { + shell(IPTABLES" -F "CHAIN" > /dev/null 2>&1"); + shell(IPTABLES" -D OUTPUT -j "CHAIN" > /dev/null 2>&1"); + shell(IPTABLES" -X "CHAIN" > /dev/null 2>&1"); +} + static char* inet_ntoa_buf(struct in_addr in, char *buf, size_t n) { const char *addr = inet_ntoa(in); snprintf(buf, n, "%s", addr); @@ -1639,7 +1645,7 @@ void rtapi_app_exit(void) { for(i = 0; i Date: Mon, 27 Apr 2026 15:33:11 +0800 Subject: [PATCH 38/43] rtapi: raise CAP_NET_ADMIN into ambient set, diagnose realtime fallback File capabilities on rtapi_app give cap_net_admin in the permitted and effective sets but not inheritable or ambient, so /sbin/iptables launched by HAL drivers like hm2_eth runs cap-less and fails with EPERM. Raise CAP_NET_ADMIN into the ambient set at startup so it survives execve() into child processes. No-op when the cap is not held. Also expand the fallback-to-POSIX warning to print the sched_setscheduler errno, the effective cap_sys_nice and cap_ipc_lock state via libcap, and mention LINUXCNC_FORCE_REALTIME as a testing-only override. Addresses the diagnostic-output request in issue #3928. Switch the existing detect_preempt_rt and has_setuid_root helpers from __attribute__((unused)) to static inline for codebase consistency. --- src/Makefile | 3 ++ src/rtapi/Submakefile | 2 +- src/rtapi/uspace_common.h | 36 ++++++++++------- src/rtapi/uspace_rtapi_main.cc | 74 ++++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 19 deletions(-) diff --git a/src/Makefile b/src/Makefile index 4507ed239c5..5eec56cdfc7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -580,6 +580,9 @@ endif # cap_net_admin - raw socket access for hm2_eth / iptables management # cap_sys_rawio - iopl() and /dev/mem for parallel port and PCI I/O # cap_sys_nice - SCHED_FIFO scheduling and CPU affinity +# Linux capabilities are not inherited across exec(), so /sbin/iptables +# launched from rtapi_app would run unprivileged. rtapi_app raises +# cap_net_admin into its ambient set at startup so it survives execve(). # Clears any setuid bit left by a prior 'make setuid' so the two paths don't # silently stack. setcap: diff --git a/src/rtapi/Submakefile b/src/rtapi/Submakefile index 531536aa8f1..b69918baa80 100644 --- a/src/rtapi/Submakefile +++ b/src/rtapi/Submakefile @@ -44,7 +44,7 @@ $(call TOOBJSDEPS, $(RTAPI_APP_SRCS)): EXTRAFLAGS += -DSIM \ -UULAPI -DRTAPI -pthread ../bin/rtapi_app: $(call TOOBJS, $(RTAPI_APP_SRCS)) $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt -lfmt $(LIBUDEV_LIBS) -ldl $(LDFLAGS) + $(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt -lfmt $(LIBUDEV_LIBS) -ldl -lcap $(LDFLAGS) TARGETS += ../bin/rtapi_app endif diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index 8ee75734841..a7981164b37 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -359,18 +359,14 @@ int rtapi_is_kernelspace() { return 0; } // for diagnostic warning at startup; callers must not gate behavior on // the kernel string, since SCHED_FIFO on a PREEMPT_DYNAMIC kernel is still // useful (better than SCHED_OTHER, worse than PREEMPT_RT). -// Marked unused because uspace_common.h is also included from ULAPI TUs -// that do not reference it. -__attribute__((unused)) -static int detect_preempt_rt() { +static inline int detect_preempt_rt() { struct utsname u; if(uname(&u) < 0) return 0; return strcasestr(u.version, "PREEMPT RT") != 0 || strcasestr(u.version, "PREEMPT_RT") != 0; } #else -__attribute__((unused)) -static int detect_preempt_rt() { +static inline int detect_preempt_rt() { return 0; } #endif @@ -382,11 +378,7 @@ static int detect_preempt_rt() { // with udev rules + a 'xenomai'/'evl' group; @hdiethelm has a follow-up // planned. Until then, an unprivileged user on a Xenomai kernel cannot // claim the Xenomai backend, and falls back to the SCHED_FIFO probe. -// Marked unused because the helper is called only when one of the -// USPACE_RTAI / USPACE_XENOMAI / USPACE_XENOMAI_EVL macros is defined, -// and the header is included from ULAPI TUs that define none of them. -__attribute__((unused)) -static int has_setuid_root() { +static inline int has_setuid_root() { return geteuid() == 0; } @@ -427,6 +419,11 @@ static int detect_xenomai_evl() { } #endif +// errno from the most recent sched_setscheduler(SCHED_FIFO) probe. Zero +// when the probe succeeded or has not run yet. Read via +// rtapi_sched_fifo_errno() from diagnostic code. +static int rtapi_sched_fifo_last_errno = 0; + // Success-probe for realtime scheduling: briefly try to set SCHED_FIFO on // the calling thread and restore the previous policy. Succeeds when the // process holds CAP_SYS_NICE (file caps or setuid root) or has a matching @@ -436,20 +433,31 @@ static int detect_xenomai_evl() { static int can_set_sched_fifo(void) { struct sched_param old_param, probe_param; int old_policy = sched_getscheduler(0); - if(old_policy < 0) return 0; - if(sched_getparam(0, &old_param) < 0) return 0; + if(old_policy < 0) { + rtapi_sched_fifo_last_errno = errno; + return 0; + } + if(sched_getparam(0, &old_param) < 0) { + rtapi_sched_fifo_last_errno = errno; + return 0; + } memset(&probe_param, 0, sizeof(probe_param)); probe_param.sched_priority = sched_get_priority_min(SCHED_FIFO); - if(sched_setscheduler(0, SCHED_FIFO, &probe_param) < 0) + if(sched_setscheduler(0, SCHED_FIFO, &probe_param) < 0) { + rtapi_sched_fifo_last_errno = errno; return 0; + } // Best-effort restore; if this fails we are still on SCHED_FIFO at // minimum priority, which is no worse than where we started. sched_setscheduler(0, old_policy, &old_param); + rtapi_sched_fifo_last_errno = 0; return 1; } +static inline int rtapi_sched_fifo_errno(void) { return rtapi_sched_fifo_last_errno; } + // rtapi_is_realtime() reports whether this process can actually run // realtime code. This matches the convention used by JACK, PipeWire, // rtkit, Xenomai, and Klipper: surface the observed capability, not diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc index 6d7b6036046..15798f4784a 100644 --- a/src/rtapi/uspace_rtapi_main.cc +++ b/src/rtapi/uspace_rtapi_main.cc @@ -46,6 +46,7 @@ #ifdef __linux__ #include #include +#include #endif #ifdef __FreeBSD__ #include @@ -665,6 +666,10 @@ static double diff_timespec(const struct timespec *time1, const struct timespec return (time1->tv_sec - time0->tv_sec) + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0; } +#ifdef __linux__ +static void raise_net_admin_ambient(void); +#endif + int main(int argc, char **argv) { if (getuid() == 0) { char *fallback_uid_str = getenv("RTAPI_UID"); @@ -696,6 +701,7 @@ int main(int argc, char **argv) { } #ifdef __linux__ setfsuid(ruid); + raise_net_admin_ambient(); #endif std::vector args; for (int i = 1; i < argc; i++) { @@ -974,15 +980,75 @@ static RtapiApp *makeDllApp(const std::string &dllName, int policy) { return result; } +// Diagnostic helper: report cap_effective state for a single capability. +// Returns "yes", "no", or "unknown" if libcap could not introspect. +#ifdef __linux__ +static const char *cap_effective_str(cap_t caps, cap_value_t cap) { + if (!caps) return "unknown"; + cap_flag_value_t v; + if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &v) != 0) return "unknown"; + return v == CAP_SET ? "yes" : "no"; +} + +// Raise CAP_NET_ADMIN into the ambient set so it survives execve() into +// child processes (iptables, ip6tables) launched by HAL drivers like +// hm2_eth. Linux file capabilities on rtapi_app give cap_net_admin in +// the permitted+effective sets but not inheritable/ambient, so without +// this iptables runs cap-less and fails with EPERM. No-op when the cap +// is not held (e.g. running unprivileged). +static void raise_net_admin_ambient(void) { + cap_t caps = cap_get_proc(); + if (!caps) return; + + cap_value_t cap = CAP_NET_ADMIN; + cap_flag_value_t v; + if (cap_get_flag(caps, cap, CAP_PERMITTED, &v) == 0 && v == CAP_SET) { + if (cap_set_flag(caps, CAP_INHERITABLE, 1, &cap, CAP_SET) == 0 + && cap_set_proc(caps) == 0) { + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, + CAP_NET_ADMIN, 0, 0) != 0 + && geteuid() != 0) { + rtapi_print_msg(RTAPI_MSG_WARN, + "rtapi_app: PR_CAP_AMBIENT_RAISE(CAP_NET_ADMIN) " + "failed: %s; iptables-using drivers may not work " + "under file caps.\n", strerror(errno)); + } + } + } + cap_free(caps); +} +#endif + static RtapiApp *makeApp() { RtapiApp *app; bool rt_ok = rtapi_is_realtime(); if (!rt_ok) { + // Surface the actual reason so the user does not have to guess + // between "no caps", "stock kernel", or "wrong rlimits" (issue + // #3928). errno comes from the SCHED_FIFO probe in + // can_set_sched_fifo(); cap state comes from libcap. + int sched_err = rtapi_sched_fifo_errno(); +#ifdef __linux__ + cap_t caps = cap_get_proc(); + const char *nice_s = cap_effective_str(caps, CAP_SYS_NICE); + const char *lock_s = cap_effective_str(caps, CAP_IPC_LOCK); +#else + const char *nice_s = "unknown"; + const char *lock_s = "unknown"; +#endif rtapi_print_msg(RTAPI_MSG_ERR, - "Note: SCHED_FIFO not permitted for this process, " - "falling back to POSIX non-realtime. " - "Run 'sudo make setcap' (preferred) or 'sudo make setuid' " - "on rtapi_app to enable realtime scheduling.\n"); + "Note: realtime scheduling unavailable " + "(sched_setscheduler SCHED_FIFO: %s).\n" + " Process capabilities: cap_sys_nice=%s cap_ipc_lock=%s.\n" + " Falling back to POSIX non-realtime.\n" + " Fix: 'sudo make setcap' (preferred) or 'sudo make setuid' " + "on rtapi_app.\n" + " Override (testing only): set LINUXCNC_FORCE_REALTIME=1.\n", + sched_err ? strerror(sched_err) : "denied", + nice_s, lock_s); +#ifdef __linux__ + if (caps) cap_free(caps); +#endif } if (!rt_ok || harden_rt() < 0) { app = makeDllApp("liblinuxcnc-uspace-posix.so.0", SCHED_OTHER); From 1ac7990b302e5a752c7aaa8f97e8db1b4fda1ba1 Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Mon, 27 Apr 2026 15:33:22 +0800 Subject: [PATCH 39/43] hm2_eth: install iptables and ip6tables rules directly Replace the shell-based "iptables" macro path with structured posix_spawn calls into /sbin/iptables and /sbin/ip6tables. The rtapi_app process raises CAP_NET_ADMIN into its ambient set at startup, so the calls succeed under both setuid-root and rootless (file-cap) installs without a separate helper binary. Restore --sport in the per-board ACCEPT rule: cleanup_iptables() now removes the chain on exit, so a stale rule from a previous run with a different ephemeral port cannot block the second invocation. Replace the IPv6 sysctl call (which needs CAP_DAC_OVERRIDE) with an ip6tables OUTPUT DROP on a parallel hm2-eth-rules-output chain, so the firewall stays inside the cap_net_admin envelope. Update the manual recipe in hm2_eth(9) to match, and document the optional sysctl.conf step for users who want full IPv6 quiescence. --- docs/src/man/man9/hm2_eth.9.adoc | 64 ++++++- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 229 ++++++++++++++---------- 2 files changed, 186 insertions(+), 107 deletions(-) diff --git a/docs/src/man/man9/hm2_eth.9.adoc b/docs/src/man/man9/hm2_eth.9.adoc index 8627786a93c..cdb29cedddd 100644 --- a/docs/src/man/man9/hm2_eth.9.adoc +++ b/docs/src/man/man9/hm2_eth.9.adoc @@ -17,15 +17,14 @@ ____ As shipped, the board address is 192.168.1.121. *no_iptables* [default: 0]:: Explicit override that disables all iptables interaction. By default - hm2_eth probes iptables at load time with a read-only listing and - silently skips rule installation if the probe fails (for example - when rtapi_app is running unprivileged via file capabilities rather - than setuid root, since Linux capabilities are not inherited across - exec() into the iptables binary). Set *no_iptables=1* when iptables - is reachable but you prefer to manage the firewall externally - (nftables, firewalld, systemd units). In both cases the - interface-isolation rules must be provided by the administrator; see - the NOTES section below. + hm2_eth installs *iptables* and *ip6tables* rules itself; rtapi_app + raises *cap_net_admin* into its ambient capability set at startup so + the calls succeed under both setuid-root and rootless (file-cap) + installs. If the cap is not held the probe fails and rule + installation is skipped with a warning; in that case configure the + rules manually using the recipe in the NOTES section below. Set + *no_iptables=1* when iptables is reachable but you prefer to manage + the firewall externally (nftables, firewalld, systemd units). ____ == DESCRIPTION @@ -157,6 +156,53 @@ At (normal) exit, hm2_eth will remove the rules. After a crash, you can manually clear the rules with *sudo iptables -F hm2-eth-rules-output*; the rules are also removed by a reboot. +=== Manual iptables configuration + +When LinuxCNC is installed without *cap_net_admin* on rtapi_app +(typically because *sudo make setcap* was not run after the build), +hm2_eth cannot install its rules and prints a warning. Set up the +chain manually as root. Adjust the IP addresses, UDP destination port, +and interface name to match your install: + +---- +HOST_IP=192.168.1.1 +BOARD_IP=192.168.1.121 +BOARD_DPORT=27181 +IFACE=eth1 + +iptables -N hm2-eth-rules-output +iptables -I OUTPUT 1 -j hm2-eth-rules-output +iptables -A hm2-eth-rules-output \ + -p udp -m udp -d $BOARD_IP --dport $BOARD_DPORT \ + -s $HOST_IP -j ACCEPT +iptables -A hm2-eth-rules-output -o $IFACE -p icmp -j DROP +iptables -A hm2-eth-rules-output -o $IFACE \ + -j REJECT --reject-with icmp-admin-prohibited +ip6tables -N hm2-eth-rules-output +ip6tables -I OUTPUT 1 -j hm2-eth-rules-output +ip6tables -A hm2-eth-rules-output -o $IFACE -j DROP +---- + +For full IPv6 quiescence (no router solicitations or neighbor discovery +on the dedicated interface), additionally add this line to +`/etc/sysctl.d/99-hm2-eth.conf` and reboot: + +---- +net.ipv6.conf.IFACE.disable_ipv6 = 1 +---- + +(The default ip6tables rule above only drops outbound IPv6; the kernel +still generates the packets.) Tear down the runtime rules with: + +---- +iptables -F hm2-eth-rules-output +iptables -D OUTPUT -j hm2-eth-rules-output +iptables -X hm2-eth-rules-output +ip6tables -F hm2-eth-rules-output +ip6tables -D OUTPUT -j hm2-eth-rules-output +ip6tables -X hm2-eth-rules-output +---- + "hardware-irq-coalesce-rx-usecs" decreases time waiting to receive a packet on most systems, but on at least some Marvel-chipset NICs it is harmful. If the line does not improve system performance, then remove it. diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index b3aa6a4ec43..5d78d85cf7e 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -45,7 +46,6 @@ #include "hostmot2-lowlevel.h" #include "hostmot2.h" #include "hm2_eth.h" -#include "eshellf.h" struct kvlist { struct rtapi_list_head list; @@ -466,60 +466,115 @@ static hm2_eth_t boards[MAX_ETH_BOARDS]; static int eth_socket_send(int sockfd, const void *buffer, int len, int flags); static int eth_socket_recv(int sockfd, void *buffer, int len, int flags); -#define IPTABLES "env \"PATH=/usr/sbin:/sbin:${PATH}\" iptables" +// hm2_eth installs iptables/ip6tables rules to isolate the dedicated Mesa +// interface from non-realtime traffic. rtapi_app raises CAP_NET_ADMIN +// into its ambient set at startup (see uspace_rtapi_main.cc), so the +// caps survive execve() into the iptables binaries even when we run +// under file caps instead of setuid root. +#define IPTABLES_BIN "/sbin/iptables" +#define IP6TABLES_BIN "/sbin/ip6tables" #define CHAIN "hm2-eth-rules-output" -static bool chain_exists() { - int result = - shell(IPTABLES" -n -L "CHAIN" > /dev/null 2>&1"); - return result == EXIT_SUCCESS; +// run_iptables(): fork+exec the named iptables binary with a +// NULL-terminated argv list (sentinel == NULL, do not omit). Returns +// the child exit status, or -1 on spawn/wait failure. When quiet, the +// child's stdout+stderr are suppressed so probe-style "is this rule +// present?" calls do not spam the log on the (expected) failure path. +static int run_iptables(const char *bin, int quiet, ...) { + char *argv[24]; + int n = 0; + // argv[0] is the program name iptables expects, not the path. + argv[n++] = (char *)(strrchr(bin, '/') ? strrchr(bin, '/') + 1 : bin); + + va_list ap; + va_start(ap, quiet); + while(n < (int)(sizeof(argv)/sizeof(argv[0])) - 1) { + char *a = va_arg(ap, char *); + if(!a) break; + argv[n++] = a; + } + argv[n] = NULL; + va_end(ap); + + posix_spawn_file_actions_t fa, *pfa = NULL; + if(quiet && posix_spawn_file_actions_init(&fa) == 0) { + if(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, + "/dev/null", O_WRONLY, 0) == 0 + && posix_spawn_file_actions_adddup2(&fa, STDOUT_FILENO, + STDERR_FILENO) == 0) { + pfa = &fa; + } + } + + pid_t pid; + int r = posix_spawn(&pid, bin, pfa, NULL, argv, environ); + if(pfa) posix_spawn_file_actions_destroy(&fa); + + if(r != 0) return -1; + int status; + if(waitpid(pid, &status, 0) < 0) return -1; + if(WIFEXITED(status)) return WEXITSTATUS(status); + return -1; } +#define IPT(quiet, ...) run_iptables(IPTABLES_BIN, (quiet), __VA_ARGS__, NULL) +#define IP6T(quiet, ...) run_iptables(IP6TABLES_BIN, (quiet), __VA_ARGS__, NULL) + static int iptables_state = -1; static bool use_iptables() { if(iptables_state == -1) { if(no_iptables) { - LL_PRINT("Skipping iptables setup (no_iptables=1); configure firewall externally.\n"); + LL_PRINT("Skipping iptables setup (no_iptables=1); " + "configure firewall externally.\n"); return (iptables_state = 0); } - // Pre-flight probe: capabilities held by rtapi_app do not propagate - // across exec(), so when we run unprivileged (file-caps instead of - // setuid root) iptables cannot touch the kernel tables. A read-only - // list of a built-in chain is the cheapest way to find out without - // leaving side effects. - if(shell(IPTABLES " -n -L INPUT > /dev/null 2>&1") != EXIT_SUCCESS) { - LL_PRINT("iptables is not available to this process " - "(running unprivileged?); skipping automatic rule " - "installation. Configure firewall externally to " - "isolate the hm2-eth interface.\n"); + // Read-only probe: list the INPUT chain. Fails when the + // process lacks CAP_NET_ADMIN (rootless without setcap, or + // setcap applied but ambient raise failed in rtapi_app). + if(IPT(1, "-n", "-L", "INPUT") != 0) { + LL_PRINT("iptables not available (missing CAP_NET_ADMIN?); " + "automatic firewall setup skipped. See hm2_eth(9) " + "NOTES for the manual rule recipe.\n"); return (iptables_state = 0); } - if(!chain_exists()) { - int res = shell(IPTABLES " -N " CHAIN); - if(res != EXIT_SUCCESS) { - LL_PRINT("ERROR: Failed to create iptables chain "CHAIN); + // Create chain only if absent; insert OUTPUT jump only if absent. + if(IPT(1, "-n", "-L", CHAIN) != 0) { + if(IPT(0, "-N", CHAIN) != 0) { + LL_PRINT("ERROR: failed to create iptables chain " CHAIN "\n"); return (iptables_state = 0); } } - // now add a jump to our chain at the start of the OUTPUT chain if it isn't in the chain already - int res = shell(IPTABLES "-C OUTPUT -j " CHAIN " 2>/dev/null || /sbin/iptables -I OUTPUT 1 -j " CHAIN); - if(res != EXIT_SUCCESS) { - LL_PRINT("ERROR: Failed to insert rule in OUTPUT chain"); - return (iptables_state = 0); + if(IPT(1, "-C", "OUTPUT", "-j", CHAIN) != 0) { + if(IPT(0, "-I", "OUTPUT", "1", "-j", CHAIN) != 0) { + LL_PRINT("ERROR: failed to insert OUTPUT jump\n"); + return (iptables_state = 0); + } } + // Mirror the chain for ip6tables so IPv6 isolation can hang off + // it. Best-effort: kernels without IPv6 support cause this to + // fail silently and the IPv6 rules are simply absent. + if(IP6T(1, "-n", "-L", CHAIN) != 0) + IP6T(1, "-N", CHAIN); + if(IP6T(1, "-C", "OUTPUT", "-j", CHAIN) != 0) + IP6T(1, "-I", "OUTPUT", "1", "-j", CHAIN); + return (iptables_state = 1); } return iptables_state; } static void clear_iptables() { - shell(IPTABLES" -F "CHAIN" > /dev/null 2>&1"); + IPT(1, "-F", CHAIN); + IP6T(1, "-F", CHAIN); } static void cleanup_iptables() { - shell(IPTABLES" -F "CHAIN" > /dev/null 2>&1"); - shell(IPTABLES" -D OUTPUT -j "CHAIN" > /dev/null 2>&1"); - shell(IPTABLES" -X "CHAIN" > /dev/null 2>&1"); + IPT(1, "-F", CHAIN); + IPT(1, "-D", "OUTPUT", "-j", CHAIN); + IPT(1, "-X", CHAIN); + IP6T(1, "-F", CHAIN); + IP6T(1, "-D", "OUTPUT", "-j", CHAIN); + IP6T(1, "-X", CHAIN); } static char* inet_ntoa_buf(struct in_addr in, char *buf, size_t n) { @@ -555,46 +610,10 @@ static char* fetch_ifname(int sockfd, char *buf, size_t n) { return NULL; } -static char *vseprintf(char *buf, char *ebuf, const char *fmt, va_list ap) { - int result = vsnprintf(buf, ebuf-buf, fmt, ap); - if(result < 0) return ebuf; - else if(buf + result > ebuf) return ebuf; - else return buf + result; -} - -static char *seprintf(char *buf, char *ebuf, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - char *result = vseprintf(buf, ebuf, fmt, ap); - va_end(ap); - return result; -} - -static int install_iptables_rule(const char *fmt, ...) { - char commandbuf[1024], *ptr = commandbuf, - *ebuf = commandbuf + sizeof(commandbuf); - ptr = seprintf(ptr, ebuf, IPTABLES" -A "CHAIN" "); - va_list ap; - va_start(ap, fmt); - ptr = vseprintf(ptr, ebuf, fmt, ap); - va_end(ap); - - if(ptr == ebuf) - { - LL_PRINT("ERROR: commandbuf too small\n"); - return -ENOSPC; - } - - int res = shell(commandbuf); - if(res == EXIT_SUCCESS) return 0; - - LL_PRINT("ERROR: Failed to execute '%s'\n", commandbuf); - return -EINVAL; -} - static int install_iptables_board(int sockfd) { struct sockaddr_in srcaddr, dstaddr; char srchost[16], dsthost[16]; // enough for 255.255.255.255\0 + char dport_s[8], sport_s[8]; socklen_t addrlen = sizeof(srcaddr); int res = getsockname(sockfd, &srcaddr, &addrlen); @@ -604,33 +623,48 @@ static int install_iptables_board(int sockfd) { res = getpeername(sockfd, &dstaddr, &addrlen); if(res < 0) return -errno; - res = install_iptables_rule( - "-p udp -m udp -d %s --dport %d -s %s --sport %d -j ACCEPT", - inet_ntoa_buf(dstaddr.sin_addr, dsthost, sizeof(dsthost)), - ntohs(dstaddr.sin_port), - inet_ntoa_buf(srcaddr.sin_addr, srchost, sizeof(srchost)), - ntohs(srcaddr.sin_port)); - return res; + if(!use_iptables()) return 0; + + inet_ntoa_buf(srcaddr.sin_addr, srchost, sizeof(srchost)); + inet_ntoa_buf(dstaddr.sin_addr, dsthost, sizeof(dsthost)); + snprintf(dport_s, sizeof(dport_s), "%d", ntohs(dstaddr.sin_port)); + snprintf(sport_s, sizeof(sport_s), "%d", ntohs(srcaddr.sin_port)); + + // --sport is safe here: cleanup_iptables() removes the chain on exit, + // so a stale rule from a previous run with a different ephemeral port + // cannot block the second invocation. + if(IPT(0, "-A", CHAIN, + "-p", "udp", "-m", "udp", + "-d", dsthost, "--dport", dport_s, + "-s", srchost, "--sport", sport_s, + "-j", "ACCEPT") != 0) + return -EINVAL; + return 0; } static int install_iptables_perinterface(const char *ifbuf) { - // without this rule, 'ping' spews a lot of messages like - // From 192.168.1.1 icmp_seq=5 Packet filtered - // many times for each ping packet sent. With this rule, - // ping prints 'ping: sendmsg: Operation not permitted' once - // per second. - int res = install_iptables_rule( - "-o %s -p icmp -j DROP", - ifbuf); - if(res < 0) return res; - - res = install_iptables_rule( - "-o %s -j REJECT --reject-with icmp-admin-prohibited", - ifbuf); - if(res < 0) return res; - - res = eshellf(HM2_LLIO_NAME, "/sbin/sysctl -q net.ipv6.conf.%s.disable_ipv6=1", ifbuf); - if(res < 0) return res; + // Without these rules, 'ping' spews a lot of "Packet filtered" + // messages. With them, ping prints 'ping: sendmsg: Operation not + // permitted' once per second. + // + // Outbound IPv6 on the dedicated interface is dropped via ip6tables + // rather than disable_ipv6 sysctl: writing the sysctl needs + // CAP_DAC_OVERRIDE (file is mode 644 root:root) and we'd rather not + // grant it to rtapi_app. Users who want full IPv6 quiescence (no + // router solicitations etc.) can additionally set + // 'net.ipv6.conf..disable_ipv6=1' in /etc/sysctl.conf. + if(!use_iptables()) return 0; + + if(IPT(0, "-A", CHAIN, "-o", (char *)ifbuf, "-p", "icmp", "-j", "DROP") != 0) + return -EINVAL; + if(IPT(0, "-A", CHAIN, "-o", (char *)ifbuf, + "-j", "REJECT", "--reject-with", "icmp-admin-prohibited") != 0) + return -EINVAL; + + // ip6tables is best-effort: kernel may not have IPv6 support + // compiled in, in which case the chain creation in use_iptables() + // already failed and this rule is simply absent. + IP6T(1, "-A", CHAIN, "-o", (char *)ifbuf, "-j", "DROP"); return 0; } @@ -734,11 +768,11 @@ static int init_board(hm2_eth_t *board, const char *board_ip) { return -errno; } - if(use_iptables()) - { - ret = install_iptables_board(board->sockfd); - if(ret < 0) return ret; - } + // install_iptables_board() is a no-op when iptables is unavailable + // (rootless install without setcap on hm2_eth_iptables, or + // no_iptables=1), so it is safe to call unconditionally. + ret = install_iptables_board(board->sockfd); + if(ret < 0) return ret; board->write_packet_ptr = board->write_packet; board->read_packet_ptr = board->read_packet; @@ -1620,8 +1654,7 @@ int rtapi_app_main(void) { if(!added) goto error; if(*added) continue; - if(use_iptables()) - install_iptables_perinterface(ifptr); + install_iptables_perinterface(ifptr); *added = 1; } From 77cc55730be9338f006b3e60dd4818bb5e8c6d87 Mon Sep 17 00:00:00 2001 From: Luca Toniolo Date: Mon, 27 Apr 2026 15:33:26 +0800 Subject: [PATCH 40/43] debian: add libcap-dev build dep rtapi_app links libcap to introspect process capabilities for the realtime-fallback diagnostic and to raise CAP_NET_ADMIN into its ambient set at startup. --- debian/control.top.in | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control.top.in b/debian/control.top.in index 1016d940fc9..54317ce6102 100644 --- a/debian/control.top.in +++ b/debian/control.top.in @@ -28,6 +28,7 @@ Build-Depends: libgl-dev | libgl1-mesa-dev, libglu1-mesa-dev, libgtk-3-dev, + libcap-dev, libmodbus-dev (>= 3.0), libgpiod-dev, @LIBREADLINE_DEV@, From 724784b085de3db123a1561c6549133be278e99d Mon Sep 17 00:00:00 2001 From: Damian Wrobel Date: Mon, 25 May 2020 20:31:29 +0200 Subject: [PATCH 41/43] rootless: incr_mem_usage() Allow the incr_mem_usage() to be used without root privileges. Signed-off-by: Damian Wrobel --- src/hal/utils/upci.c | 74 +++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/src/hal/utils/upci.c b/src/hal/utils/upci.c index 0a18f7b87b2..460ca4e25b7 100644 --- a/src/hal/utils/upci.c +++ b/src/hal/utils/upci.c @@ -401,37 +401,49 @@ static void decr_io_usage ( void ) static int incr_mem_usage ( void ) { - int eno; - - /* make sure /dev/mem is open */ - if ( memaccess == 0 ) { - /* open it */ - /* this needs privileges */ - if (seteuid(0) != 0) { - errmsg(__func__, "need root privileges (or setuid root)"); - return -1; - } - /* do it */ - memfd = open("/dev/mem", O_RDWR); - eno = errno; - /* drop privileges */ - if(seteuid(getuid()) != 0) - { - errmsg(__func__, "unable to drop root privileges"); - /* Don't continue past this point, because following code may - * execute with unexpected privileges - */ - _exit(99); - } - /* check result */ - if ( memfd < 0 ) { - errmsg(__func__,"can't open /dev/mem: %s", strerror(eno)); - return -1; - } - } - /* increment reference count */ - memaccess++; - return 0; + int retval = 0, eno = 0; + + /* Try to open /dev/mem with our existing privileges first: succeeds + * when the process holds CAP_SYS_RAWIO via file caps or is already + * setuid root. Only fall back to seteuid(0) if that path is closed. + */ + do { + if (memaccess) { + break; + } + + memfd = open("/dev/mem", O_RDWR); + if (memfd >= 0) { + break; + } + + retval = seteuid(0); + if (retval != 0) { + eno = errno; + break; + } + + memfd = open("/dev/mem", O_RDWR); + retval = memfd >= 0 ? 0 : memfd; + eno = errno; + + if (seteuid(getuid()) != 0) { + errmsg(__func__, "unable to drop root privileges"); + /* Don't continue past this point, because following code may + * execute with unexpected privileges + */ + _exit(99); + } + } while (0); + + if (retval == 0) { + /* increment reference count */ + memaccess++; + } else { + errmsg(__func__,"error: %s", strerror(eno)); + } + + return retval; } static void decr_mem_usage ( void ) From d523e0ac638e6fa37c94bab228bb3f9412131cad Mon Sep 17 00:00:00 2001 From: Damian Wrobel Date: Mon, 25 May 2020 20:09:13 +0200 Subject: [PATCH 42/43] rootless: incr_io_usage() Allow the incr_io_usage() to be used without root privileges. Signed-off-by: Damian Wrobel --- src/hal/utils/upci.c | 71 +++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/hal/utils/upci.c b/src/hal/utils/upci.c index 460ca4e25b7..89d79855293 100644 --- a/src/hal/utils/upci.c +++ b/src/hal/utils/upci.c @@ -352,37 +352,48 @@ int upci_find_device(struct upci_dev_info *p) static int incr_io_usage ( void ) { - int retval, eno; - - /* make sure we can do I/O */ - if ( ioaccess == 0 ) { - /* enable access */ - /* this needs privileges */ - if (seteuid(0) != 0) { - errmsg(__func__, "need root privileges (or setuid root)"); - return -1; - } - /* do it */ - retval = iopl(3); - eno = errno; - /* drop privileges */ - if(seteuid(getuid()) != 0) - { - errmsg(__func__, "unable to drop root privileges"); - /* Don't continue past this point, because following code may - * execute with unexpected privileges - */ - _exit(99); - } - /* check result */ - if(retval < 0 || iopl(3) < 0) { - errmsg(__func__,"opening I/O ports: %s", strerror(eno)); - return -1; - } + int retval = 0, eno = 0; + + /* Try iopl(3) with our existing privileges first: succeeds when + * the process holds CAP_SYS_RAWIO via file caps or is already + * setuid root. Only fall back to seteuid(0) if that path is closed. + */ + do { + if (ioaccess) { + break; + } + + retval = iopl(3); + if (retval == 0) { + break; + } + + retval = seteuid(0); + if (retval != 0) { + eno = errno; + break; + } + + retval = iopl(3); + eno = errno; + + if (seteuid(getuid()) != 0) { + errmsg(__func__, "unable to drop root privileges"); + /* Don't continue past this point, because following code may + * execute with unexpected privileges + */ + _exit(99); + } + } while (0); + + if (retval == 0) { + /* increment reference count */ + ioaccess++; + } else { + errmsg(__func__,"error: %s", strerror(eno)); } - /* increment reference count */ - ioaccess++; - return 0; + + return retval; } static void decr_io_usage ( void ) From b849e1d0ce34926f16269ab30fd8a86c7685b6d1 Mon Sep 17 00:00:00 2001 From: Damian Wrobel Date: Mon, 25 May 2020 21:11:31 +0200 Subject: [PATCH 43/43] Remove direct usage of inb(), outb() Signed-off-by: Damian Wrobel --- src/hal/drivers/hal_evoreg.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/hal/drivers/hal_evoreg.c b/src/hal/drivers/hal_evoreg.c index 5bc2ee34287..cf33a78a11f 100644 --- a/src/hal/drivers/hal_evoreg.c +++ b/src/hal/drivers/hal_evoreg.c @@ -66,20 +66,9 @@ #include /* isspace() */ #include /* RTAPI realtime OS API */ #include /* RTAPI realtime module decls */ +#include /* rtapi_inb(), rtapi_outb() */ #include /* HAL public API decls */ -/* If FASTIO is defined, uses outb() and inb() from , - instead of rtapi_outb() and rtapi_inb() - the ones - are inlined, and save a microsecond or two (on my 233MHz box) -*/ -#define FASTIO - -#ifdef FASTIO -#define rtapi_inb inb -#define rtapi_outb outb -#include -#endif - /* module information */ MODULE_AUTHOR("Martin Kuhnle"); MODULE_DESCRIPTION("SIEMENS-EVOREG Driver for EMC HAL");