diff --git a/common/.gitlab-ci.yml b/common/.gitlab-ci.yml new file mode 100644 index 000000000..9a18bc33d --- /dev/null +++ b/common/.gitlab-ci.yml @@ -0,0 +1,56 @@ +variables: + GIT_STRATEGY: fetch + GIT_SUBMODULE_STRATEGY: recursive + PREFIX: /usr/ + DOCKER_TAG_DEV: ${CI_BUILD_REF_SLUG} + DOCKER_IMAGE_DEV: villas/common-dev + +# For some reason, GitLab CI prunes the contents of the submodules so we need to restore them. +before_script: + - git submodule foreach git checkout . + +stages: + - prepare + - build + - test + +# Stage: prepare +############################################################################## + +# Build docker image which is used to build & test VILLASnode +docker-dev: + stage: prepare + script: + - docker build -t ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} . + tags: + - shell + - linux + +# Stage: build +############################################################################## + +build:source: + stage: build + script: + - mkdir build && cd build && cmake .. && make -j8 + artifacts: + expire_in: 1 week + name: ${CI_PROJECT_NAME}-${CI_BUILD_REF} + paths: + - build/ + image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} + tags: + - docker + +# Stage: test +############################################################################## + +#test:unit: +# stage: test +# dependencies: +# - build:source +# script: +# - build/tests/unit-tests --filter 'graph/*' +# image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} +# tags: +# - docker diff --git a/common/.gitmodules b/common/.gitmodules new file mode 100644 index 000000000..3a3d7f7f3 --- /dev/null +++ b/common/.gitmodules @@ -0,0 +1,6 @@ +[submodule "thirdparty/spdlog"] + path = thirdparty/spdlog + url = https://github.com/gabime/spdlog.git +[submodule "thirdparty/criterion"] + path = thirdparty/criterion + url = https://github.com/Snaipe/Criterion.git diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 000000000..2e0407f20 --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1,37 @@ +## CMakeLists.txt +# +# @author Daniel Krebs +# @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS) +# @license GNU General Public License (version 3) +# +# VILLASfpga +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +############################################################################## + +cmake_minimum_required(VERSION 3.7) + +project(villas-common CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) + +include(FindPkgConfig) + +pkg_check_modules(JANSSON jansson) + +add_subdirectory(lib) +add_subdirectory(tests) diff --git a/common/Dockerfile b/common/Dockerfile new file mode 100644 index 000000000..711eb8bad --- /dev/null +++ b/common/Dockerfile @@ -0,0 +1,70 @@ +# Dockerfile for VILLASfpga development. +# +# This Dockerfile builds an image which contains all library dependencies +# and tools to build VILLASfpga. +# However, VILLASfpga itself it not part of the image. +# +# This image can be used for developing VILLASfpga +# by running: +# make docker +# +# @author Steffen Vogel +# @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC +# @license GNU General Public License (version 3) +# +# VILLASfpga +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +################################################################################### + +FROM fedora:28 + +LABEL \ + org.label-schema.schema-version="1.0" \ + org.label-schema.name="VILLAScommon" \ + org.label-schema.license="GPL-3.0" \ + org.label-schema.vendor="Institute for Automation of Complex Power Systems, RWTH Aachen University" \ + org.label-schema.author.name="Steffen Vogel" \ + org.label-schema.author.email="stvogel@eonerc.rwth-aachen.de" \ + org.label-schema.description="A library for shared code across VILLAS C/C++ projects" \ + org.label-schema.url="http://fein-aachen.org/projects/villas-framework/" \ + org.label-schema.vcs-url="https://git.rwth-aachen.de/VILLASframework/VILLASfpga" \ + org.label-schema.usage="https://villas.fein-aachen.org/doc/" + +# Toolchain +RUN dnf -y install \ + gcc gcc-c++ \ + pkgconfig make cmake \ + autoconf automake autogen libtool \ + texinfo git curl tar + +# Several tools only needed for developement and testing +RUN dnf -y install \ + rpmdevtools rpm-build + +# Some of the dependencies are only available in our own repo +ADD https://villas.fein-aachen.org/packages/villas.repo /etc/yum.repos.d/ + +# Dependencies +RUN dnf -y install \ + jansson-devel + +# Build & Install Criterion +COPY thirdparty/criterion /tmp/criterion +RUN mkdir -p /tmp/criterion/build && cd /tmp/criterion/build && cmake .. && make install && rm -rf /tmp/* + +ENV LD_LIBRARY_PATH /usr/local/lib:/usr/local/lib64 + +WORKDIR /villas +ENTRYPOINT bash diff --git a/common/README.md b/common/README.md new file mode 100644 index 000000000..99908bded --- /dev/null +++ b/common/README.md @@ -0,0 +1,45 @@ +# VILLAScommon + +[![build status](https://git.rwth-aachen.de/VILLASframework/VILLAScommon/badges/develop/build.svg)](https://git.rwth-aachen.de/acs/VILLAScommon/commits/develop) + +**TODO:** Write project description + +## Documentation + +User documentation is available here: + +## Copyright + +2018, Institute for Automation of Complex Power Systems, EONERC + +## License + +This project is released under the terms of the [GPL version 3](COPYING.md). + +``` +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +``` + +For other licensing options please consult [Prof. Antonello Monti](mailto:amonti@eonerc.rwth-aachen.de). + +## Contact + +[![EONERC ACS Logo](doc/pictures/eonerc_logo.png)](http://www.acs.eonerc.rwth-aachen.de) + +- Steffen Vogel +- Daniel Krebs + +[Institute for Automation of Complex Power Systems (ACS)](http://www.acs.eonerc.rwth-aachen.de) +[EON Energy Research Center (EONERC)](http://www.eonerc.rwth-aachen.de) +[RWTH University Aachen, Germany](http://www.rwth-aachen.de) diff --git a/common/cmake/FindCriterion.cmake b/common/cmake/FindCriterion.cmake new file mode 100644 index 000000000..0e4a2c069 --- /dev/null +++ b/common/cmake/FindCriterion.cmake @@ -0,0 +1,27 @@ +# This file is licensed under the WTFPL version 2 -- you can see the full +# license over at http://www.wtfpl.net/txt/copying/ +# +# - Try to find Criterion +# +# Once done this will define +# CRITERION_FOUND - System has Criterion +# CRITERION_INCLUDE_DIRS - The Criterion include directories +# CRITERION_LIBRARIES - The libraries needed to use Criterion + +find_package(PkgConfig) + +find_path(CRITERION_INCLUDE_DIR criterion/criterion.h + PATH_SUFFIXES criterion) + +find_library(CRITERION_LIBRARY NAMES criterion libcriterion) + +set(CRITERION_LIBRARIES ${CRITERION_LIBRARY}) +set(CRITERION_INCLUDE_DIRS ${CRITERION_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +# handle the QUIET and REQUIRED arguments and set CRITERION_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(Criterion DEFAULT_MSG + CRITERION_LIBRARY CRITERION_INCLUDE_DIR) + +mark_as_advanced(CRITERION_INCLUDE_DIR CRITERION_LIBRARY) diff --git a/common/include/villas/common.h b/common/include/villas/common.h new file mode 100644 index 000000000..180479af9 --- /dev/null +++ b/common/include/villas/common.h @@ -0,0 +1,36 @@ +/** Some common defines, enums and datastructures. + * + * @file + * @author Steffen Vogel + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +/* Common states for most objects in VILLASfpga (paths, nodes, hooks, plugins) */ +enum state { + STATE_DESTROYED = 0, + STATE_INITIALIZED = 1, + STATE_PARSED = 2, + STATE_CHECKED = 3, + STATE_STARTED = 4, + STATE_LOADED = 4, /* alias for STATE_STARTED used by plugins */ + STATE_STOPPED = 5, + STATE_UNLOADED = 5 /* alias for STATE_STARTED used by plugins */ +}; diff --git a/common/include/villas/graph/directed.hpp b/common/include/villas/graph/directed.hpp new file mode 100644 index 000000000..d75123e43 --- /dev/null +++ b/common/include/villas/graph/directed.hpp @@ -0,0 +1,297 @@ +/** A directed graph. + * + * @file + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +namespace villas { +namespace graph { + + +template +class DirectedGraph { +public: + + using VertexIdentifier = Vertex::Identifier; + using EdgeIdentifier = Edge::Identifier; + using Path = std::list; + + DirectedGraph(const std::string& name = "DirectedGraph") : + lastVertexId(0), lastEdgeId(0) + { + logger = loggerGetOrCreate(name); + } + + std::shared_ptr getVertex(VertexIdentifier vertexId) const + { + if(vertexId >= lastVertexId) + throw std::invalid_argument("vertex doesn't exist"); + + // cannot use [] operator, because creates non-existing elements + // at() will throw std::out_of_range if element does not exist + return vertices.at(vertexId); + } + + template + VertexIdentifier findVertex(UnaryPredicate p) + { + for(auto& v : vertices) { + auto& vertexId = v.first; + auto& vertex = v.second; + + if(p(vertex)) { + return vertexId; + } + } + + throw std::out_of_range("vertex not found"); + } + + std::shared_ptr getEdge(EdgeIdentifier edgeId) const + { + if(edgeId >= lastEdgeId) + throw std::invalid_argument("edge doesn't exist"); + + // cannot use [] operator, because creates non-existing elements + // at() will throw std::out_of_range if element does not exist + return edges.at(edgeId); + } + + std::size_t getEdgeCount() const + { return edges.size(); } + + std::size_t getVertexCount() const + { return vertices.size(); } + + VertexIdentifier addVertex(std::shared_ptr vertex) + { + vertex->id = lastVertexId++; + + logger->debug("New vertex: {}", *vertex); + vertices[vertex->id] = vertex; + + return vertex->id; + } + + EdgeIdentifier addEdge(std::shared_ptr edge, + VertexIdentifier fromVertexId, + VertexIdentifier toVertexId) + { + // allocate edge id + edge->id = lastEdgeId++; + + // connect it + edge->from = fromVertexId; + edge->to = toVertexId; + + logger->debug("New edge {}: {} -> {}", *edge, edge->from, edge->to); + + // this is a directed graph, so only push edge to starting vertex + getVertex(edge->from)->edges.push_back(edge->id); + + // add new edge to graph + edges[edge->id] = edge; + + return edge->id; + } + + + EdgeIdentifier addDefaultEdge(VertexIdentifier fromVertexId, + VertexIdentifier toVertexId) + { + // create a new edge + std::shared_ptr edge(new EdgeType); + + return addEdge(edge, fromVertexId, toVertexId); + } + + void removeEdge(EdgeIdentifier edgeId) + { + auto edge = getEdge(edgeId); + auto startVertex = getVertex(edge->from); + + // remove edge only from starting vertex (this is a directed graph) + logger->debug("Remove edge {} from vertex {}", edgeId, edge->from); + startVertex->edges.remove(edgeId); + + logger->debug("Remove edge {}", edgeId); + edges.erase(edgeId); + } + + void removeVertex(VertexIdentifier vertexId) + { + // delete every edge that start or ends at this vertex + auto it = edges.begin(); + while(it != edges.end()) { + auto& edgeId = it->first; + auto& edge = it->second; + + bool removeEdge = false; + + if(edge->to == vertexId) { + logger->debug("Remove edge {} from vertex {}'s edge list", + edgeId, edge->from); + + removeEdge = true; + + auto startVertex = getVertex(edge->from); + startVertex->edges.remove(edge->id); + } + + if((edge->from == vertexId) or removeEdge) { + logger->debug("Remove edge {}", edgeId); + // remove edge from global edge list + it = edges.erase(it); + } else { + ++it; + } + } + + logger->debug("Remove vertex {}", vertexId); + vertices.erase(vertexId); + } + + const std::list& + vertexGetEdges(VertexIdentifier vertexId) const + { return getVertex(vertexId)->edges; } + + + using check_path_fn = std::function; + + static bool + checkPath(const Path&) + { return true; } + + bool getPath(VertexIdentifier fromVertexId, + VertexIdentifier toVertexId, + Path& path, + check_path_fn pathCheckFunc = checkPath) + { + if(fromVertexId == toVertexId) { + // arrived at the destination + return true; + } else { + auto fromVertex = getVertex(fromVertexId); + + for(auto& edgeId : fromVertex->edges) { + auto edgeOfFromVertex = getEdge(edgeId); + + // loop detection + bool loop = false; + for(auto& edgeIdInPath : path) { + auto edgeInPath = getEdge(edgeIdInPath); + if(edgeInPath->from == edgeOfFromVertex->to) { + loop = true; + break; + } + } + + if(loop) { + logger->debug("Loop detected via edge {}", edgeId); + continue; + } + + // remember the path we're investigating to detect loops + path.push_back(edgeId); + + // recursive, depth-first search + if(getPath(edgeOfFromVertex->to, toVertexId, path, pathCheckFunc) and + pathCheckFunc(path)) { + // path found, we're done + return true; + } else { + // tear down path that didn't lead to the destination + path.pop_back(); + } + } + } + + return false; + } + + void dump(const std::string& fileName = "") + { + logger->info("Vertices:"); + for(auto& v : vertices) { + auto& vertex = v.second; + + // format connected vertices into a list + std::stringstream ssEdges; + for(auto& edge : vertex->edges) { + ssEdges << getEdge(edge)->to << " "; + } + + logger->info(" {} connected to: {}", *vertex, ssEdges.str()); + } + + std::fstream s(fileName, s.out | s.trunc); + if(s.is_open()) { + s << "digraph memgraph {" << std::endl; + } + + logger->info("Edges:"); + for(auto& e : edges) { + auto& edge = e.second; + + logger->info(" {}: {} -> {}", *edge, edge->from, edge->to); + if(s.is_open()) { + auto from = getVertex(edge->from); + auto to = getVertex(edge->to); + + s << std::dec; + s << " \"" << *from << "\" -> \"" << *to << "\"" + << " [label=\"" << *edge << "\"];" << std::endl; + } + } + + if(s.is_open()) { + s << "}" << std::endl; + s.close(); + } + } + +protected: + VertexIdentifier lastVertexId; + EdgeIdentifier lastEdgeId; + + std::map> vertices; + std::map> edges; + + SpdLogger logger; +}; + +} // namespacae graph +} // namespace villas diff --git a/common/include/villas/graph/edge.hpp b/common/include/villas/graph/edge.hpp new file mode 100644 index 000000000..496a518c0 --- /dev/null +++ b/common/include/villas/graph/edge.hpp @@ -0,0 +1,57 @@ +/** A graph edge. + * + * @file + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +namespace villas { +namespace graph { + +class Edge { + template + friend class DirectedGraph; + +public: + using Identifier = std::size_t; + + friend std::ostream& + operator<< (std::ostream& stream, const Edge& edge) + { return stream << edge.id; } + + bool + operator==(const Edge& other) + { return this->id == other.id; } + + Vertex::Identifier getVertexTo() const + { return to; } + + Vertex::Identifier getVertexFrom() const + { return from; } + +private: + Identifier id; + Vertex::Identifier from; + Vertex::Identifier to; +}; + +} // namespacae graph +} // namespace villas diff --git a/common/include/villas/graph/vertex.hpp b/common/include/villas/graph/vertex.hpp new file mode 100644 index 000000000..82b52f9e6 --- /dev/null +++ b/common/include/villas/graph/vertex.hpp @@ -0,0 +1,55 @@ +/** A graph vertex. + * + * @file + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +namespace villas { +namespace graph { + +class Vertex { + template + friend class DirectedGraph; + +public: + using Identifier = std::size_t; + + const Identifier& + getIdentifier() const + { return id; } + + friend std::ostream& + operator<< (std::ostream& stream, const Vertex& vertex) + { return stream << vertex.id; } + + bool + operator==(const Vertex& other) + { return this->id == other.id; } + +private: + Identifier id; + // HACK: how to resolve this circular type dependency? + std::list edges; +}; + +} // namespacae graph +} // namespace villas diff --git a/common/include/villas/log.hpp b/common/include/villas/log.hpp new file mode 100644 index 000000000..0e5dd9a23 --- /dev/null +++ b/common/include/villas/log.hpp @@ -0,0 +1,56 @@ +/** Logging. + * + * @file + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include + +#define SPDLOG_LEVEL_NAMES { "trace", "debug", "info ", "warn ", "error", "crit ", "off " } +#define SPDLOG_NAME_WIDTH 17 + +#include +#include +#include + +#define _ESCAPE "\x1b" +#define TXT_RESET_ALL _ESCAPE "[0m" + +#define TXT_RESET_BOLD _ESCAPE "[21m" +#define TXT_BOLD(s) _ESCAPE "[1m" + std::string(s) + TXT_RESET_BOLD + +#define TXT_RESET_COLOR _ESCAPE "[39m" +#define TXT_RED(s) _ESCAPE "[31m" + std::string(s) + TXT_RESET_COLOR +#define TXT_GREEN(s) _ESCAPE "[32m" + std::string(s) + TXT_RESET_COLOR +#define TXT_YELLOW(s) _ESCAPE "[33m" + std::string(s) + TXT_RESET_COLOR +#define TXT_BLUE(s) _ESCAPE "[34m" + std::string(s) + TXT_RESET_COLOR + +using SpdLogger = std::shared_ptr; + +inline SpdLogger loggerGetOrCreate(const std::string& logger_name) +{ + auto logger = spdlog::get(logger_name); + if(not logger) { + logger = spdlog::stdout_color_mt(logger_name); + } + return logger; +} diff --git a/common/include/villas/memory.hpp b/common/include/villas/memory.hpp new file mode 100644 index 000000000..75311fb2f --- /dev/null +++ b/common/include/villas/memory.hpp @@ -0,0 +1,309 @@ +/** Memory management. + * + * @file + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include +#include + +#include +#include + +namespace villas { + +/** + * @brief Basic memory block backed by an address space in the memory graph + * + * This is a generic representation of a chunk of memory in the system. It can + * reside anywhere and represent different types of memory. + */ +class MemoryBlock { +public: + using deallocator_fn = std::function; + + MemoryBlock(size_t offset, size_t size, MemoryManager::AddressSpaceId addrSpaceId) : + offset(offset), size(size), addrSpaceId(addrSpaceId) {} + + MemoryManager::AddressSpaceId getAddrSpaceId() const + { return addrSpaceId; } + + size_t getSize() const + { return size; } + + size_t getOffset() const + { return offset; } + +protected: + size_t offset; ///< Offset (or address) inside address space + size_t size; ///< Size in bytes of this block + MemoryManager::AddressSpaceId addrSpaceId; ///< Identifier in memory graph +}; + + +/** + * @brief Wrapper for a MemoryBlock to access the underlying memory directly + * + * The underlying memory block has to be accessible for the current process, + * that means it has to be mapped accordingly and registered to the global + * memory graph. + * Furthermore, this wrapper can be owning the memory block when initialized + * with a moved unique pointer. Otherwise, it just stores a reference to the + * memory block and it's the users responsibility to take care that the memory + * block is valid. + */ +template +class MemoryAccessor { +public: + using Type = T; + + // take ownership of the MemoryBlock + MemoryAccessor(std::unique_ptr mem) : + translation(MemoryManager::get().getTranslationFromProcess(mem->getAddrSpaceId())), + memoryBlock(std::move(mem)) {} + + // just act as an accessor, do not take ownership of MemoryBlock + MemoryAccessor(const MemoryBlock& mem) : + translation(MemoryManager::get().getTranslationFromProcess(mem.getAddrSpaceId())) {} + + + T& operator*() const { + return *reinterpret_cast(translation.getLocalAddr(0)); + } + + T& operator[](size_t idx) const { + const size_t offset = sizeof(T) * idx; + return *reinterpret_cast(translation.getLocalAddr(offset)); + } + + T* operator&() const { + return reinterpret_cast(translation.getLocalAddr(0)); + } + + T* operator->() const { + return reinterpret_cast(translation.getLocalAddr(0)); + } + + const MemoryBlock& + getMemoryBlock() const + { if(not memoryBlock) throw std::bad_alloc(); else return *memoryBlock; } + +private: + /// cached memory translation for fast access + MemoryTranslation translation; + + /// take the unique pointer in case user wants this class to have ownership + std::unique_ptr memoryBlock; +}; + + +/** + * @brief Base memory allocator + * + * Note the usage of CRTP idiom here to access methods of derived allocators. + * The concept is explained here at [1]. + * + * [1] https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern + */ +template +class BaseAllocator { +public: + /// memoryAddrSpaceId: memory that is managed by this allocator + BaseAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId) : + memoryAddrSpaceId(memoryAddrSpaceId) + { + // CRTP + derivedAlloc = static_cast(this); + logger = loggerGetOrCreate(derivedAlloc->getName()); + + // default deallocation callback + free = [&](MemoryBlock* mem) { + logger->warn("no free callback defined for addr space {}, not freeing", + mem->getAddrSpaceId()); + + removeMemoryBlock(*mem); + }; + } + + virtual std::unique_ptr + allocateBlock(size_t size) = 0; + + template + MemoryAccessor + allocate(size_t num) + { + const size_t size = num * sizeof(T); + auto mem = allocateBlock(size); + + // Check if the allocated memory is really accessible by writing to the + // allocated memory and reading back. Exponentially increase offset to + // speed up testing. + MemoryAccessor byteAccessor(*mem); + size_t idx = 0; + for(int i = 0; idx < mem->getSize(); i++, idx = (1 << i)) { + auto val = static_cast(i); + byteAccessor[idx] = val; + if(byteAccessor[idx] != val) { + logger->error("Cannot access allocated memory"); + throw std::bad_alloc(); + } + } + + return MemoryAccessor(std::move(mem)); + } + +protected: + void insertMemoryBlock(const MemoryBlock& mem) + { + auto& mm = MemoryManager::get(); + mm.createMapping(mem.getOffset(), 0, mem.getSize(), + derivedAlloc->getName(), + memoryAddrSpaceId, + mem.getAddrSpaceId()); + } + + void removeMemoryBlock(const MemoryBlock& mem) + { + // this will also remove any mapping to and from the memory block + auto& mm = MemoryManager::get(); + mm.removeAddressSpace(mem.getAddrSpaceId()); + } + + MemoryManager::AddressSpaceId getAddrSpaceId() const + { return memoryAddrSpaceId; } + +protected: + MemoryBlock::deallocator_fn free; + SpdLogger logger; + +private: + MemoryManager::AddressSpaceId memoryAddrSpaceId; + DerivedAllocator* derivedAlloc; +}; + + +/** + * @brief Linear memory allocator + * + * This is the simplest kind of allocator. The idea is to keep a pointer at the + * first memory address of your memory chunk and move it every time an + * allocation is done. Due to its simplicity, this allocator doesn't allow + * specific positions of memory to be freed. Usually, all memory is freed + * together. + */ +class LinearAllocator : public BaseAllocator { +public: + LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId, + size_t memorySize, + size_t internalOffset = 0); + + size_t getAvailableMemory() const + { return memorySize - nextFreeAddress; } + + size_t getSize() const + { return memorySize; } + + std::string getName() const; + + std::unique_ptr + allocateBlock(size_t size); + +private: + static constexpr size_t alignBytes = sizeof(uintptr_t); + static constexpr size_t alignMask = alignBytes - 1; + + size_t getAlignmentPadding(uintptr_t addr) const + { return (alignBytes - (addr & alignMask)) & alignMask; } + +private: + size_t nextFreeAddress; ///< next chunk will be allocated here + size_t memorySize; ///< total size of managed memory + size_t internalOffset; ///< offset in address space (usually 0) + size_t allocationCount; ///< Number of individual allocations present +}; + + +/** + * @brief Wrapper around mmap() to create villas memory blocks + * + * This class simply wraps around mmap() and munmap() to allocate memory in the + * host memory via the OS. + */ +class HostRam { +public: + class HostRamAllocator : public BaseAllocator { + public: + HostRamAllocator(); + + std::string getName() const + { return "HostRamAlloc"; } + + std::unique_ptr + allocateBlock(size_t size); + }; + + static HostRamAllocator& + getAllocator() + { return allocator; } + +private: + static HostRamAllocator allocator; +}; + + +class HostDmaRam { +private: + + static std::string + getUdmaBufName(int num); + + static std::string + getUdmaBufBasePath(int num); + + static size_t + getUdmaBufBufSize(int num); + + static uintptr_t + getUdmaBufPhysAddr(int num); + +public: + class HostDmaRamAllocator : public LinearAllocator { + public: + HostDmaRamAllocator(int num); + + virtual ~HostDmaRamAllocator(); + + std::string getName() const + { return getUdmaBufName(num); } + + private: + int num; + }; + + static HostDmaRamAllocator& + getAllocator(int num = 0); + +private: + static std::map> allocators; +}; + +} // namespace villas diff --git a/common/include/villas/memory_manager.hpp b/common/include/villas/memory_manager.hpp new file mode 100644 index 000000000..52681a4d3 --- /dev/null +++ b/common/include/villas/memory_manager.hpp @@ -0,0 +1,273 @@ +/** Memory manager. + * + * @file + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace villas { + +/** + * @brief Translation between a local (master) to a foreign (slave) address space + * + * Memory translations can be chained together using the `+=` operator which is + * used internally by the MemoryManager to compute a translation through + * multiple hops (memory mappings). + */ +class MemoryTranslation { +public: + + /** + * @brief MemoryTranslation + * @param src Base address of local address space + * @param dst Base address of foreign address space + * @param size Size of "memory window" + */ + MemoryTranslation(uintptr_t src, uintptr_t dst, size_t size) : + src(src), dst(dst), size(size) {} + + uintptr_t + getLocalAddr(uintptr_t addrInForeignAddrSpace) const; + + uintptr_t + getForeignAddr(uintptr_t addrInLocalAddrSpace) const; + + size_t + getSize() const + { return size; } + + friend std::ostream& + operator<< (std::ostream& stream, const MemoryTranslation& translation) + { + return stream << std::hex + << "(src=0x" << translation.src + << ", dst=0x" << translation.dst + << ", size=0x" << translation.size + << ")"; + } + + /// Merge two MemoryTranslations together + MemoryTranslation& operator+=(const MemoryTranslation& other); + +private: + uintptr_t src; ///< Base address of local address space + uintptr_t dst; ///< Base address of foreign address space + size_t size; ///< Size of "memory window" +}; + + +/** + * @brief Global memory manager to resolve addresses across address spaces + * + * Every entity in the system has to register its (master) address space and + * create mappings to other (slave) address spaces that it can access. A + * directed graph is then constructed which allows to traverse addresses spaces + * through multiple mappings and resolve addresses through this "tunnel" of + * memory mappings. + */ +class MemoryManager { +private: + // This is a singleton, so private constructor ... + MemoryManager() : + memoryGraph("MemoryGraph"), + logger(loggerGetOrCreate("MemoryManager")) + { + pathCheckFunc = [&](const MemoryGraph::Path& path) { + return this->pathCheck(path); + }; + } + + // ... and no copying or assigning + MemoryManager(const MemoryManager&) = delete; + MemoryManager& operator=(const MemoryManager&) = delete; + + /** + * @brief Custom edge in memory graph representing a memory mapping + * + * A memory mapping maps from one address space into another and can only be + * traversed in the forward direction which reflects the nature of real + * memory mappings. + * + * Implementation Notes: + * The member #src is the address in the "from" address space, where the + * destination address space is mapped. The member #dest is the address in + * the destination address space, where the mapping points to. Often, #dest + * will be zero for mappings to hardware, but consider the example when + * mapping FPGA to application memory: + * The application allocates a block 1kB at address 0x843001000 in its + * address space. The mapping would then have a #dest address of 0x843001000 + * and a #size of 1024. + */ + class Mapping : public graph::Edge { + public: + std::string name; ///< Human-readable name + uintptr_t src; ///< Base address in "from" address space + uintptr_t dest; ///< Base address in "to" address space + size_t size; ///< Size of the mapping + + friend std::ostream& + operator<< (std::ostream& stream, const Mapping& mapping) + { + return stream << static_cast(mapping) << " = " + << mapping.name + << std::hex + << " (src=0x" << mapping.src + << ", dest=0x" << mapping.dest + << ", size=0x" << mapping.size + << ")"; + } + + }; + + + /** + * @brief Custom vertex in memory graph representing an address space + * + * Since most information in the memory graph is stored in the edges (memory + * mappings), this is just a small extension to the default vertex. It only + * associates an additional string #name for human-readability. + */ + class AddressSpace : public graph::Vertex { + public: + std::string name; ///< Human-readable name + + friend std::ostream& + operator<< (std::ostream& stream, const AddressSpace& addrSpace) + { + return stream << static_cast(addrSpace) << " = " + << addrSpace.name; + } + }; + + /// Memory graph with custom edges and vertices for address resolution + using MemoryGraph = graph::DirectedGraph; + +public: + using AddressSpaceId = MemoryGraph::VertexIdentifier; + using MappingId = MemoryGraph::EdgeIdentifier; + + struct InvalidTranslation : public std::exception {}; + + /// Get singleton instance + static MemoryManager& + get(); + + AddressSpaceId + getProcessAddressSpace() + { return getOrCreateAddressSpace("villas-fpga"); } + + AddressSpaceId + getPciAddressSpace() + { return getOrCreateAddressSpace("PCIe"); } + + AddressSpaceId + getProcessAddressSpaceMemoryBlock(const std::string& memoryBlock) + { return getOrCreateAddressSpace(getSlaveAddrSpaceName("villas-fpga", memoryBlock)); } + + + AddressSpaceId + getOrCreateAddressSpace(std::string name); + + void + removeAddressSpace(AddressSpaceId addrSpaceId) + { memoryGraph.removeVertex(addrSpaceId); } + + /// Create a default mapping + MappingId + createMapping(uintptr_t src, uintptr_t dest, size_t size, + const std::string& name, + AddressSpaceId fromAddrSpace, + AddressSpaceId toAddrSpace); + + /// Add a mapping + /// + /// Can be used to derive from Mapping in order to implement custom + /// constructor/destructor. + MappingId + addMapping(std::shared_ptr mapping, + AddressSpaceId fromAddrSpace, + AddressSpaceId toAddrSpace); + + + AddressSpaceId + findAddressSpace(const std::string& name); + + std::list + findPath(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId); + + MemoryTranslation + getTranslation(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId); + + MemoryTranslation + getTranslationFromProcess(AddressSpaceId foreignAddrSpaceId) + { return getTranslation(getProcessAddressSpace(), foreignAddrSpaceId); } + + static std::string + getSlaveAddrSpaceName(const std::string& ipInstance, const std::string& memoryBlock) + { return ipInstance + "/" + memoryBlock; } + + static std::string + getMasterAddrSpaceName(const std::string& ipInstance, const std::string& busInterface) + { return ipInstance + ":" + busInterface; } + + void + dump() + { memoryGraph.dump(); } + + void + dumpToFile(const std::string& fileName) + { memoryGraph.dump(fileName); } + +private: + /// Convert a Mapping to MemoryTranslation for calculations + static MemoryTranslation + getTranslationFromMapping(const Mapping& mapping) + { return MemoryTranslation(mapping.src, mapping.dest, mapping.size); } + + bool + pathCheck(const MemoryGraph::Path& path); + +private: + /// Directed graph that stores address spaces and memory mappings + MemoryGraph memoryGraph; + + /// Cache mapping of names to address space ids for fast lookup + std::map addrSpaceLookup; + + /// Logger for universal access in this class + SpdLogger logger; + + MemoryGraph::check_path_fn pathCheckFunc; + + /// Static pointer to global instance, because this is a singleton + static MemoryManager* instance; +}; + +} // namespace villas diff --git a/common/include/villas/plugin.hpp b/common/include/villas/plugin.hpp new file mode 100644 index 000000000..52d149d27 --- /dev/null +++ b/common/include/villas/plugin.hpp @@ -0,0 +1,113 @@ +/** Loadable / plugin support. + * + * @file + * @author Steffen Vogel + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace villas { + +class Plugin { +public: + + enum class Type { + Unknown, + FpgaIp, + FpgaCard, + Gpu + }; + + Plugin(Type type, const std::string& name); + virtual ~Plugin(); + + // copying a plugin doesn't make sense, so explicitly deny it + Plugin(Plugin const&) = delete; + void operator=(Plugin const&) = delete; + + int load(); + int unload(); + + virtual int parse(json_t *cfg); + virtual void dump(); + + static void + dumpList(); + + /// Find plugin by type and (optional if empty) by name. If more match, it + /// is not defined which one will be returned. + static Plugin * + lookup(Type type, std::string name); + + /// Get all plugins of a given type. + static std::list + lookup(Type type); + + // TODO: check if this makes sense! (no intermediate plugins) + bool + operator==(const Plugin& other) const; + + Type pluginType; + std::string name; + std::string description; + std::string path; + void *handle; + enum state state; + +protected: + static SpdLogger + getStaticLogger() + { return loggerGetOrCreate("plugin"); } + +private: + /* Just using a standard std::list<> to hold plugins is problematic, because + we want to push Plugins to the list from within each Plugin's constructor + that is executed during static initialization. Since the order of static + initialization is undefined in C++, it may happen that a Plugin + constructor is executed before the list could be initialized. Therefore, + we use the Nifty Counter Idiom [1] to initialize the list ourself before + the first usage. + + In short: + - allocate a buffer for the list + - initialize list before first usage + - (complicatedly) declaring a buffer is neccessary in order to avoid + that the constructor of the static list is executed again + + [1] https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter + */ + + using PluginList = std::list; + using PluginListBuffer = typename std::aligned_storage::type; + + static PluginListBuffer pluginListBuffer; ///< buffer to hold a PluginList + static PluginList& pluginList; ///< reference to pluginListBuffer + static int pluginListNiftyCounter; ///< track if pluginList has been initialized +}; + +} // namespace villas diff --git a/common/include/villas/utils.hpp b/common/include/villas/utils.hpp new file mode 100644 index 000000000..11e044d98 --- /dev/null +++ b/common/include/villas/utils.hpp @@ -0,0 +1,46 @@ +/** Utilities. + * + * @file + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include +#include + +namespace villas { +namespace utils { + +std::vector +tokenize(std::string s, std::string delimiter); + + +template +void +assertExcept(bool condition, const T& exception) +{ + if(not condition) + throw exception; +} + +} // namespace utils +} // namespace villas + diff --git a/common/lib/CMakeLists.txt b/common/lib/CMakeLists.txt new file mode 100644 index 000000000..82029c855 --- /dev/null +++ b/common/lib/CMakeLists.txt @@ -0,0 +1,39 @@ +## CMakeLists.txt +# +# @author Daniel Krebs +# @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS) +# @license GNU General Public License (version 3) +# +# VILLASfpga +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +############################################################################## + +add_library(villas-common SHARED + plugin.cpp + utils.cpp + memory.cpp + memory_manager.cpp +) + +target_include_directories(villas-common PUBLIC + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/thirdparty/spdlog/include + ${JANSSON_INCLUDE_DIRS} +) + +target_link_libraries(villas-common PUBLIC + ${JANSSON_LIBRARIES} + -ldl +) diff --git a/common/lib/memory.cpp b/common/lib/memory.cpp new file mode 100644 index 000000000..3bf07cb78 --- /dev/null +++ b/common/lib/memory.cpp @@ -0,0 +1,300 @@ +/** Memory managment. + * + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include +#include + +#include + +namespace villas { + +std::unique_ptr +HostRam::HostRamAllocator::allocateBlock(size_t size) +{ + /* Align to next bigger page size chunk */ + if (size & size_t(0xFFF)) { + size += size_t(0x1000); + size &= size_t(~0xFFF); + } + + const int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT; + const int mmap_protection = PROT_READ | PROT_WRITE; + + const void* addr = mmap(nullptr, size, mmap_protection, mmap_flags, 0, 0); + if(addr == nullptr) { + throw std::bad_alloc(); + } + + auto& mm = MemoryManager::get(); + + // assemble name for this block + std::stringstream name; + name << std::showbase << std::hex << reinterpret_cast(addr); + + auto blockAddrSpaceId = mm.getProcessAddressSpaceMemoryBlock(name.str()); + + const auto localAddr = reinterpret_cast(addr); + std::unique_ptr + mem(new MemoryBlock(localAddr, size, blockAddrSpaceId), this->free); + + insertMemoryBlock(*mem); + + return mem; +} + + +LinearAllocator::LinearAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId, + size_t memorySize, + size_t internalOffset) : + BaseAllocator(memoryAddrSpaceId), + nextFreeAddress(0), + memorySize(memorySize), + internalOffset(internalOffset), + allocationCount(0) +{ + // make sure to start at aligned offset, reduce size in case we need padding + if(const size_t paddingBytes = getAlignmentPadding(internalOffset)) { + assert(paddingBytes < memorySize); + + internalOffset += paddingBytes; + memorySize -= paddingBytes; + } + + // deallocation callback + free = [&](MemoryBlock* mem) { + logger->debug("Deallocating memory block at local addr {:#x} (addr space {})", + mem->getOffset(), mem->getAddrSpaceId()); + + removeMemoryBlock(*mem); + + allocationCount--; + if(allocationCount == 0) { + logger->debug("All allocations are deallocated now, freeing memory"); + + // all allocations have been deallocated, free all memory + nextFreeAddress = 0; + } + + logger->debug("Available memory: {:#x} bytes", getAvailableMemory()); + }; +} + + +std::string +LinearAllocator::getName() const +{ + std::stringstream name; + name << "LinearAlloc" << getAddrSpaceId(); + if(internalOffset != 0) { + name << "@0x" << std::hex << internalOffset; + } + + return name.str(); +} + + +std::unique_ptr +LinearAllocator::allocateBlock(size_t size) +{ + if(size > getAvailableMemory()) { + throw std::bad_alloc(); + } + + // assign address + const uintptr_t localAddr = nextFreeAddress + internalOffset; + + // reserve memory + nextFreeAddress += size; + + // make sure it is aligned + if(const size_t paddingBytes = getAlignmentPadding(nextFreeAddress)) { + nextFreeAddress += paddingBytes; + + // if next free address is outside this block due to padding, cap it + nextFreeAddress = std::min(nextFreeAddress, memorySize); + } + + + auto& mm = MemoryManager::get(); + + // assemble name for this block + std::stringstream blockName; + blockName << std::showbase << std::hex << localAddr; + + // create address space + auto addrSpaceName = mm.getSlaveAddrSpaceName(getName(), blockName.str()); + auto addrSpaceId = mm.getOrCreateAddressSpace(addrSpaceName); + + logger->debug("Allocated {:#x} bytes for {}, {:#x} bytes remaining", + size, addrSpaceId, getAvailableMemory()); + + std::unique_ptr + mem(new MemoryBlock(localAddr, size, addrSpaceId), this->free); + + // mount block into the memory graph + insertMemoryBlock(*mem); + + // increase the allocation count + allocationCount++; + + return mem; +} + + +HostRam::HostRamAllocator +HostRam::allocator; + +HostRam::HostRamAllocator::HostRamAllocator() : + BaseAllocator(MemoryManager::get().getProcessAddressSpace()) +{ + free = [&](MemoryBlock* mem) { + if(::munmap(reinterpret_cast(mem->getOffset()), mem->getSize()) != 0) { + logger->warn("munmap() failed for {:#x} of size {:#x}", + mem->getOffset(), mem->getSize()); + } + + removeMemoryBlock(*mem); + }; +} + + +std::map> +HostDmaRam::allocators; + +HostDmaRam::HostDmaRamAllocator::HostDmaRamAllocator(int num) : + LinearAllocator(MemoryManager::get().getOrCreateAddressSpace(getUdmaBufName(num)), getUdmaBufBufSize(num)), + num(num) +{ + auto& mm = MemoryManager::get(); + logger = loggerGetOrCreate(getName()); + + if(getSize() == 0) { + logger->error("Zero-sized DMA buffer not supported, is the kernel module loaded?"); + throw std::bad_alloc(); + } + + const uintptr_t base = getUdmaBufPhysAddr(num); + + mm.createMapping(base, 0, getSize(), getName() + "-PCI", + mm.getPciAddressSpace(), getAddrSpaceId()); + + const auto bufPath = std::string("/dev/") + getUdmaBufName(num); + const int bufFd = open(bufPath.c_str(), O_RDWR | O_SYNC); + if(bufFd != -1) { + void* buf = mmap(nullptr, getSize(), PROT_READ|PROT_WRITE, MAP_SHARED, bufFd, 0); + close(bufFd); + + if(buf != MAP_FAILED) { + mm.createMapping(reinterpret_cast(buf), 0, getSize(), + getName() + "-VA", + mm.getProcessAddressSpace(), getAddrSpaceId()); + } else { + logger->warn("Cannot map {}", bufPath); + } + } else { + logger->warn("Cannot open {}", bufPath); + } + + logger->info("Mapped {} of size {} bytes", bufPath, getSize()); +} + +HostDmaRam::HostDmaRamAllocator::~HostDmaRamAllocator() +{ + auto& mm = MemoryManager::get(); + + void* baseVirt; + try { + auto translation = mm.getTranslationFromProcess(getAddrSpaceId()); + baseVirt = reinterpret_cast(translation.getLocalAddr(0)); + } catch(const std::out_of_range&) { + // not mapped, nothing to do + return; + } + + logger->debug("Unmapping {}", getName()); + + // try to unmap it + if(::munmap(baseVirt, getSize()) != 0) { + logger->warn("munmap() failed for {:p} of size {:#x}", + baseVirt, getSize()); + } +} + +std::string +HostDmaRam::getUdmaBufName(int num) +{ + std::stringstream name; + name << "udmabuf" << num; + + return name.str(); +} + +std::string +HostDmaRam::getUdmaBufBasePath(int num) +{ + std::stringstream path; + path << "/sys/class/udmabuf/udmabuf" << num << "/"; + return path.str(); +} + +size_t +HostDmaRam::getUdmaBufBufSize(int num) +{ + std::fstream s(getUdmaBufBasePath(num) + "size", s.in); + if(s.is_open()) { + std::string line; + if(std::getline(s, line)) { + return std::strtoul(line.c_str(), nullptr, 10); + } + } + + return 0; +} + +uintptr_t +HostDmaRam::getUdmaBufPhysAddr(int num) +{ + std::fstream s(getUdmaBufBasePath(num) + "phys_addr", s.in); + if(s.is_open()) { + std::string line; + if(std::getline(s, line)) { + return std::strtoul(line.c_str(), nullptr, 16); + } + } + + return UINTPTR_MAX; +} + +HostDmaRam::HostDmaRamAllocator&HostDmaRam::getAllocator(int num) +{ + auto& allocator = allocators[num]; + if(not allocator) { + allocator = std::make_unique(num); + } + + return *allocator; +} + +} // namespace villas diff --git a/common/lib/memory_manager.cpp b/common/lib/memory_manager.cpp new file mode 100644 index 000000000..915ee8649 --- /dev/null +++ b/common/lib/memory_manager.cpp @@ -0,0 +1,256 @@ +/** Memory managment. + * + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include + +#include +#include + +using namespace villas::utils; + +namespace villas { + +MemoryManager* +MemoryManager::instance = nullptr; + +MemoryManager& +MemoryManager::get() +{ + if(instance == nullptr) { + instance = new MemoryManager; + } + + return *instance; +} + +MemoryManager::AddressSpaceId +MemoryManager::getOrCreateAddressSpace(std::string name) +{ + try { + // try fast lookup + return addrSpaceLookup.at(name); + } catch (const std::out_of_range&) { + // does not yet exist, create + std::shared_ptr addrSpace(new AddressSpace); + addrSpace->name = name; + + // cache it for the next access + addrSpaceLookup[name] = memoryGraph.addVertex(addrSpace); + + return addrSpaceLookup[name]; + } +} + +MemoryManager::MappingId +MemoryManager::createMapping(uintptr_t src, uintptr_t dest, size_t size, + const std::string& name, + MemoryManager::AddressSpaceId fromAddrSpace, + MemoryManager::AddressSpaceId toAddrSpace) +{ + std::shared_ptr mapping(new Mapping); + + mapping->name = name; + mapping->src = src; + mapping->dest = dest; + mapping->size = size; + + return addMapping(mapping, fromAddrSpace, toAddrSpace); +} + +MemoryManager::MappingId +MemoryManager::addMapping(std::shared_ptr mapping, + MemoryManager::AddressSpaceId fromAddrSpace, + MemoryManager::AddressSpaceId toAddrSpace) +{ + return memoryGraph.addEdge(mapping, fromAddrSpace, toAddrSpace); +} + +MemoryManager::AddressSpaceId +MemoryManager::findAddressSpace(const std::string& name) +{ + return memoryGraph.findVertex( + [&](const std::shared_ptr& v) { + return v->name == name; + }); +} + +std::list +MemoryManager::findPath(MemoryManager::AddressSpaceId fromAddrSpaceId, + MemoryManager::AddressSpaceId toAddrSpaceId) +{ + std::list path; + + auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId); + auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId); + + // find a path through the memory graph + MemoryGraph::Path pathGraph; + if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, pathGraph, pathCheckFunc)) { + + logger->debug("No translation found from ({}) to ({})", + *fromAddrSpace, *toAddrSpace); + + throw std::out_of_range("no translation found"); + } + + for(auto& mappingId : pathGraph) { + auto mapping = memoryGraph.getEdge(mappingId); + path.push_back(mapping->getVertexTo()); + } + + return path; +} + +MemoryTranslation +MemoryManager::getTranslation(MemoryManager::AddressSpaceId fromAddrSpaceId, + MemoryManager::AddressSpaceId toAddrSpaceId) +{ + // find a path through the memory graph + MemoryGraph::Path path; + if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, path, pathCheckFunc)) { + + auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId); + auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId); + logger->debug("No translation found from ({}) to ({})", + *fromAddrSpace, *toAddrSpace); + + throw std::out_of_range("no translation found"); + } + + // start with an identity mapping + MemoryTranslation translation(0, 0, SIZE_MAX); + + // iterate through path and merge all mappings into a single translation + for(auto& mappingId : path) { + auto mapping = memoryGraph.getEdge(mappingId); + translation += getTranslationFromMapping(*mapping); + } + + return translation; +} + +bool +MemoryManager::pathCheck(const MemoryGraph::Path& path) +{ + // start with an identity mapping + MemoryTranslation translation(0, 0, SIZE_MAX); + + // Try to add all mappings together to a common translation. If this fails + // there is a non-overlapping window + for(auto& mappingId : path) { + auto mapping = memoryGraph.getEdge(mappingId); + try { + translation += getTranslationFromMapping(*mapping); + } catch(const InvalidTranslation&) { + return false; + } + } + + return true; +} + +uintptr_t +MemoryTranslation::getLocalAddr(uintptr_t addrInForeignAddrSpace) const +{ + assert(addrInForeignAddrSpace >= dst); + assert(addrInForeignAddrSpace < (dst + size)); + return src + addrInForeignAddrSpace - dst; +} + +uintptr_t +MemoryTranslation::getForeignAddr(uintptr_t addrInLocalAddrSpace) const +{ + assert(addrInLocalAddrSpace >= src); + assert(addrInLocalAddrSpace < (src + size)); + return dst + addrInLocalAddrSpace - src; +} + +MemoryTranslation& +MemoryTranslation::operator+=(const MemoryTranslation& other) +{ + auto logger = loggerGetOrCreate("MemoryTranslation"); + // set level to debug to enable debug output + logger->set_level(spdlog::level::info); + + const uintptr_t this_dst_high = this->dst + this->size; + const uintptr_t other_src_high = other.src + other.size; + + logger->debug("this->src: {:#x}", this->src); + logger->debug("this->dst: {:#x}", this->dst); + logger->debug("this->size: {:#x}", this->size); + logger->debug("other.src: {:#x}", other.src); + logger->debug("other.dst: {:#x}", other.dst); + logger->debug("other.size: {:#x}", other.size); + logger->debug("this_dst_high: {:#x}", this_dst_high); + logger->debug("other_src_high: {:#x}", other_src_high); + + // make sure there is a common memory area + assertExcept(other.src < this_dst_high, MemoryManager::InvalidTranslation()); + assertExcept(this->dst < other_src_high, MemoryManager::InvalidTranslation()); + + const uintptr_t hi = std::max(this_dst_high, other_src_high); + const uintptr_t lo = std::min(this->dst, other.src); + + const uintptr_t diff_hi = (this_dst_high > other_src_high) + ? (this_dst_high - other_src_high) + : (other_src_high - this_dst_high); + + const bool otherSrcIsSmaller = this->dst > other.src; + const uintptr_t diff_lo = (otherSrcIsSmaller) + ? (this->dst - other.src) + : (other.src - this->dst); + + logger->debug("hi: {:#x}", hi); + logger->debug("lo: {:#x}", lo); + logger->debug("diff_hi: {:#x}", diff_hi); + logger->debug("diff_lo: {:#x}", diff_lo); + + // new size of aperture, can only stay or shrink + this->size = (hi - lo) - diff_hi - diff_lo; + + // new translation will come out other's destination (by default) + this->dst = other.dst; + + // the source stays the same and can only increase with merged translations + this->src = this->src; + + if(otherSrcIsSmaller) { + // other mapping starts at lower addresses, so we actually arrive at + // higher addresses + this->dst += diff_lo; + } else { + // other mapping starts at higher addresses than this, so we have to + // increase the start + // NOTE: for addresses equality, this just adds 0 + this->src += diff_lo; + } + + logger->debug("result src: {:#x}", this->src); + logger->debug("result dst: {:#x}", this->dst); + logger->debug("result size: {:#x}", this->size); + + return *this; +} + +} // namespace villas diff --git a/common/lib/plugin.cpp b/common/lib/plugin.cpp new file mode 100644 index 000000000..508747de2 --- /dev/null +++ b/common/lib/plugin.cpp @@ -0,0 +1,160 @@ +/** Loadable / plugin support. + * + * @author Steffen Vogel + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +namespace villas { + +// list of all registered plugins +Plugin::PluginList& +Plugin::pluginList = reinterpret_cast(Plugin::pluginListBuffer); + +Plugin::PluginListBuffer +Plugin::pluginListBuffer; + +// relies on zero initialization +int Plugin::pluginListNiftyCounter; + + +Plugin::Plugin(Type type, const std::string& name) : + pluginType(type), + name(name), + description(""), + path(""), + state(STATE_INITIALIZED) +{ + // see comment in plugin.hpp on why we need to do this (Nifty Counter Idiom) + if(pluginListNiftyCounter++ == 0) + new (&pluginList) PluginList; + + // push to global plugin list + pluginList.push_back(this); +} + +Plugin::~Plugin() +{ + // clean from global plugin list + pluginList.remove(this); +} + + +int +Plugin::parse(json_t *cfg) +{ + const char *path; + + path = json_string_value(cfg); + if (!path) + return -1; + + this->path = std::string(path); + this->state = STATE_PARSED; + + return 0; +} + +int +Plugin::load() +{ + assert(this->state == STATE_PARSED); + assert(not this->path.empty()); + + this->handle = dlopen(this->path.c_str(), RTLD_NOW); + + if (this->handle == nullptr) + return -1; + + this->state = STATE_LOADED; + + return 0; +} + +int +Plugin::unload() +{ + int ret; + + assert(this->state == STATE_LOADED); + + ret = dlclose(this->handle); + if (ret != 0) + return -1; + + this->state = STATE_UNLOADED; + + return 0; +} + +void +Plugin::dump() +{ + auto logger = getStaticLogger(); + logger->info("Name: '{}' Description: '{}'", name, description); +} + +void +Plugin::dumpList() +{ + auto logger = getStaticLogger(); + + logger->info("Registered plugins:"); + for(auto& p : pluginList) { + logger->info(" - {}", p->name); + } +} + +Plugin* +Plugin::lookup(Plugin::Type type, std::string name) +{ + for(auto& p : pluginList) { + if(p->pluginType == type and (name.empty() or p->name == name)) + return p; + } + + return nullptr; +} + +std::list +Plugin::lookup(Plugin::Type type) +{ + std::list list; + for(auto& p : pluginList) { + if(p->pluginType == type) + list.push_back(p); + } + + return list; +} + +bool +Plugin::operator==(const Plugin &other) const +{ + return (this->pluginType == other.pluginType) and (this->name == other.name); +} + +} // namespace villas diff --git a/common/lib/utils.cpp b/common/lib/utils.cpp new file mode 100644 index 000000000..0795c1017 --- /dev/null +++ b/common/lib/utils.cpp @@ -0,0 +1,57 @@ +/** Utilities. + * + * @author Daniel Krebs + * @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include + +namespace villas { +namespace utils { + +std::vector +tokenize(std::string s, std::string delimiter) +{ + std::vector tokens; + + size_t lastPos = 0; + size_t curentPos; + + while((curentPos = s.find(delimiter, lastPos)) != std::string::npos) { + const size_t tokenLength = curentPos - lastPos; + tokens.push_back(s.substr(lastPos, tokenLength)); + + // advance in string + lastPos = curentPos + delimiter.length(); + } + + // check if there's a last token behind the last delimiter + if(lastPos != s.length()) { + const size_t lastTokenLength = s.length() - lastPos; + tokens.push_back(s.substr(lastPos, lastTokenLength)); + } + + return tokens; +} + +} // namespace utils +} // namespace villas diff --git a/common/tests/CMakeLists.txt b/common/tests/CMakeLists.txt new file mode 100644 index 000000000..f76ec8ec8 --- /dev/null +++ b/common/tests/CMakeLists.txt @@ -0,0 +1,41 @@ +## CMakeLists.txt +# +# @author Daniel Krebs +# @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS) +# @license GNU General Public License (version 3) +# +# VILLASfpga +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +############################################################################## + +set(SOURCES + main.cpp + logging.cpp + graph.cpp +) + +add_executable(unit-tests ${SOURCES}) + +find_package(Criterion REQUIRED) + +target_include_directories(unit-tests PUBLIC + ${PROJECT_SOURCE_DIR}/include + ${CRITERION_INCLUDE_DIRECTORIES} +) + +target_link_libraries(unit-tests PUBLIC + villas-common + ${CRITERION_LIBRARIES} +) diff --git a/common/tests/graph.cpp b/common/tests/graph.cpp new file mode 100644 index 000000000..b84eaac7e --- /dev/null +++ b/common/tests/graph.cpp @@ -0,0 +1,159 @@ +/** Graph unit test. + * + * @author Steffen Vogel + * @copyright 2017-2018, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include +#include +#include + +static void init_graph() +{ + spdlog::set_pattern("[%T] [%l] [%n] %v"); + spdlog::set_level(spdlog::level::debug); +} + +TestSuite(graph, + .description = "Graph library", + .init = init_graph +); + +Test(graph, basic, .description = "DirectedGraph") +{ + auto logger = loggerGetOrCreate("test:graph:basic"); + villas::graph::DirectedGraph<> g("test:graph:basic"); + + logger->info("Testing basic graph construction and modification"); + + std::shared_ptr v1(new villas::graph::Vertex); + std::shared_ptr v2(new villas::graph::Vertex); + std::shared_ptr v3(new villas::graph::Vertex); + + auto v1id = g.addVertex(v1); + auto v2id = g.addVertex(v2); + auto v3id = g.addVertex(v3); + cr_assert(g.getVertexCount() == 3); + + g.addDefaultEdge(v1id, v2id); + g.addDefaultEdge(v3id, v2id); + g.addDefaultEdge(v1id, v3id); + g.addDefaultEdge(v2id, v1id); + cr_assert(g.getEdgeCount() == 4); + cr_assert(g.vertexGetEdges(v1id).size() == 2); + cr_assert(g.vertexGetEdges(v2id).size() == 1); + cr_assert(g.vertexGetEdges(v3id).size() == 1); + + g.removeVertex(v1id); + g.dump(); + cr_assert(g.getVertexCount() == 2); + cr_assert(g.vertexGetEdges(v2id).size() == 0); + + logger->info(TXT_GREEN("Passed")); +} + +Test(graph, path, .description = "Find path") +{ + auto logger = loggerGetOrCreate("test:graph:path"); + logger->info("Testing path finding algorithm"); + + using Graph = villas::graph::DirectedGraph<>; + Graph g("test:graph:path"); + + std::shared_ptr v1(new villas::graph::Vertex); + std::shared_ptr v2(new villas::graph::Vertex); + std::shared_ptr v3(new villas::graph::Vertex); + std::shared_ptr v4(new villas::graph::Vertex); + std::shared_ptr v5(new villas::graph::Vertex); + std::shared_ptr v6(new villas::graph::Vertex); + + auto v1id = g.addVertex(v1); + auto v2id = g.addVertex(v2); + auto v3id = g.addVertex(v3); + + auto v4id = g.addVertex(v4); + auto v5id = g.addVertex(v5); + auto v6id = g.addVertex(v6); + + g.addDefaultEdge(v1id, v2id); + g.addDefaultEdge(v2id, v3id); + + // create circular subgraph + g.addDefaultEdge(v4id, v5id); + g.addDefaultEdge(v5id, v4id); + g.addDefaultEdge(v5id, v6id); + + g.dump(); + + logger->info("Find simple path via two edges"); + std::list path1; + cr_assert(g.getPath(v1id, v3id, path1)); + + logger->info(" Path from {} to {} via:", v1id, v3id); + for(auto& edge : path1) { + logger->info(" -> edge {}", edge); + } + + logger->info("Find path between two unconnected sub-graphs"); + std::list path2; + cr_assert(not g.getPath(v1id, v4id, path2)); + logger->info(" no path found -> ok"); + + + logger->info("Find non-existing path in circular sub-graph"); + std::list path3; + cr_assert(not g.getPath(v4id, v2id, path3)); + logger->info(" no path found -> ok"); + + + logger->info("Find path in circular graph"); + std::list path4; + cr_assert(g.getPath(v4id, v6id, path4)); + + logger->info(" Path from {} to {} via:", v4id, v6id); + for(auto& edge : path4) { + logger->info(" -> edge {}", edge); + } + + logger->info(TXT_GREEN("Passed")); +} + +Test(graph, memory_manager, .description = "Global Memory Manager") +{ + auto logger = loggerGetOrCreate("test:graph:mm"); + auto& mm = villas::MemoryManager::get(); + + logger->info("Create address spaces"); + auto dmaRegs = mm.getOrCreateAddressSpace("DMA Registers"); + auto pcieBridge = mm.getOrCreateAddressSpace("PCIe Bridge"); + + logger->info("Create a mapping"); + mm.createMapping(0x1000, 0, 0x1000, "Testmapping", dmaRegs, pcieBridge); + + logger->info("Find address space by name"); + auto vertex = mm.findAddressSpace("PCIe Bridge"); + logger->info(" found: {}", vertex); + + mm.dump(); + + logger->info(TXT_GREEN("Passed")); +} diff --git a/common/tests/logging.cpp b/common/tests/logging.cpp new file mode 100644 index 000000000..958f6f1b4 --- /dev/null +++ b/common/tests/logging.cpp @@ -0,0 +1,125 @@ +/** Logging utilities for unit test. + * + * @author Steffen Vogel + * @copyright 2017-2018, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include +#include + +#include +#include + +extern "C" { + /* We override criterions function here */ + void criterion_log_noformat(enum criterion_severity severity, const char *msg); + void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...); + void criterion_vlog(enum criterion_logging_level level, const char *msg, va_list args); +} + +struct criterion_prefix_data { + const char *prefix; + const char *color; +}; + +static int format_msg(char *buf, size_t buflen, const char *msg, va_list args) +{ + int len = vsnprintf(buf, buflen, msg, args); + + /* Strip new line */ + char *nl = strchr(buf, '\n'); + if (nl) + *nl = 0; + + return len; +} + +void criterion_log_noformat(enum criterion_severity severity, const char *msg) +{ + auto logger = loggerGetOrCreate("criterion"); + + switch (severity) { + case CR_LOG_INFO: + logger->info(msg); + break; + + case CR_LOG_WARNING: + logger->warn(msg); + break; + + case CR_LOG_ERROR: + logger->error(msg); + break; + } +} + +void criterion_vlog(enum criterion_logging_level level, const char *msg, va_list args) +{ + char formatted_msg[1024]; + + if (level < criterion_options.logging_threshold) + return; + + format_msg(formatted_msg, sizeof(formatted_msg), msg, args); + + auto logger = loggerGetOrCreate("criterion"); + logger->info(formatted_msg); +} + +void criterion_plog(enum criterion_logging_level level, const struct criterion_prefix_data *prefix, const char *msg, ...) +{ + char formatted_msg[1024]; + + va_list args; + + if (level < criterion_options.logging_threshold) + return; + + va_start(args, msg); + format_msg(formatted_msg, sizeof(formatted_msg), msg, args); + va_end(args); + + auto logger = loggerGetOrCreate("criterion"); + + if (strstr(formatted_msg, "Warning")) + logger->warn(formatted_msg); + else if (strstr(formatted_msg, "Failed")) + logger->error(formatted_msg); + else if(!strcmp(prefix->prefix, "----") && !strcmp(prefix->color, "\33[0;34m")) + logger->info(formatted_msg); + else if (!strcmp(prefix->prefix, "----") && !strcmp(prefix->color, "\33[1;30m")) + logger->debug(formatted_msg); + else if (!strcmp(prefix->prefix, "====")) + logger->info(formatted_msg); + else if (!strcmp(prefix->prefix, "RUN ")) + logger->info("Run: {}", formatted_msg); + else if (!strcmp(prefix->prefix, "SKIP")) + logger->info("Skip: {}", formatted_msg); + else if (!strcmp(prefix->prefix, "PASS")) + logger->info("Pass: {}", formatted_msg); + else if (!strcmp(prefix->prefix, "FAIL")) + logger->error("Fail: {}", formatted_msg); + else if (!strcmp(prefix->prefix, "WARN")) + logger->warn(formatted_msg); + else if (!strcmp(prefix->prefix, "ERR ")) + logger->error(formatted_msg); +} diff --git a/common/tests/main.cpp b/common/tests/main.cpp new file mode 100644 index 000000000..a667da695 --- /dev/null +++ b/common/tests/main.cpp @@ -0,0 +1,47 @@ +/** Main Unit Test entry point. + * + * @author Steffen Vogel + * @copyright 2017-2018, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include + +#include + +int main(int argc, char *argv[]) +{ + int ret; + + spdlog::set_level(spdlog::level::debug); + spdlog::set_pattern("[%T] [%l] [%n] %v"); + + /* Run criterion tests */ + auto tests = criterion_initialize(); + + ret = criterion_handle_args(argc, argv, true); + if (ret) + ret = !criterion_run_all_tests(tests); + + criterion_finalize(tests); + + return ret; +} diff --git a/common/thirdparty/criterion b/common/thirdparty/criterion new file mode 160000 index 000000000..9b7036582 --- /dev/null +++ b/common/thirdparty/criterion @@ -0,0 +1 @@ +Subproject commit 9b70365825aced7333d7867bb5c64c63919ce510 diff --git a/common/thirdparty/spdlog b/common/thirdparty/spdlog new file mode 160000 index 000000000..d3c1ad29a --- /dev/null +++ b/common/thirdparty/spdlog @@ -0,0 +1 @@ +Subproject commit d3c1ad29a064d001da2435db56f159784ef54964