From d2ca02acfa0464e8f0250c7f93ba9322dcb1127d Mon Sep 17 00:00:00 2001 From: Christopher Wipf Date: Tue, 4 May 2021 11:19:28 -0700 Subject: [PATCH 1/2] Userspace alternative to mbuf kernel module This code can be used when models run in userspace. It removes the dependency on the special device file /dev/mbuf. POSIX shared memory is obtained in userspace. Enable this at compile time by defining USE_POSIXMBUF. When building a model, add posixmbuf=1 to the CDS parameters. --- CMakeLists.txt | 5 ++ src/daqd/CMakeLists.txt | 1 + src/drv/mbuf/mbuf_probe/CMakeLists.txt | 2 +- src/drv/rfm.c | 82 ++++++++++++----------- src/drv/shmem.c | 32 +++++++++ src/epics/seq/CMakeLists.txt | 5 +- src/epics/util/feCodeGen.pl | 6 ++ src/epics/util/lib/Parameters.pm | 4 ++ src/epics/util/lib/createUserMakefile.pm | 8 +++ src/gds/awgtpman/CMakeLists.txt | 5 +- src/gds/awgtpman/rmapi.c | 25 +------ src/ix_stream/CMakeLists.txt | 2 + src/local_dc/CMakeLists.txt | 7 +- src/mx_stream/CMakeLists.txt | 2 + src/pub_sub_stream/CMakeLists.txt | 4 ++ src/pub_sub_stream/plugins/CMakeLists.txt | 1 + 16 files changed, 123 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fc085b9..ef0cc9b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,11 @@ CHECK_FUNCTION_EXISTS(fchdir HAVE_FCHDIR) add_compile_options(-g3) +option(USE_POSIXMBUF "Obtain shared memory in userspace" OFF) +if (USE_POSIXMBUF) +add_definitions(-DUSE_POSIXMBUF) +endif() + option(USE_GPSCLOCK "Obtain GPS time in userspace from system clock" OFF) if (USE_GPSCLOCK) add_definitions(-DUSE_GPSCLOCK) diff --git a/src/daqd/CMakeLists.txt b/src/daqd/CMakeLists.txt index a0242c61..10f97a1d 100644 --- a/src/daqd/CMakeLists.txt +++ b/src/daqd/CMakeLists.txt @@ -145,6 +145,7 @@ target_link_libraries(daqd PUBLIC epics::ca epics::cas #${EPICS_BASE_CA_LIBS} ${EPICS_BASE_CAS_LIBS} ldastools::framecpp + rt driver::shmem driver::ini_parsing crc32 diff --git a/src/drv/mbuf/mbuf_probe/CMakeLists.txt b/src/drv/mbuf/mbuf_probe/CMakeLists.txt index 302c0fb7..d7ace351 100644 --- a/src/drv/mbuf/mbuf_probe/CMakeLists.txt +++ b/src/drv/mbuf/mbuf_probe/CMakeLists.txt @@ -13,7 +13,7 @@ target_include_directories(mbuf_probe PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../include ${CMAKE_SOURCE_DIR}/src/shmem ) -target_link_libraries(mbuf_probe PUBLIC driver::shmem driver::ini_parsing) +target_link_libraries(mbuf_probe PUBLIC rt driver::shmem driver::ini_parsing) target_requires_cpp11(mbuf_probe PRIVATE) install(TARGETS mbuf_probe DESTINATION bin) \ No newline at end of file diff --git a/src/drv/rfm.c b/src/drv/rfm.c index 8082a9f9..ba2ed82a 100644 --- a/src/drv/rfm.c +++ b/src/drv/rfm.c @@ -21,45 +21,6 @@ /// Pointer to start of shared memory segment for a selected system. volatile unsigned char *addr = 0; -/// Search shared memory device file names in /rtl_mem_* -/// @param[in] *sys_name Name of system, required to attach shared memory. -/// @return Pointer to start of shared memory segment for this system. -volatile void * -findSharedMemory(char *sys_name) -{ - char *s; - int fd; - char sys[128]; - char fname[128]; - strcpy(sys, sys_name); - for(s = sys; *s; s++) *s=tolower(*s); - - sprintf(fname, "/rtl_mem_%s", sys); - - int ss = 64*1024*1024; - if (!strcmp(sys_name, "ipc")) ss = 32*1024*1024; - if (!strcmp(sys_name, "shmipc")) ss = 16*1024*1024; - - if ((fd = open ("/dev/mbuf", O_RDWR | O_SYNC)) < 0) { - fprintf(stderr, "Couldn't open /dev/mbuf read/write\n"); - return 0; - } - struct mbuf_request_struct req; - req.size = ss; - strcpy(req.name, sys); - ioctl (fd, IOCTL_MBUF_ALLOCATE, &req); - ioctl (fd, IOCTL_MBUF_INFO, &req); - - addr = (volatile unsigned char *)mmap(0, ss, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (addr == MAP_FAILED) { - printf("return was %d\n",errno); - perror("mmap"); - _exit(-1); - } - printf(" %s mmapped address is 0x%lx\n", sys,(long)addr); - return addr; -} - /// Find and return pointer to shared memory. This is old, from when EPICS memory was on /// VMIC RFM cards. /// This function should be deleted in favor just having findSharedMemory return pointer, @@ -103,6 +64,7 @@ findSharedMemorySize(char *sys_name, int size) int ss = size*1024*1024; printf("Making mbuff area %s with size %d\n",sys,ss); +#ifndef USE_POSIXMBUF if ((fd = open ("/dev/mbuf", O_RDWR | O_SYNC)) < 0) { fprintf(stderr, "Couldn't open /dev/mbuf read/write\n"); return 0; @@ -112,6 +74,36 @@ findSharedMemorySize(char *sys_name, int size) strcpy(req.name, sys); ioctl (fd, IOCTL_MBUF_ALLOCATE, &req); ioctl (fd, IOCTL_MBUF_INFO, &req); +#else + int status; + struct stat statbuf; + sprintf(fname, "/%s", sys); + fd = shm_open(fname, O_RDWR | O_CREAT, 0666); + if (fd < 0) { + fprintf(stderr, "Couldn't open POSIX SHM %s\n", fname); + perror("shm_open"); + return 0; + } + // ftruncate() can be called only once to set the POSIX shared memory size. + // Any subsequent calls will fail. The size is checked using fstat() + // to determine whether ftruncate() needs to be called. + status = fstat(fd, &statbuf); + if (status) { + fprintf(stderr, "Couldn't stat POSIX SHM %s", fname); + perror("fstat"); + return 0; + } + if (statbuf.st_size == 0) { + status = ftruncate(fd, ss); + if (status) { + fprintf(stderr, "Couldn't truncate POSIX SHM %s\n", fname); + perror("ftruncate"); + return 0; + } + } else if (statbuf.st_size != ss) { + fprintf(stderr, "warning: POSIX SHM %s size %d differs from requested %d\n", fname, statbuf.st_size, ss); + } +#endif // USE_POSIXMBUF addr = (volatile unsigned char *)mmap(0, ss, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { @@ -123,3 +115,15 @@ findSharedMemorySize(char *sys_name, int size) return addr; } +/// Search shared memory device file names in /rtl_mem_* +/// @param[in] *sys_name Name of system, required to attach shared memory. +/// @return Pointer to start of shared memory segment for this system. +volatile void * +findSharedMemory(char *sys_name) +{ + int ss = 64; + if (!strcmp(sys_name, "ipc")) ss = 32; + if (!strcmp(sys_name, "shmipc")) ss = 16; + return findSharedMemorySize(sys_name, ss); +} + diff --git a/src/drv/shmem.c b/src/drv/shmem.c index e91e9e04..e50707b2 100644 --- a/src/drv/shmem.c +++ b/src/drv/shmem.c @@ -49,6 +49,7 @@ volatile void* shmem_open_segment(const char *sys_name, size_t req_size) return NULL; } +#ifndef USE_POSIXMBUF if ((fd = open ("/dev/mbuf", O_RDWR | O_SYNC)) < 0) { fprintf(stderr, "Couldn't open /dev/mbuf read/write\n"); return 0; @@ -62,6 +63,37 @@ volatile void* shmem_open_segment(const char *sys_name, size_t req_size) close(fd); return NULL; } +#else + int status; + char fname[MBUF_NAME_LEN+1]; + struct stat statbuf; + sprintf(fname, "/%s", sys_name); + fd = shm_open(fname, O_RDWR | O_CREAT, 0666); + if (fd < 0) { + fprintf(stderr, "Couldn't open POSIX SHM %s\n", fname); + perror("shm_open"); + return 0; + } + // ftruncate() can be called only once to set the POSIX shared memory size. + // Any subsequent calls will fail. The size is checked using fstat() + // to determine whether ftruncate() needs to be called. + status = fstat(fd, &statbuf); + if (status) { + fprintf(stderr, "Couldn't stat POSIX SHM %s", fname); + perror("fstat"); + return 0; + } + if (statbuf.st_size == 0) { + status = ftruncate(fd, req_size); + if (status) { + fprintf(stderr, "Couldn't truncate POSIX SHM %s\n", fname); + perror("ftruncate"); + return 0; + } + } else if (statbuf.st_size != req_size) { + fprintf(stderr, "warning: POSIX SHM %s size %d differs from requested %d\n", fname, statbuf.st_size, req_size); + } +#endif // USE_POSIXMBUF addr = (volatile void *)mmap(0, req_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { diff --git a/src/epics/seq/CMakeLists.txt b/src/epics/seq/CMakeLists.txt index 1b75be15..fe0b1941 100644 --- a/src/epics/seq/CMakeLists.txt +++ b/src/epics/seq/CMakeLists.txt @@ -1,8 +1,7 @@ if (Boost_FOUND) add_executable(standalone_edc - standalone_edcu.cc - ${CMAKE_CURRENT_SOURCE_DIR}/../../drv/rfm.c) + standalone_edcu.cc) #target_compile_options(standalone_edc PRIVATE -fsanitize=address) #target_link_libraries(standalone_edc PRIVATE asan) #target_compile_options(standalone_edc PRIVATE @@ -18,6 +17,8 @@ target_link_libraries(standalone_edc PUBLIC pv::simple_pv ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + rt + driver::shmem driver::gpsclock) target_requires_cpp11(standalone_edc PUBLIC) diff --git a/src/epics/util/feCodeGen.pl b/src/epics/util/feCodeGen.pl index c9efd27d..32a1171e 100755 --- a/src/epics/util/feCodeGen.pl +++ b/src/epics/util/feCodeGen.pl @@ -161,6 +161,7 @@ $daq_prefix="DC0"; $allBiquad = 1; $dac_dt_enable = 0; $internalclk = 0; +$posixmbuf = 0; $userspacegps = 0; # Load model name without .mdl extension. @@ -2452,6 +2453,11 @@ sub createEpicsMakefile { { print OUTME "EXTRA_CFLAGS += -DFIR_FILTERS\n"; } + if ($posixmbuf) + { + print OUTME "EXTRA_CFLAGS += -DUSE_POSIXMBUF\n"; + print OUTME "EXTRA_LDFLAGS += -lrt\n"; + } if ($userspacegps) { print OUTME "EXTRA_CFLAGS += -DUSE_GPSCLOCK\n"; diff --git a/src/epics/util/lib/Parameters.pm b/src/epics/util/lib/Parameters.pm index 4c657246..d8a281f8 100644 --- a/src/epics/util/lib/Parameters.pm +++ b/src/epics/util/lib/Parameters.pm @@ -307,6 +307,10 @@ sub parseParams { # Specify IPC rate if lower than model rate $::ipcrate = $spp[1]; } + case "posixmbuf" + { + $::posixmbuf = 1; + } case "userspacegps" { $::userspacegps = 1; diff --git a/src/epics/util/lib/createUserMakefile.pm b/src/epics/util/lib/createUserMakefile.pm index 14da30f7..bb2d9c4a 100644 --- a/src/epics/util/lib/createUserMakefile.pm +++ b/src/epics/util/lib/createUserMakefile.pm @@ -169,6 +169,10 @@ if ($::::rfmDelay) { print OUTM "CFLAGS += -DUSER_SPACE=1\n"; print OUTM "CFLAGS += -fno-builtin-sincos\n"; +if ($::posixmbuf) { + print OUTM "CFLAGS += -DUSE_POSIXMBUF\n"; +} + if ($::userspacegps) { print OUTM "CFLAGS += -DUSE_GPSCLOCK\n"; } @@ -191,6 +195,10 @@ print OUTM "LDFLAGS = -L \$(API_LIB_PATH) -lsisci\n"; print OUTM "LDFLAGS = -L \$(API_LIB_PATH) \n"; } +if ($::posixmbuf) { + print OUTM "LDFLAGS += -lrt\n"; +} + print OUTM "TARGET=$::skeleton\n\n\n"; if ($::userspacegps) { print OUTM "$::skeleton: $::skeleton.o gpsclock.o rfm.o \n\n"; diff --git a/src/gds/awgtpman/CMakeLists.txt b/src/gds/awgtpman/CMakeLists.txt index b73ffc49..92a14198 100644 --- a/src/gds/awgtpman/CMakeLists.txt +++ b/src/gds/awgtpman/CMakeLists.txt @@ -27,8 +27,7 @@ add_executable(awgtpman tconv.c shared_memory.c - ../../util/modelrate.c - ../../drv/shmem.c) + ../../util/modelrate.c) target_include_directories(awgtpman PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} @@ -44,6 +43,8 @@ target_link_libraries(awgtpman PRIVATE #testpoint_objs rawgapi_rpc rtestpoint_rpc + rt + driver::shmem ) target_compile_definitions(awgtpman PUBLIC LIGO_GDS _ADVANCED_LIGO _AWG_RM MAX_CHNNAME_SIZE=60) diff --git a/src/gds/awgtpman/rmapi.c b/src/gds/awgtpman/rmapi.c index 0dfccbe3..ffcef78d 100644 --- a/src/gds/awgtpman/rmapi.c +++ b/src/gds/awgtpman/rmapi.c @@ -30,7 +30,7 @@ static char *versionId = "Version $Id$" ; #include #include -#include "../drv/mbuf/mbuf.h" +#include "drv/shmem.h" @@ -201,9 +201,6 @@ static char *versionId = "Version $Id$" ; } -int mbuf_opened = 0; -int mbuf_fd = 0; - int rmInit (short ID) { // 0 -- our shared memory @@ -211,40 +208,24 @@ int mbuf_fd = 0; if (ID == 0 || ID == 2) { int fd; void *addr; - char fname[128]; extern char system_name[PARAM_ENTRY_LEN]; /*rmboard[ID] = rm[ID] = malloc (rmsize[ID]);*/ rmmaster[ID] = 0; - strcpy(fname, "/dev/mbuf"); - if (!mbuf_opened) { - if ((fd = open (fname, O_RDWR | O_SYNC)) < 0) { - fprintf(stderr, "Couldn't open /dev/mbuf read/write\n"); - _exit(-1); - } - mbuf_opened = 1; - mbuf_fd = fd; - } else fd = mbuf_fd; - struct mbuf_request_struct req; if (ID == 0) { - strcpy(req.name, system_name); rmsize[ID] = 64 * 1024 * 1024; + addr = shmem_open_segment(system_name, rmsize[ID]); } else { - strcpy(req.name, "ipc"); rmsize[ID] = 4 * 1024 * 1024; + addr = shmem_open_segment("ipc", rmsize[ID]); } - req.size = rmsize[ID]; - ioctl (fd, IOCTL_MBUF_ALLOCATE, &req); - //ioctl (fd, IOCTL_MBUF_INFO, &req); - addr = mmap(0, rmsize[ID], PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { printf("return was %d\n",errno); perror("mmap"); _exit(-1); } - printf("%s mmapped address is 0x%lx\n", fname, (long)addr); rmboard[ID] = rm[ID] = addr; } else if (ID == 1) { diff --git a/src/ix_stream/CMakeLists.txt b/src/ix_stream/CMakeLists.txt index 1ab8f217..5310ad2f 100644 --- a/src/ix_stream/CMakeLists.txt +++ b/src/ix_stream/CMakeLists.txt @@ -5,6 +5,7 @@ target_link_libraries(ix_test PUBLIC dolphin::sisci) add_executable(dix_xmit dix_xmit.c) target_link_libraries(dix_xmit PUBLIC args + rt driver::shmem dolphin::sisci pv::simple_pv) @@ -13,6 +14,7 @@ install(TARGETS dix_xmit DESTINATION bin) add_executable(dix_recv dix_recv.c) target_link_libraries(dix_recv PUBLIC args + rt driver::shmem dolphin::sisci) install(TARGETS dix_recv DESTINATION bin) diff --git a/src/local_dc/CMakeLists.txt b/src/local_dc/CMakeLists.txt index 020ce266..5dc0a3c4 100644 --- a/src/local_dc/CMakeLists.txt +++ b/src/local_dc/CMakeLists.txt @@ -1,8 +1,10 @@ -add_executable(local_dc local_dc.c ${CMAKE_CURRENT_SOURCE_DIR}/../drv/rfm.c) +add_executable(local_dc local_dc.c) target_link_libraries(local_dc PUBLIC args util + rt + driver::shmem driver::gpsclock) configure_file(test_local_dc.sh.in test_local_dc.sh @ONLY) @@ -16,7 +18,8 @@ add_executable(check_for_dcu_existence tests/check_for_dcu_existence.cc) target_include_directories(check_for_dcu_existence PUBLIC ${Boost_INCLUDE_DIRS}) target_link_libraries(check_for_dcu_existence PUBLIC - shmem + rt + driver::shmem args) target_requires_cpp11(check_for_dcu_existence PUBLIC) diff --git a/src/mx_stream/CMakeLists.txt b/src/mx_stream/CMakeLists.txt index 09629b8b..0f1fa828 100644 --- a/src/mx_stream/CMakeLists.txt +++ b/src/mx_stream/CMakeLists.txt @@ -40,6 +40,7 @@ if (OPENMX_FOUND) add_executable(omx_recv omx_recv.c) target_link_libraries(omx_recv PUBLIC + rt driver::shmem pv::simple_pv openmx::openmx @@ -50,6 +51,7 @@ if (OPENMX_FOUND) add_executable(omx_recv_buffered omx_recv_buffered.cc recv_buffer.cc) target_include_directories(omx_recv_buffered PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(omx_recv_buffered PUBLIC + rt driver::shmem pv::simple_pv openmx::openmx diff --git a/src/pub_sub_stream/CMakeLists.txt b/src/pub_sub_stream/CMakeLists.txt index 854b0fba..427e1282 100644 --- a/src/pub_sub_stream/CMakeLists.txt +++ b/src/pub_sub_stream/CMakeLists.txt @@ -9,6 +9,7 @@ if (libcds-pubsub_FOUND) ) target_include_directories(cps_xmit PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../include) target_link_libraries(cps_xmit PUBLIC + rt driver::shmem pv::simple_pv args @@ -21,6 +22,7 @@ if (libcds-pubsub_FOUND) ) target_include_directories(cps_xmit_asan PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../include) target_link_libraries(cps_xmit_asan PUBLIC + rt driver::shmem pv::simple_pv args @@ -40,6 +42,7 @@ if (libcds-pubsub_FOUND) ${Boost_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/../daqd) target_link_libraries(cps_recv PUBLIC + rt driver::shmem pv::simple_pv args @@ -59,6 +62,7 @@ if (libcds-pubsub_FOUND) ${Boost_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/../daqd) target_link_libraries(cps_recv_asan PUBLIC + rt driver::shmem pv::simple_pv args diff --git a/src/pub_sub_stream/plugins/CMakeLists.txt b/src/pub_sub_stream/plugins/CMakeLists.txt index 915d3cba..b88589ac 100644 --- a/src/pub_sub_stream/plugins/CMakeLists.txt +++ b/src/pub_sub_stream/plugins/CMakeLists.txt @@ -12,5 +12,6 @@ target_link_libraries(pub_sub_plugins PUBLIC cds::pub_sub util) target_link_libraries(pub_sub_plugins PRIVATE + rt driver::shmem ${CMAKE_THREAD_LIBS_INIT}) \ No newline at end of file -- GitLab From b27d9340c8e1b08695a0bf3aaf6f07655ab26e0a Mon Sep 17 00:00:00 2001 From: Christopher Wipf Date: Tue, 4 May 2021 15:55:51 -0700 Subject: [PATCH 2/2] "Soft realtime" mode for userspace models This code, together with the userspace gpstime and mbuf compile-time options, lets models run in a normal Linux host or container as ordinary userspace programs. The models cannot be used to drive ADC/DAC hardware, but can be useful for simulation or testing purposes. Instead of waiting at the end of each cycle, models wait only at the end of each 16 Hz DAQ block. The rest of the time, they run as fast as they can while staying in sync with each other. As long as they finish each 16 Hz block with several milliseconds to spare, they can absorb the normal OS scheduling jitter. Enable this by adding softrt=1 to the CDS parameters. --- src/epics/util/feCodeGen.pl | 1 + src/epics/util/lib/Parameters.pm | 6 +- src/epics/util/lib/createUserMakefile.pm | 4 + src/fe/controllerAppUser.c | 104 +++++++++++++++++- src/fe/controllerIopUser.c | 130 ++++++++++++++++++++--- src/include/softrt.h | 52 +++++++++ 6 files changed, 280 insertions(+), 17 deletions(-) create mode 100644 src/include/softrt.h diff --git a/src/epics/util/feCodeGen.pl b/src/epics/util/feCodeGen.pl index 32a1171e..55a13b2d 100755 --- a/src/epics/util/feCodeGen.pl +++ b/src/epics/util/feCodeGen.pl @@ -161,6 +161,7 @@ $daq_prefix="DC0"; $allBiquad = 1; $dac_dt_enable = 0; $internalclk = 0; +$softrt = 0; $posixmbuf = 0; $userspacegps = 0; diff --git a/src/epics/util/lib/Parameters.pm b/src/epics/util/lib/Parameters.pm index d8a281f8..47d56b1b 100644 --- a/src/epics/util/lib/Parameters.pm +++ b/src/epics/util/lib/Parameters.pm @@ -307,6 +307,10 @@ sub parseParams { # Specify IPC rate if lower than model rate $::ipcrate = $spp[1]; } + case "softrt" + { + $::softrt = 1; + } case "posixmbuf" { $::posixmbuf = 1; @@ -375,7 +379,7 @@ sub parseParams { $errmsg .= "************\n\n"; die $errmsg; } - if($::specificCpu eq -1 && $::iopModel ne 1) + if($::specificCpu eq -1 && $::iopModel ne 1 && $::softrt ne 1) { $errmsg = "\n************\n"; $errmsg .= "***ERROR: Missing Required Parameter Block Entry: specific_cpu\n"; diff --git a/src/epics/util/lib/createUserMakefile.pm b/src/epics/util/lib/createUserMakefile.pm index bb2d9c4a..dfd408d7 100644 --- a/src/epics/util/lib/createUserMakefile.pm +++ b/src/epics/util/lib/createUserMakefile.pm @@ -169,6 +169,10 @@ if ($::::rfmDelay) { print OUTM "CFLAGS += -DUSER_SPACE=1\n"; print OUTM "CFLAGS += -fno-builtin-sincos\n"; +if ($::softrt) { + print OUTM "CFLAGS += -DSOFTRT\n"; +} + if ($::posixmbuf) { print OUTM "CFLAGS += -DUSE_POSIXMBUF\n"; } diff --git a/src/fe/controllerAppUser.c b/src/fe/controllerAppUser.c index f726819f..d7573dc7 100644 --- a/src/fe/controllerAppUser.c +++ b/src/fe/controllerAppUser.c @@ -61,6 +61,13 @@ // #include "dolphin_usp.c" +#ifdef SOFTRT +#include "softrt.h" +struct softrt_struct *softrt; +long softrt_clk, softrt_clk1; +int softrt_slot = 0; +#endif + #ifdef USE_GPSCLOCK #include "drv/gpsclock.h" #endif @@ -188,7 +195,7 @@ fe_start_app_user( ) int feStatus = 0; - int clk, clk1; // Used only when run on timer enabled (test mode) + long clk, clk1; // Used only when run on timer enabled (test mode) int cnt = 0; unsigned long cpc; @@ -397,11 +404,41 @@ fe_start_app_user( ) printf( "waiting to sync %d\n", ioMemData->iodata[ ll ][ 0 ].cycle ); clock_gettime( CLOCK_MONOTONIC, &cpuClock[ CPU_TIME_CYCLE_START ] ); +#ifndef SOFTRT // Spin until cycle 0 detected in first ADC buffer location. do { } while ( ioMemData->iodata[ ll ][ 0 ].cycle != 0 ); timeSec = ioMemData->iodata[ ll ][ 0 ].timeSec; +#else + // Initialize softrt struct + softrt_init(0); + for (jj = 1; jj < SOFTRT_MAX_MODELS; jj++) + { + if (softrt->alive[jj].value == 0) + { + // get IOP cycle + softrt->cycle[jj].value = softrt->cycle[0].value; + // round up to the next GPS second + softrt->cycle[jj].value += IOP_IO_RATE - (softrt->cycle[0].value % IOP_IO_RATE); +#ifdef OVERSAMPLE + softrt->cycle[jj].value += OVERSAMPLE_TIMES - 1; +#endif + // ensure other models can continue while this model starts + softrt->daqcycle[jj].value = softrt->daqcycle[0].value + 2*DAQ_NUM_DATA_BLOCKS; + // ready to go + softrt->alive[jj].value = 1; + softrt_slot = jj; + printf("softrt slot %d %ld %ld\n", jj, softrt->cycle[0].value, softrt->cycle[jj].value); + break; + } + } + if (jj == SOFTRT_MAX_MODELS) + { + printf("error: no softrt slot available\n"); + vmeDone = 1; + } +#endif // SOFTRT clock_gettime( CLOCK_MONOTONIC, &cpuClock[ CPU_TIME_CYCLE_END ] ); timeinfo.cycleTime = BILLION * @@ -459,6 +496,39 @@ fe_start_app_user( ) if ( !iopDacEnable || dkiTrip ) feStatus |= FE_ERROR_DAC_ENABLE; } +#ifdef SOFTRT + // keep in sync with IOP + clock_gettime(CLOCK_MONOTONIC, &myTimer[0]); + softrt_clk = myTimer[0].tv_sec*BILLION + myTimer[0].tv_nsec; + softrt_clk1 = softrt_clk; + while (softrt->alive[softrt_slot].value && softrt->cycle[softrt_slot].value > softrt->cycle[0].value) + { + // wait for the IOP to catch up + if (softrt_clk - softrt_clk1 > SOFTRT_TIMEOUT) + { + softrt->alive[softrt_slot].value = 0; + printf("%d lost sync with IOP\n", softrt_slot); + break; + } + if (softrt->iopsleep.value > softrt_clk) + { + softrt_clk = softrt->iopsleep.value; + myTimer[0].tv_sec = softrt_clk/BILLION; + myTimer[0].tv_nsec = softrt_clk % BILLION; + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &myTimer[0], NULL); + } + clock_gettime(CLOCK_MONOTONIC, &myTimer[0]); + softrt_clk = myTimer[0].tv_sec*BILLION + myTimer[0].tv_nsec; + } + if (!softrt->alive[softrt_slot].value) + { + printf("%d dropped model %d %ld %ld\n", softrt_slot, softrt_slot, softrt->cycle[0].value, softrt->cycle[softrt_slot].value); + pLocalEpics->epicsOutput.stateWord = FE_ERROR_ADC; + pLocalEpics->epicsOutput.diagWord |= ADC_TIMEOUT_ERR; + deallocate_dac_channels(); + return (void*)-1; + } +#endif // SOFTRT for ( ll = 0; ll < sampleCount; ll++ ) { /// \> Control model gets its adc data from MASTER via ipc shared @@ -467,6 +537,7 @@ fe_start_app_user( ) { mm = cdsPciModules.adcConfig[ jj ]; kk = 0; +#ifndef SOFTRT clock_gettime( CLOCK_MONOTONIC, &cpuClock[ CPU_TIME_RDY_ADC ] ); /// - ---- Wait for proper timestamp in shared memory, /// indicating data ready. @@ -483,6 +554,7 @@ fe_start_app_user( ) } while ( ( ioMemData->iodata[ mm ][ ioMemCntr ].cycle != ioClock ) && ( adcinfo.adcWait < MAX_ADC_WAIT_CONTROL ) ); +#endif // SOFTRT timeSec = ioMemData->iodata[ mm ][ ioMemCntr ].timeSec; if ( cycle_gps_time == 0 ) { @@ -546,9 +618,11 @@ fe_start_app_user( ) clock_gettime( CLOCK_MONOTONIC, &cpuClock[ CPU_TIME_CYCLE_START ] ); } +#ifndef SOFTRT // After first synced ADC read, must set to code to read number // samples/cycle sampleCount = OVERSAMPLE_TIMES; +#endif /// End of ADC Read /// ************************************************************************************** @@ -566,6 +640,11 @@ fe_start_app_user( ) clock_gettime( CLOCK_MONOTONIC, &cpuClock[ CPU_TIME_USR_END ] ); odcStateWord = 0; +#ifdef SOFTRT + // feCode() finished + softrt->cycle[softrt_slot].value += sampleCount; + sampleCount = OVERSAMPLE_TIMES; +#endif /// WRITE DAC OUTPUTS ***************************************** \n /// Writing of DAC outputs is dependent on code compile option: \n @@ -680,6 +759,12 @@ fe_start_app_user( ) if ( pLocalEpics->epicsOutput.daqByteCnt > DAQ_DCU_RATE_WARNING ) feStatus |= FE_ERROR_DAQ; #endif +#ifdef SOFTRT + if ((cycleNum + 1) % DAQ_RATE == 0) + { + softrt->daqcycle[softrt_slot].value = (long)dipc->bp[dipc->cycle].timeSec*DAQ_NUM_DATA_BLOCKS + dipc->cycle; + } +#endif // ******************************************************************* /// \> Cycle 19, write updated diag info to EPICS @@ -801,6 +886,7 @@ fe_start_app_user( ) clock_gettime( CLOCK_MONOTONIC, &cpuClock[ CPU_TIME_CYCLE_END ] ); /// \> Compute code cycle time diag information. +#ifndef SOFTRT timeinfo.cycleTime = BILLION * ( cpuClock[ CPU_TIME_CYCLE_END ].tv_sec - cpuClock[ CPU_TIME_CYCLE_START ].tv_sec ) + @@ -819,6 +905,22 @@ fe_start_app_user( ) cpuClock[ CPU_TIME_USR_END ].tv_nsec - cpuClock[ CPU_TIME_CYCLE_START ].tv_nsec; timeinfo.usrTime /= 1000; +#else + if ((cycleNum + 1) % DAQ_RATE == 0) + { + clk1 = clk; + clock_gettime(CLOCK_MONOTONIC, &myTimer[0]); + clk = myTimer[0].tv_sec*BILLION + myTimer[0].tv_nsec; + timeinfo.usrTime = (clk - clk1)/DAQ_RATE/1000; + timeinfo.cycleTime = timeinfo.usrTime; + } + if (cycleNum % DAQ_RATE == 0) + { + clock_gettime(CLOCK_MONOTONIC, &myTimer[0]); + clk = myTimer[0].tv_sec*BILLION + myTimer[0].tv_nsec; + adcinfo.adcHoldTime = (clk - clk1)/DAQ_RATE/1000; + } +#endif // SOFTRT // Calculate timing max/mins/etc. captureEocTiming( cycleNum, cycle_gps_time, &timeinfo, &adcinfo ); diff --git a/src/fe/controllerIopUser.c b/src/fe/controllerIopUser.c index 43759451..07fdb42c 100644 --- a/src/fe/controllerIopUser.c +++ b/src/fe/controllerIopUser.c @@ -61,6 +61,12 @@ #include "dolphin.c" #endif +#ifdef SOFTRT +#include "softrt.h" +struct softrt_struct *softrt; +long softrt_clk, softrt_clk1; +#endif + #ifdef USE_GPSCLOCK #include "drv/gpsclock.h" #endif @@ -490,23 +496,30 @@ fe_start_iop_user( ) /// \> If IOP, Initialize the DAC module variables initDacModules( ); +#ifdef SOFTRT + softrt_init(1); +#endif // SOFTRT + pLocalEpics->epicsOutput.fe_status = INIT_SYNC; printf( "*******************************\n" ); printf( "* Running on timer! *\n" ); printf( "*******************************\n" ); - long timeoff = 0; /// Sync up to the 1Hz boundary do { - status = clock_gettime(CLOCK_REALTIME, &myTimer[0]); - } while (status != 0 || myTimer[0].tv_nsec > 13000); - /// Begin the first cycle at the start of the next second + 13 us + clock_gettime(CLOCK_REALTIME, &myTimer[0]); + } while (myTimer[0].tv_nsec > 13000); + /// Begin the first cycle at the start of the next second clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); timeSec = getGpsTimeProc(); // increments at the start of the first cycle nextstep = myTimer[1].tv_sec*BILLION + myTimer[1].tv_nsec - myTimer[0].tv_nsec + BILLION; clk = nextstep - cyclensec; startGpsTime = timeSec; pLocalEpics->epicsOutput.startgpstime = startGpsTime; +#ifdef SOFTRT + softrt->cycle[0].value = (long)timeSec*FE_RATE + cycleNum; + clk = nextstep - BILLION; +#endif /// ******************************************************************************\n /// Enter the infinite FE control loop @@ -520,15 +533,17 @@ fe_start_iop_user( ) { // Run forever until user hits reset // This is local CPU timer (no ADCs) // advance to the next cycle polling CPU cycles and microsleeping +#ifndef SOFTRT clk1 = clk; do { - status = clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); + clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); clk = myTimer[1].tv_sec*BILLION + myTimer[1].tv_nsec; - } while (status != 0 || clk < nextstep); + } while (clk < nextstep); adcinfo.adcHoldTime = (clk - clk1)/1000; if ( adcinfo.adcHoldTime < 0 ) adcinfo.adcHoldTime += 1000000; +#endif if ( cycleNum == 0 ) { @@ -540,16 +555,37 @@ fe_start_iop_user( ) timeSec++; cycle_gps_time = timeSec; pLocalEpics->epicsOutput.timeDiag = timeSec; - // Compare vs realtime clock - clock_gettime(CLOCK_REALTIME, &myTimer[0]); - pdiff = (myTimer[0].tv_nsec + 500)/1000; - if (pdiff > 500000) { pdiff -= 1000000; } - pLocalEpics->epicsOutput.irigbTime = pdiff; + // Compare vs realtime clock + clock_gettime(CLOCK_REALTIME, &myTimer[0]); + pdiff = (myTimer[0].tv_nsec + 500)/1000; + if (pdiff > 500000) { pdiff -= 1000000; } + pLocalEpics->epicsOutput.irigbTime = pdiff + 13; } ioMemCntr = ( cycleNum % IO_MEMORY_SLOTS ); // Write GPS time and cycle count as indicator to control app that adc data is // ready +#ifdef SOFTRT + // keep in sync with user models + clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); + softrt_clk = myTimer[1].tv_sec*BILLION + myTimer[1].tv_nsec; + softrt_clk1 = softrt_clk; + for (jj = 1; jj < SOFTRT_MAX_MODELS; jj++) + { + while (softrt->alive[jj].value && softrt->cycle[jj].value == softrt->cycle[0].value) + { + // wait for user model to catch up + if (softrt_clk - softrt_clk1 > SOFTRT_TIMEOUT) + { + softrt->alive[jj].value = 0; + printf("IOP dropped model %d %ld %ld\n", jj, softrt->cycle[0].value, softrt->cycle[jj].value); + break; + } + clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); + softrt_clk = myTimer[1].tv_sec*BILLION + myTimer[0].tv_nsec; + } + } +#endif // SOFTRT for ( jj = 0; jj < cdsPciModules.adcCount; jj++ ) { for ( ii = 0; ii < IO_MEMORY_SLOT_VALS; ii++ ) @@ -568,6 +604,10 @@ fe_start_iop_user( ) } ioMemData->gpsSecond = timeSec; clock_gettime( CLOCK_MONOTONIC, &cpuClock[ CPU_TIME_CYCLE_START ] ); +#ifdef SOFTRT + // signal user models to continue + softrt->cycle[0].value = (long)timeSec*FE_RATE + cycleNum; +#endif // SOFTRT /// \> Call the front end specific application ******************\n /// - -- This is where the user application produced by RCG gets called @@ -777,6 +817,58 @@ fe_start_iop_user( ) checkEpicsReset( cycleNum, (struct CDS_EPICS*)pLocalEpics ); /// \> Write data to DAQ. +#ifdef SOFTRT + // sync with real time at the end of each DAQ block + if ((cycleNum + 1) % DAQ_RATE == 0) + { + // wait for user models to write their DAQ blocks + clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); + softrt_clk = myTimer[1].tv_sec*BILLION + myTimer[1].tv_nsec; + softrt_clk1 = softrt_clk; + for (jj = 1; jj < SOFTRT_MAX_MODELS; jj++) + { + while (softrt->alive[jj].value && softrt->daqcycle[jj].value < softrt->daqcycle[0].value + 1) + { + // wait for user model to catch up + softrt_clk += 1000; + if (softrt_clk - softrt_clk1 > SOFTRT_TIMEOUT) + { + softrt->alive[jj].value = 0; + printf("IOP dropped model %d %ld %ld\n", jj, softrt->cycle[0].value, softrt->cycle[jj].value); + break; + } + myTimer[1].tv_sec = softrt_clk/BILLION; + myTimer[1].tv_nsec = softrt_clk % BILLION; + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &myTimer[1], NULL); + } + } + // compute average timeinfo + clk1 = clk; + clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); + clk = myTimer[1].tv_sec*BILLION + myTimer[1].tv_nsec; + timeinfo.usrTime = (clk - clk1)/DAQ_RATE/1000; + timeinfo.cycleTime = timeinfo.usrTime; + pLocalEpics->epicsOutput.startgpstime = startGpsTime; + // sleep until the time is near + softrt_clk = nextstep - 10000000; + if (clk < softrt_clk) + { + myTimer[2].tv_nsec = softrt_clk % BILLION; + myTimer[2].tv_sec = softrt_clk/BILLION; + softrt->iopsleep.value = softrt_clk; + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &myTimer[2], NULL); + clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); + clk = myTimer[1].tv_sec*BILLION + myTimer[1].tv_nsec; + } + // then spin until the time is right + while (clk < nextstep) + { + clock_gettime(CLOCK_MONOTONIC, &myTimer[1]); + clk = myTimer[1].tv_sec*BILLION + myTimer[1].tv_nsec; + } + adcinfo.adcHoldTime = (clk - clk1)/DAQ_RATE/1000; + } +#endif // SOFTRT #ifndef NO_DAQ // Call daqLib @@ -803,6 +895,12 @@ fe_start_iop_user( ) if ( pLocalEpics->epicsOutput.daqByteCnt > DAQ_DCU_RATE_WARNING ) feStatus |= FE_ERROR_DAQ; #endif +#ifdef SOFTRT + if ((cycleNum + 1) % DAQ_RATE == 0) + { + softrt->daqcycle[0].value = (long)dipc->bp[dipc->cycle].timeSec*DAQ_NUM_DATA_BLOCKS + dipc->cycle; + } +#endif // ******************************************************************* /// \> Cycle 19, write updated diag info to EPICS @@ -937,6 +1035,7 @@ fe_start_iop_user( ) clock_gettime( CLOCK_MONOTONIC, &cpuClock[ CPU_TIME_CYCLE_END ] ); /// \> Compute code cycle time diag information. +#ifndef SOFTRT timeinfo.cycleTime = BILLION * ( cpuClock[ CPU_TIME_CYCLE_END ].tv_sec - cpuClock[ CPU_TIME_CYCLE_START ].tv_sec ) + @@ -953,6 +1052,7 @@ fe_start_iop_user( ) cpuClock[ CPU_TIME_USR_END ].tv_nsec - cpuClock[ CPU_TIME_CYCLE_START ].tv_nsec; timeinfo.usrTime /= 1000; +#endif // SOFTRT // Calculate timing max/mins/etc. captureEocTiming( cycleNum, cycle_gps_time, &timeinfo, &adcinfo ); @@ -961,12 +1061,12 @@ fe_start_iop_user( ) cycleNum += 1; cycleNum %= CYCLE_PER_SECOND; nextstep += cyclensec; - // tweak cycle lengths to better approximate 65536 Hz - // 10^9 = 15259*65536 - ceil(65536/5.) - ceil(65536/91.) + 5 + // tweak cycle lengths to better approximate 65536 Hz + // 10^9 = 15259*65536 - ceil(65536/5.) - ceil(65536/91.) + 5 if (cycleNum % 5 == 0) { nextstep -= 1; } if (cycleNum % 91 == 0) { nextstep -= 1; } - // 5 ns residual is corrected on cycle 0 - if (cycleNum == 0) { nextstep += 5; } + // 5 ns residual is corrected on cycle 0 + if (cycleNum == 0) { nextstep += 5; } clock1Min += 1; clock1Min %= CYCLE_PER_MINUTE; if ( subcycle == DAQ_CYCLE_CHANGE ) diff --git a/src/include/softrt.h b/src/include/softrt.h new file mode 100644 index 00000000..2f5ab1ea --- /dev/null +++ b/src/include/softrt.h @@ -0,0 +1,52 @@ +#ifndef SOFTRT_INCLUDED +#define SOFTRT_INCLUDED + +#include +#include +//#include "drv/shmem.h" +// need to clean up conflicting implicit declarations in rcguserCommon.c +// so shmem functions can be included here + +#define SOFTRT_MAX_MODELS 256 +#define SOFTRT_TIMEOUT 1000000000 // nanoseconds + +// atomic counter for synchronization +// one value per cache line to avoid "false sharing" +struct softrt_counter { + atomic_long value; + char pad[64-sizeof(atomic_long)]; +}; + +struct softrt_struct { + // alive[0] : not used + // alive[1..SOFTRT_MAX_MODELS] : 1 if user model is running (else 0) + // any process may read/write + struct softrt_counter alive[SOFTRT_MAX_MODELS]; + // cycle[0] : IOP cycle counter + // cycle[1..SOFTRT_MAX_MODELS] : IOP cycle requested by user models + // update after feCode() finishes to ensure IPC consistency + // one process writes, others only read + struct softrt_counter cycle[SOFTRT_MAX_MODELS]; + // daqcycle[0..SOFTRT_MAX_MODELS] : 16 Hz cycle counter + // update after daqWrite() finishes to ensure DAQ consistency + // one process writes, others only read + struct softrt_counter daqcycle[SOFTRT_MAX_MODELS]; + // iopsleep : daqcycle sleep time in nanoseconds + // IOP process writes, others only read + struct softrt_counter iopsleep; +}; + +extern struct softrt_struct *softrt; + +inline void softrt_init(int iopinit) +{ + extern char *addr; + findSharedMemorySize("softrt", 1); + softrt = (struct softrt_struct *)addr; + if (iopinit) + { + memset((void *)softrt, 0, sizeof(struct softrt_struct)); + } +} + +#endif // SOFTRT_INCLUDED -- GitLab