Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions devIocStats/devIocStatsAnalog.c
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,16 @@ 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};
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};
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};
Expand Down
11 changes: 7 additions & 4 deletions devIocStats/devIocStatsString.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,13 @@ static validGetStrParms statsGetStrParms[] = {
{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};
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);
Expand Down
3 changes: 2 additions & 1 deletion devIocStats/devIocStatsWaveform.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ static validGetWfmParms statsGetWfmParms[] = {
{NULL, NULL, 0}};

wStats devWaveformStats = {
5, NULL, waveform_init, waveform_init_record, NULL, waveform_read};
5, NULL, (DEVSUPFUN) waveform_init, (DEVSUPFUN) waveform_init_record,
NULL, (DEVSUPFUN) waveform_read};
epicsExportAddress(dset, devWaveformStats);

/* ---------------------------------------------------------------------- */
Expand Down
44 changes: 40 additions & 4 deletions devIocStats/os/RTEMS/devIocStatsOSD.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,21 @@
* info of 100% free (but 100% of 0 is still 0).
*
*/
/*
* Updated to RTEMS 6
* Contemporary Software
* Chris Johns <chris@contemporary.software>
*/
#include "epicsVersion.h"

#define RTEMS_VERSION_INT \
VERSION_INT(__RTEMS_MAJOR__, __RTEMS_MINOR__, __RTEMS_REVISION__, 0)

#if RTEMS_VERSION_INT <= VERSION_INT(5, 0, 0, 0)
/*
* This define effects rtems.h includes on 4.10 and earlier
*/
#define __RTEMS_VIOLATE_KERNEL_VISIBILITY__
#include <rtems.h>
#include <bsp.h>
#include <rtems/libcsupport.h>
#include <rtems/libio_.h>
Expand All @@ -53,6 +66,9 @@
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_var.h>
#endif /* RTEMS 5 and earlier */

#include <rtems.h>

#undef malloc
#undef free
Expand All @@ -67,17 +83,36 @@
#include <string.h>
#include <stdlib.h>

#include "epicsVersion.h"
#if RTEMS_VERSION_INT < VERSION_INT(6, 0, 0, 0)
#define rtems_bsd_reset() bsp_reset()
#else /* RTEMS_VERSION_INT < VERSION_INT(6, 0, 0, 0) */
#define rtems_bsd_reset() bsp_reset()
#endif /* RTEMS_VERSION_INT < VERSION_INT(6, 0, 0, 0) */
Comment on lines +86 to +90

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are both arms of this if() statement identical?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question and it has been a while.

It looks like a why to separate the pre-6 functionality from the 6 and later function to call. It can be reverted?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're probably not meant to be identical — looks like a leftover from splitting the #if/#else.

A few lines further down, the RTEMS 6 reboot() redefinition uses the two-argument RTEMS 6 bsp_reset() signature:

static inline void reboot(int val) {
  (void)val;
  bsp_reset(RTEMS_FATAL_SOURCE_APPLICATION, 112233);
}

but the RTEMS-6 arm of rtems_bsd_reset() right above it still calls the old zero-argument form. My guess is the #else branch was meant to be:

#define rtems_bsd_reset() bsp_reset(RTEMS_FATAL_SOURCE_APPLICATION, 112233)

to match reboot(), and got missed when the conditional was split out. Worth double-checking against the actual RTEMS 6 bsp_reset() prototype either way.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the name be rtems_bsp_reset() and not rtems_bsd_reset()?

Yes I agree. Thanks for the analysis. I checked and RTEMS 5 and I guess earlier have:

void bsp_reset(void);

and RTEMS 6 and later has:

void bsp_reset( rtems_fatal_source source, rtems_fatal_code code );


#define RTEMS_VERSION_INT \
VERSION_INT(__RTEMS_MAJOR__, __RTEMS_MINOR__, __RTEMS_REVISION__, 0)
#if RTEMS_LEGACY_STACK

#include <rtems/rtems_bsdnet.h>

#define sysBootLine rtems_bsdnet_bootp_cmdline
#else
#define sysBootLine "BOOTP cmdline not supported"
#endif
/* Override default STARTUP environment variable to use INIT */
#undef STARTUP
#define STARTUP "INIT"

#define CLUSTSIZES 2 /* only regular mbufs and clusters */

#if RTEMS_VERSION_INT >= VERSION_INT(6, 0, 0, 0)

#define NO_OF_CPUS rtems_configuration_get_maximum_processors()

#include <bsp/bootcard.h>
static inline void reboot(int val) {
(void)val;
bsp_reset(RTEMS_FATAL_SOURCE_APPLICATION, 112233);
}
Comment on lines +111 to +114

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this use the RTEMS higher-level APIs instead of the BSP one?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bsp_reset should be a physical means of reset, ie a register with a signal or triggering a watchdog. It is called at the end of an orderly shutdown. Directly calling it mean you do not have an orderly shutdown. Using exit(something) would be an orderly shutdown however any issues in the exit processing can hang a system.

@thelunatic thelunatic Aug 7, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would rtems_shutdown_executive be appropriate here?

If we set BSP_RESET_BOARD_AT_EXIT in the config.ini file, it will reach bsp_reset(), and we can avoid handling bsp_* functions from EPICS applications/modules.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would rtems_shutdown_executive be appropriate here?

If we set BSP_RESET_BOARD_AT_EXIT in the config.ini file, it will reach bsp_reset(), and we can avoid handling bsp_* functions from EPICS applications/modules.

I see this setting as a per BSP or hardware specific settings combined with the system requirements. I am not comfortable having this setting be made a requirement for EPICS as it makes a fragile link between a BSP configuration, the BSP default and EPICS. And I doubt the calls are consistently implemented in all BSPs so both options work.

The option lets a BSP return to the boot loader or a reset happen. Some BSPs do not have a means to return to a boot loader as the boot process is destructive and others it is easier as a boot loader can handle a reset, ie hardware security reasons.

Should EPICS be consistent in how a device restarts? If yes then what role does a BSP reset have here in iocStats?

As an example the MVME5500 or MVME2700 return the boot loader on exit(). And on the boards I have access to once there they sit. I have no idea if a reset can be scripted and how recovery happens in a real system? Another example a Zynq has a software reset signal that is different to the power on reset (POR) signal and you often need custom hardware to manage this. The bsp_reset() is wired to the software reset.

#else /* RTEMS 6 or later */
#ifdef RTEMS_BSP_PGM_EXEC_AFTER /* only defined on uC5282 */
#define reboot(x) bsp_reset(0)
#elif (defined(__PPC__) && RTEMS_VERSION_INT > VERSION_INT(4, 9, 0, 0))
Expand All @@ -91,3 +126,4 @@
#else
#define reboot(x) rtemsReboot()
#endif
#endif /* RTEMS 6 or later */
66 changes: 64 additions & 2 deletions devIocStats/os/RTEMS/osdClustInfo.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,71 @@

#include <devIocStats.h>

/* This would otherwise need _KERNEL to be defined... */
extern struct mbstat mbstat;
#include <sys/param.h>
#include <sys/mbuf.h>

int devIocStatsInitClusterInfo(void) { return 0; }

#if RTEMS_LIBBSD_STACK

#include <memstat.h>

int devIocStatsGetClusterInfo(int pool, clustInfo *pval) {
struct memory_type_list *mtlp;
struct memory_type *mtp;

if (pool == DATA_POOL) {
return -1;
}

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, MBUF_MEM_NAME);
if (mtp == NULL) {
memstat_mtl_free(mtlp);
return -1;
}

(*pval)[0][0] = memstat_get_size(mtp);
(*pval)[0][1] = memstat_get_count(mtp);
(*pval)[0][2] = memstat_get_free(mtp);
(*pval)[0][3] = (*pval)[0][1] - (*pval)[0][2];

mtp = memstat_mtl_find(mtlp, ALLOCATOR_UMA, MBUF_CLUSTER_MEM_NAME);
if (mtp == NULL) {
memstat_mtl_free(mtlp);
return -1;
}

(*pval)[0][0] = memstat_get_size(mtp);
(*pval)[0][1] = memstat_get_count(mtp);
(*pval)[0][2] = memstat_get_free(mtp);
(*pval)[0][3] = (*pval)[0][1] - (*pval)[0][2];

memstat_mtl_free(mtlp);

return 0;
}

int devIocStatsGetClusterUsage(int pool, int* pval) {
(void) pool;
(void) pval;
return -1;
}

#else /* RTEMS_LIBBSD_STACK */

/* This would otherwise need _KERNEL to be defined... */
extern struct mbstat mbstat;

int devIocStatsGetClusterInfo(int pool, clustInfo *pval) {
if (pool == DATA_POOL)
return -1;
Expand All @@ -86,3 +146,5 @@ int devIocStatsGetClusterUsage(int pool, int *pval) {

return 0;
}

#endif /* RTEMS_LIBBSD_STACK */
37 changes: 34 additions & 3 deletions devIocStats/os/RTEMS/osdCpuUsage.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,38 @@

#include <devIocStats.h>

static double prev_total = 0;
static double prev_idle = 0;

#if __RTEMS_MAJOR__ >= 6

#include <sys/resource.h>

static double oldActiveUsage;
static double oldIdleUsage;

/*
* See IEEE Std 1003.1-1988 (“POSIX.1”) for getrusage()
*/
static void cpu_ticks(double *total, double *idle) {
struct rusage stats;
double curActive;
double curIdle;
getrusage(RUSAGE_SELF, &stats);
curActive = (double)stats.ru_utime.tv_sec + stats.ru_utime.tv_usec / 1e6;
curIdle = (double)stats.ru_stime.tv_sec + stats.ru_stime.tv_usec / 1e6;
*idle = curIdle - oldIdleUsage;
*total = *idle + (curActive - oldActiveUsage);
oldActiveUsage = curActive;
oldIdleUsage = curIdle;
Comment on lines +68 to +74

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vijay says there may be a proper RTEMS API coming for this, but only for RTEMS-7; if so that would be a good change to make use of for the supported RTEMS version(s).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which API is this?

@thelunatic thelunatic Aug 7, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was talking about rtems_cpu_task_get_usage MR here: https://gitlab.rtems.org/rtems/rtos/rtems/-/merge_requests/1347

I haven't looked at how well it fits in here, but looks like the rtems API can be used here instead of custom coding it again. This code might get stale again if RTEMS is updated, and it will likely be easier to use an rtems api instead of implementing it here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using rtems_task_iterate() to loop over each task and sum the values of _Thread_Get_CPU_time_used(task)?

Unfortunately, _Thread_Get_CPU_time_used() seems to be a score function, but if there's no alternative yet perhaps its the best option for now. I think its been around since RTEMS 5.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using rtems_task_iterate() to loop over each task and sum the values of _Thread_Get_CPU_time_used(task)?

Yes this is at the core of the approach I used in getusage back in 2024.

Unfortunately, _Thread_Get_CPU_time_used() seems to be a score function, but if there's no alternative yet perhaps its the best option for now. I think its been around since RTEMS 5.

In the early days of EPICS integration we were not strict as everyone worked to get something going. The downside of this is we are still paying the price today as we extract EPICS from deep inside RTEMS. A key part of this effort is epics-base/epics-base#905.

I was talking about rtems_cpu_task_get_usage MR here: https://gitlab.rtems.org/rtems/rtos/rtems/-/merge_requests/1347

Ah OK, the GSoC project. That call fills the gap of getrusage where you are asking about the CPU usage of specific thread that is not the executing thread. I thought iocStats was about the current load on the board and as RTEMS is a single process system calling getrusage with RUSAGE_SELF gives you that value?

I haven't looked at how well it fits in here, but looks like the rtems API can be used here instead of custom coding it again. This code might get stale again if RTEMS is updated, and it will likely be easier to use an rtems api instead of implementing it here.

It is per thread and yes correct.

I am little confused by this discussion as I thought I had done this and exported the result via the standards supported getrusage call back in July 2024?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am little confused by this discussion as I thought I had done this and exported the result via the standards supported getrusage call back in July 2024?

I understand this change a bit more now after your comments. getrusage and the code proposed here looks good to me.

}

#else /* RTEMS 6 or later */

/*
* Direct access to the Object information table
*/

#if (__RTEMS_MAJOR__ > 4) || (__RTEMS_MAJOR__ == 4 && __RTEMS_MINOR__ > 7)
typedef char objName[13];
#define RTEMS_OBJ_GET_NAME(tc, name) \
Expand Down Expand Up @@ -79,9 +111,6 @@ typedef char *objName;
* from the RTEMS source.
*/

static double prev_total = 0;
static double prev_idle = 0;

static void cpu_ticks(double *total, double *idle) {
Objects_Information *obj;
Thread_Control *tc;
Expand Down Expand Up @@ -116,6 +145,8 @@ static void cpu_ticks(double *total, double *idle) {
}
}

#endif /* RTEMS 6 or later */

int devIocStatsInitCpuUsage(void) {
cpu_ticks(&prev_total, &prev_idle);
#ifdef SSRLAPPSMISCUTILS
Expand Down
1 change: 0 additions & 1 deletion devIocStats/os/RTEMS/osdCpuUtilization.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ int devIocStatsInitCpuUtilization(loadInfo *pval) {
return 0;
}

/* FIXME: This relies on the device support calling it after CpuUsage */
int devIocStatsGetCpuUtilization(loadInfo *pval) {
pval->iocLoad = pval->cpuLoad;
return 0;
Expand Down
8 changes: 8 additions & 0 deletions devIocStats/os/RTEMS/osdFdUsage.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,24 @@

#include <devIocStats.h>

#if __RTEMS_MAJOR__ >= 6
#include <rtems/libio_.h>
#endif /* _RTEMS_MAJOR__ >= 6 */

int devIocStatsInitFDUsage(void) { return 0; }

int devIocStatsGetFDUsage(fdInfo *pval) {
#if __RTEMS_MAJOR__ >= 6
pval->used = rtems_libio_count_open_iops();
#else
int i, tot;

for (tot = 0, i = 0; i < rtems_libio_number_iops; i++) {
if (rtems_libio_iops[i].flags & LIBIO_FLAGS_OPEN)
tot++;
}
pval->used = tot;
#endif /* _RTEMS_MAJOR__ >= 6 */
pval->max = rtems_libio_number_iops;
return 0;
}
41 changes: 39 additions & 2 deletions devIocStats/os/RTEMS/osdIFErrors.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,47 @@

#include <devIocStats.h>

int devIocStatsInitIFErrors(void) { return 0; }

#if RTEMS_LIBBSD_STACK

#include <ifaddrs.h>
#include <net/if.h>

int devIocStatsGetIFErrors(ifErrInfo *pval) {
struct ifaddrs *ifap, *ifa;

/* add all interfaces' errors */
pval->ierrors = 0;
pval->oerrors = 0;

if (getifaddrs(&ifap) != 0) {
return -1;
}

for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr->sa_family != AF_LINK) {
continue;
}
#define IFA_STAT(s) (((struct if_data *)ifa->ifa_data)->ifi_##s)
pval->ierrors += IFA_STAT(ierrors);
pval->oerrors += IFA_STAT(oerrors);
}

freeifaddrs(ifap);

return 0;
}

#else /* RTEMS_LIBBSD_STACK */

#include <net/if_var.h>

/* This would otherwise need _KERNEL to be defined... */
extern struct ifnet *ifnet;

int devIocStatsInitIFErrors(void) { return 0; }

int devIocStatsGetIFErrors(ifErrInfo *pval) {

struct ifnet *ifp;

/* add all interfaces' errors */
Expand All @@ -57,3 +92,5 @@ int devIocStatsGetIFErrors(ifErrInfo *pval) {
}
return 0;
}

#endif /* RTEMS_LIBBSD_STACK */
4 changes: 4 additions & 0 deletions devIocStats/os/RTEMS/osdSuspTasks.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
int devIocStatsInitSuspTasks(void) { return 0; }

int devIocStatsGetSuspTasks(int *pval) {
#if __RTEMS_MAJOR__ < 6
Objects_Control *o;
Objects_Id id = OBJECTS_ID_INITIAL_INDEX;
Objects_Id nid;
Expand All @@ -59,4 +60,7 @@ int devIocStatsGetSuspTasks(int *pval) {
}
*pval = n;
return 0;
#else /* __RTEMS_MAJOR__ < 6 */
return -1;
#endif /* __RTEMS_MAJOR__ < 6 */
}
5 changes: 5 additions & 0 deletions devIocStats/os/RTEMS/osdWorkspaceUsage.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ int devIocStatsInitWorkspaceUsage(void) { return 0; }

int devIocStatsGetWorkspaceUsage(memInfo *pval) {
Heap_Information_block info;
#if __RTEMS_MAJOR__ >= 6
malloc_info(&info);
pval->numBytesTotal = info.Stats.size;
#else /* __RTEMS_MAJOR__ >= 6 */
#ifdef RTEMS_PROTECTED_HEAP
_Protected_heap_Get_information(&_Workspace_Area, &info);
#else /* RTEMS_PROTECTED_HEAP */
Expand All @@ -45,6 +49,7 @@ int devIocStatsGetWorkspaceUsage(memInfo *pval) {
#else
pval->numBytesTotal = _Configuration_Table->work_space_size;
#endif
#endif /* _RTEMS_MAJOR__ >= 6 */
pval->numBytesFree = info.Free.total;
pval->numBytesAlloc = info.Used.total;
return 0;
Expand Down
Loading