@@ -79,6 +79,20 @@ static inline int detect_preempt_rt() {
7979}
8080#endif
8181
82+ #ifdef __linux__
83+ // detect_preempt_dynamic() inspects uname for the PREEMPT_DYNAMIC marker.
84+ static inline int detect_preempt_dynamic () {
85+ struct utsname u;
86+ if (uname (&u) < 0 ) return 0 ;
87+ return strcasestr (u.version , " PREEMPT_DYNAMIC" ) != 0
88+ || strcasestr (u.version , " PREEMPT_DYNAMIC" ) != 0 ;
89+ }
90+ #else
91+ static inline int detect_preempt_dynamic () {
92+ return 0 ;
93+ }
94+ #endif
95+
8296// FIXME: detect_rtai/detect_xenomai/detect_xenomai_evl currently gate on
8397// setuid because the RTAI/Xenomai backends still need root for iopl()
8498// (RTAI) or RTDM device access (Xenomai/EVL). Long-term these should
@@ -127,10 +141,52 @@ static int detect_xenomai_evl() {
127141}
128142#endif
129143
130- // errno from the most recent sched_setscheduler(SCHED_FIFO) probe. Zero
131- // when the probe succeeded or has not run yet. Read via
132- // rtapi_sched_fifo_errno() from diagnostic code.
133- static int rtapi_sched_fifo_last_errno = 0 ;
144+ static int detect_force (){
145+ const char *force = getenv (" LINUXCNC_FORCE_REALTIME" );
146+ if (force != NULL && atoi (force) != 0 ){
147+ return 1 ;
148+ }else {
149+ return 0 ;
150+ }
151+ }
152+
153+ #ifdef __linux__
154+ // Diagnostic helper: report cap_effective state for a single capability.
155+ // Returns "yes", "no", or "unknown" if libcap could not introspect.
156+ static const char *cap_effective_str (cap_t caps, cap_value_t cap) {
157+ if (!caps) return " unknown" ;
158+ cap_flag_value_t v;
159+ if (cap_get_flag (caps, cap, CAP_EFFECTIVE , &v) != 0 ) return " unknown" ;
160+ return v == CAP_SET ? " yes" : " no" ;
161+ }
162+ #else
163+ static const char *cap_effective_str (cap_t caps, cap_value_t cap) {
164+ return " unknown" ;
165+ }
166+ #endif
167+
168+ static void report_sched_fifo_error (int sched_err){
169+ // Surface the actual reason so the user does not have to guess
170+ // between "no caps", "stock kernel", or "wrong rlimits" (issue
171+ // #3928). errno comes from the SCHED_FIFO probe in
172+ // can_set_sched_fifo(); cap state comes from libcap.
173+ cap_t caps = cap_get_proc ();
174+ const char *nice_s = cap_effective_str (caps, CAP_SYS_NICE );
175+ const char *lock_s = cap_effective_str (caps, CAP_IPC_LOCK );
176+ rtapi_print_msg (RTAPI_MSG_ERR ,
177+ " Note: realtime scheduling unavailable "
178+ " (sched_setscheduler SCHED_FIFO: %s).\n "
179+ " Process capabilities: cap_sys_nice=%s cap_ipc_lock=%s.\n "
180+ " Falling back to POSIX non-realtime.\n "
181+ " Fix: 'sudo make setcap' (preferred) or 'sudo make setuid' "
182+ " on rtapi_app.\n "
183+ " Override (testing only): set LINUXCNC_FORCE_REALTIME=1.\n " ,
184+ sched_err ? strerror (sched_err) : " denied" ,
185+ nice_s, lock_s);
186+ #ifdef __linux__
187+ if (caps) cap_free (caps);
188+ #endif
189+ }
134190
135191// Success-probe for realtime scheduling: briefly try to set SCHED_FIFO on
136192// the calling thread and restore the previous policy. Succeeds when the
@@ -142,29 +198,63 @@ static int can_set_sched_fifo(void) {
142198 struct sched_param old_param, probe_param;
143199 int old_policy = sched_getscheduler (0 );
144200 if (old_policy < 0 ) {
145- rtapi_sched_fifo_last_errno = errno;
201+ report_sched_fifo_error ( errno) ;
146202 return 0 ;
147203 }
148204 if (sched_getparam (0 , &old_param) < 0 ) {
149- rtapi_sched_fifo_last_errno = errno;
205+ report_sched_fifo_error ( errno) ;
150206 return 0 ;
151207 }
152208
153209 memset (&probe_param, 0 , sizeof (probe_param));
154210 probe_param.sched_priority = sched_get_priority_min (SCHED_FIFO );
155211 if (sched_setscheduler (0 , SCHED_FIFO , &probe_param) < 0 ) {
156- rtapi_sched_fifo_last_errno = errno;
212+ report_sched_fifo_error ( errno) ;
157213 return 0 ;
158214 }
159215
160216 // Best-effort restore; if this fails we are still on SCHED_FIFO at
161217 // minimum priority, which is no worse than where we started.
162218 sched_setscheduler (0 , old_policy, &old_param);
163- rtapi_sched_fifo_last_errno = 0 ;
164219 return 1 ;
165220}
166221
167- static inline int rtapi_sched_fifo_errno (void ) { return rtapi_sched_fifo_last_errno; }
222+ rtapi_realtime_status_t rtapi_realtime_status (void ){
223+ static rtapi_realtime_status_t cached = REALTIME_STATUS_UNINITIALIZED ;
224+ if (cached != REALTIME_STATUS_UNINITIALIZED ){
225+ return cached;
226+ }
227+
228+ if (!detect_force () && !can_set_sched_fifo ()){
229+ cached = REALTIME_STATUS_NONE ;
230+ return cached;
231+ }
232+
233+ if (detect_rtai ()){
234+ cached = REALTIME_STATUS_LXRT ;
235+ return cached;
236+ }
237+ if (detect_xenomai ()){
238+ cached = REALTIME_STATUS_XENOMAI ;
239+ return cached;
240+ }
241+ if (detect_xenomai_evl ()){
242+ cached = REALTIME_STATUS_XENOMAI_EVL ;
243+ return cached;
244+ }
245+ if (detect_preempt_rt ()){
246+ cached = REALTIME_STATUS_PREEMT_RT ;
247+ return cached;
248+ }
249+ if (detect_preempt_dynamic ()){
250+ cached = REALTIME_STATUS_PREEMT_DYNAMIC ;
251+ return cached;
252+ }
253+
254+ rtapi_print_msg (RTAPI_MSG_ERR , " rtapi_realtime_status(): Bug, something unknown is running\n " );
255+ cached = REALTIME_STATUS_NONE ;
256+ return cached;
257+ }
168258
169259// rtapi_is_realtime() reports whether this process can actually run
170260// realtime code. This matches the convention used by JACK, PipeWire,
@@ -174,17 +264,7 @@ static inline int rtapi_sched_fifo_errno(void) { return rtapi_sched_fifo_last_er
174264// wrapper-based installs like NixOS /run/wrappers) and silently masked
175265// LINUXCNC_FORCE_REALTIME (see issue #3928).
176266int rtapi_is_realtime () {
177- static int cached = -1 ;
178- if (cached != -1 ) return cached;
179-
180- const char *force = getenv (" LINUXCNC_FORCE_REALTIME" );
181- if (force != NULL && atoi (force) != 0 )
182- return (cached = 1 );
183-
184- if (detect_rtai () || detect_xenomai () || detect_xenomai_evl ())
185- return (cached = 1 );
186-
187- return (cached = can_set_sched_fifo ());
267+ return rtapi_realtime_status () > 0 ;
188268}
189269
190270struct message_t {
@@ -1149,16 +1229,7 @@ static RtapiApp *makeDllApp(const std::string &dllName, int policy) {
11491229 return result;
11501230}
11511231
1152- // Diagnostic helper: report cap_effective state for a single capability.
1153- // Returns "yes", "no", or "unknown" if libcap could not introspect.
11541232#ifdef __linux__
1155- static const char *cap_effective_str (cap_t caps, cap_value_t cap) {
1156- if (!caps) return " unknown" ;
1157- cap_flag_value_t v;
1158- if (cap_get_flag (caps, cap, CAP_EFFECTIVE , &v) != 0 ) return " unknown" ;
1159- return v == CAP_SET ? " yes" : " no" ;
1160- }
1161-
11621233// Raise CAP_NET_ADMIN into the ambient set so it survives execve() into
11631234// child processes (iptables, ip6tables) launched by HAL drivers like
11641235// hm2_eth. Linux file capabilities on rtapi_app give cap_net_admin in
@@ -1190,59 +1261,34 @@ static void raise_net_admin_ambient(void) {
11901261
11911262static RtapiApp *makeApp () {
11921263 RtapiApp *app;
1193- bool rt_ok = rtapi_is_realtime ();
1194- if (!rt_ok) {
1195- // Surface the actual reason so the user does not have to guess
1196- // between "no caps", "stock kernel", or "wrong rlimits" (issue
1197- // #3928). errno comes from the SCHED_FIFO probe in
1198- // can_set_sched_fifo(); cap state comes from libcap.
1199- int sched_err = rtapi_sched_fifo_errno ();
1200- #ifdef __linux__
1201- cap_t caps = cap_get_proc ();
1202- const char *nice_s = cap_effective_str (caps, CAP_SYS_NICE );
1203- const char *lock_s = cap_effective_str (caps, CAP_IPC_LOCK );
1204- #else
1205- const char *nice_s = " unknown" ;
1206- const char *lock_s = " unknown" ;
1207- #endif
1208- rtapi_print_msg (RTAPI_MSG_ERR ,
1209- " Note: realtime scheduling unavailable "
1210- " (sched_setscheduler SCHED_FIFO: %s).\n "
1211- " Process capabilities: cap_sys_nice=%s cap_ipc_lock=%s.\n "
1212- " Falling back to POSIX non-realtime.\n "
1213- " Fix: 'sudo make setcap' (preferred) or 'sudo make setuid' "
1214- " on rtapi_app.\n "
1215- " Override (testing only): set LINUXCNC_FORCE_REALTIME=1.\n " ,
1216- sched_err ? strerror (sched_err) : " denied" ,
1217- nice_s, lock_s);
1218- #ifdef __linux__
1219- if (caps) cap_free (caps);
1220- #endif
1221- }
1222- if (!rt_ok) {
1264+ rtapi_realtime_status_t rt_status = rtapi_realtime_status ();
1265+ if (rt_status == REALTIME_STATUS_NONE ) {
12231266 app = makeDllApp (" liblinuxcnc-uspace-posix.so.0" , SCHED_OTHER );
12241267 } else {
12251268 WithRoot r;
12261269 harden_rt ();
1227- if (detect_xenomai_evl () ) {
1270+ if (rt_status == REALTIME_STATUS_XENOMAI_EVL ) {
12281271 app = makeDllApp (" liblinuxcnc-uspace-xenomai-evl.so.0" , SCHED_FIFO );
1229- } else if (detect_xenomai () ) {
1272+ } else if (rt_status == REALTIME_STATUS_XENOMAI ) {
12301273 app = makeDllApp (" liblinuxcnc-uspace-xenomai.so.0" , SCHED_FIFO );
1231- } else if (detect_rtai () ) {
1274+ } else if (rt_status == REALTIME_STATUS_RTAI ) {
12321275 app = makeDllApp (" liblinuxcnc-uspace-rtai.so.0" , SCHED_FIFO );
1233- } else {
1276+ } else if (rt_status == REALTIME_STATUS_PREEMT_RT || rt_status == REALTIME_STATUS_PREEMT_DYNAMIC ) {
12341277 // SCHED_FIFO available but no Xenomai/RTAI backend. Warn if the
12351278 // kernel is not PREEMPT_RT: SCHED_FIFO still beats SCHED_OTHER,
12361279 // but latency on a PREEMPT_DYNAMIC stock kernel can be tens of
12371280 // milliseconds, which will surprise users who expect the same
12381281 // bounds as a PREEMPT_RT or Xenomai setup.
1239- if (! detect_preempt_rt () ) {
1282+ if (rt_status == REALTIME_STATUS_PREEMT_DYNAMIC ) {
12401283 rtapi_print_msg (RTAPI_MSG_ERR ,
12411284 " Note: SCHED_FIFO available but kernel is not PREEMPT_RT. "
12421285 " Latency may be unbounded; install a PREEMPT_RT kernel "
12431286 " for hard realtime guarantees.\n " );
12441287 }
12451288 app = makeDllApp (" liblinuxcnc-uspace-posix.so.0" , SCHED_FIFO );
1289+ } else {
1290+ app = nullptr ;
1291+ rtapi_print_msg (RTAPI_MSG_ERR , " Bug: rt_status = %i in not handled\n " , rt_status);
12461292 }
12471293 }
12481294
0 commit comments