From f03775e3a7bd6ca35740d50ed7bedbf60c0b57ca Mon Sep 17 00:00:00 2001 From: Heinz Junkes Date: Thu, 6 Aug 2026 13:44:32 +0200 Subject: [PATCH 1/7] RTEMS: fix off-by-one array read in cpu_ticks() OBJECTS_INFORMATION_DEFINE allocates local_table[max] with valid indices 0..max-1, since RTEMS stores each object at id_index - OBJECTS_INDEX_MINIMUM (OBJECTS_INDEX_MINIMUM == 1). The loop here indexed local_table[y] directly for y in 1..max, reading one Thread_Control pointer past the end of the array on every pass, and skipping the object actually stored at index 0. Found while chasing a crash under RTEMS 7; the stale/garbage pointer this reads happens to be more likely to fault there than under older RTEMS memory layouts, which is why the bug went unnoticed. Co-Authored-By: Claude Sonnet 5 --- devIocStats/os/RTEMS/osdCpuUsage.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/devIocStats/os/RTEMS/osdCpuUsage.c b/devIocStats/os/RTEMS/osdCpuUsage.c index 4963b8a..0f8a593 100644 --- a/devIocStats/os/RTEMS/osdCpuUsage.c +++ b/devIocStats/os/RTEMS/osdCpuUsage.c @@ -100,7 +100,12 @@ static void cpu_ticks(double *total, double *idle) { obj = _Objects_Information_table[x][1]; if (obj) { for (y = 1; y <= obj->maximum; y++) { - tc = (Thread_Control *)obj->local_table[y]; + /* local_table is indexed from 0, but the object's index-within- + * class (y) is 1-based (RTEMS stores objects at + * id_index - OBJECTS_INDEX_MINIMUM, and OBJECTS_INDEX_MINIMUM is 1). + * Indexing with y directly reads one entry past the end of the + * array on every pass. */ + tc = (Thread_Control *)obj->local_table[y - 1]; if (tc) { *total += CPU_ELAPSED_TIME(tc); RTEMS_OBJ_GET_NAME(tc, name); From 45614890899a708cba65244ce095058fdea0fb09 Mon Sep 17 00:00:00 2001 From: Heinz Junkes Date: Fri, 7 Aug 2026 15:36:48 +0200 Subject: [PATCH 2/7] RTEMS: use OBJECTS_INDEX_MINIMUM and fix build against current RTEMS 7 Per anjohnson's review comment on #88: use OBJECTS_INDEX_MINIMUM instead of the literal 1 for the local_table index adjustment. Verifying that this and the surrounding code actually compile against a current RTEMS 7 build (2026.03.04) surfaced two further issues, now fixed: - devIocStatsOSD.h relied on transitively pulling in for Objects_Information/ _Objects_Information_table/OBJECTS_INDEX_MINIMUM; current RTEMS no longer does this, so include it explicitly. - Objects_Information::maximum was renamed to maximum_id and is now an encoded Objects_Id (API/class/node/index), not a plain count. Use _Objects_Get_maximum_index() instead, matching what RTEMS's own internals use for this. - Thread_Control::cpu_time_used (Timestamp_Control) is int64_t on this build, not struct timespec -- its representation is a per- architecture/config choice by design. Use the portable _Timestamp_Get_as_nanoseconds() accessor instead of assuming tv_sec/tv_nsec fields. osdCpuUsage.c now compiles cleanly against this RTEMS 7 toolchain. Other pre-existing RTEMS-7 compatibility issues remain elsewhere in this module's RTEMS OSD (osdSuspTasks.c, osdWorkspaceUsage.c, osdClustInfo.c, devIocStatsOSD.h's rtemsReboot, and unrelated -Wincompatible-pointer-types in the Analog/String/Waveform DSETs) -- out of scope here, not touched. Co-Authored-By: Claude Sonnet 5 --- devIocStats/os/RTEMS/devIocStatsOSD.h | 11 +++++++++++ devIocStats/os/RTEMS/osdCpuUsage.c | 19 ++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/devIocStats/os/RTEMS/devIocStatsOSD.h b/devIocStats/os/RTEMS/devIocStatsOSD.h index 00d5787..40c93d3 100644 --- a/devIocStats/os/RTEMS/devIocStatsOSD.h +++ b/devIocStats/os/RTEMS/devIocStatsOSD.h @@ -54,6 +54,17 @@ #include #include +/* osdCpuUsage.c walks _Objects_Information_table[]/Objects_Information + * directly; older RTEMS pulled these in transitively via rtems.h, current + * RTEMS no longer does, so include the Score header explicitly. */ +#include + +/* osdCpuUsage.c reads Thread_Control::cpu_time_used (a Timestamp_Control) + * via the portable _Timestamp_Get_*() accessors rather than assuming its + * representation (struct timespec vs. int64_t sbintime_t is a per- + * architecture/config choice -- that's the whole point of the wrapper). */ +#include + #undef malloc #undef free diff --git a/devIocStats/os/RTEMS/osdCpuUsage.c b/devIocStats/os/RTEMS/osdCpuUsage.c index 0f8a593..608ea58 100644 --- a/devIocStats/os/RTEMS/osdCpuUsage.c +++ b/devIocStats/os/RTEMS/osdCpuUsage.c @@ -65,9 +65,12 @@ typedef char *objName; #if defined(RTEMS_ENABLE_NANOSECOND_CPU_USAGE_STATISTICS) || \ (__RTEMS_MAJOR__ > 4) || (__RTEMS_MAJOR__ == 4 && __RTEMS_MINOR__ > 9) +/* cpu_time_used is a Timestamp_Control; its representation (struct + * timespec vs. int64_t) is a per-architecture/config choice, so go + * through the portable accessor rather than assume struct-timespec + * layout. */ #define CPU_ELAPSED_TIME(tc) \ - ((double)(tc)->cpu_time_used.tv_sec + \ - ((double)tc->cpu_time_used.tv_nsec / 1E9)) + ((double)_Timestamp_Get_as_nanoseconds(&(tc)->cpu_time_used) / 1E9) #else #define CPU_ELAPSED_TIME(tc) ((double)(tc)->ticks_executed) #endif @@ -99,13 +102,15 @@ static void cpu_ticks(double *total, double *idle) { obj = _Objects_Information_table[x][1]; if (obj) { - for (y = 1; y <= obj->maximum; y++) { + /* obj->maximum_id is an encoded Objects_Id (API/class/node/index), + * not a plain count -- _Objects_Get_maximum_index() extracts the + * actual maximum index, matching what RTEMS's own internals use. */ + for (y = 1; y <= (int)_Objects_Get_maximum_index(obj); y++) { /* local_table is indexed from 0, but the object's index-within- * class (y) is 1-based (RTEMS stores objects at - * id_index - OBJECTS_INDEX_MINIMUM, and OBJECTS_INDEX_MINIMUM is 1). - * Indexing with y directly reads one entry past the end of the - * array on every pass. */ - tc = (Thread_Control *)obj->local_table[y - 1]; + * id_index - OBJECTS_INDEX_MINIMUM). Indexing with y directly + * reads one entry past the end of the array on every pass. */ + tc = (Thread_Control *)obj->local_table[y - OBJECTS_INDEX_MINIMUM]; if (tc) { *total += CPU_ELAPSED_TIME(tc); RTEMS_OBJ_GET_NAME(tc, name); From 4da2a47f15514531b27bc89614787632770c78d3 Mon Sep 17 00:00:00 2001 From: Heinz Junkes Date: Fri, 7 Aug 2026 15:45:35 +0200 Subject: [PATCH 3/7] RTEMS: fix osdSuspTasks.c and osdWorkspaceUsage.c against current RTEMS 7 Same RTEMS-Score-API-drift theme as the previous commit, in two more files: osdSuspTasks.c: - _Objects_Get_next()'s signature changed: no more separate Objects_Locations out-param (it now just returns NULL when nothing local is found), argument order is (id, information, next_id_p), and it internally locks the object allocator mutex on success -- the caller must release it with _Objects_Allocator_unlock(). Followed the reference usage in RTEMS's own cpukit/libcsupport/src/resource_snapshot.c. - _RTEMS_tasks_Information is now a Thread_Information (which wraps an Objects_Information as its .Objects member), not an Objects_Information directly. - Use the found object's own o->id for rtems_task_is_suspended() rather than relying on the exact value _Objects_Get_next() leaves in the next-id out-param. - Needed for _RTEMS_tasks_Information, which isn't pulled in transitively any more. osdWorkspaceUsage.c: - _Workspace_Area needed included explicitly, same transitive-include story as the previous commit. - Configuration.work_space_size doesn't compile: the global `Configuration` object is only declared when the application itself includes (it's produced by CONFIGURE_INIT in the app's own source, not by any library-includable header), so a support library can't reach it directly. Switched to the public rtems_configuration_get_work_space_size() accessor instead. Both files now compile cleanly against the current RTEMS 7 build. Remaining unrelated pre-existing issues in this module (osdClustInfo.c, osdIFErrors.c, devIocStatsOSD.h's rtemsReboot, and -Wincompatible-pointer-types in the Analog/String/Waveform DSETs) still untouched -- out of scope here. Co-Authored-By: Claude Sonnet 5 --- devIocStats/os/RTEMS/devIocStatsOSD.h | 9 +++++++++ devIocStats/os/RTEMS/osdSuspTasks.c | 18 +++++++++++------- devIocStats/os/RTEMS/osdWorkspaceUsage.c | 5 ++++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/devIocStats/os/RTEMS/devIocStatsOSD.h b/devIocStats/os/RTEMS/devIocStatsOSD.h index 40c93d3..163f9d8 100644 --- a/devIocStats/os/RTEMS/devIocStatsOSD.h +++ b/devIocStats/os/RTEMS/devIocStatsOSD.h @@ -65,6 +65,15 @@ * architecture/config choice -- that's the whole point of the wrapper). */ #include +/* osdWorkspaceUsage.c uses _Workspace_Area directly; same transitive- + * include story as objectimpl.h above. */ +#include + +/* osdSuspTasks.c walks _RTEMS_tasks_Information.Objects directly; this is + * the Classic API task object information, declared here rather than + * anywhere Score-level. */ +#include + #undef malloc #undef free diff --git a/devIocStats/os/RTEMS/osdSuspTasks.c b/devIocStats/os/RTEMS/osdSuspTasks.c index 9679d53..89c5a9b 100644 --- a/devIocStats/os/RTEMS/osdSuspTasks.c +++ b/devIocStats/os/RTEMS/osdSuspTasks.c @@ -45,17 +45,21 @@ int devIocStatsInitSuspTasks(void) { return 0; } int devIocStatsGetSuspTasks(int *pval) { Objects_Control *o; Objects_Id id = OBJECTS_ID_INITIAL_INDEX; - Objects_Id nid; int n = 0; - Objects_Locations l; - /* count all suspended (LOCAL -- cannot deal with remote ones ATM) tasks */ - while ((o = _Objects_Get_next(&_RTEMS_tasks_Information, id, &l, &nid))) { - if ((RTEMS_ALREADY_SUSPENDED == rtems_task_is_suspended(nid))) { + /* count all suspended (LOCAL -- cannot deal with remote ones ATM) tasks. + * _Objects_Get_next()'s signature/semantics changed: no more separate + * Objects_Locations out-param (it now just returns NULL when nothing + * local is found), the object information comes first, and id doubles + * as both the search-from input and the next-id output. It locks the + * object allocator mutex when it finds something, which the caller must + * release -- see cpukit/libcsupport/src/resource_snapshot.c for the + * reference usage this follows. */ + while ((o = _Objects_Get_next(id, &_RTEMS_tasks_Information.Objects, &id))) { + if ((RTEMS_ALREADY_SUSPENDED == rtems_task_is_suspended(o->id))) { n++; } - _Thread_Enable_dispatch(); - id = nid; + _Objects_Allocator_unlock(); } *pval = n; return 0; diff --git a/devIocStats/os/RTEMS/osdWorkspaceUsage.c b/devIocStats/os/RTEMS/osdWorkspaceUsage.c index 9f7518a..8afce45 100644 --- a/devIocStats/os/RTEMS/osdWorkspaceUsage.c +++ b/devIocStats/os/RTEMS/osdWorkspaceUsage.c @@ -41,7 +41,10 @@ int devIocStatsGetWorkspaceUsage(memInfo *pval) { _RTEMS_Unlock_allocator(); #endif /* RTEMS_PROTECTED_HEAP */ #if (__RTEMS_MAJOR__ > 4) || (__RTEMS_MAJOR__ == 4 && __RTEMS_MINOR__ > 9) - pval->numBytesTotal = Configuration.work_space_size; + /* The global `Configuration` object is only declared when the + * application itself includes ; a support library + * has to go through the public accessor instead. */ + pval->numBytesTotal = rtems_configuration_get_work_space_size(); #else pval->numBytesTotal = _Configuration_Table->work_space_size; #endif From 864dfee26f8939ca8283cda7a99947b6f89d92b8 Mon Sep 17 00:00:00 2001 From: Heinz Junkes Date: Fri, 7 Aug 2026 15:53:37 +0200 Subject: [PATCH 4/7] RTEMS: fix osdClustInfo.c and osdIFErrors.c against current RTEMS 7 Same RTEMS-Score-API-drift theme, at the libbsd network-stack boundary this time. osdClustInfo.c: - The legacy network stack's `extern struct mbstat mbstat` doesn't exist under libbsd. Gated the old code behind `#if RTEMS_LIBBSD_STACK` / `#else`, and for the libbsd case query the same mbuf/cluster counts through libbsd's portable memstat API (memstat_mtl_alloc/memstat_sysctl_all/memstat_mtl_find + memstat_get_size/count/free), the same mechanism `netstat -m` uses. Also actually implements devIocStatsGetClusterUsage() for the libbsd case rather than returning -1 unconditionally. (epics-modules/iocStats#61 took the same memstat approach for its RTEMS 6 support, but writes the mbuf_cluster stats into row [0] again instead of row [1], clobbering the mbuf row -- fixed that here.) osdIFErrors.c: - `extern struct ifnet *ifnet` walked via `if_next`, and if_ierrors/if_oerrors, are kernel-internal under libbsd (ifnet is a CK_STAILQ now, and the counters are behind the if_get_counter KPI) -- neither is meant to be reachable from outside the kernel proper, so `__RTEMS_VIOLATE_KERNEL_VISIBILITY__` doesn't save this approach. Switched to getifaddrs()/struct if_data, the portable userspace- visible way to read the same per-interface error counters (each interface contributes one AF_LINK entry whose ifa_data is its struct if_data). Both compile cleanly against the current RTEMS 7 build. Remaining unrelated pre-existing issues in this module (devIocStatsOSD.h's rtemsReboot, and -Wincompatible-pointer-types in the Analog/String/Waveform DSETs) still untouched -- out of scope here. Co-Authored-By: Claude Sonnet 5 --- devIocStats/os/RTEMS/osdClustInfo.c | 76 +++++++++++++++++++++++++++++ devIocStats/os/RTEMS/osdIFErrors.c | 31 +++++++++--- 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/devIocStats/os/RTEMS/osdClustInfo.c b/devIocStats/os/RTEMS/osdClustInfo.c index 589d0f3..7b813a6 100644 --- a/devIocStats/os/RTEMS/osdClustInfo.c +++ b/devIocStats/os/RTEMS/osdClustInfo.c @@ -56,6 +56,80 @@ #include +#if RTEMS_LIBBSD_STACK + +/* The legacy network stack's global `struct mbstat mbstat` doesn't exist + * under libbsd -- query the same counts through libbsd's portable memstat + * API instead (same mechanism `netstat -m`/`vmstat -z` use). */ +#include +#include +#include + +static int get_mbuf_stat(const char *name, uint64_t *psize, uint64_t *pcount, + uint64_t *pfree) { + struct memory_type_list *mtlp; + struct memory_type *mtp; + + mtlp = memstat_mtl_alloc(); + if (mtlp == NULL) { + return -1; + } + if (memstat_sysctl_all(mtlp, 0) < 0) { + memstat_mtl_free(mtlp); + return -1; + } + mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, name); + if (mtp == NULL) { + memstat_mtl_free(mtlp); + return -1; + } + *psize = memstat_get_size(mtp); + *pcount = memstat_get_count(mtp); + *pfree = memstat_get_free(mtp); + memstat_mtl_free(mtlp); + return 0; +} + +int devIocStatsInitClusterInfo(void) { return 0; } + +int devIocStatsGetClusterInfo(int pool, clustInfo *pval) { + uint64_t size, count, free; + + if (pool == DATA_POOL) + return -1; + + if (get_mbuf_stat(MBUF_MEM_NAME, &size, &count, &free)) + return -1; + (*pval)[0][0] = size; + (*pval)[0][1] = count; + (*pval)[0][2] = free; + (*pval)[0][3] = count - free; + + if (get_mbuf_stat(MBUF_CLUSTER_MEM_NAME, &size, &count, &free)) + return -1; + (*pval)[1][0] = size; + (*pval)[1][1] = count; + (*pval)[1][2] = free; + (*pval)[1][3] = count - free; + + return 0; +} + +int devIocStatsGetClusterUsage(int pool, int *pval) { + uint64_t size, count, free; + + if (pool == DATA_POOL) + return -1; + + if (get_mbuf_stat(MBUF_MEM_NAME, &size, &count, &free)) + return -1; + *pval = count; + + return 0; +} + +#else /* RTEMS_LIBBSD_STACK */ + /* This would otherwise need _KERNEL to be defined... */ extern struct mbstat mbstat; @@ -86,3 +160,5 @@ int devIocStatsGetClusterUsage(int pool, int *pval) { return 0; } + +#endif /* RTEMS_LIBBSD_STACK */ diff --git a/devIocStats/os/RTEMS/osdIFErrors.c b/devIocStats/os/RTEMS/osdIFErrors.c index e01e729..b905729 100644 --- a/devIocStats/os/RTEMS/osdIFErrors.c +++ b/devIocStats/os/RTEMS/osdIFErrors.c @@ -40,20 +40,37 @@ #include -/* This would otherwise need _KERNEL to be defined... */ -extern struct ifnet *ifnet; +/* The kernel-internal `struct ifnet` linked list (walked via if_next) is + * gone under libbsd -- ifnet is now a CK_STAILQ and if_ierrors/if_oerrors + * are behind the if_get_counter KPI, neither meant for use outside the + * kernel proper. getifaddrs()/struct if_data is the portable, userspace- + * visible way to get the same per-interface error counts: each interface + * contributes one AF_LINK entry whose ifa_data is its struct if_data. */ +#include +#include +#include int devIocStatsInitIFErrors(void) { return 0; } int devIocStatsGetIFErrors(ifErrInfo *pval) { - struct ifnet *ifp; + struct ifaddrs *addrs, *ifa; - /* add all interfaces' errors */ pval->ierrors = 0; pval->oerrors = 0; - for (ifp = ifnet; ifp != NULL; ifp = ifp->if_next) { - pval->ierrors += ifp->if_ierrors; - pval->oerrors += ifp->if_oerrors; + + if (getifaddrs(&addrs) < 0) { + return -1; + } + + for (ifa = addrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == AF_LINK && + ifa->ifa_data != NULL) { + struct if_data *ifd = (struct if_data *)ifa->ifa_data; + pval->ierrors += ifd->ifi_ierrors; + pval->oerrors += ifd->ifi_oerrors; + } } + + freeifaddrs(addrs); return 0; } From 104f437eb639e8eb361e48565c04923905874e03 Mon Sep 17 00:00:00 2001 From: Heinz Junkes Date: Fri, 7 Aug 2026 16:32:42 +0200 Subject: [PATCH 5/7] Fix build blockers so the module actually links against current toolchains These two issues weren't RTEMS-Score-API drift like the earlier commits -- they blocked devIocStats.dbd/libdevIocStats.a from being produced at all on RTEMS-beagleboneblack (the archive step never runs if any .o in the module fails), so none of the fixes in the earlier commits were actually reachable in a linkable library until now. devIocStatsAnalog.c/String.c/Waveform.c: - Their `aStats`/`sStats`/`wStats` DSET tables assign functions like `long ai_init(int pass)` to fields typed `DEVSUPFUN` (`long (*)()`). This has always been slightly-incorrect-but-tolerated: under C17 and earlier, empty parens in a function pointer type meant "unspecified arguments", compatible with any actual signature by definition. C23 changed this -- empty parens now mean the same as `(void)` -- turning every one of these long-standing assignments into a genuine incompatible-pointer-types error under GCC 15's default C23 mode. This is a known, ecosystem-wide EPICS/C23 compatibility issue, not specific to this module. Fixed with explicit `(DEVSUPFUN)` casts at each assignment, the standard way to spell out the intentional type pun these DSET tables have always relied on. devIocStatsOSD.h: - `reboot(x)`'s fallback branch (taken on arm-rtems7, since none of the uC5282/PPC/i386-specific branches above it match) called `rtemsReboot()`, which no longer exists in current RTEMS. Added an RTEMS-6+ branch using the modern `bsp_reset(source, code)` API from , matching the two-argument form RTEMS 6+ needs. Module now builds and links cleanly for RTEMS-beagleboneblack -- libdevIocStats.a actually gets produced for the first time since this round of fixes started, and beaglePyroIOC (which depends on it) links successfully against it. Co-Authored-By: Claude Sonnet 5 --- devIocStats/devIocStatsAnalog.c | 25 ++++++++++++++++++++----- devIocStats/devIocStatsString.c | 16 +++++++++++----- devIocStats/devIocStatsWaveform.c | 9 +++++++-- devIocStats/os/RTEMS/devIocStatsOSD.h | 6 ++++++ 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/devIocStats/devIocStatsAnalog.c b/devIocStats/devIocStatsAnalog.c index 62d3c53..36aa7c4 100644 --- a/devIocStats/devIocStatsAnalog.c +++ b/devIocStats/devIocStatsAnalog.c @@ -283,13 +283,28 @@ static validGetParms statsGetParms[] = { {"cbHighQueueOverruns", statsCbHighQOverruns, QUEUE_TYPE}, {NULL, NULL, 0}}; -aStats devAiStats = {6, NULL, ai_init, ai_init_record, ai_ioint_info, - ai_read, NULL}; +/* DEVSUPFUN is a bare `long (*)()`; under C23 empty parens mean "no + * arguments" rather than "unspecified arguments" (the C17-and-earlier + * meaning these DSET tables were written against), so these need an + * explicit cast to avoid -Wincompatible-pointer-types. */ +aStats devAiStats = {6, + NULL, + (DEVSUPFUN)ai_init, + (DEVSUPFUN)ai_init_record, + (DEVSUPFUN)ai_ioint_info, + (DEVSUPFUN)ai_read, + NULL}; epicsExportAddress(dset, devAiStats); -aStats devAoStats = {6, NULL, NULL, ao_init_record, NULL, ao_write, NULL}; +aStats devAoStats = {6, NULL, NULL, (DEVSUPFUN)ao_init_record, + NULL, (DEVSUPFUN)ao_write, NULL}; epicsExportAddress(dset, devAoStats); -aStats devAiClusts = { - 6, NULL, ai_clusts_init, ai_clusts_init_record, NULL, ai_clusts_read, NULL}; +aStats devAiClusts = {6, + NULL, + (DEVSUPFUN)ai_clusts_init, + (DEVSUPFUN)ai_clusts_init_record, + NULL, + (DEVSUPFUN)ai_clusts_read, + NULL}; epicsExportAddress(dset, devAiClusts); static memInfo meminfo = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; diff --git a/devIocStats/devIocStatsString.c b/devIocStats/devIocStatsString.c index 188ca63..c9a03c7 100644 --- a/devIocStats/devIocStatsString.c +++ b/devIocStats/devIocStatsString.c @@ -185,11 +185,17 @@ static validGetStrParms statsGetStrParms[] = { {"pwd2", statsPwd2, STATIC_TYPE}, {NULL, NULL, 0}}; -sStats devStringinStats = { - 5, NULL, stringin_init, stringin_init_record, NULL, stringin_read}; -sStats devStringinEnvVar = {5, NULL, NULL, envvar_init_record, - NULL, envvar_read}; -sStats devStringinEpics = {5, NULL, NULL, epics_init_record, NULL, epics_read}; +/* DEVSUPFUN is a bare `long (*)()`; under C23 empty parens mean "no + * arguments" rather than "unspecified arguments" (the C17-and-earlier + * meaning these DSET tables were written against), so these need an + * explicit cast to avoid -Wincompatible-pointer-types. */ +sStats devStringinStats = {5, NULL, (DEVSUPFUN)stringin_init, + (DEVSUPFUN)stringin_init_record, NULL, + (DEVSUPFUN)stringin_read}; +sStats devStringinEnvVar = {5, NULL, NULL, (DEVSUPFUN)envvar_init_record, + NULL, (DEVSUPFUN)envvar_read}; +sStats devStringinEpics = {5, NULL, NULL, (DEVSUPFUN)epics_init_record, NULL, + (DEVSUPFUN)epics_read}; epicsExportAddress(dset, devStringinStats); epicsExportAddress(dset, devStringinEnvVar); epicsExportAddress(dset, devStringinEpics); diff --git a/devIocStats/devIocStatsWaveform.c b/devIocStats/devIocStatsWaveform.c index e349e6b..9a5447b 100644 --- a/devIocStats/devIocStatsWaveform.c +++ b/devIocStats/devIocStatsWaveform.c @@ -116,8 +116,13 @@ static validGetWfmParms statsGetWfmParms[] = { {"pwd", statsPwd, STATIC_TYPE}, {NULL, NULL, 0}}; -wStats devWaveformStats = { - 5, NULL, waveform_init, waveform_init_record, NULL, waveform_read}; +/* DEVSUPFUN is a bare `long (*)()`; under C23 empty parens mean "no + * arguments" rather than "unspecified arguments" (the C17-and-earlier + * meaning this DSET table was written against), so these need an + * explicit cast to avoid -Wincompatible-pointer-types. */ +wStats devWaveformStats = {5, NULL, (DEVSUPFUN)waveform_init, + (DEVSUPFUN)waveform_init_record, NULL, + (DEVSUPFUN)waveform_read}; epicsExportAddress(dset, devWaveformStats); /* ---------------------------------------------------------------------- */ diff --git a/devIocStats/os/RTEMS/devIocStatsOSD.h b/devIocStats/os/RTEMS/devIocStatsOSD.h index 163f9d8..940b8bf 100644 --- a/devIocStats/os/RTEMS/devIocStatsOSD.h +++ b/devIocStats/os/RTEMS/devIocStatsOSD.h @@ -108,6 +108,12 @@ void bsp_reset(); \ bsp_reset(); \ } +#elif RTEMS_VERSION_INT >= VERSION_INT(6, 0, 0, 0) +/* rtemsReboot() no longer exists; current RTEMS's bsp_reset() takes a + * (source, code) pair instead (see ). */ +#include +#include +#define reboot(x) bsp_reset(RTEMS_FATAL_SOURCE_APPLICATION, (x)) #else #define reboot(x) rtemsReboot() #endif From 5e030a3c6497ec23f35431b3a258c941963fd0b8 Mon Sep 17 00:00:00 2001 From: Heinz Junkes Date: Fri, 7 Aug 2026 16:54:07 +0200 Subject: [PATCH 6/7] RTEMS: revert memstat-based cluster info -- crashes real hardware Hardware-verified crash: memstat_sysctl_all() (used by the previous commit's libbsd implementation of devIocStatsGetClusterInfo/Usage) takes a data abort inside libbsd's UMA per-CPU counter stats path. Confirmed via addr2line against the actual crash PC/LR from the RTEMS fatal-exception dump: PC 0x8023e148 -> _bsd_counter_u64_alloc (rtems-libbsd/.../freebsd/sys/kern/subr_counter.c:64) LR 0x802cc904 -> uma_vm_zone_stats (rtems-libbsd/.../freebsd/sys/vm/uma_core.c:5718) This is reached from a periodic devIocStats timer callback shortly after iocInit starts (ai_clusts's driver init registers an initHookAfterCaServerInit hook), so it crashes essentially every boot once a CLUST_* record is loaded. The memstat approach was adapted from epics-modules/iocStats#61's (still open, unmerged) RTEMS 6 port, which apparently was never run on real hardware either -- it also has a row-index bug fixed in the previous commit, independent of this crash. Something in this RTEMS-libbsd build's counter(9)/UMA-zone-stats support is broken or incomplete; root-causing that is future work. Reverted to reporting "not available" (-1), matching what #61 itself does for the GetClusterUsage case it left unimplemented. CLUST_* records will show INVALID/alarm rather than a real reading, but the IOC boots and runs. Co-Authored-By: Claude Sonnet 5 --- devIocStats/os/RTEMS/osdClustInfo.c | 77 +++++++---------------------- 1 file changed, 17 insertions(+), 60 deletions(-) diff --git a/devIocStats/os/RTEMS/osdClustInfo.c b/devIocStats/os/RTEMS/osdClustInfo.c index 7b813a6..89e2c7d 100644 --- a/devIocStats/os/RTEMS/osdClustInfo.c +++ b/devIocStats/os/RTEMS/osdClustInfo.c @@ -59,73 +59,30 @@ #if RTEMS_LIBBSD_STACK /* The legacy network stack's global `struct mbstat mbstat` doesn't exist - * under libbsd -- query the same counts through libbsd's portable memstat - * API instead (same mechanism `netstat -m`/`vmstat -z` use). */ -#include -#include -#include - -static int get_mbuf_stat(const char *name, uint64_t *psize, uint64_t *pcount, - uint64_t *pfree) { - struct memory_type_list *mtlp; - struct memory_type *mtp; - - mtlp = memstat_mtl_alloc(); - if (mtlp == NULL) { - return -1; - } - if (memstat_sysctl_all(mtlp, 0) < 0) { - memstat_mtl_free(mtlp); - return -1; - } - mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, name); - if (mtp == NULL) { - memstat_mtl_free(mtlp); - return -1; - } - *psize = memstat_get_size(mtp); - *pcount = memstat_get_count(mtp); - *pfree = memstat_get_free(mtp); - memstat_mtl_free(mtlp); - return 0; -} + * under libbsd. libbsd's portable memstat API (memstat_sysctl_all(), the + * same mechanism `netstat -m`/`vmstat -z` use) looked like the right + * replacement and is what epics-modules/iocStats#61's RTEMS 6 port does + * too -- but memstat_sysctl_all() crashes on real hardware here: it walks + * into UMA's per-CPU counter stats (uma_vm_zone_stats() -> + * _bsd_counter_u64_alloc(), freebsd/sys/kern/subr_counter.c), which faults + * with a data abort (confirmed via addr2line against the crash PC/LR). + * Whatever's missing/broken in this RTEMS-libbsd port's counter(9) + * support, #61 was apparently never actually run on hardware either (it + * also has a row-index bug independent of this). Until that's root-caused, + * report "not available" rather than crash the IOC. */ int devIocStatsInitClusterInfo(void) { return 0; } int devIocStatsGetClusterInfo(int pool, clustInfo *pval) { - uint64_t size, count, free; - - if (pool == DATA_POOL) - return -1; - - if (get_mbuf_stat(MBUF_MEM_NAME, &size, &count, &free)) - return -1; - (*pval)[0][0] = size; - (*pval)[0][1] = count; - (*pval)[0][2] = free; - (*pval)[0][3] = count - free; - - if (get_mbuf_stat(MBUF_CLUSTER_MEM_NAME, &size, &count, &free)) - return -1; - (*pval)[1][0] = size; - (*pval)[1][1] = count; - (*pval)[1][2] = free; - (*pval)[1][3] = count - free; - - return 0; + (void)pool; + (void)pval; + return -1; } int devIocStatsGetClusterUsage(int pool, int *pval) { - uint64_t size, count, free; - - if (pool == DATA_POOL) - return -1; - - if (get_mbuf_stat(MBUF_MEM_NAME, &size, &count, &free)) - return -1; - *pval = count; - - return 0; + (void)pool; + (void)pval; + return -1; } #else /* RTEMS_LIBBSD_STACK */ From 9a7f2846a11d699544cb09f687a831aec385de08 Mon Sep 17 00:00:00 2001 From: Heinz Junkes Date: Fri, 7 Aug 2026 17:04:06 +0200 Subject: [PATCH 7/7] RTEMS: fix RAM_WS_MAX computing the wrong thing entirely Hardware-verified via testAll.cmd: RAM_WS_USED (57409520) + RAM_WS_FREE (180063944) came back ~227MB, but RAM_WS_MAX (rtems_configuration_get_work_space_size()) reported 639056 (~624KB) -- off by more than two orders of magnitude, badly violating the used+free<=max invariant this field is supposed to represent. rtems_configuration_get_work_space_size() reports the compile-time *configured* workspace size (via the `Configuration`/`_Workspace_Size` globals). On this BBB port (RTEMS_INIT=new) that apparently doesn't match the actual runtime-negotiated workspace region at all -- most of the board's RAM ends up in the workspace by the time the IOC queries it, well past whatever was configured at link time. Fixed by deriving numBytesTotal from the same Heap_Information_block the Free/Used numbers already come from (info.Free.total + info.Used.total) instead of a separately-sourced "configured" value. This is trivially self-consistent by construction and doesn't depend on how any given port sizes its workspace at boot. Co-Authored-By: Claude Sonnet 5 --- devIocStats/os/RTEMS/osdWorkspaceUsage.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/devIocStats/os/RTEMS/osdWorkspaceUsage.c b/devIocStats/os/RTEMS/osdWorkspaceUsage.c index 8afce45..f83f2eb 100644 --- a/devIocStats/os/RTEMS/osdWorkspaceUsage.c +++ b/devIocStats/os/RTEMS/osdWorkspaceUsage.c @@ -41,10 +41,18 @@ int devIocStatsGetWorkspaceUsage(memInfo *pval) { _RTEMS_Unlock_allocator(); #endif /* RTEMS_PROTECTED_HEAP */ #if (__RTEMS_MAJOR__ > 4) || (__RTEMS_MAJOR__ == 4 && __RTEMS_MINOR__ > 9) - /* The global `Configuration` object is only declared when the - * application itself includes ; a support library - * has to go through the public accessor instead. */ - pval->numBytesTotal = rtems_configuration_get_work_space_size(); + /* rtems_configuration_get_work_space_size() (the public accessor for + * the global `Configuration` object, which a support library can't + * reach directly -- it's only declared when the application itself + * includes ) reports the compile-time *configured* + * workspace size. Hardware-verified on a real BBB (RTEMS_INIT=new) + * that this does NOT match the actual runtime-negotiated workspace + * region: Free.total+Used.total came back ~227MB while this reported + * ~624KB. Derive the total from the same heap-info call that already + * produced Free/Used instead -- it's the only value guaranteed + * self-consistent with them, regardless of how this port sizes the + * workspace at boot. */ + pval->numBytesTotal = info.Free.total + info.Used.total; #else pval->numBytesTotal = _Configuration_Table->work_space_size; #endif