diff --git a/CMakeLists.txt b/CMakeLists.txt index b1f8c2daa..095af2862 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,6 +308,7 @@ add_feature_info(NODE_REDIS WITH_NODE_REDIS "Build with add_feature_info(NODE_RTP WITH_NODE_RTP "Build with rtp node-type") add_feature_info(NODE_SHMEM WITH_NODE_SHMEM "Build with shmem node-type") add_feature_info(NODE_SIGNAL WITH_NODE_SIGNAL "Build with signal node-type") +add_feature_info(NODE_SMU WITH_NODE_SMU "Build with smu node-type") add_feature_info(NODE_SOCKET WITH_NODE_SOCKET "Build with socket node-type") add_feature_info(NODE_STATS WITH_NODE_STATS "Build with stats node-type") add_feature_info(NODE_TEMPER WITH_NODE_TEMPER "Build with temper node-type") diff --git a/include/villas/nodes/smu.hpp b/include/villas/nodes/smu.hpp new file mode 100644 index 000000000..d8b0959ed --- /dev/null +++ b/include/villas/nodes/smu.hpp @@ -0,0 +1,229 @@ +/** Node-type for loopback connections. + * + * @file + * @author Manuel Pitz + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @license Apache 2.0 + *********************************************************************************/ + +#pragma once + +#include +#include +#include +#include + + +extern "C" { + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + + +#define SMU_NCH 8 + +namespace villas { +namespace node { + +//static const smu_dsp_t dsp_cfg_default = {FR_1PPS, {"1","1","1","1","1","1","1","1"}, {"V1", "V2", "V3", "V4", "I1", "I2", "I3", "I4"}}; +//const smu_dsp_t dsp_cfg_default = {FR_10PPS, {"1","1","1","1","1","1","1","1"}, {"V1", "V2", "V3", "V4", "I1", "I2", "I3", "I4"}, "dsp_raw"}; +//const smu_net_t net_cfg_default = {"id", "", 7080, IPv4, TCP, "JSON", "", 7090, IPv4, TCP, "net_raw", ""}; + +/// ---------- SMU: DAQ settings section ---------- + +/** + * @brief DAQ sampling rate (kHz / kSPS) + * + */ +typedef enum : unsigned char +{ + FS_1kSPS = 1, + FS_2kSPS = 2, + FS_5kSPS = 5, + FS_10kSPS = 10, + FS_20kSPS = 20, + FS_25kSPS = 25, + FS_50kSPS = 50, + FS_100kSPS = 100, + FS_200kSPS = 200 +} daq_rate_t; + +/** + * @brief DAQ buffer frame rate (Hz / FPS) + * + */ +typedef enum : unsigned char +{ + FB_10FPS = 10, + FB_20FPS = 20, + FB_50FPS = 50, + FB_100FPS = 100, + FB_200FPS = 200 +} daq_buff_t; + +/** + * @brief DAQ acquisition mode + * + */ +typedef enum : unsigned char +{ + MODE_ONESHOT, /*!< DAQ runs for 1 second */ + MODE_FREERUN, /*!< DAQ runs continuously */ + MODE_VIRTUAL, /*!< DAQ runs virtually */ + MODE_MAX +} daq_mode_t; + +/** + * @brief DAQ synchronization mode + * + */ +typedef enum : unsigned char +{ + SYNC_NONE, /*!< Time base synchronization disabled */ + SYNC_PPS, /*!< Time base synchronization from GPS via pulse-per-second */ + SYNC_NTP, /*!< Time base synchronization from Server via network-time-protocol */ + SYNC_MAX +} daq_sync_t; + +/** + * @brief DAQ parameters + * + */ +typedef struct +{ + daq_rate_t rate; /*!< DAQ sampling rate */ + daq_buff_t buff; /*!< DAQ buffer frame rate */ + daq_mode_t mode; /*!< DAQ acquisition mode */ + daq_sync_t sync; /*!< DAQ synchronization mode */ +} __attribute__((__packed__)) smu_daq_t; + +/// ---------- SMU: DSP settings section ---------- + +/** + * @brief DSP reporting rate (Hz / PPS) + * + */ +typedef enum : unsigned short int +{ + FR_EVENT = 0, + FR_1PPS = 1, + FR_2PPS = 2, + FR_5PPS = 5, + FR_10PPS = 10, + FR_20PPS = 20, + FR_25PPS = 25, + FR_50PPS = 50, + FR_100PPS = 100 +} dsp_rate_t; + + +/** + * @brief SMU multi-channel sample code + * + */ +typedef struct { + int16_t ch[SMU_NCH]; +} __attribute__((__packed__)) smu_mcsc_t; + +/** + * @brief SMU multi-channel sample real + * + */ +typedef struct { + float ch[SMU_NCH]; +} __attribute__((__packed__)) smu_mcsr_t; + +/** + * @brief SMU multi-channel conversion coefficients + * + */ +typedef struct { + float k[SMU_NCH]; +} __attribute__((__packed__)) smu_mcsk_t; + + +/** + * @brief IOCTL definitions + * + * Here are defined the different IOCTL commands used + * to communicate from the userspace to the kernel module. + */ +#define SMU_DEV "/dev/smu" +#define SMU_IOC_MAGIC 'k' +#define SMU_IOC_REG_TASK _IO (SMU_IOC_MAGIC, 0) +#define SMU_IOC_RESET _IO (SMU_IOC_MAGIC, 1) +#define SMU_IOC_START _IO (SMU_IOC_MAGIC, 2) +#define SMU_IOC_STOP _IO (SMU_IOC_MAGIC, 3) +#define SMU_IOC_SET_CONF _IOW(SMU_IOC_MAGIC, 4, smu_conf_t*) +#define SMU_IOC_GET_CONF _IOR(SMU_IOC_MAGIC, 5, smu_conf_t*) + +/** + * @brief Signal definitions + * + * Here are defined the different values used to send + * signals to the userspace + */ +#define SMU_SIG_SYNC 41 +#define SMU_SIG_DATA 42 + +#define PAGE_NUM 800 +#define PAGE_SIZE 4096 +#define MMAP_SIZE (PAGE_SIZE*PAGE_NUM) + +class SMUNode : public Node { + +private: + daq_mode_t mode; + daq_rate_t sample_rate; + daq_buff_t fps; + daq_sync_t sync; + smu_daq_t daq_cfg; + Dumper dumpers[SMU_NCH]; + int fd; + uint8_t* shared_mem; + uint32_t shared_mem_pos; + uint32_t shared_mem_inc; + uint32_t shared_mem_dim; + struct sigaction act; + + + size_t buffer_size; + static inline std::atomic mem_pos = 0; + static inline pthread_mutex_t mutex; + static inline pthread_cond_t cv; + +protected: + virtual + int _read(struct Sample * smps[], unsigned cnt); + +public: + SMUNode(const uuid_t &id = {}, const std::string &name = ""); + static void data_available_signal(int, siginfo_t *info, void*); + static void sync_signal(int, siginfo_t *info, void*); + + virtual + int parse(json_t *json) override; + + virtual + int prepare(); + + virtual + int start(); + + virtual + int stop(); +}; + +} /* namespace node */ +} /* namespace villas */ diff --git a/lib/nodes/CMakeLists.txt b/lib/nodes/CMakeLists.txt index 3625dc7cc..5078e0ec8 100644 --- a/lib/nodes/CMakeLists.txt +++ b/lib/nodes/CMakeLists.txt @@ -36,6 +36,10 @@ if(WITH_NODE_TEST_RTT) list(APPEND NODE_SRC test_rtt.cpp) endif() +if(WITH_NODE_SMU) + list(APPEND NODE_SRC smu.cpp) +endif() + if(WITH_NODE_SOCKET) list(APPEND NODE_SRC socket.cpp) endif() diff --git a/lib/nodes/smu.cpp b/lib/nodes/smu.cpp new file mode 100644 index 000000000..af3f1a23d --- /dev/null +++ b/lib/nodes/smu.cpp @@ -0,0 +1,298 @@ +/** Node-type for smu data aquisition. + * + * @file + * @author Manuel Pitz + * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC + * @license Apache 2.0 + *********************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include + + +using namespace villas; +using namespace villas::node; +using namespace villas::utils; + +SMUNode::SMUNode(const uuid_t &id, const std::string &name) : + Node(id, name), + mode(MODE_FREERUN), + sample_rate(FS_10kSPS), + fps(FB_10FPS), + sync(SYNC_PPS), + daq_cfg({sample_rate, fps, mode, sync}) +{ + return; +} + +int SMUNode::prepare() +{ + assert(state == State::CHECKED); + return Node::prepare(); +} + +int SMUNode::parse(json_t *json) +{ + int ret = Node::parse(json); + if (ret) + return ret; + + json_error_t err; + + + json_t *jsonDumpers = nullptr; + + char *modeIn = nullptr; + char *syncIn = nullptr; + char *fpsIn = nullptr; + char *sample_rateIn = nullptr; + json_t *in_json = nullptr; + + + ret = json_unpack_ex(json, &err, 0, "{ s?: s, s?: s, s?: s, s?: s, s?: o, s?: o }", + "mode", &modeIn, + "sync", &syncIn, + "fps", &fpsIn, + "sample_rate", &sample_rateIn, + "dumper", &jsonDumpers, + "in", &in_json + ); + if (ret) + throw ConfigError(json, err, "node-config-node-smu"); + + if (jsonDumpers) { + auto signals = getInputSignals(); + + size_t i; + json_t *jsonDumper; + ret = json_unpack_ex(jsonDumpers, &err, 0, "{ o }", + &jsonDumper + ); + json_array_foreach(jsonDumpers, i, jsonDumper) { + char *nameIn = nullptr; + char *pathIn = nullptr; + + ret = json_unpack_ex(jsonDumper, &err, 0, "{ s: s, s: s }", + "name", &nameIn, + "path", &pathIn + ); + if (ret) + throw ConfigError(json, err, "node-config-node-smu", "Failed to parse dumper configuration"); + + int idx = signals->getIndexByName(nameIn); + if (idx == -1) + throw ConfigError(json, err, "node-config-node-smu", "Could not find signal {} for dumper", nameIn); + + dumpers[idx].setPath(pathIn); + dumpers[idx].openSocket(); + dumpers[idx].setActive(); + + } + + } + + if(!modeIn || strcmp(modeIn, "MODE_FREERUN")==0) + mode = MODE_FREERUN; + else if (strcmp(modeIn, "MODE_ONESHOT")==0) + mode = MODE_ONESHOT; + else if (strcmp(modeIn, "MODE_VIRTUAL")==0) + mode = MODE_VIRTUAL; + else if (strcmp(modeIn, "MODE_MAX")==0) + mode = MODE_MAX; + + if(!syncIn || strcmp(syncIn, "SYNC_PPS")==0) + sync = SYNC_PPS; + else if (strcmp(syncIn, "SYNC_NONE")==0) + sync = SYNC_NONE; + else if (strcmp(syncIn, "SYNC_NTP")==0) + sync = SYNC_NTP; + else if (strcmp(syncIn, "SYNC_MAX")==0) + sync = SYNC_MAX; + + if(!fpsIn || strcmp(fpsIn, "FB_10FPS")==0) + fps = FB_10FPS; + else if (strcmp(fpsIn, "FB_20FPS")==0) + fps = FB_20FPS; + else if (strcmp(fpsIn, "FB_50FPS")==0) + fps = FB_50FPS; + else if (strcmp(fpsIn, "FB_100FPS")==0) + fps = FB_100FPS; + else if (strcmp(fpsIn, "FB_200FPS")==0) + fps = FB_200FPS; + + if (!sample_rateIn || strcmp(sample_rateIn, "FS_10kSPS")==0) + sample_rate = FS_10kSPS; + else if (strcmp(sample_rateIn, "FS_1kSPS")==0) + sample_rate = FS_1kSPS; + else if (strcmp(sample_rateIn, "FS_2kSPS")==0) + sample_rate = FS_2kSPS; + else if (strcmp(sample_rateIn, "FS_5kSPS")==0) + sample_rate = FS_5kSPS; + else if (strcmp(sample_rateIn, "FS_20kSPS")==0) + sample_rate = FS_20kSPS; + else if (strcmp(sample_rateIn, "FS_25kSPS")==0) + sample_rate = FS_25kSPS; + else if (strcmp(sample_rateIn, "FS_50kSPS")==0) + sample_rate = FS_50kSPS; + else if (strcmp(sample_rateIn, "FS_100kSPS")==0) + sample_rate = FS_100kSPS; + else if (strcmp(sample_rateIn, "FS_200kSPS")==0) { + sample_rate = FS_200kSPS; + } + + + + if (json_is_object(in_json) && json_object_get(in_json, "vectorize")) { + throw ConfigError(json, "node-config-node-smu", "Vectorize cannot be overwritten for this node type!"); + } + in.vectorize = sample_rate * 1000 / fps; + + return 0; +} + +int SMUNode::start() +{ + assert(state == State::PREPARED || + state == State::PAUSED); + + int ret = Node::start(); + + + daq_cfg.rate = sample_rate; + daq_cfg.buff = fps; + daq_cfg.mode = mode; + daq_cfg.sync = sync; + + + fd = ::open(SMU_DEV, O_RDWR); + if (fd < 0) + logger->warn("Fail to open the device driver"); + + // Associate kernel-to-user Task ID + if (ioctl(fd, SMU_IOC_REG_TASK, 0)){ + logger->warn("Fail to register the driver"); + ::close(fd); + // return; + } + + // Map kernel-to-user shared memory + void *sh_mem = mmap(nullptr, MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + shared_mem = (uint8_t*)sh_mem; + if (shared_mem == MAP_FAILED) + logger->warn("Fail to map driver memory"); + + // Install PPS signal handler + sigemptyset(&act.sa_mask); + act.sa_flags = (SA_SIGINFO | SA_RESTART); + act.sa_sigaction = sync_signal; + if (sigaction(SMU_SIG_SYNC, &act, nullptr)) + logger->warn("Fail to install PPS handler"); + + // Install ADC signal handler + sigemptyset(&act.sa_mask); + act.sa_flags = (SA_SIGINFO | SA_RESTART); + act.sa_sigaction = data_available_signal; + if (sigaction(SMU_SIG_DATA, &act, nullptr)) + logger->warn("Fail to install ADC handler"); + + // Update DAQ memory configuration + shared_mem_pos = 0; + shared_mem_inc = sizeof(smu_mcsc_t); + shared_mem_dim = daq_cfg.rate; + + // Stop DAQ driver + if (ioctl(fd, SMU_IOC_STOP, 0)) + logger->warn("Fail to stop the driver"); + + // Configure DAQ driver + if (::write(fd, &daq_cfg, sizeof(daq_cfg))) + logger->warn("Fail to configure the driver"); + + // Start DAQ driver + if (ioctl(fd, SMU_IOC_START, 0)) { + logger->warn("Fail to start the driver"); + } + + if (!ret) + state = State::STARTED; + + return ret; +} + +int SMUNode::stop() +{ + // Stop DAQ driver + if(!ioctl(fd, SMU_IOC_STOP, 0)) { + // Unmap kernel-to-user shared memory + munmap(shared_mem, MMAP_SIZE); + while(close(fd) != 0){} + } + + return 1; +} + +void SMUNode::sync_signal(int, siginfo_t *info, void*) +{ + + //mem_pos = (info->si_value.sival_int); + /* Signal uldaq_read() about new data */ + //pthread_cond_signal(&u->in.cv); +} + +void SMUNode::data_available_signal(int, siginfo_t *info, void*) +{ + + mem_pos = (info->si_value.sival_int); + /* Signal uldaq_read() about new data */ + pthread_cond_signal(&cv); +} + + +int SMUNode::_read(struct Sample *smps[], unsigned cnt) +{ + struct timespec ts; + ts.tv_sec = time(nullptr); + ts.tv_nsec = 0; + + pthread_mutex_lock(&mutex); + + pthread_cond_wait(&cv, &mutex); + size_t mem_pos_local = mem_pos; + + smu_mcsc_t* p = (smu_mcsc_t*)shared_mem; + + + for (unsigned j = 0; j < cnt; j++) { + struct Sample *t = smps[j]; + + for (unsigned i = 0; i < 8; i++) { + int16_t data = p[mem_pos_local].ch[i]; + t->data[i].f = ((double)data); + if (dumpers[i].isActive()) + dumpers[i].writeDataBinary(1, &(t->data[i].f)); + } + + mem_pos_local++; + + t->flags = (int) SampleFlags::HAS_TS_ORIGIN | (int) SampleFlags::HAS_DATA | (int) SampleFlags::HAS_SEQUENCE; + t->ts.origin = ts; + t->sequence = sequence++; + t->length = 8; + t->signals = in.signals; + } + + pthread_mutex_unlock(&mutex); + + return cnt; +} + +static char n[] = "smu"; +static char d[] = "SMU data acquisition"; +static NodePlugin p;