From 97be5d4d20c8bf54cbcbb9a62b46a44228701fe4 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 12 Jun 2025 13:32:38 +0200 Subject: [PATCH 1/2] feat(node-opal-async): Add new node-type for running VILLASnode as an Asynchronous process in OPAL-RT's RT-LAB Signed-off-by: Steffen Vogel --- CMakeLists.txt | 4 +- cmake/FindOpalAsyncApi.cmake | 34 ++ .../components/schemas/config/node_obj.yaml | 1 + .../schemas/config/nodes/_opal_async.yaml | 7 + .../schemas/config/nodes/opal_async.yaml | 42 ++ etc/examples/nodes/opal_async.conf | 21 + include/villas/nodes/opal_async.hpp | 146 ++++++ lib/nodes/CMakeLists.txt | 9 +- lib/nodes/opal_async.cpp | 421 ++++++++++++++++++ src/CMakeLists.txt | 7 + src/opal-async-param.cpp | 42 ++ src/villas-node.cpp | 4 +- tests/integration/test-config.sh | 1 + 13 files changed, 735 insertions(+), 4 deletions(-) create mode 100644 cmake/FindOpalAsyncApi.cmake create mode 100644 doc/openapi/components/schemas/config/nodes/_opal_async.yaml create mode 100644 doc/openapi/components/schemas/config/nodes/opal_async.yaml create mode 100644 etc/examples/nodes/opal_async.conf create mode 100644 include/villas/nodes/opal_async.hpp create mode 100644 lib/nodes/opal_async.cpp create mode 100644 src/opal-async-param.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 60f4f5b1e..6fc139cbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,7 @@ find_package(FileSystem) find_package(Criterion) find_package(OpalOrchestra) find_package(LibXml2) +find_package(OpalAsyncApi) # Check for tools find_program(PROTOBUFC_COMPILER NAMES protoc-c) @@ -198,7 +199,8 @@ cmake_dependent_option(WITH_NODE_MODBUS "Build with modbus node-type" cmake_dependent_option(WITH_NODE_MQTT "Build with mqtt node-type" "${WITH_DEFAULTS}" "MOSQUITTO_FOUND" OFF) cmake_dependent_option(WITH_NODE_NANOMSG "Build with nanomsg node-type" "${WITH_DEFAULTS}" "NANOMSG_FOUND" OFF) cmake_dependent_option(WITH_NODE_NGSI "Build with ngsi node-type" "${WITH_DEFAULTS}" "" OFF) -cmake_dependent_option(WITH_NODE_OPAL_ORCHESTRA "Build with the opal-orchestra node-type" "${WITH_DEFAULTS}" "OpalOrchestra_FOUND; LibXml2_FOUND" OFF) +cmake_dependent_option(WITH_NODE_OPAL_ORCHESTRA "Build with opal-orchestra node-type" "${WITH_DEFAULTS}" "OpalOrchestra_FOUND; LibXml2_FOUND" OFF) +cmake_dependent_option(WITH_NODE_OPAL_ASYNC "Build with opal.async node-type" "${WITH_DEFAULTS}" "OpalAsyncApi_FOUND" OFF) cmake_dependent_option(WITH_NODE_REDIS "Build with redis node-type" "${WITH_DEFAULTS}" "HIREDIS_FOUND; REDISPP_FOUND" OFF) cmake_dependent_option(WITH_NODE_RTP "Build with rtp node-type" "${WITH_DEFAULTS}" "re_FOUND" OFF) cmake_dependent_option(WITH_NODE_SHMEM "Build with shmem node-type" "${WITH_DEFAULTS}" "HAS_SEMAPHORE; HAS_MMAN" OFF) diff --git a/cmake/FindOpalAsyncApi.cmake b/cmake/FindOpalAsyncApi.cmake new file mode 100644 index 000000000..94154c396 --- /dev/null +++ b/cmake/FindOpalAsyncApi.cmake @@ -0,0 +1,34 @@ +# CMakeLists.txt +# +# Author: Steffen Vogel +# SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH +# SPDX-License-Identifier: Apache-2.0 + +set(TARGET_RTLAB_ROOT "/usr/opalrt" CACHE STRING "RT-LAB Root directory") + +if(EXISTS "${TARGET_RTLAB_ROOT}/common/bin/opalmodelmk") + set(ENV{TARGET_RTLAB_ROOT} ${TARGET_RTLAB_ROOT}) + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/get_vars.mk" + "all:\n\techo $(OPAL_LIBS) $(OPAL_LIBPATH)\n") + + execute_process( + COMMAND make -sf ${TARGET_RTLAB_ROOT}/common/bin/opalmodelmk + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + OUTPUT_VARIABLE OPAL_VARS + ) + + string(STRIP ${OPAL_VARS} OPAL_VARS) + string(REPLACE " " ";" OPAL_VARS ${OPAL_VARS}) + + set(OPAL_LIBRARIES -lOpalCore -lOpalUtils ${OPAL_VARS} -lirc -ldl -pthread -lrt) + set(OPAL_INCLUDE_DIR ${TARGET_RTLAB_ROOT}/common/include_target) + + add_library(OpalAsyncApi INTERFACE) + target_include_directories(OpalAsyncApi INTERFACE ${OPAL_INCLUDE_DIR}) + target_link_libraries(OpalAsyncApi INTERFACE ${OPAL_LIBRARIES}) +endif() + +find_package_handle_standard_args(OpalAsyncApi DEFAULT_MSG OPAL_LIBRARIES OPAL_INCLUDE_DIR) + +mark_as_advanced(OPAL_LIBRARIES OPAL_INCLUDE_DIR) diff --git a/doc/openapi/components/schemas/config/node_obj.yaml b/doc/openapi/components/schemas/config/node_obj.yaml index 3363db42d..c3e796171 100644 --- a/doc/openapi/components/schemas/config/node_obj.yaml +++ b/doc/openapi/components/schemas/config/node_obj.yaml @@ -39,6 +39,7 @@ discriminator: mqtt: nodes/_mqtt.yaml nanomsg: nodes/_nanomsg.yaml ngsi: nodes/_ngsi.yaml + opal_async: nodes/_opal_async.yaml opendss: nodes/_opendss.yaml opal.orchestra: nodes/_opal_orchestra.yaml redis: nodes/_redis.yaml diff --git a/doc/openapi/components/schemas/config/nodes/_opal_async.yaml b/doc/openapi/components/schemas/config/nodes/_opal_async.yaml new file mode 100644 index 000000000..e9fabe6ac --- /dev/null +++ b/doc/openapi/components/schemas/config/nodes/_opal_async.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=http://json-schema.org/draft-07/schema +# SPDX-FileCopyrightText: 2025 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 +--- +allOf: +- $ref: ../node_obj.yaml +- $ref: opal_async.yaml diff --git a/doc/openapi/components/schemas/config/nodes/opal_async.yaml b/doc/openapi/components/schemas/config/nodes/opal_async.yaml new file mode 100644 index 000000000..f100a7864 --- /dev/null +++ b/doc/openapi/components/schemas/config/nodes/opal_async.yaml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=http://json-schema.org/draft-07/schema +# Author: Steffen Vogel +# SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH +# SPDX-License-Identifier: Apache-2.0 +--- +allOf: + - type: object + properties: + id: + description: The Send/Recv ID of the RT-Lab OpAsyncSend/Recv blocks. + min: 1 + default: 1 + type: integer + + in: + type: object + properties: + reply: + description: Send a confirmation to the Simulink model that signals have been received and processed. + default: false + type: boolean + + shmem: + description: Shared-memory parameters for communication with OpAsyncGenCtrl block of Simulink model. + type: object + required: + - async_name + - async_size + - system_ctrl_name + properties: + async_name: + description: Name of the shared memory region used for data exchange with the Simulink model. + type: string + async_size: + description: Size of the shared memory region used for data exchange with the Simulink model. + type: integer + system_ctrl_name: + description: Name of the shared memory region used for logging. + type: string + + - $ref: ../node_signals.yaml + - $ref: ../node.yaml diff --git a/etc/examples/nodes/opal_async.conf b/etc/examples/nodes/opal_async.conf new file mode 100644 index 000000000..98a85959b --- /dev/null +++ b/etc/examples/nodes/opal_async.conf @@ -0,0 +1,21 @@ +# Author: Steffen Vogel +# SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH +# SPDX-License-Identifier: Apache-2.0 + +nodes = { + opal_async_node1 = { + type = "opal.async" + + # The Send/Recv ID of the RT-Lab OpAsyncSend/Recv blocks. + id = 1 + + in = { + # Send a confirmation to the Simulink model that signals have been received and processed. + reply = false + + hooks = ( + "stats" + ) + } + } +} diff --git a/include/villas/nodes/opal_async.hpp b/include/villas/nodes/opal_async.hpp new file mode 100644 index 000000000..5486817ae --- /dev/null +++ b/include/villas/nodes/opal_async.hpp @@ -0,0 +1,146 @@ +/* Node type: OPAL-RT Asynchronous Process (libOpalAsyncApi) + * + * Author: Steffen Vogel + * SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include +#include +#include + +// Define RTLAB before including OpalPrint.h for messages to be sent +// to the OpalDisplay. Otherwise stdout will be used. +#define RTLAB + +#include +#include +#include + +#include +#include +#include +#include + +namespace villas { +namespace node { +namespace opal { + +class LogSink final : public spdlog::sinks::base_sink { +private: + std::string shmemSystemCtrlName; + +public: + explicit LogSink(const std::string &shmemName); + + ~LogSink() override; + +protected: + void sink_it_(const spdlog::details::log_msg &msg) override; + + void flush_() override {} +}; + +} // namespace opal + +// Forward declarations +struct Sample; + +class OpalAsyncNode : public Node { + +protected: + // RT-LAB -> VILLASnode + // Corresponds to AsyncAPI's *Send* direction + struct { + unsigned id; + bool present; + unsigned length; + + bool reply; + int mode; + + Opal_SendAsyncParam params; + } in; + + // VILLASnode -> RT-LAB + // Corresponds to AsyncAPI's *Recv* direction + struct { + unsigned id; + bool present; + unsigned length; + + Opal_RecvAsyncParam params; + } out; + + bool ready; + std::mutex readyLock; + std::condition_variable readyCv; + + virtual int _read(struct Sample *smps[], unsigned cnt) override; + + virtual int _write(struct Sample *smps[], unsigned cnt) override; + +public: + OpalAsyncNode(const uuid_t &id = {}, const std::string &name = "") + : Node(id, name), ready(false) { + in.id = 1; + in.present = false; + in.length = 0; + in.reply = true; + + out.id = 1; + out.present = false; + out.length = 0; + } + + virtual const std::string &getDetails() override; + + virtual int start() override; + virtual int stop() override; + + virtual int parse(json_t *json) override; + + void markReady(); + void waitReady(); +}; + +class OpalAsyncNodeFactory : public NodeFactory { + +public: + using NodeFactory::NodeFactory; + + virtual Node *make(const uuid_t &id = {}, + const std::string &nme = "") override { + auto *n = new OpalAsyncNode(id, nme); + + init(n); + + return n; + } + + virtual int getFlags() const override { + return (int)NodeFactory::Flags::SUPPORTS_READ | + (int)NodeFactory::Flags::SUPPORTS_WRITE | + (int)NodeFactory::Flags::PROVIDES_SIGNALS; + } + + virtual std::string getName() const override { return "opal.async"; } + + virtual std::string getDescription() const override { + return "OPAL Asynchronous Process (libOpalAsyncApi)"; + } + + virtual int getVectorize() const override { return 1; } + + virtual int start(SuperNode *sn) override; + + virtual int stop() override; +}; + +} // namespace node +} // namespace villas diff --git a/lib/nodes/CMakeLists.txt b/lib/nodes/CMakeLists.txt index b861d7f03..068ad9fba 100644 --- a/lib/nodes/CMakeLists.txt +++ b/lib/nodes/CMakeLists.txt @@ -71,6 +71,13 @@ if(WITH_NODE_IEC61850) list(APPEND LIBRARIES PkgConfig::LIBIEC61850) endif() +# Enable OPAL-RT Asynchronous Process support +if(WITH_NODE_OPAL_ASYNC) + list(APPEND NODE_SRC opal_async.cpp) + list(APPEND LIBRARIES ${OPAL_LIBRARIES}) + list(APPEND INCLUDE_DIRS ${OPAL_INCLUDE_DIR}) +endif() + if(WITH_NODE_MODBUS) list(APPEND NODE_SRC modbus.cpp) list(APPEND LIBRARIES PkgConfig::MODBUS) @@ -91,7 +98,7 @@ endif() # Enable NGSI support if(WITH_NODE_NGSI) list(APPEND NODE_SRC ngsi.cpp) - list(APPEND INCLUDE_DIRECTORIES ${CURL_INCLUDE_DIRS}) + list(APPEND INCLUDE_DIRS ${CURL_INCLUDE_DIRS}) list(APPEND LIBRARIES ${CURL_LIBRARIES}) endif() diff --git a/lib/nodes/opal_async.cpp b/lib/nodes/opal_async.cpp new file mode 100644 index 000000000..33a1e40bd --- /dev/null +++ b/lib/nodes/opal_async.cpp @@ -0,0 +1,421 @@ +/* Node type: OPAL-RT Asynchronous Process (libOpalAsyncApi) + * + * Author: Steffen Vogel + * SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include + +using namespace villas; +using namespace villas::node; +using namespace villas::node::opal; +using namespace villas::utils; + +// This defines the maximum number of signals (doubles) that can be sent +// or received by any individual OpAsyncSend/Recv blocks in the model. This +// only applies to the "model <-> asynchronous process" communication. +#define MAXSENDSIZE 64 +#define MAXRECVSIZE 64 + +// Private static storage + +// Shared Memory identifiers and size, provided via argv or config. +static std::string shmemAsyncName; + +// Shared Memory identifiers and size, provided via argv or config. +static std::string shmemSystemCtrlName; + +// Shared Memory identifiers and size, provided via argv or config. +static size_t shmemAsyncSize; + +// A list of available send / receive icon IDs. +static std::vector idsSend, idsRecv; + +// An spdlog sink which logs outputs to the OPAL-RT simulation via OpalPrint. +static spdlog::sink_ptr sink; + +// String and Float parameters, provided by the RT-LAB OpAsyncGenCtrl block. +static Opal_GenAsyncParam_Ctrl params; + +static std::thread awaiter; + +static std::mutex lock; // Protecting state and nodeMap +static int state; +static std::map nodeMap; + +static void waitForModelState(int state) { + while (OpalGetAsyncModelState() != state) + usleep(1000); +} + +static void waitForAsyncSendRequests() { + auto logger = logging.get("node:opal"); + int ret; + unsigned id; + + waitForModelState(STATE_RUN); + + while (true) { + ret = OpalWaitForAsyncSendRequest(&id); + if (ret != EOK) { + state = OpalGetAsyncModelState(); + if (state == STATE_RESET || state == STATE_STOP) + break; + } + + try { + std::lock_guard lk(lock); + nodeMap.at(id)->markReady(); + } catch (std::out_of_range &e) { + logger->warn("Received send request for unknown id={}", id); + } + } + + // Model has been stopped or resetted + // We stop all OPAL nodes here. + for (auto const &x : nodeMap) + x.second->stop(); +} + +static int dumpGlobal() { + auto logger = logging.get("node:opal"); + + logger->debug("Controller ID: {}", params.controllerID); + + std::stringstream sss, rss; + for (auto i : idsSend) + sss << i << " "; + + for (auto i : idsRecv) + rss << i << " "; + + logger->debug("Send Blocks: {}", sss.str()); + logger->debug("Receive Blocks: {}", rss.str()); + + logger->debug("Control Block Parameters:"); + for (int i = 0; i < GENASYNC_NB_FLOAT_PARAM; i++) + logger->debug("FloatParam[{}] = {}", i, (double)params.FloatParam[i]); + + for (int i = 0; i < GENASYNC_NB_STRING_PARAM; i++) + logger->debug("StringParam[{}] = {}", i, params.StringParam[i]); + + return 0; +} + +LogSink::LogSink(const std::string &shmemName) + : shmemSystemCtrlName(shmemName) { + // Enable the OpalPrint function. This prints to the OpalDisplay. + int ret = + OpalSystemCtrl_Register(const_cast(shmemSystemCtrlName.c_str())); + if (ret != EOK) + throw RuntimeError("OpalPrint() access not available ({})", ret); +} + +LogSink::~LogSink() { + OpalSystemCtrl_UnRegister((char *)shmemSystemCtrlName.c_str()); +} + +void LogSink::sink_it_(const spdlog::details::log_msg &msg) { + spdlog::memory_buf_t buf; + + buf.clear(); + formatter_->format(msg, buf); + + char *bufPtr; + + bufPtr = bufPtr.data(); + bufPtr[buf.size()] = '\0'; + + OpalPrint(bufPtr); +} + +int OpalAsyncNode::parse(json_t *json) { + int ret, rply = -1, id = -1; + + ret = Node::parse(json); + if (ret) + return ret; + + json_error_t err; + ret = json_unpack_ex(json, &err, 0, "{ s: i, s?: { s?: b } }", "id", &id, + "in", "reply", &rply); + if (ret) + throw ConfigError(json, err, "node-config-node-opal-async"); + + if (id >= 0) { + in.id = id; + out.id = id; + } + + if (rply != -1) { + in.reply = rply; + } + + return 0; +} + +const std::string &OpalAsyncNode::getDetails() { + // @todo Print send_params, recv_params + + if (details.empty()) + details = fmt::format("out.id={}, in.id={}, in.reply={}, in.mode={}", + out.id, in.id, in.reply, in.mode); + + return details; +} + +int OpalAsyncNode::start() { + int ret, szSend, szRecv; + + // Check if this instance has OpAsyncSend/Recv blocks in the RT-LAB model + for (auto i : idsSend) + in.present |= i == in.id; + + for (auto i : idsRecv) + out.present |= i == out.id; + + // Get some more informations and parameters from OPAL-RT + if (in.present) { + ret = OpalGetAsyncSendParameters(&in.params, sizeof(Opal_SendAsyncParam), + in.id); + if (ret != EOK) + throw RuntimeError("Failed to get parameters of send icon {}", in.id); + + ret = OpalGetAsyncSendIconMode(&in.mode, in.id); + if (ret != EOK) + throw RuntimeError("Failed to get send mode of icon {}", in.id); + + ret = OpalGetAsyncSendIconDataLength(&szSend, in.id); + if (ret != EOK) + throw RuntimeError("Failed to get send length for icon {}", in.id); + + in.length = szSend / sizeof(double); + + Node::in.signals = + std::make_shared(in.length, SignalType::FLOAT); + } + + if (out.present) { + ret = OpalGetAsyncRecvParameters(&out.params, sizeof(Opal_RecvAsyncParam), + out.id); + if (ret != EOK) + throw RuntimeError( + "Failed to get parameters of OpAsyncRecv block with id={}", out.id); + + ret = OpalGetAsyncRecvIconDataLength(&szRecv, out.id); + if (ret != EOK) + throw RuntimeError("Failed to get send length for icon {}", out.id); + + out.length = szRecv / sizeof(double); + } + + // Register node ID in node map + std::unique_lock lk(lock); + nodeMap[in.id] = this; + + return Node::start(); +} + +int OpalAsyncNode::stop() { + int ret = Node::stop(); + if (ret) + return ret; + + std::unique_lock lk(lock); + + nodeMap.erase(in.id); + + return 0; +} + +int OpalAsyncNode::_read(struct Sample *smps[], unsigned cnt) { + int ret; + + struct Sample *smp = smps[0]; + + double data[smp->capacity]; + + assert(cnt == 1); + + if (!in.present) + throw RuntimeError("RT-LAB has no OpAsyncSend block with id={}", in.id); + + // Block until we are ready to receive new data + waitReady(); + + // No errors encountered yet + ret = OpalSetAsyncSendIconError(0, in.id); + if (ret != EOK) + throw RuntimeError("Failed to clear send icon error"); + + smp->flags = (int)SampleFlags::HAS_DATA; + smp->signals = Node::in.signals; + smp->length = in.length > smp->capacity ? smp->capacity : in.length; + + // Read data from the model + ret = OpalGetAsyncSendIconData(data, smp->length * sizeof(double), in.id); + if (ret != EOK) + throw RuntimeError("Failed to get data of OpAsyncSend block with id={}", + in.id); + + for (unsigned i = 0; i < smp->length; i++) + smp->data[i].f = data[i]; + + // This next call allows the execution of the "asynchronous" process + // to actually be synchronous with the model. To achieve this, you + // should set the "Sending Mode" in the OpAsyncSend block to + // NEED_REPLY_BEFORE_NEXT_SEND or NEED_REPLY_NOW. This will force + // the model to wait for this process to call this + // OpalAsyncSendRequestDone function before continuing. + if (in.reply && + (in.mode == NEED_REPLY_BEFORE_NEXT_SEND || in.mode == NEED_REPLY_NOW)) { + ret = OpalAsyncSendRequestDone(in.id); + if (ret != EOK) + throw RuntimeError("Failed to complete send request of icon {}", in.id); + } + + return 1; +} + +int OpalAsyncNode::_write(struct Sample *smps[], unsigned cnt) { + assert(cnt == 1); + + struct Sample *smp = smps[0]; + + int ret; + unsigned realLen; + double data[smp->length]; + + if (!out.present) + throw RuntimeError("RT-LAB has no OpAsyncRecv block with id={}", out.id); + + ret = OpalSetAsyncRecvIconStatus(smp->sequence, + out.id); // Set the status to the message ID + if (ret != EOK) + throw RuntimeError( + "Failed to update status of OpAsyncRecv block with id={}", out.id); + + ret = OpalSetAsyncRecvIconError(0, out.id); // Clear the error + if (ret != EOK) + throw RuntimeError("Failed to update error of OpAsyncRecv block with id={}", + out.id); + + // Get the number of signals to send back to the model + if (out.length > smp->length) + logger->warn( + "RT-LAB model is expecting more signals ({}) than in message ({})", + out.length, smp->length); + + if (out.length < smp->length) { + logger->warn("RT-LAB model can only accept {} of {} signals", out.length, + smp->length); + realLen = out.length; + } else + realLen = smp->length; + + for (unsigned i = 0; i < MIN(realLen, smp->signals->size()); i++) { + auto sig = smp->signals->getByIndex(i); + data[i] = smp->data[i].cast(sig->type, SignalType::FLOAT).f; + } + + ret = OpalSetAsyncRecvIconData(data, realLen * sizeof(double), out.id); + if (ret != EOK) + throw RuntimeError("Failed to set receive data of icon {}", out.id); + + return 1; +} + +void OpalAsyncNode::markReady() { + std::unique_lock lk(readyLock); + + ready = true; + readyCv.notify_all(); +} + +void OpalAsyncNode::waitReady() { + std::unique_lock lk(readyLock); + + readyCv.wait(lk, [=] { return this->ready; }); + ready = false; +} + +int OpalAsyncNode::start(SuperNode *sn) { + int ret, idsRecvLen, idsSendLen; + + auto *shmemAsyncNamePtr = std::getenv("OPAL_ASYNC_SHMEM_NAME"); + auto *shmemAsyncSizePtr = std::getenv("OPAL_ASYNC_SHMEM_SIZE"); + auto *shmemSystemCtrlNamePtr = std::getenv("OPAL_PRINT_SHMEM_NAME"); + + if (!shmemAsyncNamePtr || !shmemAsyncSizePtr || !shmemSystemCtrlNamePtr) + throw RuntimeError("Missing OPAL_ environment variables"); + + shmemAsyncName = shmemAsyncNamePtr; + shmemAsyncSize = atoi(shmemAsyncSizePtr); + shmemSystemCtrlName = shmemSystemCtrlNamePtr; + + // Register OpalPrint sink for spdlog + sink = std::make_shared(shmemSystemCtrlName); + logging.replaceStdSink(sink); + + // Open shared memory region created by the RT-LAB model. + ret = OpalOpenAsyncMem(shmemAsyncSize, + const_cast(shmemAsyncName.c_str())); + if (ret != EOK) + throw RuntimeError("Model shared memory not available ({})", ret); + + // Get list of SendIDs and RecvIDs + ret = OpalGetNbAsyncSendIcon(&idsSendLen); + if (ret != EOK) + throw RuntimeError("Failed to get number of OpAsyncSend blocks ({})", ret); + + ret = OpalGetNbAsyncRecvIcon(&idsRecvLen); + if (ret != EOK) + throw RuntimeError("Failed to get number of OpAsyncRecv blocks ({})", ret); + + idsSend.resize(idsSendLen); + idsRecv.resize(idsRecvLen); + + ret = OpalGetAsyncSendIDList(idsSend.data(), idsSendLen * sizeof(int)); + if (ret != EOK) + throw RuntimeError("Failed to get list of send icon ids ({})", ret); + + ret = OpalGetAsyncRecvIDList(idsRecv.data(), idsRecvLen * sizeof(int)); + if (ret != EOK) + throw RuntimeError("Failed to get list of receive icon ids ({})", ret); + + logger->info("Started VILLASnode as asynchronous process"); + + dumpGlobal(); + + awaiter = std::thread(waitForAsyncSendRequests); + + return NodeFactory::start(sn); +} + +int OpalAsyncNode::stop() { + int ret; + + logger->debug("Stopping waiter thread"); + + awaiter.join(); + + logger->debug("Closing OPAL-RT shared memory mapping"); + + ret = OpalCloseAsyncMem(shmemAsyncSize, shmemAsyncName.c_str()); + if (ret != EOK) + throw RuntimeError("Failed to close shared memory region ({})", ret); + + return NodeFactory::stop(); +} + +static OpalAsyncNodeFactory p; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f2c233c96..7fd670bb4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -67,6 +67,13 @@ if(WITH_HOOKS) list(APPEND SRCS villas-hook) endif() +if(WITH_NODE_OPAL) + add_executable(opal-async-param opal-async-param.cpp) + target_link_libraries(opal-async-param PRIVATE OpalAsyncApi) + + list(APPEND SRCS opal-async-param) +endif() + install( TARGETS ${SRCS} COMPONENT bin diff --git a/src/opal-async-param.cpp b/src/opal-async-param.cpp new file mode 100644 index 000000000..fd1ca453d --- /dev/null +++ b/src/opal-async-param.cpp @@ -0,0 +1,42 @@ +/* Retrieve parameters from OPAL-RTs AsyncApi + * + * Author: Steffen Vogel + * SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH + * SPDX-License-Identifier: Apache-2.0 + * + +#include +#include + +#include +#include + +int main(int argc, char *argv[]) { + Opal_GenAsyncParam_Ctrl params; + + if (argc != 4) { + std::cerr << "Usage: AsyncShmemName AsyncShmemSize PrintShmemName" + << std::endl; + return -1; + } + + auto err = OpalOpenAsyncMem(atoi(argv[2]), argv[1]); + if (err != EOK) { + std::cerr << "Model shared memory not available (" << err << ")" + << std::endl; + return -1; + } + + memset(¶ms, 0, sizeof(params)); + + err = OpalGetAsyncCtrlParameters(¶ms, sizeof(params)); + if (err != EOK) { + std::cerr << "Could not get controller parameters (" << err << ")" + << std::endl; + return -1; + } + + std::cout << params.StringParam[0] << std::endl; + + return 0; +} diff --git a/src/villas-node.cpp b/src/villas-node.cpp index 190e2e7e3..8f8a03726 100644 --- a/src/villas-node.cpp +++ b/src/villas-node.cpp @@ -78,9 +78,9 @@ class Node : public Tool { << std::endl << " and wait for provisioning over the web interface." << std::endl - << std::endl + << std::endl; - << "Supported node-types:" << std::endl; + std::cout << "Supported node-types:" << std::endl; for (auto p : registry->lookup()) { if (!p->isHidden()) std::cout << " - " << std::left << std::setw(18) << p->getName() diff --git a/tests/integration/test-config.sh b/tests/integration/test-config.sh index 32b776cbf..d7786b989 100755 --- a/tests/integration/test-config.sh +++ b/tests/integration/test-config.sh @@ -13,6 +13,7 @@ CONFIGS=$(find ${SRCDIR}/etc/ -name '*.conf' -o -name '*.json') for CONFIG in ${CONFIGS}; do if [ "$(basename ${CONFIG})" == "opal_orchestra.conf" ] || + [ "$(basename ${CONFIG})" == "opal_async.conf" ] || [ "$(basename ${CONFIG})" == "fpga.conf" ] || [ "$(basename ${CONFIG})" == "paths.conf" ] || [ "$(basename ${CONFIG})" == "tricks.json" ] || From 3ee70a58c84a6cdb6574a3b8efdc8f05ede5a77b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Thu, 6 Nov 2025 09:00:47 +0100 Subject: [PATCH 2/2] fix(cppcheck): Fix various cppcheck warnings and portability issues Signed-off-by: Steffen Vogel --- include/villas/mapping.hpp | 2 - include/villas/nodes/opal_orchestra/ddf.hpp | 4 +- .../villas/nodes/opal_orchestra/signal.hpp | 4 +- include/villas/nodes/test_rtt.hpp | 10 ++-- include/villas/pool.hpp | 16 +++--- include/villas/sample.hpp | 6 +-- lib/formats/opal_asyncip.cpp | 4 +- lib/formats/villas_binary.cpp | 29 +++++------ lib/hooks/average.cpp | 3 +- lib/hooks/dp.cpp | 7 +-- lib/kernel/nl.cpp | 6 +-- lib/mapping.cpp | 3 +- lib/memory/managed.cpp | 9 ++-- lib/node_capi.cpp | 2 +- lib/nodes/can.cpp | 3 +- lib/nodes/comedi.cpp | 12 ++--- lib/nodes/iec61850_goose.cpp | 4 +- lib/nodes/infiniband.cpp | 10 ++-- lib/nodes/opal_orchestra.cpp | 5 +- lib/nodes/opal_orchestra/signal.cpp | 4 +- lib/nodes/socket.cpp | 8 +-- lib/nodes/uldaq.cpp | 4 +- lib/nodes/zeromq.cpp | 6 +-- lib/pool.cpp | 9 +++- lib/queue.cpp | 15 +++--- lib/sample.cpp | 50 +++++++++++-------- lib/shmem.cpp | 2 +- lib/socket_addr.cpp | 8 +-- tools/run-cppcheck.sh | 1 + 29 files changed, 136 insertions(+), 110 deletions(-) diff --git a/include/villas/mapping.hpp b/include/villas/mapping.hpp index 04e1da90b..9cee884ab 100644 --- a/include/villas/mapping.hpp +++ b/include/villas/mapping.hpp @@ -42,9 +42,7 @@ class MappingEntry { using Ptr = std::shared_ptr; enum class Type { UNKNOWN, DATA, STATS, HEADER, TIMESTAMP }; - enum class HeaderType { LENGTH, SEQUENCE }; - enum class TimestampType { ORIGIN, RECEIVED }; Node *node; // The node to which this mapping refers. diff --git a/include/villas/nodes/opal_orchestra/ddf.hpp b/include/villas/nodes/opal_orchestra/ddf.hpp index cc41b9888..a7393ce2e 100644 --- a/include/villas/nodes/opal_orchestra/ddf.hpp +++ b/include/villas/nodes/opal_orchestra/ddf.hpp @@ -38,7 +38,9 @@ class DataItem : public Item { unsigned short length; double defaultValue; - explicit DataItem(std::string name) : name(std::move(name)) {} + explicit DataItem(std::string name) + : name(std::move(name)), type(SignalType::BOOLEAN), length(0), + defaultValue(0) {} static const unsigned int IDENTIFIER_NAME_LENGTH = 64; diff --git a/include/villas/nodes/opal_orchestra/signal.hpp b/include/villas/nodes/opal_orchestra/signal.hpp index be5dcab6a..9ba6c4d5a 100644 --- a/include/villas/nodes/opal_orchestra/signal.hpp +++ b/include/villas/nodes/opal_orchestra/signal.hpp @@ -42,11 +42,11 @@ orchestra::SignalType signalTypeFromString(const std::string &t); std::string signalTypeToString(orchestra::SignalType t); -node::SignalData toNodeSignalData(const char *orchestraData, +node::SignalData toNodeSignalData(const void *orchestraData, orchestra::SignalType orchestraType, node::SignalType &villasType); -void toOrchestraSignalData(char *orchestraData, +void toOrchestraSignalData(void *orchestraData, orchestra::SignalType orchestraType, const SignalData &villasData, node::SignalType villasType); diff --git a/include/villas/nodes/test_rtt.hpp b/include/villas/nodes/test_rtt.hpp index abf26db2e..4732dda09 100644 --- a/include/villas/nodes/test_rtt.hpp +++ b/include/villas/nodes/test_rtt.hpp @@ -65,12 +65,12 @@ class TestRTT : public Node { double getEstimatedDuration() const; }; - Task task; // The periodic task for test_rtt_read() - Format::Ptr formatter; // The format of the output file + Task task; // The periodic task for test_rtt_read(). + Format::Ptr formatter; // The format of the output file. FILE *stream; - std::list cases; // List of test cases - std::list::iterator current; // Currently running test case + std::list cases; // List of test cases. + std::list::iterator current; // Currently running test case. std::string output; // The directory where we place the results. std::string prefix; // An optional prefix in the filename. @@ -92,7 +92,7 @@ class TestRTT : public Node { }; TestRTT(const uuid_t &id = {}, const std::string &name = "") - : Node(id, name), task(), formatter(nullptr), stream(nullptr), + : Node(id, name), task(), formatter(nullptr), stream(nullptr), current(), shutdown(false) {} virtual ~TestRTT(){}; diff --git a/include/villas/pool.hpp b/include/villas/pool.hpp index dfbb4714b..6987018b6 100644 --- a/include/villas/pool.hpp +++ b/include/villas/pool.hpp @@ -20,23 +20,23 @@ namespace villas { namespace node { -// A thread-safe memory pool +// A thread-safe memory pool. struct Pool { enum State state; off_t - buffer_off; // Offset from the struct address to the underlying memory area + buffer_off; // Offset from the struct address to the underlying memory area. - size_t len; // Length of the underlying memory area - size_t blocksz; // Length of a block in bytes - size_t alignment; // Alignment of a block in bytes + size_t len; // Length of the underlying memory area. + size_t blocksz; // Length of a block in bytes. + size_t alignment; // Alignment of a block in bytes. - struct CQueue queue; // The queue which is used to keep track of free blocks + struct CQueue queue; // The queue which is used to keep track of free blocks. }; -#define pool_buffer(p) ((char *)(p) + (p)->buffer_off) +const char *pool_buffer(const struct Pool *pool); -/* Initiazlize a pool +/* Initialize a pool. * * @param[inout] p The pool data structure. * @param[in] cnt The total number of blocks which are reserverd by this pool. diff --git a/include/villas/sample.hpp b/include/villas/sample.hpp index 32e2c8c19..2521e6b9c 100644 --- a/include/villas/sample.hpp +++ b/include/villas/sample.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -95,10 +96,7 @@ struct Sample { #define SAMPLE_NON_POOL PTRDIFF_MIN // Get the address of the pool to which the sample belongs. -#define sample_pool(s) \ - ((s)->pool_off == SAMPLE_NON_POOL \ - ? nullptr \ - : (struct Pool *)((char *)(s) + (s)->pool_off)) +std::optional sample_pool(const struct Sample *smp); struct Sample *sample_alloc(struct Pool *p); diff --git a/lib/formats/opal_asyncip.cpp b/lib/formats/opal_asyncip.cpp index eb438329e..aa05b4f19 100644 --- a/lib/formats/opal_asyncip.cpp +++ b/lib/formats/opal_asyncip.cpp @@ -22,7 +22,7 @@ int OpalAsyncIPFormat::sprint(char *buf, size_t len, size_t *wbytes, ssize_t slen = len; for (i = 0; i < cnt && ptr - buf < slen; i++) { - auto *pl = (struct Payload *)ptr; + auto *pl = reinterpret_cast(ptr); auto *smp = smps[i]; auto wlen = smp->length * sizeof(double) + sizeof(struct Payload); @@ -66,7 +66,7 @@ int OpalAsyncIPFormat::sscan(const char *buf, size_t len, size_t *rbytes, return -1; // Packet size is invalid: Must be multiple of 8 bytes for (i = 0; i < cnt && ptr - buf + sizeof(struct Payload) < len; i++) { - auto *pl = (struct Payload *)ptr; + auto *pl = reinterpret_cast(ptr); auto *smp = smps[i]; auto rlen = le16toh(pl->msg_len); diff --git a/lib/formats/villas_binary.cpp b/lib/formats/villas_binary.cpp index 2e2dccef0..7e6a7f6f6 100644 --- a/lib/formats/villas_binary.cpp +++ b/lib/formats/villas_binary.cpp @@ -27,7 +27,7 @@ int VillasBinaryFormat::sprint(char *buf, size_t len, size_t *wbytes, char *ptr = buf; for (i = 0; i < cnt; i++) { - struct Message *msg = (struct Message *)ptr; + struct Message *msg = reinterpret_cast(ptr); const struct Sample *smp = smps[i]; if (ptr + MSG_LEN(smp->length) > buf + len) @@ -38,7 +38,7 @@ int VillasBinaryFormat::sprint(char *buf, size_t len, size_t *wbytes, return ret; if (web) { - // TODO: convert to little endian + // TODO: Convert to little endian. } else msg_hton(msg); @@ -59,39 +59,40 @@ int VillasBinaryFormat::sscan(const char *buf, size_t len, size_t *rbytes, uint8_t sid; // source_index if (len % 4 != 0) - return -1; // Packet size is invalid: Must be multiple of 4 bytes + return -1; // Packet size is invalid: Must be multiple of 4 bytes. for (i = 0, j = 0; i < cnt; i++) { - struct Message *msg = (struct Message *)ptr; - struct Sample *smp = smps[j]; + auto *msg = reinterpret_cast(ptr); + auto *smp = smps[j]; smp->signals = signals; - // Complete buffer has been parsed + // Complete buffer has been parsed. if (ptr == buf + len) break; - // Check if header is still in buffer bounaries + // Check if header is still in buffer bounaries. if (ptr + sizeof(struct Message) > buf + len) - return -2; // Invalid msg received + return -2; // Invalid message received. values = web ? msg->length : ntohs(msg->length); - // Check if remainder of message is in buffer boundaries + // Check if remainder of message is in buffer boundaries. if (ptr + MSG_LEN(values) > buf + len) - return -3; // Invalid msg receive + return -3; // Invalid message received. if (web) { - // TODO: convert from little endian + // TODO: convert from little endian. } else - msg_ntoh(msg); + // TODO: Check if this is safe... + msg_ntoh(const_cast(msg)); ret = msg_to_sample(msg, smp, signals, &sid); if (ret) - return ret; // Invalid msg received + return ret; // Invalid msg received. if (validate_source_index && sid != source_index) { - // source index mismatch: we skip this sample + // Source index mismatch: we skip this sample. } else j++; diff --git a/lib/hooks/average.cpp b/lib/hooks/average.cpp index 3b428ab51..d6fb840d2 100644 --- a/lib/hooks/average.cpp +++ b/lib/hooks/average.cpp @@ -82,7 +82,8 @@ class AverageHook : public MultiSignalHook { if (offset >= smp->length) return Reason::ERROR; - sample_data_insert(smp, (union SignalData *)&avg, offset, 1); + sample_data_insert(smp, reinterpret_cast(&avg), offset, + 1); return Reason::OK; } diff --git a/lib/hooks/dp.cpp b/lib/hooks/dp.cpp index ebce15a66..75697c9e5 100644 --- a/lib/hooks/dp.cpp +++ b/lib/hooks/dp.cpp @@ -279,7 +279,8 @@ class DPHook : public Hook { istep(coeffs, &signal); sample_data_remove(smp, signal_index, fharmonics_len); - sample_data_insert(smp, (union SignalData *)&signal, signal_index, 1); + sample_data_insert(smp, reinterpret_cast(&signal), + signal_index, 1); } else { double signal = smp->data[signal_index].f; std::complex coeffs[fharmonics_len]; @@ -287,8 +288,8 @@ class DPHook : public Hook { step(&signal, coeffs); sample_data_remove(smp, signal_index, 1); - sample_data_insert(smp, (union SignalData *)coeffs, signal_index, - fharmonics_len); + sample_data_insert(smp, reinterpret_cast(coeffs), + signal_index, fharmonics_len); } time += timestep; diff --git a/lib/kernel/nl.cpp b/lib/kernel/nl.cpp index 72de36c4d..97e490bc0 100644 --- a/lib/kernel/nl.cpp +++ b/lib/kernel/nl.cpp @@ -121,8 +121,8 @@ struct rtnl_link *villas::kernel::nl::get_egress_link(struct sockaddr *sa) { switch (sa->sa_family) { case AF_INET: case AF_INET6: { - struct sockaddr_in *sin = (struct sockaddr_in *)sa; - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + struct sockaddr_in *sin = reinterpret_cast(sa); + struct sockaddr_in6 *sin6 = reinterpret_cast(sa); struct nl_addr *addr = (sa->sa_family == AF_INET) @@ -139,7 +139,7 @@ struct rtnl_link *villas::kernel::nl::get_egress_link(struct sockaddr *sa) { } case AF_PACKET: { - struct sockaddr_ll *sll = (struct sockaddr_ll *)sa; + struct sockaddr_ll *sll = reinterpret_cast(sa); ifindex = sll->sll_ifindex; break; diff --git a/lib/mapping.cpp b/lib/mapping.cpp index 5fec8fb31..8cac18073 100644 --- a/lib/mapping.cpp +++ b/lib/mapping.cpp @@ -88,7 +88,8 @@ int MappingEntry::parseString(const std::string &str) { } MappingEntry::MappingEntry() - : node(nullptr), type(Type::UNKNOWN), length(0), offset(0), nodeName() {} + : node(nullptr), type(Type::UNKNOWN), length(0), offset(0), data(0), + nodeName() {} int MappingEntry::parse(json_t *json) { const char *str; diff --git a/lib/memory/managed.cpp b/lib/memory/managed.cpp index e0be1c5b9..ab62f49f6 100644 --- a/lib/memory/managed.cpp +++ b/lib/memory/managed.cpp @@ -34,7 +34,7 @@ static struct Allocation *managed_alloc(size_t len, size_t alignment, if (block->used) continue; - char *cptr = (char *)block + sizeof(struct Block); + char *cptr = reinterpret_cast(block) + sizeof(struct Block); size_t avail = block->length; uintptr_t uptr = (uintptr_t)cptr; @@ -58,7 +58,8 @@ static struct Allocation *managed_alloc(size_t len, size_t alignment, * position, so we just change its len and create a new block * descriptor for the actual block we're handling. */ block->length = gap - sizeof(struct Block); - struct Block *newblock = (struct Block *)(cptr - sizeof(struct Block)); + struct Block *newblock = + reinterpret_cast(cptr - sizeof(struct Block)); newblock->prev = block; newblock->next = block->next; block->next = newblock; @@ -73,7 +74,7 @@ static struct Allocation *managed_alloc(size_t len, size_t alignment, if (avail > len + sizeof(struct Block)) { // Imperfect fit, so create another block for the remaining part - struct Block *newblock = (struct Block *)(cptr + len); + struct Block *newblock = reinterpret_cast(cptr + len); newblock->prev = block; newblock->next = block->next; block->next = newblock; @@ -160,7 +161,7 @@ struct Type *villas::node::memory::managed(void *ptr, size_t len) { cptr += ALIGN(sizeof(struct Type), sizeof(void *)); // Initialize first free memory block - mb = (struct Block *)cptr; + mb = reinterpret_cast(cptr); mb->prev = nullptr; mb->next = nullptr; mb->used = false; diff --git a/lib/node_capi.cpp b/lib/node_capi.cpp index 92ee338e5..ab96b1bdc 100644 --- a/lib/node_capi.cpp +++ b/lib/node_capi.cpp @@ -164,7 +164,7 @@ vsample *sample_pack(unsigned seq, struct timespec *ts_origin, smp->flags = (int)SampleFlags::HAS_SEQUENCE | (int)SampleFlags::HAS_DATA | (int)SampleFlags::HAS_TS_ORIGIN; - memcpy((double *)smp->data, values, sizeof(double) * len); + memcpy(reinterpret_cast(smp->data), values, sizeof(double) * len); return (vsample *)smp; } diff --git a/lib/nodes/can.cpp b/lib/nodes/can.cpp index f9e66478a..784691ccf 100644 --- a/lib/nodes/can.cpp +++ b/lib/nodes/can.cpp @@ -199,7 +199,8 @@ int villas::node::can_start(NodeCompat *n) { addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; - ret = bind(c->socket, (struct sockaddr *)&addr, sizeof(addr)); + ret = + bind(c->socket, reinterpret_cast(&addr), sizeof(addr)); if (ret < 0) throw SystemError("Could not bind to interface with name '{}' ({}).", c->interface_name, ifr.ifr_ifindex); diff --git a/lib/nodes/comedi.cpp b/lib/nodes/comedi.cpp index f37898a96..5248b8334 100644 --- a/lib/nodes/comedi.cpp +++ b/lib/nodes/comedi.cpp @@ -327,9 +327,9 @@ static int comedi_start_out(NodeCompat *n) { d->chanspecs[channel].maxdata); if (d->sample_size == sizeof(sampl_t)) - *((sampl_t *)d->bufptr) = raw; + *reinterpret_cast(d->bufptr) = raw; else - *((lsampl_t *)d->bufptr) = raw; + *reinterpret_cast(d->bufptr) = raw; d->bufptr += d->sample_size; } @@ -578,9 +578,9 @@ int villas::node::comedi_read(NodeCompat *n, struct Sample *const smps[], unsigned int raw; if (d->sample_size == sizeof(sampl_t)) - raw = *((sampl_t *)(c->bufptr)); + raw = *reinterpret_cast(c->bufptr); else - raw = *((lsampl_t *)(c->bufptr)); + raw = *reinterpret_cast(c->bufptr); c->bufptr += d->sample_size; @@ -904,9 +904,9 @@ int villas::node::comedi_write(NodeCompat *n, struct Sample *const smps[], } if (d->sample_size == sizeof(sampl_t)) - *((sampl_t *)d->bufptr) = raw_value; + *(reinterpret_cast(d->bufptr)) = raw_value; else - *((lsampl_t *)d->bufptr) = raw_value; + *(reinterpret_cast(d->bufptr)) = raw_value; d->bufptr += d->sample_size; } diff --git a/lib/nodes/iec61850_goose.cpp b/lib/nodes/iec61850_goose.cpp index 8a551892e..45003a1c3 100644 --- a/lib/nodes/iec61850_goose.cpp +++ b/lib/nodes/iec61850_goose.cpp @@ -173,7 +173,7 @@ MmsValue *GooseSignal::newMmsFloat(double d, int size) { } std::optional GooseSignal::lookupMmsType(int mms_type) { - auto check = [mms_type](Descriptor descriptor) { + auto check = [mms_type](const Descriptor &descriptor) { return descriptor.mms_type == mms_type; }; @@ -186,7 +186,7 @@ std::optional GooseSignal::lookupMmsType(int mms_type) { std::optional GooseSignal::lookupMmsTypeName(char const *name) { - auto check = [name](Descriptor descriptor) { + auto check = [name](const Descriptor &descriptor) { return descriptor.name == name; }; diff --git a/lib/nodes/infiniband.cpp b/lib/nodes/infiniband.cpp index 24b501e6b..457300260 100644 --- a/lib/nodes/infiniband.cpp +++ b/lib/nodes/infiniband.cpp @@ -772,8 +772,9 @@ int villas::node::ib_read(NodeCompat *n, struct Sample *const smps[], } // Get Memory Region - mr = memory::ib_get_mr(pool_buffer( // cppcheck-suppress dangerousTypeCast - sample_pool(smps[0]))); + auto pool = sample_pool(smps[0]); + auto *buf = pool_buffer(*pool); + mr = memory::ib_get_mr(const_cast(buf)); for (int i = 0; i < max_wr_post; i++) { int j = 0; @@ -885,8 +886,9 @@ int villas::node::ib_write(NodeCompat *n, struct Sample *const smps[], // First, write // Get Memory Region - mr = memory::ib_get_mr(pool_buffer( // cppcheck-suppress dangerousTypeCast - sample_pool(smps[0]))); + auto pool = sample_pool(smps[0]); + auto *buf = pool_buffer(*pool); + mr = memory::ib_get_mr(const_cast(buf)); for (sent = 0; sent < cnt; sent++) { int j = 0; diff --git a/lib/nodes/opal_orchestra.cpp b/lib/nodes/opal_orchestra.cpp index 2a8bcc2b3..181ac7758 100644 --- a/lib/nodes/opal_orchestra.cpp +++ b/lib/nodes/opal_orchestra.cpp @@ -58,7 +58,8 @@ class OpalOrchestraMapping { unsigned short length; OpalOrchestraMapping(std::shared_ptr item, std::string path) - : item(item), path(std::move(path)), signals(), signalList(), indices() {} + : item(item), path(std::move(path)), signals(), signalList(), indices(), + key(0), buffer(nullptr), typeSize(0), length(0) {} void addSignal(Signal::Ptr signal, std::optional orchestraIdx) { if (!orchestraIdx) { @@ -333,7 +334,7 @@ class OpalOrchestraNode : public Node { public: OpalOrchestraNode(const uuid_t &id = {}, const std::string &name = "", unsigned int key = 0) - : Node(id, name), task(), connectionKey(key), domain(), + : Node(id, name), task(), connectionKey(key), status(nullptr), domain(), subscribeMappings(), publishMappings(), rate(1), connectTimeout(5), skipWaitToGo(false), dataDefinitionFileOverwrite(false), dataDefinitionFileWriteOnly(false) {} diff --git a/lib/nodes/opal_orchestra/signal.cpp b/lib/nodes/opal_orchestra/signal.cpp index 48be3e39c..eca4a452a 100644 --- a/lib/nodes/opal_orchestra/signal.cpp +++ b/lib/nodes/opal_orchestra/signal.cpp @@ -116,7 +116,7 @@ villas::node::orchestra::signalTypeToString(orchestra::SignalType t) { } SignalData -villas::node::orchestra::toNodeSignalData(const char *orchestraData, +villas::node::orchestra::toNodeSignalData(const void *orchestraData, orchestra::SignalType orchestraType, node::SignalType &villasType) { @@ -165,7 +165,7 @@ villas::node::orchestra::toNodeSignalData(const char *orchestraData, } void villas::node::orchestra::toOrchestraSignalData( - char *orchestraData, orchestra::SignalType orchestraType, + void *orchestraData, orchestra::SignalType orchestraType, const SignalData &villasData, node::SignalType villasType) { auto villasTypeCasted = toNodeSignalType(orchestraType); auto villasDataCasted = villasData.cast(villasType, villasTypeCasted); diff --git a/lib/nodes/socket.cpp b/lib/nodes/socket.cpp index 295fa681b..bcf49fd69 100644 --- a/lib/nodes/socket.cpp +++ b/lib/nodes/socket.cpp @@ -377,8 +377,8 @@ static void socket_tcp_connection(NodeCompat *n, Socket *s) { while (retries < MAX_CONNECTION_RETRIES) { n->logger->info("Attempting to connect to TCP server: attempt={}...", retries + 1); - ret = - connect(s->sd, (struct sockaddr *)&s->out.saddr, sizeof(s->in.saddr)); + ret = connect(s->sd, reinterpret_cast(&s->out.saddr), + sizeof(s->in.saddr)); if (ret == 0) { s->tcp_connected = true; break; @@ -451,7 +451,7 @@ int villas::node::socket_read(NodeCompat *n, struct Sample *const smps[], // Strip IP header from packet if (s->layer == SocketLayer::IP) { - struct ip *iphdr = (struct ip *)ptr; + struct ip *iphdr = reinterpret_cast(ptr); bytes -= iphdr->ip_hl * 4; ptr += iphdr->ip_hl * 4; @@ -472,7 +472,7 @@ int villas::node::socket_read(NodeCompat *n, struct Sample *const smps[], } if (s->verify_source && socket_compare_addr(&src.sa, &s->out.saddr.sa) != 0) { - char *buf = socket_print_addr((struct sockaddr *)&src); + char *buf = socket_print_addr(reinterpret_cast(&src)); n->logger->warn("Received packet from unauthorized source: {}", buf); free(buf); diff --git a/lib/nodes/uldaq.cpp b/lib/nodes/uldaq.cpp index 3cad43034..8a9c6189b 100644 --- a/lib/nodes/uldaq.cpp +++ b/lib/nodes/uldaq.cpp @@ -428,14 +428,14 @@ int villas::node::uldaq_check(NodeCompat *n) { for (int i = 0; i < num_ranges_diff; i++) { err = ulAIGetInfo(u->device_handle, AI_INFO_DIFF_RANGE, i, - (long long *)&ranges_diff[i]); + reinterpret_cast(&ranges_diff[i])); if (err != ERR_NO_ERROR) return -1; } for (int i = 0; i < num_ranges_se; i++) { err = ulAIGetInfo(u->device_handle, AI_INFO_SE_RANGE, i, - (long long *)&ranges_se[i]); + reinterpret_cast(&ranges_se[i])); if (err != ERR_NO_ERROR) return -1; } diff --git a/lib/nodes/zeromq.cpp b/lib/nodes/zeromq.cpp index 98bc19ea8..a1225f112 100644 --- a/lib/nodes/zeromq.cpp +++ b/lib/nodes/zeromq.cpp @@ -39,10 +39,10 @@ static int get_monitor_event(void *monitor, int *value, char **address) { assert(zmq_msg_more(&msg)); - uint8_t *data = (uint8_t *)zmq_msg_data(&msg); - uint16_t event = *(uint16_t *)(data); + uint8_t *data = reinterpret_cast(zmq_msg_data(&msg)); + uint16_t event = *reinterpret_cast(data); if (value) - *value = *(uint32_t *)(data + 2); + *value = *reinterpret_cast(data + 2); // Second frame in message contains event address zmq_msg_init(&msg); diff --git a/lib/pool.cpp b/lib/pool.cpp index 612108a79..784e56446 100644 --- a/lib/pool.cpp +++ b/lib/pool.cpp @@ -14,6 +14,10 @@ using namespace villas; +const char *villas::node::pool_buffer(const struct Pool *pool) { + return reinterpret_cast(pool) + pool->buffer_off; +} + int villas::node::pool_init(struct Pool *p, size_t cnt, size_t blocksz, struct memory::Type *m) { int ret; @@ -33,7 +37,8 @@ int villas::node::pool_init(struct Pool *p, size_t cnt, size_t blocksz, logger->debug("Allocated {:#x} bytes for memory pool", p->len); - p->buffer_off = (char *)buffer - (char *)p; + p->buffer_off = + reinterpret_cast(buffer) - reinterpret_cast(p); ret = queue_init(&p->queue, LOG2_CEIL(cnt), m); if (ret) @@ -57,7 +62,7 @@ int villas::node::pool_destroy(struct Pool *p) { if (ret) return ret; - void *buffer = (char *)p + p->buffer_off; + void *buffer = reinterpret_cast(p) + p->buffer_off; ret = memory::free(buffer); if (ret == 0) p->state = State::DESTROYED; diff --git a/lib/queue.cpp b/lib/queue.cpp index 966d62eb3..b85b54aa6 100644 --- a/lib/queue.cpp +++ b/lib/queue.cpp @@ -56,7 +56,8 @@ int villas::node::queue_init(struct CQueue *q, size_t size, if (!buffer) return -2; - q->buffer_off = (char *)buffer - (char *)q; + q->buffer_off = + reinterpret_cast(buffer) - reinterpret_cast(q); for (size_t i = 0; i != size; i += 1) std::atomic_store_explicit(&buffer[i].sequence, i, @@ -76,7 +77,7 @@ int villas::node::queue_init(struct CQueue *q, size_t size, } int villas::node::queue_destroy(struct CQueue *q) { - void *buffer = (char *)q + q->buffer_off; + void *buffer = reinterpret_cast(q) + q->buffer_off; int ret = 0; if (q->state == State::DESTROYED) @@ -103,7 +104,8 @@ int villas::node::queue_push(struct CQueue *q, void *ptr) { State::STOPPED) return -1; - buffer = (struct CQueue_cell *)((char *)q + q->buffer_off); + buffer = reinterpret_cast(reinterpret_cast(q) + + q->buffer_off); pos = std::atomic_load_explicit(&q->tail, std::memory_order_relaxed); while (true) { cell = &buffer[pos & q->buffer_mask]; @@ -121,7 +123,7 @@ int villas::node::queue_push(struct CQueue *q, void *ptr) { pos = std::atomic_load_explicit(&q->tail, std::memory_order_relaxed); } - cell->data_off = (char *)ptr - (char *)q; + cell->data_off = reinterpret_cast(ptr) - reinterpret_cast(q); std::atomic_store_explicit(&cell->sequence, pos + 1, std::memory_order_release); @@ -137,7 +139,8 @@ int villas::node::queue_pull(struct CQueue *q, void **ptr) { State::STOPPED) return -1; - buffer = (struct CQueue_cell *)((char *)q + q->buffer_off); + buffer = reinterpret_cast(reinterpret_cast(q) + + q->buffer_off); pos = std::atomic_load_explicit(&q->head, std::memory_order_relaxed); while (true) { cell = &buffer[pos & q->buffer_mask]; @@ -155,7 +158,7 @@ int villas::node::queue_pull(struct CQueue *q, void **ptr) { pos = std::atomic_load_explicit(&q->head, std::memory_order_relaxed); } - *ptr = (char *)q + cell->data_off; + *ptr = reinterpret_cast(q) + cell->data_off; std::atomic_store_explicit(&cell->sequence, pos + q->buffer_mask + 1, std::memory_order_release); diff --git a/lib/sample.cpp b/lib/sample.cpp index a2c9c4cf9..ac48e8674 100644 --- a/lib/sample.cpp +++ b/lib/sample.cpp @@ -23,10 +23,12 @@ using namespace villas::node; using namespace villas::utils; int villas::node::sample_init(struct Sample *s) { - struct Pool *p = sample_pool(s); + auto pool = sample_pool(s); s->length = 0; - s->capacity = (p->blocksz - sizeof(struct Sample)) / sizeof(s->data[0]); + s->capacity = + pool ? ((*pool)->blocksz - sizeof(struct Sample)) / sizeof(s->data[0]) + : 0; s->refcnt = ATOMIC_VAR_INIT(1); new (&s->signals) std::shared_ptr; @@ -41,7 +43,7 @@ struct Sample *villas::node::sample_alloc(struct Pool *p) { if (!s) return nullptr; - s->pool_off = (char *)p - (char *)s; + s->pool_off = reinterpret_cast(p) - reinterpret_cast(s); int ret = sample_init(s); if (ret) { @@ -55,7 +57,7 @@ struct Sample *villas::node::sample_alloc(struct Pool *p) { struct Sample *villas::node::sample_alloc_mem(int capacity) { size_t sz = SAMPLE_LENGTH(capacity); - auto *s = (struct Sample *)new char[sz]; + auto *s = reinterpret_cast(new char[sz]); if (!s) throw MemoryAllocationError(); @@ -71,12 +73,11 @@ struct Sample *villas::node::sample_alloc_mem(int capacity) { } void villas::node::sample_free(struct Sample *s) { - struct Pool *p = sample_pool(s); - - if (p) - pool_put(p, s); + auto pool = sample_pool(s); + if (pool) + pool_put(*pool, s); else - delete[] (char *)s; + delete[] reinterpret_cast(s); } int villas::node::sample_alloc_many(struct Pool *p, struct Sample *smps[], @@ -88,7 +89,8 @@ int villas::node::sample_alloc_many(struct Pool *p, struct Sample *smps[], return ret; for (int i = 0; i < ret; i++) { - smps[i]->pool_off = (char *)p - (char *)smps[i]; + smps[i]->pool_off = + reinterpret_cast(p) - reinterpret_cast(smps[i]); sample_init(smps[i]); } @@ -148,13 +150,12 @@ int villas::node::sample_copy(struct Sample *dst, const struct Sample *src) { struct Sample *villas::node::sample_clone(struct Sample *orig) { struct Sample *clone; - struct Pool *pool; - pool = sample_pool(orig); + auto pool = sample_pool(orig); if (!pool) return nullptr; - clone = sample_alloc(pool); + clone = sample_alloc(*pool); if (!clone) return nullptr; @@ -166,19 +167,15 @@ struct Sample *villas::node::sample_clone(struct Sample *orig) { int villas::node::sample_clone_many(struct Sample *dsts[], const struct Sample *const srcs[], int cnt) { - int alloced, copied; - struct Pool *pool; - if (cnt <= 0) return 0; - pool = sample_pool(srcs[0]); + auto pool = sample_pool(srcs[0]); if (!pool) return 0; - alloced = sample_alloc_many(pool, dsts, cnt); - - copied = sample_copy_many(dsts, srcs, alloced); + auto alloced = sample_alloc_many(*pool, dsts, cnt); + auto copied = sample_copy_many(dsts, srcs, alloced); return copied; } @@ -325,3 +322,16 @@ void villas::node::sample_data_remove(struct Sample *smp, size_t offset, smp->length -= len; } + +// Get the address of the pool to which the sample belongs. +std::optional +villas::node::sample_pool(const struct Sample *smp) { + if (smp->pool_off == SAMPLE_NON_POOL) + return {}; + + auto *cptr = reinterpret_cast(smp) + smp->pool_off; + auto *ptr = const_cast(cptr); + auto *pool = reinterpret_cast(ptr); + + return std::optional(pool); +} diff --git a/lib/shmem.cpp b/lib/shmem.cpp index d6740ed2b..19c0e871a 100644 --- a/lib/shmem.cpp +++ b/lib/shmem.cpp @@ -137,7 +137,7 @@ int villas::node::shmem_int_open(const char *wname, const char *rname, cptr = (char *)base + sizeof(struct memory::Type) + sizeof(struct memory::Block); - shared = (struct ShmemShared *)cptr; + shared = reinterpret_cast(cptr); shm->read.base = base; shm->read.name = rname; shm->read.len = len; diff --git a/lib/socket_addr.cpp b/lib/socket_addr.cpp index 2d72e9f5a..d0fa57480 100644 --- a/lib/socket_addr.cpp +++ b/lib/socket_addr.cpp @@ -24,7 +24,7 @@ using namespace villas::node; using namespace villas::utils; char *villas::node::socket_print_addr(struct sockaddr *saddr) { - union sockaddr_union *sa = (union sockaddr_union *)saddr; + union sockaddr_union *sa = reinterpret_cast(saddr); auto *buf = reinterpret_cast(::malloc(256)); if (!buf) throw MemoryAllocationError(); @@ -82,7 +82,7 @@ char *villas::node::socket_print_addr(struct sockaddr *saddr) { int villas::node::socket_parse_address(const char *addr, struct sockaddr *saddr, enum SocketLayer layer, int flags) { // TODO: Add support for IPv6 - union sockaddr_union *sa = (union sockaddr_union *)saddr; + union sockaddr_union *sa = reinterpret_cast(saddr); char *copy = strdup(addr); int ret; @@ -195,8 +195,8 @@ int villas::node::socket_compare_addr(struct sockaddr *x, struct sockaddr *y) { if (a != b) \ return a < b ? -1 : 1 - union sockaddr_union *xu = (union sockaddr_union *)x; - union sockaddr_union *yu = (union sockaddr_union *)y; + union sockaddr_union *xu = reinterpret_cast(x); + union sockaddr_union *yu = reinterpret_cast(y); CMP(x->sa_family, y->sa_family); diff --git a/tools/run-cppcheck.sh b/tools/run-cppcheck.sh index 8dc73ac26..ee84ab588 100755 --- a/tools/run-cppcheck.sh +++ b/tools/run-cppcheck.sh @@ -17,6 +17,7 @@ cppcheck -j $(nproc) \ --platform=unix64 \ --error-exitcode=1 \ --inline-suppr \ + --std=c++20 \ --enable=warning,performance,portability \ --suppressions-list=${SCRIPT_DIR}/cppcheck-supressions.txt \ --project=${BUILD_DIR}/compile_commands.json \