mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
initial commit
This commit is contained in:
parent
e5a518275f
commit
d7dd73f5f6
26 changed files with 2669 additions and 0 deletions
56
common/.gitlab-ci.yml
Normal file
56
common/.gitlab-ci.yml
Normal file
|
@ -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
|
6
common/.gitmodules
vendored
Normal file
6
common/.gitmodules
vendored
Normal file
|
@ -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
|
37
common/CMakeLists.txt
Normal file
37
common/CMakeLists.txt
Normal file
|
@ -0,0 +1,37 @@
|
|||
## CMakeLists.txt
|
||||
#
|
||||
# @author Daniel Krebs <github@daniel-krebs.net>
|
||||
# @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 <http://www.gnu.org/licenses/>.
|
||||
##############################################################################
|
||||
|
||||
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)
|
70
common/Dockerfile
Normal file
70
common/Dockerfile
Normal file
|
@ -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 <stvogel@eonerc.rwth-aachen.de>
|
||||
# @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 <http://www.gnu.org/licenses/>.
|
||||
###################################################################################
|
||||
|
||||
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
|
45
common/README.md
Normal file
45
common/README.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
# VILLAScommon
|
||||
|
||||
[](https://git.rwth-aachen.de/acs/VILLAScommon/commits/develop)
|
||||
|
||||
**TODO:** Write project description
|
||||
|
||||
## Documentation
|
||||
|
||||
User documentation is available here: <https://villas.fein-aachen.org/doc/>
|
||||
|
||||
## 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 <http://www.gnu.org/licenses/>.
|
||||
```
|
||||
|
||||
For other licensing options please consult [Prof. Antonello Monti](mailto:amonti@eonerc.rwth-aachen.de).
|
||||
|
||||
## Contact
|
||||
|
||||
[](http://www.acs.eonerc.rwth-aachen.de)
|
||||
|
||||
- Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
- Daniel Krebs <dkrebs@eonerc.rwth-aachen.de>
|
||||
|
||||
[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)
|
27
common/cmake/FindCriterion.cmake
Normal file
27
common/cmake/FindCriterion.cmake
Normal file
|
@ -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)
|
36
common/include/villas/common.h
Normal file
36
common/include/villas/common.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/** Some common defines, enums and datastructures.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#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 */
|
||||
};
|
297
common/include/villas/graph/directed.hpp
Normal file
297
common/include/villas/graph/directed.hpp
Normal file
|
@ -0,0 +1,297 @@
|
|||
/** A directed graph.
|
||||
*
|
||||
* @file
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/graph/vertex.hpp>
|
||||
#include <villas/graph/edge.hpp>
|
||||
|
||||
|
||||
namespace villas {
|
||||
namespace graph {
|
||||
|
||||
|
||||
template<typename VertexType = Vertex, typename EdgeType = Edge>
|
||||
class DirectedGraph {
|
||||
public:
|
||||
|
||||
using VertexIdentifier = Vertex::Identifier;
|
||||
using EdgeIdentifier = Edge::Identifier;
|
||||
using Path = std::list<EdgeIdentifier>;
|
||||
|
||||
DirectedGraph(const std::string& name = "DirectedGraph") :
|
||||
lastVertexId(0), lastEdgeId(0)
|
||||
{
|
||||
logger = loggerGetOrCreate(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<VertexType> 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<class UnaryPredicate>
|
||||
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<EdgeType> 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<VertexType> vertex)
|
||||
{
|
||||
vertex->id = lastVertexId++;
|
||||
|
||||
logger->debug("New vertex: {}", *vertex);
|
||||
vertices[vertex->id] = vertex;
|
||||
|
||||
return vertex->id;
|
||||
}
|
||||
|
||||
EdgeIdentifier addEdge(std::shared_ptr<EdgeType> 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<EdgeType> 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<EdgeIdentifier>&
|
||||
vertexGetEdges(VertexIdentifier vertexId) const
|
||||
{ return getVertex(vertexId)->edges; }
|
||||
|
||||
|
||||
using check_path_fn = std::function<bool(const Path&)>;
|
||||
|
||||
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<VertexIdentifier, std::shared_ptr<VertexType>> vertices;
|
||||
std::map<EdgeIdentifier, std::shared_ptr<EdgeType>> edges;
|
||||
|
||||
SpdLogger logger;
|
||||
};
|
||||
|
||||
} // namespacae graph
|
||||
} // namespace villas
|
57
common/include/villas/graph/edge.hpp
Normal file
57
common/include/villas/graph/edge.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/** A graph edge.
|
||||
*
|
||||
* @file
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace villas {
|
||||
namespace graph {
|
||||
|
||||
class Edge {
|
||||
template<typename VertexType, typename EdgeType>
|
||||
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
|
55
common/include/villas/graph/vertex.hpp
Normal file
55
common/include/villas/graph/vertex.hpp
Normal file
|
@ -0,0 +1,55 @@
|
|||
/** A graph vertex.
|
||||
*
|
||||
* @file
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace villas {
|
||||
namespace graph {
|
||||
|
||||
class Vertex {
|
||||
template<typename VertexType, typename EdgeType>
|
||||
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<std::size_t> edges;
|
||||
};
|
||||
|
||||
} // namespacae graph
|
||||
} // namespace villas
|
56
common/include/villas/log.hpp
Normal file
56
common/include/villas/log.hpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
/** Logging.
|
||||
*
|
||||
* @file
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#define SPDLOG_LEVEL_NAMES { "trace", "debug", "info ", "warn ", "error", "crit ", "off " }
|
||||
#define SPDLOG_NAME_WIDTH 17
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/fmt/ostr.h>
|
||||
|
||||
#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<spdlog::logger>;
|
||||
|
||||
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;
|
||||
}
|
309
common/include/villas/memory.hpp
Normal file
309
common/include/villas/memory.hpp
Normal file
|
@ -0,0 +1,309 @@
|
|||
/** Memory management.
|
||||
*
|
||||
* @file
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/memory_manager.hpp>
|
||||
|
||||
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<void(MemoryBlock*)>;
|
||||
|
||||
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<typename T>
|
||||
class MemoryAccessor {
|
||||
public:
|
||||
using Type = T;
|
||||
|
||||
// take ownership of the MemoryBlock
|
||||
MemoryAccessor(std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn> 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<T*>(translation.getLocalAddr(0));
|
||||
}
|
||||
|
||||
T& operator[](size_t idx) const {
|
||||
const size_t offset = sizeof(T) * idx;
|
||||
return *reinterpret_cast<T*>(translation.getLocalAddr(offset));
|
||||
}
|
||||
|
||||
T* operator&() const {
|
||||
return reinterpret_cast<T*>(translation.getLocalAddr(0));
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
return reinterpret_cast<T*>(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, MemoryBlock::deallocator_fn> 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<typename DerivedAllocator>
|
||||
class BaseAllocator {
|
||||
public:
|
||||
/// memoryAddrSpaceId: memory that is managed by this allocator
|
||||
BaseAllocator(MemoryManager::AddressSpaceId memoryAddrSpaceId) :
|
||||
memoryAddrSpaceId(memoryAddrSpaceId)
|
||||
{
|
||||
// CRTP
|
||||
derivedAlloc = static_cast<DerivedAllocator*>(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<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
allocateBlock(size_t size) = 0;
|
||||
|
||||
template<typename T>
|
||||
MemoryAccessor<T>
|
||||
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<volatile uint8_t> byteAccessor(*mem);
|
||||
size_t idx = 0;
|
||||
for(int i = 0; idx < mem->getSize(); i++, idx = (1 << i)) {
|
||||
auto val = static_cast<uint8_t>(i);
|
||||
byteAccessor[idx] = val;
|
||||
if(byteAccessor[idx] != val) {
|
||||
logger->error("Cannot access allocated memory");
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}
|
||||
|
||||
return MemoryAccessor<T>(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<LinearAllocator> {
|
||||
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<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
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<HostRamAllocator> {
|
||||
public:
|
||||
HostRamAllocator();
|
||||
|
||||
std::string getName() const
|
||||
{ return "HostRamAlloc"; }
|
||||
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
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<int, std::unique_ptr<HostDmaRamAllocator>> allocators;
|
||||
};
|
||||
|
||||
} // namespace villas
|
273
common/include/villas/memory_manager.hpp
Normal file
273
common/include/villas/memory_manager.hpp
Normal file
|
@ -0,0 +1,273 @@
|
|||
/** Memory manager.
|
||||
*
|
||||
* @file
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/graph/directed.hpp>
|
||||
|
||||
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<const Edge&>(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<const Vertex&>(addrSpace) << " = "
|
||||
<< addrSpace.name;
|
||||
}
|
||||
};
|
||||
|
||||
/// Memory graph with custom edges and vertices for address resolution
|
||||
using MemoryGraph = graph::DirectedGraph<AddressSpace, Mapping>;
|
||||
|
||||
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> mapping,
|
||||
AddressSpaceId fromAddrSpace,
|
||||
AddressSpaceId toAddrSpace);
|
||||
|
||||
|
||||
AddressSpaceId
|
||||
findAddressSpace(const std::string& name);
|
||||
|
||||
std::list<AddressSpaceId>
|
||||
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<std::string, AddressSpaceId> 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
|
113
common/include/villas/plugin.hpp
Normal file
113
common/include/villas/plugin.hpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
/** Loadable / plugin support.
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <jansson.h>
|
||||
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/common.h>
|
||||
|
||||
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<Plugin*>
|
||||
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<Plugin *>;
|
||||
using PluginListBuffer = typename std::aligned_storage<sizeof (Plugin::PluginList), alignof (Plugin::PluginList)>::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
|
46
common/include/villas/utils.hpp
Normal file
46
common/include/villas/utils.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
/** Utilities.
|
||||
*
|
||||
* @file
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace villas {
|
||||
namespace utils {
|
||||
|
||||
std::vector<std::string>
|
||||
tokenize(std::string s, std::string delimiter);
|
||||
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
assertExcept(bool condition, const T& exception)
|
||||
{
|
||||
if(not condition)
|
||||
throw exception;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
} // namespace villas
|
||||
|
39
common/lib/CMakeLists.txt
Normal file
39
common/lib/CMakeLists.txt
Normal file
|
@ -0,0 +1,39 @@
|
|||
## CMakeLists.txt
|
||||
#
|
||||
# @author Daniel Krebs <github@daniel-krebs.net>
|
||||
# @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 <http://www.gnu.org/licenses/>.
|
||||
##############################################################################
|
||||
|
||||
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
|
||||
)
|
300
common/lib/memory.cpp
Normal file
300
common/lib/memory.cpp
Normal file
|
@ -0,0 +1,300 @@
|
|||
/** Memory managment.
|
||||
*
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
#include <villas/memory.hpp>
|
||||
|
||||
namespace villas {
|
||||
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
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<uintptr_t>(addr);
|
||||
|
||||
auto blockAddrSpaceId = mm.getProcessAddressSpaceMemoryBlock(name.str());
|
||||
|
||||
const auto localAddr = reinterpret_cast<uintptr_t>(addr);
|
||||
std::unique_ptr<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
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<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
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<MemoryBlock, MemoryBlock::deallocator_fn>
|
||||
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<void*>(mem->getOffset()), mem->getSize()) != 0) {
|
||||
logger->warn("munmap() failed for {:#x} of size {:#x}",
|
||||
mem->getOffset(), mem->getSize());
|
||||
}
|
||||
|
||||
removeMemoryBlock(*mem);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
std::map<int, std::unique_ptr<HostDmaRam::HostDmaRamAllocator>>
|
||||
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<uintptr_t>(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<void*>(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<HostDmaRamAllocator>(num);
|
||||
}
|
||||
|
||||
return *allocator;
|
||||
}
|
||||
|
||||
} // namespace villas
|
256
common/lib/memory_manager.cpp
Normal file
256
common/lib/memory_manager.cpp
Normal file
|
@ -0,0 +1,256 @@
|
|||
/** Memory managment.
|
||||
*
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <cstdint>
|
||||
|
||||
#include <villas/utils.hpp>
|
||||
#include <villas/memory_manager.hpp>
|
||||
|
||||
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<AddressSpace> 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> 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> 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<AddressSpace>& v) {
|
||||
return v->name == name;
|
||||
});
|
||||
}
|
||||
|
||||
std::list<MemoryManager::AddressSpaceId>
|
||||
MemoryManager::findPath(MemoryManager::AddressSpaceId fromAddrSpaceId,
|
||||
MemoryManager::AddressSpaceId toAddrSpaceId)
|
||||
{
|
||||
std::list<AddressSpaceId> 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
|
160
common/lib/plugin.cpp
Normal file
160
common/lib/plugin.cpp
Normal file
|
@ -0,0 +1,160 @@
|
|||
/** Loadable / plugin support.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <villas/plugin.hpp>
|
||||
|
||||
namespace villas {
|
||||
|
||||
// list of all registered plugins
|
||||
Plugin::PluginList&
|
||||
Plugin::pluginList = reinterpret_cast<Plugin::PluginList&>(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*>
|
||||
Plugin::lookup(Plugin::Type type)
|
||||
{
|
||||
std::list<Plugin*> 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
|
57
common/lib/utils.cpp
Normal file
57
common/lib/utils.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/** Utilities.
|
||||
*
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <villas/utils.hpp>
|
||||
|
||||
namespace villas {
|
||||
namespace utils {
|
||||
|
||||
std::vector<std::string>
|
||||
tokenize(std::string s, std::string delimiter)
|
||||
{
|
||||
std::vector<std::string> 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
|
41
common/tests/CMakeLists.txt
Normal file
41
common/tests/CMakeLists.txt
Normal file
|
@ -0,0 +1,41 @@
|
|||
## CMakeLists.txt
|
||||
#
|
||||
# @author Daniel Krebs <github@daniel-krebs.net>
|
||||
# @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 <http://www.gnu.org/licenses/>.
|
||||
##############################################################################
|
||||
|
||||
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}
|
||||
)
|
159
common/tests/graph.cpp
Normal file
159
common/tests/graph.cpp
Normal file
|
@ -0,0 +1,159 @@
|
|||
/** Graph unit test.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <criterion/criterion.h>
|
||||
#include <villas/graph/directed.hpp>
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/memory_manager.hpp>
|
||||
|
||||
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<villas::graph::Vertex> v1(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> v2(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> 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<villas::graph::Vertex> v1(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> v2(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> v3(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> v4(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> v5(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> 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<Graph::EdgeIdentifier> 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<Graph::EdgeIdentifier> 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<Graph::EdgeIdentifier> path3;
|
||||
cr_assert(not g.getPath(v4id, v2id, path3));
|
||||
logger->info(" no path found -> ok");
|
||||
|
||||
|
||||
logger->info("Find path in circular graph");
|
||||
std::list<Graph::EdgeIdentifier> 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"));
|
||||
}
|
125
common/tests/logging.cpp
Normal file
125
common/tests/logging.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
/** Logging utilities for unit test.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <cstdarg>
|
||||
#include <memory>
|
||||
|
||||
#include <criterion/logging.h>
|
||||
#include <criterion/options.h>
|
||||
|
||||
#include <villas/log.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
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);
|
||||
}
|
47
common/tests/main.cpp
Normal file
47
common/tests/main.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
/** Main Unit Test entry point.
|
||||
*
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <criterion/criterion.h>
|
||||
#include <criterion/options.h>
|
||||
|
||||
#include <villas/log.hpp>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
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;
|
||||
}
|
1
common/thirdparty/criterion
vendored
Submodule
1
common/thirdparty/criterion
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 9b70365825aced7333d7867bb5c64c63919ce510
|
1
common/thirdparty/spdlog
vendored
Submodule
1
common/thirdparty/spdlog
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit d3c1ad29a064d001da2435db56f159784ef54964
|
Loading…
Add table
Reference in a new issue