From c3164e93ef27edc2396dc2600019fb512c6574e3 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 21 Nov 2017 21:31:08 +0100 Subject: [PATCH] imported source code from VILLASfpga repo and made it compile --- fpga/.editorconfig | 17 + fpga/.gitlab-ci.yml | 94 + fpga/CMakeLists.txt | 8 + fpga/cmake/FindCriterion.cmake | 27 + fpga/doc/pictures/eonerc_logo.png | Bin 0 -> 9348 bytes fpga/doc/pictures/villas_fpga.png | Bin 0 -> 14274 bytes fpga/doc/pictures/villas_fpga.svg | 113 + fpga/etc/fpga-simple.conf | 73 + fpga/etc/fpga.conf | 168 + fpga/include/villas/common.h | 36 + fpga/include/villas/config.h | 71 + fpga/include/villas/fpga/card.h | 95 + fpga/include/villas/fpga/ip.h | 119 + fpga/include/villas/fpga/ips/dft.h | 52 + fpga/include/villas/fpga/ips/dma.h | 88 + fpga/include/villas/fpga/ips/fifo.h | 52 + fpga/include/villas/fpga/ips/intc.h | 56 + fpga/include/villas/fpga/ips/model.h | 147 + fpga/include/villas/fpga/ips/rtds_axis.h | 62 + fpga/include/villas/fpga/ips/switch.h | 67 + fpga/include/villas/fpga/ips/timer.h | 43 + fpga/include/villas/fpga/vlnv.h | 53 + fpga/include/villas/kernel/kernel.h | 90 + fpga/include/villas/kernel/pci.h | 62 + fpga/include/villas/kernel/vfio.h | 112 + fpga/include/villas/list.h | 112 + fpga/include/villas/log.h | 194 + fpga/include/villas/log_config.h | 37 + fpga/include/villas/plugin.h | 82 + fpga/include/villas/utils.h | 276 + fpga/lib/CMakeLists.txt | 53 + fpga/lib/card.c | 313 + fpga/lib/ip.c | 167 + fpga/lib/ips/dft.c | 140 + fpga/lib/ips/dma.c | 657 ++ fpga/lib/ips/fifo.c | 153 + fpga/lib/ips/intc.c | 180 + fpga/lib/ips/model.c | 427 ++ fpga/lib/ips/rtds_axis.c | 80 + fpga/lib/ips/switch.c | 221 + fpga/lib/ips/timer.c | 61 + fpga/lib/kernel/kernel.c | 282 + fpga/lib/kernel/pci.c | 294 + fpga/lib/kernel/vfio.c | 641 ++ fpga/lib/list.c | 203 + fpga/lib/log.c | 309 + fpga/lib/log_config.c | 85 + fpga/lib/log_helper.c | 138 + fpga/lib/plugin.c | 111 + fpga/lib/utils.c | 401 ++ fpga/lib/vlnv.c | 64 + fpga/scripts/hwdef-parse.py | 200 + fpga/scripts/hwdef-villas.xml | 8119 ++++++++++++++++++++++ fpga/scripts/rebind_device.sh | 47 + fpga/scripts/reset_pci_device.sh | 35 + fpga/tests/CMakeLists.txt | 24 + fpga/tests/dma.c | 94 + fpga/tests/fifo.c | 74 + fpga/tests/hls.c | 80 + fpga/tests/intc.c | 57 + fpga/tests/main.c | 91 + fpga/tests/rtds_rtt.c | 81 + fpga/tests/tmrctr.c | 70 + fpga/tests/xsg.c | 97 + 64 files changed, 16455 insertions(+) create mode 100644 fpga/.editorconfig create mode 100644 fpga/.gitlab-ci.yml create mode 100644 fpga/CMakeLists.txt create mode 100644 fpga/cmake/FindCriterion.cmake create mode 100644 fpga/doc/pictures/eonerc_logo.png create mode 100644 fpga/doc/pictures/villas_fpga.png create mode 100644 fpga/doc/pictures/villas_fpga.svg create mode 100644 fpga/etc/fpga-simple.conf create mode 100644 fpga/etc/fpga.conf create mode 100644 fpga/include/villas/common.h create mode 100644 fpga/include/villas/config.h create mode 100644 fpga/include/villas/fpga/card.h create mode 100644 fpga/include/villas/fpga/ip.h create mode 100644 fpga/include/villas/fpga/ips/dft.h create mode 100644 fpga/include/villas/fpga/ips/dma.h create mode 100644 fpga/include/villas/fpga/ips/fifo.h create mode 100644 fpga/include/villas/fpga/ips/intc.h create mode 100644 fpga/include/villas/fpga/ips/model.h create mode 100644 fpga/include/villas/fpga/ips/rtds_axis.h create mode 100644 fpga/include/villas/fpga/ips/switch.h create mode 100644 fpga/include/villas/fpga/ips/timer.h create mode 100644 fpga/include/villas/fpga/vlnv.h create mode 100644 fpga/include/villas/kernel/kernel.h create mode 100644 fpga/include/villas/kernel/pci.h create mode 100644 fpga/include/villas/kernel/vfio.h create mode 100644 fpga/include/villas/list.h create mode 100644 fpga/include/villas/log.h create mode 100644 fpga/include/villas/log_config.h create mode 100644 fpga/include/villas/plugin.h create mode 100644 fpga/include/villas/utils.h create mode 100644 fpga/lib/CMakeLists.txt create mode 100644 fpga/lib/card.c create mode 100644 fpga/lib/ip.c create mode 100644 fpga/lib/ips/dft.c create mode 100644 fpga/lib/ips/dma.c create mode 100644 fpga/lib/ips/fifo.c create mode 100644 fpga/lib/ips/intc.c create mode 100644 fpga/lib/ips/model.c create mode 100644 fpga/lib/ips/rtds_axis.c create mode 100644 fpga/lib/ips/switch.c create mode 100644 fpga/lib/ips/timer.c create mode 100644 fpga/lib/kernel/kernel.c create mode 100644 fpga/lib/kernel/pci.c create mode 100644 fpga/lib/kernel/vfio.c create mode 100644 fpga/lib/list.c create mode 100644 fpga/lib/log.c create mode 100644 fpga/lib/log_config.c create mode 100644 fpga/lib/log_helper.c create mode 100644 fpga/lib/plugin.c create mode 100644 fpga/lib/utils.c create mode 100644 fpga/lib/vlnv.c create mode 100755 fpga/scripts/hwdef-parse.py create mode 100644 fpga/scripts/hwdef-villas.xml create mode 100755 fpga/scripts/rebind_device.sh create mode 100755 fpga/scripts/reset_pci_device.sh create mode 100644 fpga/tests/CMakeLists.txt create mode 100644 fpga/tests/dma.c create mode 100644 fpga/tests/fifo.c create mode 100644 fpga/tests/hls.c create mode 100644 fpga/tests/intc.c create mode 100644 fpga/tests/main.c create mode 100644 fpga/tests/rtds_rtt.c create mode 100644 fpga/tests/tmrctr.c create mode 100644 fpga/tests/xsg.c diff --git a/fpga/.editorconfig b/fpga/.editorconfig new file mode 100644 index 000000000..a31bb2637 --- /dev/null +++ b/fpga/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[{etc,include,lib,plugins,src,tests,tools}/**.{c,h}] +charset = utf-8 +indent_style = tab +indent_size = 8 +trim_trailing_whitespace=true diff --git a/fpga/.gitlab-ci.yml b/fpga/.gitlab-ci.yml new file mode 100644 index 000000000..8c92f218a --- /dev/null +++ b/fpga/.gitlab-ci.yml @@ -0,0 +1,94 @@ +variables: + GIT_STRATEGY: fetch + GIT_SUBMODULE_STRATEGY: recursive + PREFIX: /usr/ + DOCKER_TAG_DEV: ${CI_COMMIT_REF_NAME} + DOCKER_IMAGE_DEV: villas/fpga-dev + +stages: + - prepare + - build + - test + - deploy + - docker + +# For some reason, GitLab CI prunes the contents of the submodules so we need to restore them. +before_script: + - git submodule foreach git checkout . + +# 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 + artifacts: + expire_in: 1 week + name: ${CI_PROJECT_NAME}-${CI_BUILD_REF} + paths: + - build/ + image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} + tags: + - docker + +#build:packages: +# stage: build +# script: +# - mkdir build && cd build && cmake .. && make package +# 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: +# - make test +# image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG_DEV} +# tags: +# - docker +# - fpga + +# Stage: deploy +############################################################################## + +#deploy:packages: +# stage: deploy +# script: +# - ssh ${DEPLOY_USER}@${DEPLOY_HOST} mkdir -p ${DEPLOY_PATH}/{dist,../packages} +# - rsync ${RSYNC_OPTS} build/*.rpm ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/../packages/ +# - rsync ${RSYNC_OPTS} build//*.tar.gz ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/dist/ +# - ssh ${DEPLOY_USER}@${DEPLOY_HOST} createrepo ${DEPLOY_PATH}/../packages +# dependencies: +# - build:packages +# tags: +# - villas-deploy +# only: +# - tags +# +#deploy:git-mirror: +# stage: deploy +# script: +# - git push --force --mirror --prune https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com:VILLASframework/VILLASnode.git diff --git a/fpga/CMakeLists.txt b/fpga/CMakeLists.txt new file mode 100644 index 000000000..3bb0552ad --- /dev/null +++ b/fpga/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.5) + +project(VILLASfpga C) + +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) + +add_subdirectory(lib) +add_subdirectory(tests) diff --git a/fpga/cmake/FindCriterion.cmake b/fpga/cmake/FindCriterion.cmake new file mode 100644 index 000000000..0e4a2c069 --- /dev/null +++ b/fpga/cmake/FindCriterion.cmake @@ -0,0 +1,27 @@ +# This file is licensed under the WTFPL version 2 -- you can see the full +# license over at http://www.wtfpl.net/txt/copying/ +# +# - Try to find Criterion +# +# Once done this will define +# CRITERION_FOUND - System has Criterion +# CRITERION_INCLUDE_DIRS - The Criterion include directories +# CRITERION_LIBRARIES - The libraries needed to use Criterion + +find_package(PkgConfig) + +find_path(CRITERION_INCLUDE_DIR criterion/criterion.h + PATH_SUFFIXES criterion) + +find_library(CRITERION_LIBRARY NAMES criterion libcriterion) + +set(CRITERION_LIBRARIES ${CRITERION_LIBRARY}) +set(CRITERION_INCLUDE_DIRS ${CRITERION_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +# handle the QUIET and REQUIRED arguments and set CRITERION_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(Criterion DEFAULT_MSG + CRITERION_LIBRARY CRITERION_INCLUDE_DIR) + +mark_as_advanced(CRITERION_INCLUDE_DIR CRITERION_LIBRARY) diff --git a/fpga/doc/pictures/eonerc_logo.png b/fpga/doc/pictures/eonerc_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..81c3ad0caac94c8c04735b3033d67082d95028ea GIT binary patch literal 9348 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 literal 0 HcmV?d00001 diff --git a/fpga/doc/pictures/villas_fpga.svg b/fpga/doc/pictures/villas_fpga.svg new file mode 100644 index 000000000..509e098b1 --- /dev/null +++ b/fpga/doc/pictures/villas_fpga.svg @@ -0,0 +1,113 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/fpga/etc/fpga-simple.conf b/fpga/etc/fpga-simple.conf new file mode 100644 index 000000000..a0625dde0 --- /dev/null +++ b/fpga/etc/fpga-simple.conf @@ -0,0 +1,73 @@ +/** Example configuration file for VILLASfpga. + * + * The syntax of this file is similar to JSON. + * A detailed description of the format can be found here: + * http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files + * + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +# Some global settings are used by multiple configuration files +# and therefore defined in separate files +@include "global.conf" +@include "plugins.conf" + +fpgas = { + vc707 = { + /* Card identification */ + id = "10ee:7022"; + slot = "01:00.0"; + + intc = 0x5000; + reset = 0x2000; + do_reset = true; + + ips = { + switch_0 = { + vlnv = "xilinx.com:ip:axis_interconnect:2.1" + baseaddr = 0x0000; + numports = 3; + + paths = ( + { in = "dma_0", out = "rtds_0" }, + { in = "rtds_0", out = "dma_0" } + ) + }, + rtds_0 = { + vlnv = "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0" + baseaddr = 0x3000; + port = 0; + }, + dma_0 = { + vlnv = "xilinx.com:ip:axi_dma:7.1"; + baseaddr = 0x1000; + port = 2; + irq = 0 + } + } + } +} + +nodes = { + rtds = { + datamover = "dma_0"; + use_irqs = false; + } +} diff --git a/fpga/etc/fpga.conf b/fpga/etc/fpga.conf new file mode 100644 index 000000000..11daa06a1 --- /dev/null +++ b/fpga/etc/fpga.conf @@ -0,0 +1,168 @@ +/** Example configuration file for VILLASfpga / VILLASfpga. + * + * The syntax of this file is similar to JSON. + * A detailed description of the format can be found here: + * http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files + * + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +# Some global settings are used by multiple configuration files +# and therefore defined in separate files +@include "global.conf" +@include "plugins.conf" + +############ Dictionary of FPGAs ############ + +fpgas = { + vc707 = { + id = "10ee:7022"; # Card identification + slot = "01:00.0"; # Usually only id or slot is required + + do_reset = true; # Perform a full reset of the FPGA board + # Requires a IP core named 'axi_reset_0' + + ############ List of IP cores on FPGA ############ + # + # Every IP core can have the following settings: + # baseaddr Baseaddress as accessible from BAR0 memory region + # irq Interrupt index of MSI interrupt controller + # port Port index of AXI4-Stream interconnect + + ips = { + ### Utility IPs + axi_pcie_intc_0 = { + vlnv = "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0"; + baseaddr = 0xb000; + }, + switch_0 = { + vlnv = "xilinx.com:ip:axis_interconnect:2.1" + baseaddr = 0x5000; + num_ports = 10; + + paths = ( + // { in = "fifo_mm_s_0", out = "fifo_mm_s_0" }, # Loopback fifo_mm_s_0 + // { in = "dma_0", out = "dma_0" }, # Loopback dma_0 + // { in = "dma_1", out = "dma_1" } # Loopback dma_1 + // { in = "rtds_axis_0", out = "fifo_mm_s_0", reverse = true } # Linux <-> RTDS + // { in = "rtds_axis_0", out = "dma_0", reverse = true } # Linux (dma_0) <-> RTDS + { in = "rtds_axis_0", out = "dma_1", reverse = true } # Linux (dma_1) <-> RTDS + // { in = "rtds_axis_0", out = "fifo_mm_s_0", reverse = true } # Linux (fifo_mm_s_0) <-> RTDS + // { in = "dma_0", out = "hls_dft_0", reverse = true } # DFT <-> Linux + // { in = "rtds_axis_0", out = "hls_dft_0", reverse = true }, # DFT <-> RTDS + ) + }, + axi_reset_0 = { + vlnv = "xilinx.com:ip:axi_gpio:2.0"; + baseaddr = 0x7000; + }, + timer_0 = { + vlnv = "xilinx.com:ip:axi_timer:2.0"; + baseaddr = 0x4000; + irq = 0; + }, + + ### Data mover IPs + dma_0 = { + vlnv = "xilinx.com:ip:axi_dma:7.1"; + baseaddr = 0x3000; + port = 1; + irq = 3; /* 3 - 4 */ + }, + dma_1 = { + vlnv = "xilinx.com:ip:axi_dma:7.1"; + baseaddr = 0x2000; + port = 6; + irq = 3; /* 3 - 4 */ + }, + fifo_mm_s_0 = { + vlnv = "xilinx.com:ip:axi_fifo_mm_s:4.1"; + baseaddr = 0x6000; + baseaddr_axi4 = 0xC000; + port = 2; + irq = 2; + }, + + ### Interface IPs + rtds_axis_0 = { + vlnv = "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0"; + baseaddr = 0x8000; + port = 0; + irq = 5; /* 5 -7 */ + }, + + ### Model IPs + hls_dft_0 = { + vlnv = "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0"; + baseaddr = 0x9000; + port = 5; + irq = 1; + + period = 400; /* in samples: 20ms / 50uS = 400*/ + harmonics = [ 0, 1, 3, 5, 7 ] + decimation = 0; /* 0 = disabled */ + //harmonics = [ 0, 1, 2, 5, 22 ] + }, + axis_data_fifo_0 = { + vlnv = "xilinx.com:ip:axis_data_fifo:1.1"; + port = 3; + }, + axis_data_fifo_1 = { + vlnv = "xilinx.com:ip:axis_data_fifo:1.1"; + port = 6; + }, + } + } +} + +############ Dictionary of nodes ############ + +nodes = { + dma_0 = { + type = "fpga"; # Datamovers to VILLASfpga + datamover = "dma_0"; # Name of IP core in fpga.ips + use_irqs = false; # Use polling or MSI interrupts? + }, + dma_1 = { + type = "fpga"; + datamover = "dma_1"; + use_irqs = false; + }, + fifo_0 = { + type = "fpga"; + datamover = "fifo_mm_s_0"; + use_irqs = false; + }, + simple_circuit = { + type = "cbuilder"; + model = "simple_circuit", + timestep = 25e-6; # in seconds + parameters = [ + 1.0, # R2 = 1 Ohm + 0.001 # C2 = 1000 uF + ]; + } +} + +############ List of paths ############ + +paths = ( + { in = "dma_1", out = "simple_circuit", reverse = true } +) diff --git a/fpga/include/villas/common.h b/fpga/include/villas/common.h new file mode 100644 index 000000000..c837774e6 --- /dev/null +++ b/fpga/include/villas/common.h @@ -0,0 +1,36 @@ +/** Some common defines, enums and datastructures. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +/* Common states for most objects in VILLASfpga (paths, nodes, hooks, plugins) */ +enum state { + STATE_DESTROYED = 0, + STATE_INITIALIZED = 1, + STATE_PARSED = 2, + STATE_CHECKED = 3, + STATE_STARTED = 4, + STATE_LOADED = 4, /* alias for STATE_STARTED used by plugins */ + STATE_STOPPED = 5, + STATE_UNLOADED = 5 /* alias for STATE_STARTED used by plugins */ +}; diff --git a/fpga/include/villas/config.h b/fpga/include/villas/config.h new file mode 100644 index 000000000..f75ff7fba --- /dev/null +++ b/fpga/include/villas/config.h @@ -0,0 +1,71 @@ +/** Static server configuration + * + * This file contains some compiled-in settings. + * This settings are not part of the configuration file. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#ifndef V + #define V 2 +#endif + +/* Paths */ +#define PLUGIN_PATH PREFIX "/share/villas/node/plugins" +#define WEB_PATH PREFIX "/share/villas/node/web" +#define SYSFS_PATH "/sys" +#define PROCFS_PATH "/proc" + +/** Default number of values in a sample */ +#define DEFAULT_SAMPLELEN 64 +#define DEFAULT_QUEUELEN 1024 + +/** Number of hugepages which are requested from the the kernel. + * @see https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt */ +#define DEFAULT_NR_HUGEPAGES 100 + +/** Width of log output in characters */ +#define LOG_WIDTH 80 +#define LOG_HEIGHT 25 + +/** Socket priority */ +#define SOCKET_PRIO 7 + +/* Protocol numbers */ +#define IPPROTO_VILLAS 137 +#define ETH_P_VILLAS 0xBABE + +#define USER_AGENT "VILLASfpga (" BUILDID ")" + +/* Required kernel version */ +#define KERNEL_VERSION_MAJ 3 +#define KERNEL_VERSION_MIN 6 + +/** PCIe BAR number of VILLASfpga registers */ +#define FPGA_PCI_BAR 0 +#define FPGA_PCI_VID_XILINX 0x10ee +#define FPGA_PCI_PID_VFPGA 0x7022 + +/** AXI Bus frequency for all components + * except RTDS AXI Stream bridge which runs at RTDS_HZ (100 Mhz) */ +#define FPGA_AXI_HZ 125000000 // 125 MHz diff --git a/fpga/include/villas/fpga/card.h b/fpga/include/villas/fpga/card.h new file mode 100644 index 000000000..89d19c0c1 --- /dev/null +++ b/fpga/include/villas/fpga/card.h @@ -0,0 +1,95 @@ +/** FPGA card + * + * This class represents a FPGA device. + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +#include "common.h" +#include "kernel/pci.h" +#include "kernel/vfio.h" + +/* Forward declarations */ +struct fpga_ip; +struct vfio_container; + +struct fpga_card { + char *name; /**< The name of the FPGA card */ + + enum state state; /**< The state of this FPGA card. */ + + struct pci *pci; + struct pci_device filter; /**< Filter for PCI device. */ + + struct vfio_container *vfio_container; + struct vfio_device vfio_device; /**< VFIO device handle. */ + + int do_reset; /**< Reset VILLASfpga during startup? */ + int affinity; /**< Affinity for MSI interrupts */ + + struct list ips; /**< List of IP components on FPGA. */ + + char *map; /**< PCI BAR0 mapping for register access */ + + size_t maplen; + size_t dmalen; + + /* Some IP cores are special and referenced here */ + struct fpga_ip *intc; + struct fpga_ip *reset; + struct fpga_ip *sw; +}; + +/** Initialize FPGA card and its IP components. */ +int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc); + +/** Parse configuration of FPGA card including IP cores from config. */ +int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name); + +int fpga_card_parse_list(struct list *l, json_t *cfg); + +/** Check if the FPGA card configuration is plausible. */ +int fpga_card_check(struct fpga_card *c); + +/** Start FPGA card. */ +int fpga_card_start(struct fpga_card *c); + +/** Stop FPGA card. */ +int fpga_card_stop(struct fpga_card *c); + +/** Destroy FPGA card. */ +int fpga_card_destroy(struct fpga_card *c); + +/** Dump details of FPGA card to stdout. */ +void fpga_card_dump(struct fpga_card *c); + +/** Reset the FPGA to a known state */ +int fpga_card_reset(struct fpga_card *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/ip.h b/fpga/include/villas/fpga/ip.h new file mode 100644 index 000000000..b89fc5841 --- /dev/null +++ b/fpga/include/villas/fpga/ip.h @@ -0,0 +1,119 @@ +/** Interlectual Property component. + * + * This class represents a module within the FPGA. + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +#include "common.h" + +#include "fpga/vlnv.h" + +#include "fpga/ips/dma.h" +#include "fpga/ips/switch.h" +#include "fpga/ips/fifo.h" +#include "fpga/ips/rtds_axis.h" +#include "fpga/ips/timer.h" +#include "fpga/ips/model.h" +#include "fpga/ips/dft.h" +#include "fpga/ips/intc.h" + +enum fpga_ip_types { + FPGA_IP_TYPE_DM_DMA, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ + FPGA_IP_TYPE_DM_FIFO, /**< A datamover IP exchanges streaming data between the FPGA and the CPU. */ + FPGA_IP_TYPE_MODEL, /**< A model IP simulates a system on the FPGA. */ + FPGA_IP_TYPE_MATH, /**< A math IP performs some kind of mathematical operation on the streaming data */ + FPGA_IP_TYPE_MISC, /**< Other IP components like timer, counters, interrupt conctrollers or routing. */ + FPGA_IP_TYPE_INTERFACE /**< A interface IP connects the FPGA to another system or controller. */ +} type; + +struct fpga_ip_type { + struct fpga_vlnv vlnv; + + enum fpga_ip_types type; + + int (*init)(struct fpga_ip *c); + int (*parse)(struct fpga_ip *c, json_t *cfg); + int (*check)(struct fpga_ip *c); + int (*start)(struct fpga_ip *c); + int (*stop)(struct fpga_ip *c); + int (*destroy)(struct fpga_ip *c); + int (*reset)(struct fpga_ip *c); + void (*dump)(struct fpga_ip *c); + + size_t size; /**< Amount of memory which should be reserved for struct fpga_ip::_vd */ +}; + +struct fpga_ip { + char *name; /**< Name of the FPGA IP component. */ + struct fpga_vlnv vlnv; /**< The Vendor, Library, Name, Version tag of the FPGA IP component. */ + + enum state state; /**< The current state of the FPGA IP component. */ + + struct fpga_ip_type *_vt; /**< Vtable containing FPGA IP type function pointers. */ + void *_vd; /**< Virtual data (used by struct fpga_ip::_vt functions) */ + + uintptr_t baseaddr; /**< The baseadress of this FPGA IP component */ + uintptr_t baseaddr_axi4; /**< Used by AXI4 FIFO DM */ + + int port; /**< The port of the AXI4-Stream switch to which this FPGA IP component is connected. */ + int irq; /**< The interrupt number of the FPGA IP component. */ + + struct fpga_card *card; /**< The FPGA to which this IP instance belongs to. */ +}; + +/** Initialize IP core. */ +int fpga_ip_init(struct fpga_ip *c, struct fpga_ip_type *vt); + +/** Parse IP core configuration from configuration file */ +int fpga_ip_parse(struct fpga_ip *c, json_t *cfg, const char *name); + +/** Check configuration of IP core. */ +int fpga_ip_check(struct fpga_ip *c); + +/** Start IP core. */ +int fpga_ip_start(struct fpga_ip *c); + +/** Stop IP core. */ +int fpga_ip_stop(struct fpga_ip *c); + +/** Release dynamic memory allocated by this IP core. */ +int fpga_ip_destroy(struct fpga_ip *c); + +/** Dump details about this IP core to stdout. */ +void fpga_ip_dump(struct fpga_ip *c); + +/** Reset IP component to its initial state. */ +int fpga_ip_reset(struct fpga_ip *c); + +/** Find a registered FPGA IP core type with the given VLNV identifier. */ +struct fpga_ip_type * fpga_ip_type_lookup(const char *vstr); + + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/dft.h b/fpga/include/villas/fpga/ips/dft.h new file mode 100644 index 000000000..74835d5d4 --- /dev/null +++ b/fpga/include/villas/fpga/ips/dft.h @@ -0,0 +1,52 @@ +/** Moving window / Recursive DFT implementation based on HLS + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +/* Forward declaration */ +struct ip; + +struct dft { + XHls_dft inst; + + int period; /* in samples */ + int num_harmonics; + float *fharmonics; + int decimation; +}; + +int dft_parse(struct fpga_ip *c, json_t *cfg); + +int dft_start(struct fpga_ip *c); + +int dft_stop(struct fpga_ip *c); + +int dft_destroy(struct fpga_ip *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/dma.h b/fpga/include/villas/fpga/ips/dma.h new file mode 100644 index 000000000..7a13fb903 --- /dev/null +++ b/fpga/include/villas/fpga/ips/dma.h @@ -0,0 +1,88 @@ +/** DMA related helper functions. + * + * These functions present a simpler interface to Xilinx' DMA driver (XAxiDma_*). + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include +#include + +#include + +/* Forward declarations */ +struct fpga_ip; + +#define FPGA_DMA_BASEADDR 0x00000000 +#define FPGA_DMA_BOUNDARY 0x1000 +#define FPGA_DMA_BD_OFFSET 0xC0000000 +#define FPGA_DMA_BD_SIZE (32 << 20) // 32 MB + +#define XAXIDMA_SR_SGINCL_MASK 0x00000008 + +struct dma_mem { + char *base_virt; + char *base_phys; + size_t len; +}; + +struct dma { + XAxiDma inst; + + struct dma_mem bd; +}; + +struct ip; + +int dma_mem_split(struct dma_mem *o, struct dma_mem *a, struct dma_mem *b); + +int dma_alloc(struct fpga_ip *c, struct dma_mem *mem, size_t len, int flags); +int dma_free(struct fpga_ip *c, struct dma_mem *mem); + +int dma_write(struct fpga_ip *c, char *buf, size_t len); +int dma_read(struct fpga_ip *c, char *buf, size_t len); +int dma_read_complete(struct fpga_ip *c, char **buf, size_t *len); +int dma_write_complete(struct fpga_ip *c, char **buf, size_t *len); + +int dma_sg_write(struct fpga_ip *c, char *buf, size_t len); +int dma_sg_read(struct fpga_ip *c, char *buf, size_t len); + +int dma_sg_write_complete(struct fpga_ip *c, char **buf, size_t *len); +int dma_sg_read_complete(struct fpga_ip *c, char **buf, size_t *len); + +int dma_simple_read(struct fpga_ip *c, char *buf, size_t len); +int dma_simple_write(struct fpga_ip *c, char *buf, size_t len); + +int dma_simple_read_complete(struct fpga_ip *c, char **buf, size_t *len); +int dma_simple_write_complete(struct fpga_ip *c, char **buf, size_t *len); + +int dma_ping_pong(struct fpga_ip *c, char *src, char *dst, size_t len); + +int dma_start(struct fpga_ip *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/fifo.h b/fpga/include/villas/fpga/ips/fifo.h new file mode 100644 index 000000000..4724c1ac5 --- /dev/null +++ b/fpga/include/villas/fpga/ips/fifo.h @@ -0,0 +1,52 @@ +/** FIFO related helper functions + * + * These functions present a simpler interface to Xilinx' FIFO driver (XLlFifo_*) + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +#include +#include + +struct fifo { + XLlFifo inst; + + uint32_t baseaddr_axi4; +}; + +/* Forward declarations */ +struct ip; + +int fifo_start(struct fpga_ip *c); + +ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len); + +ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/intc.h b/fpga/include/villas/fpga/ips/intc.h new file mode 100644 index 000000000..bb7d44739 --- /dev/null +++ b/fpga/include/villas/fpga/ips/intc.h @@ -0,0 +1,56 @@ +/** AXI-PCIe Interrupt controller + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +enum intc_flags { + INTC_ENABLED = (1 << 0), + INTC_POLLING = (1 << 1) +}; + +struct intc { + int num_irqs; /**< Number of available MSI vectors */ + + int efds[32]; /**< Event FDs */ + int nos[32]; /**< Interrupt numbers from /proc/interrupts */ + + int flags[32]; /**< Mask of intc_flags */ +}; + +int intc_init(struct fpga_ip *c); + +int intc_destroy(struct fpga_ip *c); + +int intc_enable(struct fpga_ip *c, uint32_t mask, int poll); + +int intc_disable(struct fpga_ip *c, uint32_t mask); + +uint64_t intc_wait(struct fpga_ip *c, int irq); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/model.h b/fpga/include/villas/fpga/ips/model.h new file mode 100644 index 000000000..9d7f6b555 --- /dev/null +++ b/fpga/include/villas/fpga/ips/model.h @@ -0,0 +1,147 @@ +/** Interface to Xilinx System Generator Models via PCIe + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include "list.h" + +#define XSG_MAPLEN 0x1000 +#define XSG_MAGIC 0xDEADBABE + +/* Forward declaration */ +struct ip; + +enum model_type { + MODEL_TYPE_HLS, + MODEL_TYPE_XSG +}; + +enum model_xsg_block_type { + XSG_BLOCK_GATEWAY_IN = 0x1000, + XSG_BLOCK_GATEWAY_OUT = 0x1001, + XSG_BLOCK_INFO = 0x2000 +}; + +enum model_parameter_type { + MODEL_PARAMETER_TYPE_UFIX, + MODEL_PARAMETER_TYPE_FIX, + MODEL_PARAMETER_TYPE_FLOAT, + MODEL_PARAMETER_TYPE_BOOLEAN +}; + +enum model_parameter_direction { + MODEL_PARAMETER_IN, + MODEL_PARAMETER_OUT, + MODEL_PARAMETER_INOUT +}; + +union model_parameter_value { + uint32_t ufix; + int32_t fix; + float flt; + bool bol; +}; + +struct xsg_model { + uint32_t *map; + ssize_t maplen; +}; + +struct hls_model { + +}; + +struct model { + enum model_type type; /**< Either HLS or XSG model */ + + struct list parameters; /**< List of model parameters. */ + struct list infos; /**< A list of key / value pairs with model details */ + + union { + struct xsg_model xsg; /**< XSG specific model data */ + struct hls_model hls; /**< HLS specific model data */ + }; +}; + +struct model_info { + char *field; + char *value; +}; + +struct model_parameter { + char *name; /**< Name of the parameter */ + + enum model_parameter_direction direction; /**< Read / Write / Read-write? */ + enum model_parameter_type type; /**< Data type. Integers are represented by MODEL_GW_TYPE_(U)FIX with model_gw::binpt == 0 */ + + int binpt; /**< Binary point for type == MODEL_GW_TYPE_(U)FIX */ + uintptr_t offset; /**< Register offset to model::baseaddress */ + + union model_parameter_value default_value; + + struct fpga_ip *ip; /**< A pointer to the model structure to which this parameters belongs to. */ +}; + +/** Initialize a model */ +int model_init(struct fpga_ip *c); + +/** Parse model */ +int model_parse(struct fpga_ip *c, json_t *cfg); + +/** Destroy a model */ +int model_destroy(struct fpga_ip *c); + +/** Print detailed information about the model to the screen. */ +void model_dump(struct fpga_ip *c); + +/** Add a new parameter to the model */ +void model_parameter_add(struct fpga_ip *c, const char *name, enum model_parameter_direction dir, enum model_parameter_type type); + +/** Remove an existing parameter by its name */ +int model_parameter_remove(struct fpga_ip *c, const char *name); + +/** Read a model parameter. + * + * Note: the data type of the register is taken into account. + * All datatypes are converted to double. + */ +int model_parameter_read(struct model_parameter *p, double *v); + +/** Update a model parameter. + * + * Note: the data type of the register is taken into account. + * The double argument will be converted to the respective data type of the + * GatewayIn/Out block. + */ +int model_parameter_write(struct model_parameter *p, double v); + +int model_parameter_update(struct model_parameter *p, struct model_parameter *u); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/rtds_axis.h b/fpga/include/villas/fpga/ips/rtds_axis.h new file mode 100644 index 000000000..52f1f49d4 --- /dev/null +++ b/fpga/include/villas/fpga/ips/rtds_axis.h @@ -0,0 +1,62 @@ +/** Driver for AXI Stream wrapper around RTDS_InterfaceModule (rtds_axis ) + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +/* Forward declarations */ +struct ip; + +#define RTDS_HZ 100000000 // 100 MHz + +#define RTDS_AXIS_MAX_TX 64 /**< The amount of values which is supported by the vfpga card */ +#define RTDS_AXIS_MAX_RX 64 /**< The amount of values which is supported by the vfpga card */ + +/* Register offsets */ +#define RTDS_AXIS_SR_OFFSET 0x00 /**< Status Register (read-only). See RTDS_AXIS_SR_* constant. */ +#define RTDS_AXIS_CR_OFFSET 0x04 /**< Control Register (read/write) */ +#define RTDS_AXIS_TSCNT_LOW_OFFSET 0x08 /**< Lower 32 bits of timestep counter (read-only). */ +#define RTDS_AXIS_TSCNT_HIGH_OFFSET 0x0C /**< Higher 32 bits of timestep counter (read-only). */ +#define RTDS_AXIS_TS_PERIOD_OFFSET 0x10 /**< Period in clock cycles of previous timestep (read-only). */ +#define RTDS_AXIS_COALESC_OFFSET 0x14 /**< IRQ Coalescing register (read/write). */ +#define RTDS_AXIS_VERSION_OFFSET 0x18 /**< 16 bit version field passed back to the rack for version reporting (visible from “status” command, read/write). */ +#define RTDS_AXIS_MRATE 0x1C /**< Multi-rate register */ + +/* Status register bits */ +#define RTDS_AXIS_SR_CARDDETECTED (1 << 0)/**< ‘1’ when RTDS software has detected and configured card. */ +#define RTDS_AXIS_SR_LINKUP (1 << 1)/**< ‘1’ when RTDS communication link has been negotiated. */ +#define RTDS_AXIS_SR_TX_FULL (1 << 2)/**< Tx buffer is full, writes that happen when UserTxFull=’1’ will be dropped (Throttling / buffering is performed by hardware). */ +#define RTDS_AXIS_SR_TX_INPROGRESS (1 << 3)/**< Indicates when data is being put on link. */ +#define RTDS_AXIS_SR_CASE_RUNNING (1 << 4)/**< There is currently a simulation running. */ + +/* Control register bits */ +#define RTDS_AXIS_CR_DISABLE_LINK 0 /**< Disable SFP TX when set */ + +void rtds_axis_dump(struct fpga_ip *c); + +double rtds_axis_dt(struct fpga_ip *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/switch.h b/fpga/include/villas/fpga/ips/switch.h new file mode 100644 index 000000000..7fcf08fa6 --- /dev/null +++ b/fpga/include/villas/fpga/ips/switch.h @@ -0,0 +1,67 @@ +/** AXI Stream interconnect related helper functions + * + * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include +#include + +#include "list.h" + +/* Forward declarations */ +struct ip; + +struct sw_path { + const char *in; + const char *out; +}; + +struct sw { + XAxis_Switch inst; + + int num_ports; + struct list paths; +}; + +struct ip; + +int switch_start(struct fpga_ip *c); + +/** Initialize paths which have been parsed by switch_parse() */ +int switch_init_paths(struct fpga_ip *c); + +int switch_destroy(struct fpga_ip *c); + +int switch_parse(struct fpga_ip *c, json_t *cfg); + +int switch_connect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si); + +int switch_disconnect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si); + +/** @} */ diff --git a/fpga/include/villas/fpga/ips/timer.h b/fpga/include/villas/fpga/ips/timer.h new file mode 100644 index 000000000..31fac741e --- /dev/null +++ b/fpga/include/villas/fpga/ips/timer.h @@ -0,0 +1,43 @@ +/** Timer related helper functions + * + * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#pragma once + +#include + +/* Forward declarations */ +struct fpga_ip; + +struct timer { + XTmrCtr inst; +}; + +int timer_start(struct fpga_ip *c); + +/** @} */ diff --git a/fpga/include/villas/fpga/vlnv.h b/fpga/include/villas/fpga/vlnv.h new file mode 100644 index 000000000..d24385271 --- /dev/null +++ b/fpga/include/villas/fpga/vlnv.h @@ -0,0 +1,53 @@ +/** Vendor, Library, Name, Version (VLNV) tag. + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga VILLASfpga + * @{ + */ + +#ifndef _FPGA_VLNV_H_ +#define _FPGA_VLNV_H_ + +/* Forward declarations */ +struct list; + +struct fpga_vlnv { + char *vendor; + char *library; + char *name; + char *version; +}; + +/** Return the first IP block in list \p l which matches the VLNV */ +struct fpga_ip * fpga_vlnv_lookup(struct list *l, struct fpga_vlnv *v); + +/** Check if IP block \p c matched VLNV. */ +int fpga_vlnv_cmp(struct fpga_vlnv *a, struct fpga_vlnv *b); + +/** Tokenizes VLNV \p vlnv and stores it into \p c */ +int fpga_vlnv_parse(struct fpga_vlnv *c, const char *vlnv); + +/** Release memory allocated by fpga_vlnv_parse(). */ +int fpga_vlnv_destroy(struct fpga_vlnv *v); + +#endif /** _FPGA_VLNV_H_ @} */ diff --git a/fpga/include/villas/kernel/kernel.h b/fpga/include/villas/kernel/kernel.h new file mode 100644 index 000000000..9a7f5eccb --- /dev/null +++ b/fpga/include/villas/kernel/kernel.h @@ -0,0 +1,90 @@ +/** Linux kernel related functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +/** @addtogroup fpga Kernel @{ */ + +#pragma once + +#include +#include + +/* Forward declarations */ +struct version; + +//#include + +/** Check if current process has capability \p cap. + * + * @retval 0 If capabilty is present. + * @retval <0 If capability is not present. + */ +//int kernel_check_cap(cap_value_t cap); + +/** Get number of reserved hugepages. */ +int kernel_get_nr_hugepages(); + +/** Set number of reserved hugepages. */ +int kernel_set_nr_hugepages(int nr); + +/** Get kernel cmdline parameter + * + * See https://www.kernel.org/doc/Documentation/kernel-parameters.txt + * + * @param param The cmdline parameter to look for. + * @param buf The string buffer to which the parameter value will be copied to. + * @param len The length of the buffer \p value + * @retval 0 Parameter \p key was found and value was copied to \p value + * @reval <>0 Kernel was not booted with parameter \p key + */ +int kernel_get_cmdline_param(const char *param, char *buf, size_t len); + +/** Get the version of the kernel. */ +int kernel_get_version(struct version *v); + +/** Checks if a kernel module is loaded + * + * @param module the name of the module + * @retval 0 Module is loaded. + * @reval <>0 Module is not loaded. + */ +int kernel_module_loaded(const char *module); + +/** Load kernel module via modprobe */ +int kernel_module_load(const char *module); + +/** Set parameter of loaded kernel module */ +int kernel_module_set_param(const char *module, const char *param, const char *value); + +/** Get cacheline size in bytes */ +int kernel_get_cacheline_size(); + +/** Get the size of a standard page in bytes. */ +int kernel_get_page_size(); + +/** Get the size of a huge page in bytes. */ +int kernel_get_hugepage_size(); + +/** Set SMP affinity of IRQ */ +int kernel_irq_setaffinity(unsigned irq, uintmax_t new, uintmax_t *old); + +/** @} */ diff --git a/fpga/include/villas/kernel/pci.h b/fpga/include/villas/kernel/pci.h new file mode 100644 index 000000000..821d0d6df --- /dev/null +++ b/fpga/include/villas/kernel/pci.h @@ -0,0 +1,62 @@ +/** Linux PCI helpers + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + **********************************************************************************/ + +/** @addtogroup fpga Kernel @{ */ + +#pragma once + +#include "list.h" + +#define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) +#define PCI_FUNC(devfn) ((devfn) & 0x07) + +struct pci_device { + struct { + int vendor; + int device; + int class; + } id; + + struct { + int domain; + int bus; + int device; + int function; + } slot; /**< Bus, Device, Function (BDF) */ +}; + +struct pci { + struct list devices; /**< List of available PCI devices in the system (struct pci_device) */ +}; + +/** Initialize Linux PCI handle. + * + * This search for all available PCI devices under /sys/bus/pci + * + * @retval 0 Success. Everything went well. + * @retval <0 Error. Something went wrong. + */ +int pci_init(struct pci *p); + +/** Destroy handle. */ +int pci_destroy(struct pci *p); + +int pci_device_parse_slot(struct pci_device *f, const char *str, const char **error); + +int pci_device_parse_id(struct pci_device *f, const char *str, const char **error); + +int pci_device_compare(const struct pci_device *d, const struct pci_device *f); + +struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *filter); + +/** Bind a new LKM to the PCI device */ +int pci_attach_driver(struct pci_device *d, const char *driver); + +/** Return the IOMMU group of this PCI device or -1 if the device is not in a group. */ +int pci_get_iommu_group(struct pci_device *d); + +/** @} */ diff --git a/fpga/include/villas/kernel/vfio.h b/fpga/include/villas/kernel/vfio.h new file mode 100644 index 000000000..bb6f12329 --- /dev/null +++ b/fpga/include/villas/kernel/vfio.h @@ -0,0 +1,112 @@ +/** Virtual Function IO wrapper around kernel API + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + *********************************************************************************/ + +/** @addtogroup fpga Kernel @{ */ + +#pragma once + +#include +#include +#include + +#include +#include + +#include "list.h" + +#define VFIO_DEV(x) "/dev/vfio/" x + +/* Forward declarations */ +struct pci_device; + +struct vfio_group { + int fd; /**< VFIO group file descriptor */ + int index; /**< Index of the IOMMU group as listed under /sys/kernel/iommu_groups/ */ + + struct vfio_group_status status; /**< Status of group */ + + struct list devices; + + struct vfio_container *container; /**< The VFIO container to which this group is belonging */ +}; + +struct vfio_device { + char *name; /**< Name of the device as listed under /sys/kernel/iommu_groups/[vfio_group::index]/devices/ */ + int fd; /**< VFIO device file descriptor */ + + struct vfio_device_info info; + struct vfio_irq_info *irqs; + struct vfio_region_info *regions; + + void **mappings; + + struct pci_device *pci_device; /**< libpci handle of the device */ + struct vfio_group *group; /**< The VFIO group this device belongs to */ +}; + +struct vfio_container { + int fd; + int version; + int extensions; + + uint64_t iova_next; /**< Next free IOVA address */ + + struct list groups; +}; + +/** Initialize a new VFIO container. */ +int vfio_init(struct vfio_container *c); + +/** Initialize a VFIO group and attach it to an existing VFIO container. */ +int vfio_group_attach(struct vfio_group *g, struct vfio_container *c, int index); + +/** Initialize a VFIO device, lookup the VFIO group it belongs to, create the group if not already existing. */ +int vfio_device_attach(struct vfio_device *d, struct vfio_container *c, const char *name, int index); + +/** Initialie a VFIO-PCI device (uses vfio_device_attach() internally) */ +int vfio_pci_attach(struct vfio_device *d, struct vfio_container *c, struct pci_device *pdev); + +/** Hot resets a VFIO-PCI device */ +int vfio_pci_reset(struct vfio_device *d); + +int vfio_pci_msi_init(struct vfio_device *d, int efds[32]); + +int vfio_pci_msi_deinit(struct vfio_device *d, int efds[32]); + +int vfio_pci_msi_find(struct vfio_device *d, int nos[32]); + +/** Enable memory accesses and bus mastering for PCI device */ +int vfio_pci_enable(struct vfio_device *d); + +/** Reset a VFIO device */ +int vfio_device_reset(struct vfio_device *d); + +/** Release memory and close container */ +int vfio_destroy(struct vfio_container *c); + +/** Release memory of group */ +int vfio_group_destroy(struct vfio_group *g); + +/** Release memory of device */ +int vfio_device_destroy(struct vfio_device *g); + +/** Print a dump of all attached groups and their devices including regions and IRQs */ +void vfio_dump(struct vfio_container *c); + +/** Map a device memory region to the application address space (e.g. PCI BARs) */ +void * vfio_map_region(struct vfio_device *d, int idx); + +/** Map VM to an IOVA, which is accessible by devices in the container */ +int vfio_map_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len); + +/** Unmap DMA memory */ +int vfio_unmap_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len); + +/** munmap() a region which has been mapped by vfio_map_region() */ +int vfio_unmap_region(struct vfio_device *d, int idx); + +/** @} */ diff --git a/fpga/include/villas/list.h b/fpga/include/villas/list.h new file mode 100644 index 000000000..f6ba29dae --- /dev/null +++ b/fpga/include/villas/list.h @@ -0,0 +1,112 @@ +/** A generic list implementation. + * + * This is a generic implementation of a list which can store void pointers to + * arbitrary data. The data itself is not stored or managed by the list. + * + * Internally, an array of pointers is used to store the pointers. + * If needed, this array will grow by realloc(). + * + * @file + * @author Steffen Vogel + * @copyright 20177, Institute for Automation of Complex Power Systems, EONERC + *********************************************************************************/ + +#pragma once + +#include +#include + +#include "common.h" + +#define LIST_CHUNKSIZE 16 + +/** Static list initialization */ +#define LIST_INIT() { \ + .array = NULL, \ + .length = 0, \ + .capacity = 0, \ + .lock = PTHREAD_MUTEX_INITIALIZER, \ + .state = STATE_INITIALIZED \ +} + +#define LIST_INIT_STATIC(l) \ +__attribute__((constructor(105))) static void UNIQUE(__ctor)() {\ + if ((l)->state == STATE_DESTROYED) \ + list_init(l); \ +} \ +__attribute__((destructor(105))) static void UNIQUE(__dtor)() { \ + list_destroy(l, NULL, false); \ +} + +#define list_length(list) ((list)->length) +#define list_at_safe(list, index) ((list)->length > index ? (list)->array[index] : NULL) +#define list_at(list, index) ((list)->array[index]) + +#define list_first(list) list_at(list, 0) +#define list_last(list) list_at(list, (list)->length-1) + +/** Callback to destroy list elements. + * + * @param data A pointer to the data which should be freed. + */ +typedef int (*dtor_cb_t)(void *); + +/** Callback to search or sort a list. */ +typedef int (*cmp_cb_t)(const void *, const void *); + +/* The list data structure. */ +struct list { + void **array; /**< Array of pointers to list elements */ + size_t capacity; /**< Size of list::array in elements */ + size_t length; /**< Number of elements of list::array which are in use */ + pthread_mutex_t lock; /**< A mutex to allow thread-safe accesses */ + enum state state; /**< The state of this list. */ +}; + +/** Initialize a list. + * + * @param l A pointer to the list data structure. + */ +int list_init(struct list *l); + +/** Destroy a list and call destructors for all list elements + * + * @param free free() all list members during when calling list_destroy() + * @param dtor A function pointer to a desctructor which will be called for every list item when the list is destroyed. + * @param l A pointer to the list data structure. + */ +int list_destroy(struct list *l, dtor_cb_t dtor, bool free); + +/** Append an element to the end of the list */ +void list_push(struct list *l, void *p); + +/** Remove all occurences of a list item */ +void list_remove(struct list *l, void *p); + +/** Return the first list element which is identified by a string in its first member variable. + * + * List elements are pointers to structures of the following form: + * + * struct obj { + * char *name; + * // more members + * } + * + * @see Only possible because of §1424 of http://c0x.coding-guidelines.com/6.7.2.1.html + */ +void * list_lookup(struct list *l, const char *name); + +/** Return the first element of the list for which cmp returns zero */ +void * list_search(struct list *l, cmp_cb_t cmp, void *ctx); + +/** Returns the number of occurences for which cmp returns zero when called on all list elements. */ +int list_count(struct list *l, cmp_cb_t cmp, void *ctx); + +/** Return 0 if list contains pointer p */ +int list_contains(struct list *l, void *p); + +/** Sort the list using the quicksort algorithm of libc */ +void list_sort(struct list *l, cmp_cb_t cmp); + +/** Set single element in list */ +int list_set(struct list *l, int index, void *value); diff --git a/fpga/include/villas/log.h b/fpga/include/villas/log.h new file mode 100644 index 000000000..6d8771ee8 --- /dev/null +++ b/fpga/include/villas/log.h @@ -0,0 +1,194 @@ +/** Logging and debugging routines + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#include "common.h" +#include "log_config.h" + +#ifdef __GNUC__ + #define INDENT int __attribute__ ((__cleanup__(log_outdent), unused)) _old_indent = log_indent(1); + #define NOINDENT int __attribute__ ((__cleanup__(log_outdent), unused)) _old_indent = log_noindent(); +#else + #define INDENT ; + #define NOINDENT ; +#endif + +/* The log level which is passed as first argument to print() */ +#define LOG_LVL_DEBUG CLR_GRY("Debug") +#define LOG_LVL_INFO CLR_WHT("Info ") +#define LOG_LVL_WARN CLR_YEL("Warn ") +#define LOG_LVL_ERROR CLR_RED("Error") +#define LOG_LVL_STATS CLR_MAG("Stats") + +/** Debug facilities. + * + * To be or-ed with the debug level + */ +enum log_facilities { + LOG_POOL = (1L << 8), + LOG_QUEUE = (1L << 9), + LOG_CONFIG = (1L << 10), + LOG_HOOK = (1L << 11), + LOG_PATH = (1L << 12), + LOG_NODE = (1L << 13), + LOG_MEM = (1L << 14), + LOG_WEB = (1L << 15), + LOG_API = (1L << 16), + LOG_LOG = (1L << 17), + LOG_VFIO = (1L << 18), + LOG_PCI = (1L << 19), + LOG_XIL = (1L << 20), + LOG_TC = (1L << 21), + LOG_IF = (1L << 22), + LOG_ADVIO = (1L << 23), + + /* Node-types */ + LOG_SOCKET = (1L << 24), + LOG_FILE = (1L << 25), + LOG_FPGA = (1L << 26), + LOG_NGSI = (1L << 27), + LOG_WEBSOCKET = (1L << 28), + LOG_OPAL = (1L << 30), + + /* Classes */ + LOG_NODES = LOG_NODE | LOG_SOCKET | LOG_FILE | LOG_FPGA | LOG_NGSI | LOG_WEBSOCKET | LOG_OPAL, + LOG_KERNEL = LOG_VFIO | LOG_PCI | LOG_TC | LOG_IF, + LOG_ALL = ~0xFF +}; + +struct log { + enum state state; + + struct timespec epoch; /**< A global clock used to prefix the log messages. */ + + struct winsize window; /**< Size of the terminal window. */ + int width; /**< The real usable log output width which fits into one line. */ + + /** Debug level used by the debug() macro. + * It defaults to V (defined by the Makefile) and can be + * overwritten by the 'debug' setting in the configuration file. */ + int level; + long facilities; /**< Debug facilities used by the debug() macro. */ + const char *path; /**< Path of the log file. */ + char *prefix; /**< Prefix each line with this string. */ + int syslog; /**< Whether or not to log to syslogd. */ + + FILE *file; /**< Send all log output to this file / stdout / stderr. */ +}; + +/** The global log instance. */ +struct log *global_log; +struct log default_log; + +/** Initialize log object */ +int log_init(struct log *l, int level, long faciltities); + +int log_start(struct log *l); + +int log_stop(struct log *l); + +/** Destroy log object */ +int log_destroy(struct log *l); + +/** Change log indention for current thread. + * + * The argument level can be negative! + */ +int log_indent(int levels); + +/** Disable log indention of current thread. */ +int log_noindent(); + +/** A helper function the restore the previous log indention level. + * + * This function is usually called by a __cleanup__ handler (GCC C Extension). + * See INDENT macro. + */ +void log_outdent(int *); + +/** Set logging facilities based on expression. + * + * Currently we support two types of expressions: + * 1. A comma seperated list of logging facilities + * 2. A comma seperated list of logging facilities which is prefixes with an exclamation mark '!' + * + * The first case enables only faciltities which are in the list. + * The second case enables all faciltities with exception of those which are in the list. + * + * @param expression The expression + * @return The new facilties mask (see enum log_faciltities) + */ +int log_set_facility_expression(struct log *l, const char *expression); + +/** Logs variadic messages to stdout. + * + * @param lvl The log level + * @param fmt The format string (printf alike) + */ +void log_print(struct log *l, const char *lvl, const char *fmt, ...) + __attribute__ ((format(printf, 3, 4))); + +/** Logs variadic messages to stdout. + * + * @param lvl The log level + * @param fmt The format string (printf alike) + * @param va The variadic argument list (see stdarg.h) + */ +void log_vprint(struct log *l, const char *lvl, const char *fmt, va_list va); + +/** Printf alike debug message with level. */ +void debug(long lvl, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); + +/** Printf alike info message. */ +void info(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +/** Printf alike warning message. */ +void warn(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +/** Printf alike statistics message. */ +void stats(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +/** Print error and exit. */ +void error(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +/** Print error and strerror(errno). */ +void serror(const char *fmt, ...) + __attribute__ ((format(printf, 1, 2))); + +#ifdef __cplusplus +} +#endif diff --git a/fpga/include/villas/log_config.h b/fpga/include/villas/log_config.h new file mode 100644 index 000000000..e2a71333f --- /dev/null +++ b/fpga/include/villas/log_config.h @@ -0,0 +1,37 @@ +/** Logging routines that depend on jansson. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +struct log; + +#include + +#include "log.h" + +/** Parse logging configuration. */ +int log_parse(struct log *l, json_t *cfg); + +/** Print configuration error and exit. */ +void jerror(json_error_t *err, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); diff --git a/fpga/include/villas/plugin.h b/fpga/include/villas/plugin.h new file mode 100644 index 000000000..8c915b573 --- /dev/null +++ b/fpga/include/villas/plugin.h @@ -0,0 +1,82 @@ +/** Loadable / plugin support. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include "utils.h" +#include "fpga/ip.h" + +/** @todo This is ugly as hell and broken on OS X / Clang anyway. */ +#define REGISTER_PLUGIN(p) \ +__attribute__((constructor(110))) static void UNIQUE(__ctor)() {\ + if (plugins.state == STATE_DESTROYED) \ + list_init(&plugins); \ + list_push(&plugins, p); \ +} \ +__attribute__((destructor(110))) static void UNIQUE(__dtor)() { \ + if (plugins.state != STATE_DESTROYED) \ + list_remove(&plugins, p); \ +} + +extern struct list plugins; + +enum plugin_type { + PLUGIN_TYPE_FPGA_IP, +}; + +struct plugin { + const char *name; + const char *description; + void *handle; + char *path; + + enum plugin_type type; + + enum state state; + + int (*load)(struct plugin *p); + int (*unload)(struct plugin *p); + + struct fpga_ip_type ip; +}; + +/** Return a pointer to the plugin structure */ +#define plugin(vt) ((struct plugin *) ((char *) (vt) - offsetof(struct plugin, api))) + +#define plugin_name(vt) plugin(vt)->name +#define plugin_description(vt) plugin(vt)->description + +int plugin_init(struct plugin *p); + +int plugin_destroy(struct plugin *p); + +int plugin_parse(struct plugin *p, json_t *cfg); + +int plugin_load(struct plugin *p); + +int plugin_unload(struct plugin *p); + +void plugin_dump(enum plugin_type type); + +/** Find registered and loaded plugin with given name and type. */ +struct plugin * plugin_lookup(enum plugin_type type, const char *name); diff --git a/fpga/include/villas/utils.h b/fpga/include/villas/utils.h new file mode 100644 index 000000000..b552cc52b --- /dev/null +++ b/fpga/include/villas/utils.h @@ -0,0 +1,276 @@ +/** Various helper functions. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "log.h" + +#ifdef __GNUC__ + #define LIKELY(x) __builtin_expect((x),1) + #define UNLIKELY(x) __builtin_expect((x),0) +#else + #define LIKELY(x) (x) + #define UNLIKELY(x) (x) +#endif + +/* Some color escape codes for pretty log messages */ +#define CLR(clr, str) "\e[" XSTR(clr) "m" str "\e[0m" +#define CLR_GRY(str) CLR(30, str) /**< Print str in gray */ +#define CLR_RED(str) CLR(31, str) /**< Print str in red */ +#define CLR_GRN(str) CLR(32, str) /**< Print str in green */ +#define CLR_YEL(str) CLR(33, str) /**< Print str in yellow */ +#define CLR_BLU(str) CLR(34, str) /**< Print str in blue */ +#define CLR_MAG(str) CLR(35, str) /**< Print str in magenta */ +#define CLR_CYN(str) CLR(36, str) /**< Print str in cyan */ +#define CLR_WHT(str) CLR(37, str) /**< Print str in white */ +#define CLR_BLD(str) CLR( 1, str) /**< Print str in bold */ + +/* Alternate character set + * + * The suffixed of the BOX_ macro a constructed by + * combining the following letters in the written order: + * - U for a line facing upwards + * - D for a line facing downwards + * - L for a line facing leftwards + * - R for a line facing rightwards + * + * E.g. a cross can be constructed by combining all line fragments: + * BOX_UDLR + */ +#define BOX(chr) "\e(0" chr "\e(B" +#define BOX_LR BOX("\x71") /**< Boxdrawing: ─ */ +#define BOX_UD BOX("\x78") /**< Boxdrawing: │ */ +#define BOX_UDR BOX("\x74") /**< Boxdrawing: ├ */ +#define BOX_UDLR BOX("\x6E") /**< Boxdrawing: ┼ */ +#define BOX_UDL BOX("\x75") /**< Boxdrawing: ┤ */ +#define BOX_ULR BOX("\x76") /**< Boxdrawing: ┴ */ +#define BOX_UL BOX("\x6A") /**< Boxdrawing: ┘ */ +#define BOX_DLR BOX("\x77") /**< Boxdrawing: ┘ */ +#define BOX_DL BOX("\x6B") /**< Boxdrawing: ┘ */ + +/* CPP stringification */ +#define XSTR(x) STR(x) +#define STR(x) #x + +#define CONCAT_DETAIL(x, y) x##y +#define CONCAT(x, y) CONCAT_DETAIL(x, y) +#define UNIQUE(x) CONCAT(x, __COUNTER__) + +#define ALIGN(x, a) ALIGN_MASK(x, (uintptr_t) (a) - 1) +#define ALIGN_MASK(x, m) (((uintptr_t) (x) + (m)) & ~(m)) +#define IS_ALIGNED(x, a) (ALIGN(x, a) == (uintptr_t) x) + +#define SWAP(x,y) do { \ + __auto_type _x = x; \ + __auto_type _y = y; \ + x = _y; \ + y = _x; \ +} while(0) + +/** Round-up integer division */ +#define CEIL(x, y) (((x) + (y) - 1) / (y)) + +/** Get nearest up-rounded power of 2 */ +#define LOG2_CEIL(x) (1 << (log2i((x) - 1) + 1)) + +/** Check if the number is a power of 2 */ +#define IS_POW2(x) (((x) != 0) && !((x) & ((x) - 1))) + +/** Calculate the number of elements in an array. */ +#define ARRAY_LEN(a) ( sizeof (a) / sizeof (a)[0] ) + +/* Return the bigger value */ +#define MAX(a, b) ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) + +/* Return the smaller value */ +#define MIN(a, b) ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +#ifndef offsetof + #define offsetof(type, member) __builtin_offsetof(type, member) +#endif + +#ifndef container_of + #define container_of(ptr, type, member) ({ const typeof( ((type *) 0)->member ) *__mptr = (ptr); \ + (type *) ( (char *) __mptr - offsetof(type, member) ); \ + }) +#endif + +#define BITS_PER_LONGLONG (sizeof(long long) * 8) + +/* Some helper macros */ +#define BITMASK(h, l) (((~0ULL) << (l)) & (~0ULL >> (BITS_PER_LONGLONG - 1 - (h)))) +#define BIT(nr) (1UL << (nr)) + +/* Forward declarations */ +struct timespec; + +/** Print copyright message to stdout. */ +void print_copyright(); + +/** Print version to stdout. */ +void print_version(); + +/** Normal random variate generator using the Box-Muller method + * + * @param m Mean + * @param s Standard deviation + * @return Normal variate random variable (Gaussian) + */ +double box_muller(float m, float s); + +/** Double precission uniform random variable */ +double randf(); + +/** Concat formatted string to an existing string. + * + * This function uses realloc() to resize the destination. + * Please make sure to only on dynamic allocated destionations!!! + * + * @param dest A pointer to a malloc() allocated memory region + * @param fmt A format string like for printf() + * @param ... Optional parameters like for printf() + * @retval The the new value of the dest buffer. + */ +char * strcatf(char **dest, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); + +/** Variadic version of strcatf() */ +char * vstrcatf(char **dest, const char *fmt, va_list va) + __attribute__ ((format(printf, 2, 0))); + +/** Format string like strcatf() just starting with empty string */ +#define strf(fmt, ...) strcatf(&(char *) { NULL }, fmt, ##__VA_ARGS__) +#define vstrf(fmt, va) vstrcatf(&(char *) { NULL }, fmt, va) + +#ifdef __linux__ +/** Convert integer to cpu_set_t. + * + * @param set An integer number which is used as the mask + * @param cset A pointer to the cpu_set_t datastructure + */ +void cpuset_from_integer(uintmax_t set, cpu_set_t *cset); + +/** Convert cpu_set_t to an integer. */ +void cpuset_to_integer(cpu_set_t *cset, uintmax_t *set); + +/** Parses string with list of CPU ranges. + * + * From: https://github.com/mmalecki/util-linux/blob/master/lib/cpuset.c + * + * @retval 0 On success. + * @retval 1 On error. + * @retval 2 If fail is set and a cpu number passed in the list doesn't fit + * into the cpu_set. If fail is not set cpu numbers that do not fit are + * ignored and 0 is returned instead. + */ +int cpulist_parse(const char *str, cpu_set_t *set, int fail); + +/** Returns human readable representation of the cpuset. + * + * From: https://github.com/mmalecki/util-linux/blob/master/lib/cpuset.c + * + * The output format is a list of CPUs with ranges (for example, "0,1,3-9"). + */ +char * cpulist_create(char *str, size_t len, cpu_set_t *set); +#endif + +/** Allocate and initialize memory. */ +void * alloc(size_t bytes); + +/** Allocate and copy memory. */ +void * memdup(const void *src, size_t bytes); + +/** Call quit() in the main thread. */ +void die(); + +/** Used by version_parse(), version_compare() */ +struct version { + int major; + int minor; +}; + +/** Compare two versions. */ +int version_cmp(struct version *a, struct version *b); + +/** Parse a dotted version string. */ +int version_parse(const char *s, struct version *v); + +/** Check assertion and exit if failed. */ +#ifndef assert + #define assert(exp) do { \ + if (!EXPECT(exp, 0)) \ + error("Assertion failed: '%s' in %s(), %s:%d", \ + XSTR(exp), __FUNCTION__, __BASE_FILE__, __LINE__); \ + } while (0) +#endif + +/** Fill buffer with random data */ +ssize_t read_random(char *buf, size_t len); + +/** Get CPU timestep counter */ +__attribute__((always_inline)) static inline uint64_t rdtsc() +{ + uint64_t tsc; + + __asm__ ("rdtsc;" + "shl $32, %%rdx;" + "or %%rdx,%%rax" + : "=a" (tsc) + : + : "%rcx", "%rdx", "memory"); + + return tsc; +} + +/** Get log2 of long long integers */ +static inline int log2i(long long x) { + if (x == 0) + return 1; + + return sizeof(x) * 8 - __builtin_clzll(x) - 1; +} + +/** Sleep with rdtsc */ +void rdtsc_sleep(uint64_t nanosecs, uint64_t start); + +/** Register a exit callback for program termination: SIGINT, SIGKILL & SIGALRM. */ +int signals_init(void (*cb)(int signal, siginfo_t *sinfo, void *ctx)); + +/** Send signal \p sig to main thread. */ +void killme(int sig); + +pid_t spawn(const char *name, char *const argv[]); + +/** Determines the string length as printed on the screen (ignores escable sequences). */ +size_t strlenp(const char *str); diff --git a/fpga/lib/CMakeLists.txt b/fpga/lib/CMakeLists.txt new file mode 100644 index 000000000..e83e6382a --- /dev/null +++ b/fpga/lib/CMakeLists.txt @@ -0,0 +1,53 @@ +set(SOURCES + ip.c + vlnv.c + card.c + + ips/timer.c + ips/model.c + ips/switch.c + ips/dft.c + ips/fifo.c + ips/dma.c + ips/intc.c + ips/rtds_axis.c + + kernel/kernel.c + kernel/pci.c + kernel/vfio.c + + plugin.c + utils.c + list.c + log.c + log_config.c + log_helper.c +) + +include(FindPkgConfig) + +pkg_check_modules(JANSSON jansson) +pkg_check_modules(XIL libxil) + +find_package(Threads) + +add_library(villas-fpga ${SOURCES}) + +target_compile_definitions(villas-fpga PRIVATE + BUILDID=\"abc\" + _GNU_SOURCE +) + +target_include_directories(villas-fpga PUBLIC + ../include/villas + ${XIL_INCLUDE_DIRS} + ${JANSSON_INCLUDE_DIRS} +) + +target_link_libraries(villas-fpga PUBLIC + ${XIL_LIBRARIES} + ${JANSSON_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${CMAKE_DL_LIBS} + m +) diff --git a/fpga/lib/card.c b/fpga/lib/card.c new file mode 100644 index 000000000..2e07b130a --- /dev/null +++ b/fpga/lib/card.c @@ -0,0 +1,313 @@ +/** FPGA card. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "config.h" +#include "log.h" +#include "log_config.h" +#include "list.h" +#include "utils.h" + +#include "kernel/pci.h" +#include "kernel/vfio.h" + +#include "fpga/ip.h" +#include "fpga/card.h" + +int fpga_card_init(struct fpga_card *c, struct pci *pci, struct vfio_container *vc) +{ + assert(c->state = STATE_DESTROYED); + + c->vfio_container = vc; + c->pci = pci; + + list_init(&c->ips); + + /* Default values */ + c->filter.id.vendor = FPGA_PCI_VID_XILINX; + c->filter.id.device = FPGA_PCI_PID_VFPGA; + + c->affinity = 0; + c->do_reset = 0; + + c->state = STATE_INITIALIZED; + + return 0; +} + +int fpga_card_parse(struct fpga_card *c, json_t *cfg, const char *name) +{ + int ret; + + json_t *json_ips; + json_t *json_slot = NULL; + json_t *json_id = NULL; + json_error_t err; + + c->name = strdup(name); + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: b, s?: o, s?: o, s: o }", + "affinity", &c->affinity, + "do_reset", &c->do_reset, + "slot", &json_slot, + "id", &json_id, + "ips", &json_ips + ); + if (ret) + jerror(&err, "Failed to parse FPGA vard configuration"); + + if (json_slot) { + const char *err, *slot; + + slot = json_string_value(json_slot); + if (slot) { + ret = pci_device_parse_slot(&c->filter, slot, &err); + if (ret) + error("Failed to parse PCI slot: %s", err); + } + else + error("PCI slot must be a string"); + } + + if (json_id) { + const char *err, *id; + + id = json_string_value(json_id); + if (id) { + ret = pci_device_parse_id(&c->filter, (char*) id, &err); + if (ret) + error("Failed to parse PCI id: %s", err); + } + else + error("PCI ID must be a string"); + } + + if (!json_is_object(json_ips)) + error("FPGA card IPs section must be an object"); + + const char *name_ip; + json_t *json_ip; + json_object_foreach(json_ips, name_ip, json_ip) { + const char *vlnv; + + struct fpga_ip_type *vt; + struct fpga_ip *ip = (struct fpga_ip *) alloc(sizeof(struct fpga_ip)); + + ip->card = c; + + ret = json_unpack_ex(json_ip, &err, 0, "{ s: s }", "vlnv", &vlnv); + if (ret) + error("Failed to parse FPGA IP '%s' of card '%s'", name_ip, name); + + vt = fpga_ip_type_lookup(vlnv); + if (!vt) + error("FPGA IP core VLNV identifier '%s' is invalid", vlnv); + + ret = fpga_ip_init(ip, vt); + if (ret) + error("Failed to initalize FPGA IP core"); + + ret = fpga_ip_parse(ip, json_ip, name_ip); + if (ret) + error("Failed to parse FPGA IP core"); + + list_push(&c->ips, ip); + } + + c->state = STATE_PARSED; + + return 0; +} + +int fpga_card_parse_list(struct list *cards, json_t *cfg) +{ + int ret; + + if (!json_is_object(cfg)) + error("FPGA card configuration section must be a JSON object"); + + const char *name; + json_t *json_fpga; + json_object_foreach(cfg, name, json_fpga) { + struct fpga_card *c = (struct fpga_card *) alloc(sizeof(struct fpga_card)); + + ret = fpga_card_parse(c, json_fpga, name); + if (ret) + error("Failed to parse FPGA card configuration"); + + list_push(cards, c); + } + + return 0; +} + +int fpga_card_start(struct fpga_card *c) +{ + int ret; + + struct pci_device *pdev; + + assert(c->state == STATE_CHECKED); + + /* Search for FPGA card */ + pdev = pci_lookup_device(c->pci, &c->filter); + if (!pdev) + error("Failed to find PCI device"); + + /* Attach PCIe card to VFIO container */ + ret = vfio_pci_attach(&c->vfio_device, c->vfio_container, pdev); + if (ret) + error("Failed to attach VFIO device"); + + /* Map PCIe BAR */ + c->map = vfio_map_region(&c->vfio_device, VFIO_PCI_BAR0_REGION_INDEX); + if (c->map == MAP_FAILED) + serror("Failed to mmap() BAR0"); + + /* Enable memory access and PCI bus mastering for DMA */ + ret = vfio_pci_enable(&c->vfio_device); + if (ret) + serror("Failed to enable PCI device"); + + /* Reset system? */ + if (c->do_reset) { + /* Reset / detect PCI device */ + ret = vfio_pci_reset(&c->vfio_device); + if (ret) + serror("Failed to reset PCI device"); + + ret = fpga_card_reset(c); + if (ret) + error("Failed to reset FGPA card"); + } + + /* Initialize IP cores */ + for (size_t j = 0; j < list_length(&c->ips); j++) { + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + ret = fpga_ip_start(i); + if (ret) + error("Failed to initalize FPGA IP core: %s (%u)", i->name, ret); + } + + c->state = STATE_STARTED; + + return 0; +} + +int fpga_card_stop(struct fpga_card *c) +{ + int ret; + + assert(c->state == STATE_STOPPED); + + for (size_t j = 0; j < list_length(&c->ips); j++) { + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + ret = fpga_ip_stop(i); + if (ret) + error("Failed to stop FPGA IP core: %s (%u)", i->name, ret); + } + + c->state = STATE_STOPPED; + + return 0; +} + +void fpga_card_dump(struct fpga_card *c) +{ + info("VILLASfpga card:"); + { INDENT + info("Slot: %04x:%02x:%02x.%d", c->vfio_device.pci_device->slot.domain, c->vfio_device.pci_device->slot.bus, c->vfio_device.pci_device->slot.device, c->vfio_device.pci_device->slot.function); + info("Vendor ID: %04x", c->vfio_device.pci_device->id.vendor); + info("Device ID: %04x", c->vfio_device.pci_device->id.device); + info("Class ID: %04x", c->vfio_device.pci_device->id.class); + + info("BAR0 mapped at %p", c->map); + + info("IP blocks:"); + for (size_t j = 0; j < list_length(&c->ips); j++) { INDENT + struct fpga_ip *i = (struct fpga_ip *) list_at(&c->ips, j); + + fpga_ip_dump(i); + } + } + + vfio_dump(c->vfio_device.group->container); +} + +int fpga_card_check(struct fpga_card *c) +{ + /* Check FPGA configuration */ + c->reset = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_gpio", NULL }); + if (!c->reset) + error("FPGA is missing a reset controller"); + + c->intc = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }); + if (!c->intc) + error("FPGA is missing a interrupt controller"); + + c->sw = fpga_vlnv_lookup(&c->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axis_interconnect", NULL }); + if (!c->sw) + warn("FPGA is missing an AXI4-Stream switch"); + + return 0; +} + +int fpga_card_destroy(struct fpga_card *c) +{ + list_destroy(&c->ips, (dtor_cb_t) fpga_ip_destroy, true); + + return 0; +} + +int fpga_card_reset(struct fpga_card *c) +{ + int ret; + char state[4096]; + + /* Save current state of PCI configuration space */ + ret = pread(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); + if (ret != sizeof(state)) + return -1; + + uint32_t *rst_reg = (uint32_t *) (c->map + c->reset->baseaddr); + + debug(3, "FPGA: reset"); + rst_reg[0] = 1; + + usleep(100000); + + /* Restore previous state of PCI configuration space */ + ret = pwrite(c->vfio_device.fd, state, sizeof(state), (off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40); + if (ret != sizeof(state)) + return -1; + + /* After reset the value should be zero again */ + if (rst_reg[0]) + return -2; + + c->state = STATE_INITIALIZED; + + return 0; +} diff --git a/fpga/lib/ip.c b/fpga/lib/ip.c new file mode 100644 index 000000000..816bf2e7f --- /dev/null +++ b/fpga/lib/ip.c @@ -0,0 +1,167 @@ +/** FPGA IP component. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "log_config.h" +#include "log.h" +#include "plugin.h" + +int fpga_ip_init(struct fpga_ip *c, struct fpga_ip_type *vt) +{ + int ret; + + assert(c->state == STATE_DESTROYED); + + c->_vt = vt; + c->_vd = alloc(vt->size); + + ret = c->_vt->init ? c->_vt->init(c) : 0; + if (ret) + return ret; + + c->state = STATE_INITIALIZED; + + debug(8, "IP Core %s initalized (%u)", c->name, ret); + + return ret; +} + +int fpga_ip_parse(struct fpga_ip *c, json_t *cfg, const char *name) +{ + int ret, baseaddr = -1; + + assert(c->state != STATE_STARTED && c->state != STATE_DESTROYED); + + c->name = strdup(name); + c->baseaddr = -1; + c->irq = -1; + c->port = -1; + + json_error_t err; + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: i, s?: i }", + "baseaddr", &baseaddr, + "irq", &c->irq, + "port", &c->port + ); + if (ret) + jerror(&err, "Failed to parse configuration for FPGA IP '%s'", name); + + c->baseaddr = baseaddr; + + /* Type sepecific settings */ + ret = c->_vt && c->_vt->parse ? c->_vt->parse(c, cfg) : 0; + if (ret) + error("Failed to parse settings for IP core '%s'", name); + + c->state = STATE_PARSED; + + return 0; +} + +int fpga_ip_start(struct fpga_ip *c) +{ + int ret; + + assert(c->state == STATE_CHECKED); + + ret = c->_vt->start ? c->_vt->start(c) : 0; + if (ret) + return ret; + + c->state = STATE_STARTED; + + return 0; +} + +int fpga_ip_stop(struct fpga_ip *c) +{ + int ret; + + assert(c->state == STATE_STARTED); + + ret = c->_vt->stop ? c->_vt->stop(c) : 0; + if (ret) + return ret; + + c->state = STATE_STOPPED; + + return 0; +} + +int fpga_ip_destroy(struct fpga_ip *c) +{ + int ret; + + assert(c->state != STATE_DESTROYED); + + fpga_vlnv_destroy(&c->vlnv); + + ret = c->_vt->destroy ? c->_vt->destroy(c) : 0; + if (ret) + return ret; + + c->state = STATE_DESTROYED; + + free(c->_vd); + + return 0; +} + +int fpga_ip_reset(struct fpga_ip *c) +{ + debug(3, "Reset IP core: %s", c->name); + + return c->_vt->reset ? c->_vt->reset(c) : 0; +} + +void fpga_ip_dump(struct fpga_ip *c) +{ + assert(c->state != STATE_DESTROYED); + + info("IP %s: vlnv=%s:%s:%s:%s baseaddr=%#jx, irq=%d, port=%d", + c->name, c->vlnv.vendor, c->vlnv.library, c->vlnv.name, c->vlnv.version, + c->baseaddr, c->irq, c->port); + + if (c->_vt->dump) + c->_vt->dump(c); +} + +struct fpga_ip_type * fpga_ip_type_lookup(const char *vstr) +{ + int ret; + + struct fpga_vlnv vlnv; + + ret = fpga_vlnv_parse(&vlnv, vstr); + if (ret) + return NULL; + + /* Try to find matching IP type */ + for (size_t i = 0; i < list_length(&plugins); i++) { + struct plugin *p = (struct plugin *) list_at(&plugins, i); + + if (p->type == PLUGIN_TYPE_FPGA_IP && !fpga_vlnv_cmp(&vlnv, &p->ip.vlnv)) + return &p->ip; + } + + return NULL; +} diff --git a/fpga/lib/ips/dft.c b/fpga/lib/ips/dft.c new file mode 100644 index 000000000..dcb0b95b4 --- /dev/null +++ b/fpga/lib/ips/dft.c @@ -0,0 +1,140 @@ +/** Moving window / Recursive DFT implementation based on HLS + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "log.h" +#include "log_config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/dft.h" + +int dft_parse(struct fpga_ip *c, json_t *cfg) +{ + struct dft *dft = (struct dft *) c->_vd; + + int ret; + + json_t *json_harms; + json_error_t err; + + ret = json_unpack_ex(cfg, &err, 0, "{ s: i, s: i, s: o }", + "period", &dft->period, + "decimation", &dft->decimation, + "harmonics", &json_harms + ); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + if (!json_is_array(json_harms)) + error("DFT IP core requires 'harmonics' to be an array of integers!"); + + dft->num_harmonics = json_array_size(json_harms); + if (dft->num_harmonics <= 0) + error("DFT IP core requires 'harmonics' to contain at least 1 value!"); + + dft->fharmonics = alloc(sizeof(float) * dft->num_harmonics); + + size_t index; + json_t *json_harm; + json_array_foreach(json_harms, index, json_harm) { + if (!json_is_real(json_harm)) + error("DFT IP core requires all 'harmonics' values to be of floating point type"); + + dft->fharmonics[index] = (float) json_number_value(json_harm) / dft->period; + } + + return 0; +} + +int dft_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct dft *dft = (struct dft *) c->_vd; + + XHls_dft *xdft = &dft->inst; + XHls_dft_Config xdft_cfg = { + .Ctrl_BaseAddress = (uintptr_t) f->map + c->baseaddr + }; + + ret = XHls_dft_CfgInitialize(xdft, &xdft_cfg); + if (ret != XST_SUCCESS) + return ret; + + int max_harmonics = XHls_dft_Get_fharmonics_TotalBytes(xdft) / sizeof(dft->fharmonics[0]); + + if (dft->num_harmonics > max_harmonics) + error("DFT IP core supports a maximum of %u harmonics", max_harmonics); + + XHls_dft_Set_num_harmonics_V(xdft, dft->num_harmonics); + + XHls_dft_Set_decimation_V(xdft, dft->decimation); + + memcpy((void *) (uintptr_t) XHls_dft_Get_fharmonics_BaseAddress(xdft), dft->fharmonics, dft->num_harmonics * sizeof(dft->fharmonics[0])); + + XHls_dft_EnableAutoRestart(xdft); + XHls_dft_Start(xdft); + + return 0; +} + +int dft_stop(struct fpga_ip *c) +{ + struct dft *dft = (struct dft *) c->_vd; + + XHls_dft *xdft = &dft->inst; + + XHls_dft_DisableAutoRestart(xdft); + + return 0; +} + +int dft_destroy(struct fpga_ip *c) +{ + struct dft *dft = (struct dft *) c->_vd; + + if (dft->fharmonics) { + free(dft->fharmonics); + dft->fharmonics = NULL; + } + + return 0; +} + +static struct plugin p = { + .name = "Discrete Fourier Transform", + .description = "Perfom Discrete Fourier Transforms with variable number of harmonics on the FPGA", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "acs.eonerc.rwth-aachen.de", "hls", "hls_dft", NULL }, + .type = FPGA_IP_TYPE_MATH, + .start = dft_start, + .stop = dft_stop, + .destroy = dft_destroy, + .parse = dft_parse, + .size = sizeof(struct dft) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/dma.c b/fpga/lib/ips/dma.c new file mode 100644 index 000000000..c02175709 --- /dev/null +++ b/fpga/lib/ips/dma.c @@ -0,0 +1,657 @@ +/** DMA related helper functions + * + * These functions present a simpler interface to Xilinx' DMA driver (XAxiDma_*) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include + +#include "log.h" +#include "plugin.h" +#include "utils.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/dma.h" + +int dma_mem_split(struct dma_mem *o, struct dma_mem *a, struct dma_mem *b) +{ + int split = o->len / 2; + + a->base_virt = o->base_virt; + a->base_phys = o->base_phys; + + b->base_virt = a->base_virt + split; + b->base_phys = a->base_phys + split; + + a->len = split; + b->len = o->len - split; + + return 0; +} + +int dma_alloc(struct fpga_ip *c, struct dma_mem *mem, size_t len, int flags) +{ + int ret; + + struct fpga_card *f = c->card; + + /* Align to next bigger page size chunk */ + if (len & 0xFFF) { + len += 0x1000; + len &= ~0xFFF; + } + + mem->len = len; + mem->base_phys = (void *) -1; /* find free */ + mem->base_virt = mmap(0, mem->len, PROT_READ | PROT_WRITE, flags | MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, 0, 0); + if (mem->base_virt == MAP_FAILED) + return -1; + + ret = vfio_map_dma(f->vfio_device.group->container, (uint64_t) mem->base_virt, (uint64_t) mem->base_phys, mem->len); + if (ret) + return -2; + + return 0; +} + +int dma_free(struct fpga_ip *c, struct dma_mem *mem) +{ + int ret; + + ret = vfio_unmap_dma(c->card->vfio_device.group->container, (uint64_t) mem->base_virt, (uint64_t) mem->base_phys, mem->len); + if (ret) + return ret; + + ret = munmap(mem->base_virt, mem->len); + if (ret) + return ret; + + return 0; +} + +int dma_ping_pong(struct fpga_ip *c, char *src, char *dst, size_t len) +{ + int ret; + + ret = dma_read(c, dst, len); + if (ret) + return ret; + + ret = dma_write(c, src, len); + if (ret) + return ret; + + ret = dma_write_complete(c, NULL, NULL); + if (ret) + return ret; + + ret = dma_read_complete(c, NULL, NULL); + if (ret) + return ret; + + return 0; +} + +int dma_write(struct fpga_ip *c, char *buf, size_t len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + debug(25, "DMA write: dmac=%s buf=%p len=%#zx", c->name, buf, len); + + return xdma->HasSg + ? dma_sg_write(c, buf, len) + : dma_simple_write(c, buf, len); +} + +int dma_read(struct fpga_ip *c, char *buf, size_t len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + debug(25, "DMA read: dmac=%s buf=%p len=%#zx", c->name, buf, len); + + return xdma->HasSg + ? dma_sg_read(c, buf, len) + : dma_simple_read(c, buf, len); +} + +int dma_read_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + debug(25, "DMA read complete: dmac=%s", c->name); + + return xdma->HasSg + ? dma_sg_read_complete(c, buf, len) + : dma_simple_read_complete(c, buf, len); +} + +int dma_write_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + debug(25, "DMA write complete: dmac=%s", c->name); + + return xdma->HasSg + ? dma_sg_write_complete(c, buf, len) + : dma_simple_write_complete(c, buf, len); +} + +int dma_sg_write(struct fpga_ip *c, char *buf, size_t len) +{ + int ret, bdcnt; + + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); + XAxiDma_Bd *bds, *bd; + + uint32_t remaining, bdlen, bdbuf, cr; + + /* Checks */ + if (!xdma->HasSg) + return -1; + + if (len < 1) + return -2; + + if (!xdma->HasMm2S) + return -3; + + if (!ring->HasDRE) { + uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; + if ((uintptr_t) buf & mask) + return -4; + } + + bdcnt = CEIL(len, FPGA_DMA_BOUNDARY); + ret = XAxiDma_BdRingAlloc(ring, bdcnt, &bds); + if (ret != XST_SUCCESS) + return -5; + + remaining = len; + bdbuf = (uintptr_t) buf; + bd = bds; + for (int i = 0; i < bdcnt; i++) { + bdlen = MIN(remaining, FPGA_DMA_BOUNDARY); + + ret = XAxiDma_BdSetBufAddr(bd, bdbuf); + if (ret != XST_SUCCESS) + goto out; + + ret = XAxiDma_BdSetLength(bd, bdlen, ring->MaxTransferLen); + if (ret != XST_SUCCESS) + goto out; + + /* Set SOF / EOF / ID */ + cr = 0; + if (i == 0) + cr |= XAXIDMA_BD_CTRL_TXSOF_MASK; + if (i == bdcnt - 1) + cr |= XAXIDMA_BD_CTRL_TXEOF_MASK; + + XAxiDma_BdSetCtrl(bd, cr); + XAxiDma_BdSetId(bd, (uintptr_t) buf); + + remaining -= bdlen; + bdbuf += bdlen; + bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); + } + + /* Give the BD to DMA to kick off the transmission. */ + ret = XAxiDma_BdRingToHw(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -8; + + return 0; + +out: + ret = XAxiDma_BdRingUnAlloc(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -6; + + return -5; +} + +int dma_sg_read(struct fpga_ip *c, char *buf, size_t len) +{ + int ret, bdcnt; + + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); + XAxiDma_Bd *bds, *bd; + + uint32_t remaining, bdlen, bdbuf; + + /* Checks */ + if (!xdma->HasSg) + return -1; + + if (len < 1) + return -2; + + if (!xdma->HasS2Mm) + return -3; + + if (!ring->HasDRE) { + uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; + if ((uintptr_t) buf & mask) + return -4; + } + + bdcnt = CEIL(len, FPGA_DMA_BOUNDARY); + ret = XAxiDma_BdRingAlloc(ring, bdcnt, &bds); + if (ret != XST_SUCCESS) + return -5; + + bdbuf = (uintptr_t) buf; + remaining = len; + bd = bds; + for (int i = 0; i < bdcnt; i++) { + bdlen = MIN(remaining, FPGA_DMA_BOUNDARY); + ret = XAxiDma_BdSetLength(bd, bdlen, ring->MaxTransferLen); + if (ret != XST_SUCCESS) + goto out; + + ret = XAxiDma_BdSetBufAddr(bd, bdbuf); + if (ret != XST_SUCCESS) + goto out; + + /* Receive BDs do not need to set anything for the control + * The hardware will set the SOF/EOF bits per stream ret */ + XAxiDma_BdSetCtrl(bd, 0); + XAxiDma_BdSetId(bd, (uintptr_t) buf); + + remaining -= bdlen; + bdbuf += bdlen; + bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); + } + + ret = XAxiDma_BdRingToHw(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -8; + + return 0; + +out: + ret = XAxiDma_BdRingUnAlloc(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -6; + + return -5; +} + +int dma_sg_write_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); + XAxiDma_Bd *bds; + + int processed, ret; + + /* Wait until the one BD TX transaction is done */ + while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DMA_TO_DEVICE) & XAXIDMA_IRQ_IOC_MASK)) + intc_wait(c->card->intc, c->irq); + XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); + + processed = XAxiDma_BdRingFromHw(ring, XAXIDMA_ALL_BDS, &bds); + + if (len != NULL) + *len = XAxiDma_BdGetActualLength(bds, XAXIDMA_MAX_TRANSFER_LEN); + + if (buf != NULL) + *buf = (char *) (uintptr_t) XAxiDma_BdGetId(bds); + + /* Free all processed TX BDs for future transmission */ + ret = XAxiDma_BdRingFree(ring, processed, bds); + if (ret != XST_SUCCESS) + return -1; + + return 0; +} + +int dma_sg_read_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); + XAxiDma_Bd *bds, *bd; + + int ret, bdcnt; + uint32_t recvlen, sr; + uintptr_t recvbuf = 0; + + if (!xdma->HasSg) + return -1; + + while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DEVICE_TO_DMA) & XAXIDMA_IRQ_IOC_MASK)) + intc_wait(c->card->intc, c->irq + 1); + XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); + + bdcnt = XAxiDma_BdRingFromHw(ring, XAXIDMA_ALL_BDS, &bds); + + recvlen = 0; + + bd = bds; + for (int i = 0; i < bdcnt; i++) { + recvlen += XAxiDma_BdGetActualLength(bd, ring->MaxTransferLen); + + sr = XAxiDma_BdGetSts(bd); + if (sr & XAXIDMA_BD_STS_RXSOF_MASK) + if (i != 0) + warn("sof not first"); + + if (sr & XAXIDMA_BD_STS_RXEOF_MASK) + if (i != bdcnt - 1) + warn("eof not last"); + + recvbuf = XAxiDma_BdGetId(bd); + + bd = (XAxiDma_Bd *) XAxiDma_BdRingNext(ring, bd); + } + + if (len != NULL) + *len = recvlen; + if (buf != NULL) + *buf = (char *) recvbuf; + + /* Free all processed RX BDs for future transmission */ + ret = XAxiDma_BdRingFree(ring, bdcnt, bds); + if (ret != XST_SUCCESS) + return -3; + + return 0; +} + +int dma_simple_read(struct fpga_ip *c, char *buf, size_t len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); + + /* Checks */ + if (xdma->HasSg) + return -1; + + if ((len < 1) || (len > FPGA_DMA_BOUNDARY)) + return -2; + + if (!xdma->HasS2Mm) + return -3; + + if (!ring->HasDRE) { + uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; + if ((uintptr_t) buf & mask) + return -4; + } + + if(!(XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK)) { + if (XAxiDma_Busy(xdma, XAXIDMA_DEVICE_TO_DMA)) + return -5; + } + + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_OFFSET, LOWER_32_BITS((uintptr_t) buf)); + if (xdma->AddrWidth > 32) + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_DESTADDR_MSB_OFFSET, UPPER_32_BITS((uintptr_t) buf)); + + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET) | XAXIDMA_CR_RUNSTOP_MASK); + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len); + + return XST_SUCCESS; +} + +int dma_simple_write(struct fpga_ip *c, char *buf, size_t len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); + + /* Checks */ + if (xdma->HasSg) + return -1; + + if ((len < 1) || (len > FPGA_DMA_BOUNDARY)) + return -2; + + if (!xdma->HasMm2S) + return -3; + + if (!ring->HasDRE) { + uint32_t mask = xdma->MicroDmaMode ? XAXIDMA_MICROMODE_MIN_BUF_ALIGN : ring->DataWidth - 1; + if ((uintptr_t) buf & mask) + return -4; + } + + /* If the engine is doing transfer, cannot submit */ + if(!(XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SR_OFFSET) & XAXIDMA_HALTED_MASK)) { + if (XAxiDma_Busy(xdma, XAXIDMA_DMA_TO_DEVICE)) + return -5; + } + + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_OFFSET, LOWER_32_BITS((uintptr_t) buf)); + if (xdma->AddrWidth > 32) + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_SRCADDR_MSB_OFFSET, UPPER_32_BITS((uintptr_t) buf)); + + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_CR_OFFSET, XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_CR_OFFSET) | XAXIDMA_CR_RUNSTOP_MASK); + XAxiDma_WriteReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET, len); + + return XST_SUCCESS; +} + +int dma_simple_read_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetRxRing(xdma); + + while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DEVICE_TO_DMA) & XAXIDMA_IRQ_IOC_MASK)) + intc_wait(c->card->intc, c->irq + 1); + XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); + + if (len) + *len = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET); + + if (buf) { + *buf = (char *) (uintptr_t) XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_DESTADDR_OFFSET); + if (xdma->AddrWidth > 32) + *buf += XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_DESTADDR_MSB_OFFSET); + } + + return 0; +} + +int dma_simple_write_complete(struct fpga_ip *c, char **buf, size_t *len) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + XAxiDma_BdRing *ring = XAxiDma_GetTxRing(xdma); + + while (!(XAxiDma_IntrGetIrq(xdma, XAXIDMA_DMA_TO_DEVICE) & XAXIDMA_IRQ_IOC_MASK)) + intc_wait(c->card->intc, c->irq); + XAxiDma_IntrAckIrq(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); + + if (len) + *len = XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_BUFFLEN_OFFSET); + + if (buf) { + *buf = (char *) (uintptr_t) XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SRCADDR_OFFSET); + if (xdma->AddrWidth > 32) + *buf += XAxiDma_ReadReg(ring->ChanBase, XAXIDMA_SRCADDR_MSB_OFFSET); + } + + return 0; +} + +static int dma_setup_ring(XAxiDma_BdRing *ring, struct dma_mem *bdbuf) +{ + int delay = 0; + int coalesce = 1; + int ret, cnt; + + XAxiDma_Bd clearbd; + + /* Disable all RX interrupts before RxBD space setup */ + XAxiDma_BdRingIntDisable(ring, XAXIDMA_IRQ_ALL_MASK); + + /* Set delay and coalescing */ + XAxiDma_BdRingSetCoalesce(ring, coalesce, delay); + + /* Setup Rx BD space */ + cnt = XAxiDma_BdRingCntCalc(XAXIDMA_BD_MINIMUM_ALIGNMENT, bdbuf->len); + + ret = XAxiDma_BdRingCreate(ring, (uintptr_t) bdbuf->base_phys, (uintptr_t) bdbuf->base_virt, XAXIDMA_BD_MINIMUM_ALIGNMENT, cnt); + if (ret != XST_SUCCESS) + return -1; + + XAxiDma_BdClear(&clearbd); + ret = XAxiDma_BdRingClone(ring, &clearbd); + if (ret != XST_SUCCESS) + return -2; + + /* Start the channel */ + ret = XAxiDma_BdRingStart(ring); + if (ret != XST_SUCCESS) + return -3; + + return XST_SUCCESS; +} + +static int dma_init_rings(XAxiDma *xdma, struct dma_mem *bd) +{ + int ret; + + struct dma_mem bd_rx, bd_tx; + + ret = dma_mem_split(bd, &bd_rx, &bd_tx); + if (ret) + return -1; + + ret = dma_setup_ring(XAxiDma_GetRxRing(xdma), &bd_rx); + if (ret != XST_SUCCESS) + return -2; + + ret = dma_setup_ring(XAxiDma_GetTxRing(xdma), &bd_tx); + if (ret != XST_SUCCESS) + return -3; + + return 0; +} + +int dma_start(struct fpga_ip *c) +{ + int ret, sg; + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma *xdma = &dma->inst; + + /* Guess DMA type */ + sg = (XAxiDma_In32((uintptr_t) c->card->map + c->baseaddr + XAXIDMA_TX_OFFSET+ XAXIDMA_SR_OFFSET) & + XAxiDma_In32((uintptr_t) c->card->map + c->baseaddr + XAXIDMA_RX_OFFSET+ XAXIDMA_SR_OFFSET) & XAXIDMA_SR_SGINCL_MASK) ? 1 : 0; + + XAxiDma_Config xdma_cfg = { + .BaseAddr = (uintptr_t) c->card->map + c->baseaddr, + .HasStsCntrlStrm = 0, + .HasMm2S = 1, + .HasMm2SDRE = 1, + .Mm2SDataWidth = 128, + .HasS2Mm = 1, + .HasS2MmDRE = 1, /* Data Realignment Engine */ + .HasSg = sg, + .S2MmDataWidth = 128, + .Mm2sNumChannels = 1, + .S2MmNumChannels = 1, + .Mm2SBurstSize = 64, + .S2MmBurstSize = 64, + .MicroDmaMode = 0, + .AddrWidth = 32 + }; + + ret = XAxiDma_CfgInitialize(xdma, &xdma_cfg); + if (ret != XST_SUCCESS) + return -1; + + /* Perform selftest */ + ret = XAxiDma_Selftest(xdma); + if (ret != XST_SUCCESS) + return -2; + + /* Map buffer descriptors */ + if (xdma->HasSg) { + ret = dma_alloc(c, &dma->bd, FPGA_DMA_BD_SIZE, 0); + if (ret) + return -3; + + ret = dma_init_rings(xdma, &dma->bd); + if (ret) + return -4; + } + + /* Enable completion interrupts for both channels */ + XAxiDma_IntrEnable(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DMA_TO_DEVICE); + XAxiDma_IntrEnable(xdma, XAXIDMA_IRQ_IOC_MASK, XAXIDMA_DEVICE_TO_DMA); + + return 0; +} + +int dma_reset(struct fpga_ip *c) +{ + struct dma *dma = (struct dma *) c->_vd; + + XAxiDma_Reset(&dma->inst); + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's AXI4 Direct Memory Access Controller", + .description = "Transfer data streams between VILLASnode and VILLASfpga", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axi_dma", NULL }, + .type = FPGA_IP_TYPE_DM_DMA, + .init = dma_start, + .reset = dma_reset, + .size = sizeof(struct dma) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/fifo.c b/fpga/lib/ips/fifo.c new file mode 100644 index 000000000..1f4c058f3 --- /dev/null +++ b/fpga/lib/ips/fifo.c @@ -0,0 +1,153 @@ +/** FIFO related helper functions + * + * These functions present a simpler interface to Xilinx' FIFO driver (XLlFifo_*) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "utils.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/fifo.h" +#include "fpga/ips/intc.h" + +int fifo_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xfifo = &fifo->inst; + XLlFifo_Config fifo_cfg = { + .BaseAddress = (uintptr_t) f->map + c->baseaddr, + .Axi4BaseAddress = (uintptr_t) c->card->map + fifo->baseaddr_axi4, + .Datainterface = (fifo->baseaddr_axi4 != -1) ? 1 : 0 /* use AXI4 for Data, AXI4-Lite for control */ + }; + + ret = XLlFifo_CfgInitialize(xfifo, &fifo_cfg, (uintptr_t) c->card->map + c->baseaddr); + if (ret != XST_SUCCESS) + return -1; + + XLlFifo_IntEnable(xfifo, XLLF_INT_RC_MASK); /* Receive complete IRQ */ + + return 0; +} + +int fifo_stop(struct fpga_ip *c) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xfifo = &fifo->inst; + + XLlFifo_IntDisable(xfifo, XLLF_INT_RC_MASK); /* Receive complete IRQ */ + + return 0; +} + +ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xllfifo = &fifo->inst; + + uint32_t tdfv; + + tdfv = XLlFifo_TxVacancy(xllfifo); + if (tdfv < len) + return -1; + + XLlFifo_Write(xllfifo, buf, len); + XLlFifo_TxSetLen(xllfifo, len); + + return len; +} + +ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo *xllfifo = &fifo->inst; + + size_t nextlen = 0; + uint32_t rxlen; + + while (!XLlFifo_IsRxDone(xllfifo)) + intc_wait(c->card->intc, c->irq); + XLlFifo_IntClear(xllfifo, XLLF_INT_RC_MASK); + + /* Get length of next frame */ + rxlen = XLlFifo_RxGetLen(xllfifo); + nextlen = MIN(rxlen, len); + + /* Read from FIFO */ + XLlFifo_Read(xllfifo, buf, nextlen); + + return nextlen; +} + +int fifo_parse(struct fpga_ip *c, json_t *cfg) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + int baseaddr_axi4 = -1, ret; + + json_error_t err; + + fifo->baseaddr_axi4 = -1; + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i }", "baseaddr_axi4", &baseaddr_axi4); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + fifo->baseaddr_axi4 = baseaddr_axi4; + + return 0; +} + +int fifo_reset(struct fpga_ip *c) +{ + struct fifo *fifo = (struct fifo *) c->_vd; + + XLlFifo_Reset(&fifo->inst); + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's AXI4 FIFO data mover", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axi_fifo_mm_s", NULL }, + .type = FPGA_IP_TYPE_DM_FIFO, + .start = fifo_start, + .stop = fifo_stop, + .parse = fifo_parse, + .reset = fifo_reset, + .size = sizeof(struct fifo) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/intc.c b/fpga/lib/ips/intc.c new file mode 100644 index 000000000..7fcf109d6 --- /dev/null +++ b/fpga/lib/ips/intc.c @@ -0,0 +1,180 @@ +/** AXI-PCIe Interrupt controller + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "config.h" +#include "log.h" +#include "plugin.h" + +#include "kernel/vfio.h" +#include "kernel/kernel.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/intc.h" + +int intc_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + if (c != f->intc) + error("There can be only one interrupt controller per FPGA"); + + intc->num_irqs = vfio_pci_msi_init(&f->vfio_device, intc->efds); + if (intc->num_irqs < 0) + return -1; + + ret = vfio_pci_msi_find(&f->vfio_device, intc->nos); + if (ret) + return -2; + + /* For each IRQ */ + for (int i = 0; i < intc->num_irqs; i++) { + /* Pin to core */ + ret = kernel_irq_setaffinity(intc->nos[i], f->affinity, NULL); + if (ret) + serror("Failed to change affinity of VFIO-MSI interrupt"); + + /* Setup vector */ + XIntc_Out32(base + XIN_IVAR_OFFSET + i * 4, i); + } + + XIntc_Out32(base + XIN_IMR_OFFSET, 0); /* Use manual acknowlegement for all IRQs */ + XIntc_Out32(base + XIN_IAR_OFFSET, 0xFFFFFFFF); /* Acknowlege all pending IRQs manually */ + XIntc_Out32(base + XIN_IMR_OFFSET, 0xFFFFFFFF); /* Use fast acknowlegement for all IRQs */ + XIntc_Out32(base + XIN_IER_OFFSET, 0x00000000); /* Disable all IRQs by default */ + XIntc_Out32(base + XIN_MER_OFFSET, XIN_INT_HARDWARE_ENABLE_MASK | XIN_INT_MASTER_ENABLE_MASK); + + debug(4, "FPGA: enabled interrupts"); + + return 0; +} + +int intc_destroy(struct fpga_ip *c) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + vfio_pci_msi_deinit(&f->vfio_device, intc->efds); + + return 0; +} + +int intc_enable(struct fpga_ip *c, uint32_t mask, int flags) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uint32_t ier, imr; + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + /* Current state of INTC */ + ier = XIntc_In32(base + XIN_IER_OFFSET); + imr = XIntc_In32(base + XIN_IMR_OFFSET); + + /* Clear pending IRQs */ + XIntc_Out32(base + XIN_IAR_OFFSET, mask); + + for (int i = 0; i < intc->num_irqs; i++) { + if (mask & (1 << i)) + intc->flags[i] = flags; + } + + if (flags & INTC_POLLING) { + XIntc_Out32(base + XIN_IMR_OFFSET, imr & ~mask); + XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); + } + else { + XIntc_Out32(base + XIN_IER_OFFSET, ier | mask); + XIntc_Out32(base + XIN_IMR_OFFSET, imr | mask); + } + + debug(3, "New ier = %#x", XIntc_In32(base + XIN_IER_OFFSET)); + debug(3, "New imr = %#x", XIntc_In32(base + XIN_IMR_OFFSET)); + debug(3, "New isr = %#x", XIntc_In32(base + XIN_ISR_OFFSET)); + + debug(8, "FPGA: Interupt enabled: mask=%#x flags=%#x", mask, flags); + + return 0; +} + +int intc_disable(struct fpga_ip *c, uint32_t mask) +{ + struct fpga_card *f = c->card; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET); + + XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask); + + return 0; +} + +uint64_t intc_wait(struct fpga_ip *c, int irq) +{ + struct fpga_card *f = c->card; + struct intc *intc = (struct intc *) c->_vd; + + uintptr_t base = (uintptr_t) f->map + c->baseaddr; + + if (intc->flags[irq] & INTC_POLLING) { + uint32_t isr, mask = 1 << irq; + + do { + isr = XIntc_In32(base + XIN_ISR_OFFSET); + pthread_testcancel(); + } while ((isr & mask) != mask); + + XIntc_Out32(base + XIN_IAR_OFFSET, mask); + + return 1; + } + else { + uint64_t cnt; + ssize_t ret = read(intc->efds[irq], &cnt, sizeof(cnt)); + if (ret != sizeof(cnt)) + return 0; + + return cnt; + } +} + +static struct plugin p = { + .name = "Xilinx's programmable interrupt controller", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "acs.eonerc.rwth-aachen.de", "user", "axi_pcie_intc", NULL }, + .type = FPGA_IP_TYPE_MISC, + .start = intc_start, + .destroy = intc_destroy, + .size = sizeof(struct intc) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/model.c b/fpga/lib/ips/model.c new file mode 100644 index 000000000..c69b4ba49 --- /dev/null +++ b/fpga/lib/ips/model.c @@ -0,0 +1,427 @@ +/** Interface to Xilinx System Generator Models via PCIe + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include + +#include "utils.h" +#include "log.h" +#include "log_config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/model.h" + +static int model_parameter_destroy(struct model_parameter *p) +{ + free(p->name); + + return 0; +} + +static int model_info_destroy(struct model_info *i) +{ + free(i->field); + free(i->value); + + return 0; +} + +static uint32_t model_xsg_map_checksum(uint32_t *map, size_t len) +{ + uint32_t chks = 0; + + for (int i = 2; i < len-1; i++) + chks += map[i]; + + return chks; /* moduluo 2^32 because of overflow */ +} + +static int model_xsg_map_parse(uint32_t *map, size_t len, struct list *parameters, struct list *infos) +{ +#define copy_string(off) strndup((char *) (data + (off)), (length - (off)) * 4); + int j; + struct model_info *i; + + /* Check magic */ + if (map[0] != XSG_MAGIC) + error("Invalid magic: %#x", map[0]); + + for (j = 2; j < len-1;) { + uint16_t type = map[j] & 0xFFFF; + uint16_t length = map[j] >> 16; + uint32_t *data = &map[j+1]; + + switch (type) { + case XSG_BLOCK_GATEWAY_IN: + case XSG_BLOCK_GATEWAY_OUT: + if (length < 4) + break; /* block is to small to describe a gateway */ + + struct model_parameter *e, *p = (struct model_parameter *) alloc(sizeof(struct model_parameter)); + + p->name = copy_string(3); + p->default_value.flt = *((float *) &data[1]); + p->offset = data[2]; + p->direction = type & 0x1; + p->type = (data[0] >> 0) & 0xFF; + p->binpt = (data[0] >> 8) & 0xFF; + + e = list_lookup(parameters, p->name); + if (e) + model_parameter_update(e, p); + else + list_push(parameters, p); + break; + + case XSG_BLOCK_INFO: + i = alloc(sizeof(struct model_info)); + + i->field = copy_string(0); + i->value = copy_string((int) ceil((double) (strlen(i->field) + 1) / 4)) + + list_push(infos, i); + break; + + default: + warn("Unknown block type: %#06x", type); + } + + j += length + 1; + } + + return 0; + +#undef copy_string +} + +static uint32_t model_xsg_map_read_word(uint32_t offset, void *baseaddr) +{ + volatile uint32_t *addr = baseaddr + 0x00; + volatile uint32_t *data = baseaddr + 0x04; + + *addr = offset; /* Update addr reg */ + + return *data; /* Read data reg */ +} + +static int model_xsg_map_read(uint32_t *map, size_t len, void *baseaddr) +{ + size_t maplen; + uint32_t magic; + + /* Check magic */ + magic = model_xsg_map_read_word(0, baseaddr); + if (magic != XSG_MAGIC) + return -1; + + maplen = model_xsg_map_read_word(1, baseaddr); + if (maplen < 3) + return -2; + + /* Read Data */ + int i; + for (i = 0; i < MIN(maplen, len); i++) + map[i] = model_xsg_map_read_word(i, baseaddr); + + return i; +} + +int model_parse(struct fpga_ip *c, json_t *cfg) +{ + struct model *m = (struct model *) c->_vd; + + int ret; + + json_t *json_params; + json_error_t err; + + if (strcmp(c->vlnv.library, "hls") == 0) + m->type = MODEL_TYPE_HLS; + else if (strcmp(c->vlnv.library, "sysgen") == 0) + m->type = MODEL_TYPE_XSG; + else + error("Unsupported model type: %s", c->vlnv.library); + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: o }", "parameters", &json_params); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + if (json_params) { + if (!json_is_object(json_params)) + error("Setting 'parameters' must be a JSON object"); + + const char *name; + json_t *value; + json_object_foreach(json_params, name, value) { + if (!json_is_real(value)) + error("Parameters of FPGA IP '%s' must be of type floating point", c->name); + + struct model_parameter *p = (struct model_parameter *) alloc(sizeof(struct model_parameter)); + + p->name = strdup(name); + p->default_value.flt = json_real_value(value); + + list_push(&m->parameters, p); + } + } + + return 0; +} + +static int model_init_from_xsg_map(struct model *m, void *baseaddr) +{ + int ret, chks; + + if (baseaddr == (void *) -1) + return -1; + + m->xsg.map = alloc(XSG_MAPLEN); + m->xsg.maplen = model_xsg_map_read(m->xsg.map, XSG_MAPLEN, baseaddr); + if (m->xsg.maplen < 0) + return -1; + + debug(5, "XSG: memory map length = %#zx", m->xsg.maplen); + + chks = m->xsg.map[m->xsg.maplen - 1]; + if (chks != model_xsg_map_checksum(m->xsg.map, m->xsg.maplen)) + return -2; + + ret = model_xsg_map_parse(m->xsg.map, m->xsg.maplen, &m->parameters, &m->infos); + if (ret) + return -3; + + debug(5, "XSG: Parsed %zu parameters and %zu model infos", list_length(&m->parameters), list_length(&m->infos)); + + return 0; +} + +int model_init(struct fpga_ip *c) +{ + int ret; + + struct model *m = (struct model *) c->_vd; + + list_init(&m->parameters); + list_init(&m->infos); + + if (!fpga_vlnv_cmp(&c->vlnv, &(struct fpga_vlnv) { NULL, "sysgen", NULL, NULL })) + ret = model_init_from_xsg_map(m, c->card->map + c->baseaddr); + else + ret = 0; + + /* Set default values for parameters */ + for (size_t i = 0; i < list_length(&m->parameters); i++) { + struct model_parameter *p = (struct model_parameter *) list_at(&m->parameters, i); + + p->ip = c; + + if (p->direction == MODEL_PARAMETER_IN) { + model_parameter_write(p, p->default_value.flt); + info("Set parameter '%s' updated to default value: %f", p->name, p->default_value.flt); + } + } + + if (ret) + error("Failed to init XSG model: %d", ret); + + return 0; +} + +int model_destroy(struct fpga_ip *c) +{ + struct model *m = (struct model *) c->_vd; + + list_destroy(&m->parameters, (dtor_cb_t) model_parameter_destroy, true); + list_destroy(&m->infos, (dtor_cb_t) model_info_destroy, true); + + if (m->xsg.map != NULL) + free(m->xsg.map); + + return 0; +} + +void model_dump(struct fpga_ip *c) +{ + struct model *m = (struct model *) c->_vd; + + const char *param_type[] = { "UFix", "Fix", "Float", "Boolean" }; + const char *parameter_dirs[] = { "In", "Out", "In/Out" }; + + { INDENT + info("Parameters:"); + for (size_t i = 0; i < list_length(&m->parameters); i++) { INDENT + struct model_parameter *p = (struct model_parameter *) list_at(&m->parameters, i); + + if (p->direction == MODEL_PARAMETER_IN) + info("%#jx: %s (%s) = %.3f %s %u", + p->offset, + p->name, + parameter_dirs[p->direction], + p->default_value.flt, + param_type[p->type], + p->binpt + ); + else if (p->direction == MODEL_PARAMETER_OUT) + info("%#jx: %s (%s)", + p->offset, + p->name, + parameter_dirs[p->direction] + ); + } + + info("Infos:"); + for (size_t j = 0; j < list_length(&m->infos); j++) { INDENT + struct model_info *i = (struct model_info *) list_at(&m->infos, j); + + info("%s: %s", i->field, i->value); + } + } +} + +int model_parameter_read(struct model_parameter *p, double *v) +{ + struct fpga_ip *c = p->ip; + + union model_parameter_value *ptr = (union model_parameter_value *) (c->card->map + c->baseaddr + p->offset); + + switch (p->type) { + case MODEL_PARAMETER_TYPE_UFIX: + *v = (double) ptr->ufix / (1 << p->binpt); + break; + + case MODEL_PARAMETER_TYPE_FIX: + *v = (double) ptr->fix / (1 << p->binpt); + break; + + case MODEL_PARAMETER_TYPE_FLOAT: + *v = (double) ptr->flt; + break; + + case MODEL_PARAMETER_TYPE_BOOLEAN: + *v = (double) ptr->ufix ? 1 : 0; + } + + return 0; +} + +int model_parameter_write(struct model_parameter *p, double v) +{ + struct fpga_ip *c = p->ip; + + union model_parameter_value *ptr = (union model_parameter_value *) (c->card->map + c->baseaddr + p->offset); + + switch (p->type) { + case MODEL_PARAMETER_TYPE_UFIX: + ptr->ufix = (uint32_t) (v * (1 << p->binpt)); + break; + + case MODEL_PARAMETER_TYPE_FIX: + ptr->fix = (int32_t) (v * (1 << p->binpt)); + break; + + case MODEL_PARAMETER_TYPE_FLOAT: + ptr->flt = (float) v; + break; + + case MODEL_PARAMETER_TYPE_BOOLEAN: + ptr->bol = (bool) v; + break; + } + + return 0; +} + +void model_parameter_add(struct fpga_ip *c, const char *name, enum model_parameter_direction dir, enum model_parameter_type type) +{ + struct model *m = (struct model *) c->_vd; + struct model_parameter *p = (struct model_parameter *) alloc(sizeof(struct model_parameter)); + + p->name = strdup(name); + p->type = type; + p->direction = dir; + + list_push(&m->parameters, p); +} + +int model_parameter_remove(struct fpga_ip *c, const char *name) +{ + struct model *m = (struct model *) c->_vd; + struct model_parameter *p; + + p = list_lookup(&m->parameters, name); + if (!p) + return -1; + + list_remove(&m->parameters, p); + + return 0; +} + +int model_parameter_update(struct model_parameter *p, struct model_parameter *u) +{ + if (strcmp(p->name, u->name) != 0) + return -1; + + p->direction = u->direction; + p->type = u->type; + p->binpt = u->binpt; + p->offset = u->offset; + + return 0; +} + +static struct plugin p_hls = { + .name = "Xilinx High Level Synthesis (HLS) model", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { NULL, "hls", NULL, NULL }, + .type = FPGA_IP_TYPE_MODEL, + .init = model_init, + .destroy = model_destroy, + .dump = model_dump, + .parse = model_parse + } +}; + +REGISTER_PLUGIN(&p_hls) + +static struct plugin p_sysgen = { + .name = "Xilinx System Generator for DSP (XSG) model", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { NULL, "sysgen", NULL, NULL }, + .type = FPGA_IP_TYPE_MODEL, + .init = model_init, + .destroy = model_destroy, + .dump = model_dump, + .parse = model_parse, + .size = sizeof(struct model) + } +}; + +REGISTER_PLUGIN(&p_sysgen) diff --git a/fpga/lib/ips/rtds_axis.c b/fpga/lib/ips/rtds_axis.c new file mode 100644 index 000000000..5b9137759 --- /dev/null +++ b/fpga/lib/ips/rtds_axis.c @@ -0,0 +1,80 @@ +/** Driver for AXI Stream wrapper around RTDS_InterfaceModule (rtds_axis ) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "log.h" +#include "utils.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/rtds_axis.h" + +void rtds_axis_dump(struct fpga_ip *c) +{ + /* Check RTDS_Axis registers */ + uint32_t *regs = (uint32_t *) (c->card->map + c->baseaddr); + + uint32_t sr = regs[RTDS_AXIS_SR_OFFSET/4]; + info("RTDS AXI Stream interface details"); + { INDENT + info("RTDS status: %#08x", sr); + { INDENT + info("Card detected: %s", sr & RTDS_AXIS_SR_CARDDETECTED ? CLR_GRN("yes") : CLR_RED("no")); + info("Link up: %s", sr & RTDS_AXIS_SR_LINKUP ? CLR_GRN("yes") : CLR_RED("no")); + info("TX queue full: %s", sr & RTDS_AXIS_SR_TX_FULL ? CLR_RED("yes") : CLR_GRN("no")); + info("TX in progress: %s", sr & RTDS_AXIS_SR_TX_INPROGRESS ? CLR_YEL("yes") : "no"); + info("Case running: %s", sr & RTDS_AXIS_SR_CASE_RUNNING ? CLR_GRN("yes") : CLR_RED("no")); + } + + info("RTDS control: %#08x", regs[RTDS_AXIS_CR_OFFSET/4]); + info("RTDS IRQ coalesc: %u", regs[RTDS_AXIS_COALESC_OFFSET/4]); + info("RTDS IRQ version: %#06x", regs[RTDS_AXIS_VERSION_OFFSET/4]); + info("RTDS IRQ multi-rate: %u", regs[RTDS_AXIS_MRATE/4]); + + info("RTDS timestep counter: %lu", (uint64_t) regs[RTDS_AXIS_TSCNT_LOW_OFFSET/4] | (uint64_t) regs[RTDS_AXIS_TSCNT_HIGH_OFFSET/4] << 32); + info("RTDS timestep period: %.3f uS", rtds_axis_dt(c) * 1e6); + } +} + +double rtds_axis_dt(struct fpga_ip *c) +{ + uint32_t *regs = (uint32_t *) (c->card->map + c->baseaddr); + uint16_t dt = regs[RTDS_AXIS_TS_PERIOD_OFFSET/4]; + + return (dt == 0xFFFF) ? -1.0 : (double) dt / RTDS_HZ; +} + +static struct plugin p = { + .name = "RTDS's AXI4-Stream - GTFPGA interface", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "acs.eonerc.rwth-aachen.de", "user", "rtds_axis", NULL }, + .type = FPGA_IP_TYPE_INTERFACE, + .dump = rtds_axis_dump, + .size = 0 + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/switch.c b/fpga/lib/ips/switch.c new file mode 100644 index 000000000..73c647be1 --- /dev/null +++ b/fpga/lib/ips/switch.c @@ -0,0 +1,221 @@ +/** AXI Stream interconnect related helper functions + * + * These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "list.h" +#include "log.h" +#include "log_config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/switch.h" + +int switch_start(struct fpga_ip *c) +{ + int ret; + + struct fpga_card *f = c->card; + struct sw *sw = (struct sw *) c->_vd; + + XAxis_Switch *xsw = &sw->inst; + + if (c != f->sw) + error("There can be only one AXI4-Stream interconnect per FPGA"); + + + /* Setup AXI-stream switch */ + XAxis_Switch_Config sw_cfg = { + .BaseAddress = (uintptr_t) f->map + c->baseaddr, + .MaxNumMI = sw->num_ports, + .MaxNumSI = sw->num_ports + }; + + ret = XAxisScr_CfgInitialize(xsw, &sw_cfg, (uintptr_t) c->card->map + c->baseaddr); + if (ret != XST_SUCCESS) + return -1; + + /* Disable all masters */ + XAxisScr_RegUpdateDisable(xsw); + XAxisScr_MiPortDisableAll(xsw); + XAxisScr_RegUpdateEnable(xsw); + + switch_init_paths(c); + + return 0; +} + +int switch_init_paths(struct fpga_ip *c) +{ + int ret; + struct sw *sw = (struct sw *) c->_vd; + + XAxis_Switch *xsw = &sw->inst; + + XAxisScr_RegUpdateDisable(xsw); + XAxisScr_MiPortDisableAll(xsw); + + for (size_t i = 0; i < list_length(&sw->paths); i++) { + struct sw_path *p = (struct sw_path *) list_at(&sw->paths, i); + struct fpga_ip *mi, *si; + + mi = list_lookup(&c->card->ips, p->out); + si = list_lookup(&c->card->ips, p->in); + + if (!mi || !si || mi->port == -1 || si->port == -1) + error("Invalid path configuration for FPGA"); + + ret = switch_connect(c, mi, si); + if (ret) + error("Failed to configure switch"); + } + + XAxisScr_RegUpdateEnable(xsw); + + return 0; +} + +int switch_destroy(struct fpga_ip *c) +{ + struct sw *sw = (struct sw *) c->_vd; + + list_destroy(&sw->paths, NULL, true); + + return 0; +} + +int switch_parse(struct fpga_ip *c, json_t *cfg) +{ + struct sw *sw = (struct sw *) c->_vd; + + int ret; + size_t index; + json_error_t err; + json_t *json_path, *json_paths = NULL; + + list_init(&sw->paths); + + ret = json_unpack_ex(cfg, &err, 0, "{ s: i, s?: o }", + "num_ports", &sw->num_ports, + "paths", &json_paths + ); + if (ret) + jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name); + + if (!json_paths) + return 0; /* no switch config available */ + + if (!json_is_array(json_paths)) + error("Setting 'paths' of FPGA IP '%s' should be an array of JSON objects", c->name); + + json_array_foreach(json_paths, index, json_path) { + struct sw_path *p = (struct sw_path *) alloc(sizeof(struct sw_path)); + int reverse = 0; + + ret = json_unpack_ex(json_path, &err, 0, "{ s?: b, s: s, s: s }", + "reverse", &reverse, + "in", &p->in, + "out", &p->out + ); + if (ret) + jerror(&err, "Failed to parse path %zu of FPGA IP '%s'", index, c->name); + + list_push(&sw->paths, p); + + if (reverse) { + struct sw_path *r = memdup(p, sizeof(struct sw_path)); + + r->in = p->out; + r->out = p->in; + + list_push(&sw->paths, r); + } + } + + return 0; +} + +int switch_connect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si) +{ + struct sw *sw = (struct sw *) c->_vd; + XAxis_Switch *xsw = &sw->inst; + + uint32_t mux, port; + + /* Check if theres already something connected */ + for (int i = 0; i < sw->num_ports; i++) { + mux = XAxisScr_ReadReg(xsw->Config.BaseAddress, XAXIS_SCR_MI_MUX_START_OFFSET + i * 4); + if (!(mux & XAXIS_SCR_MI_X_DISABLE_MASK)) { + port = mux & ~XAXIS_SCR_MI_X_DISABLE_MASK; + + if (port == si->port) { + warn("Switch: Slave port %s (%u) has been connected already to port %u. Disconnecting...", si->name, si->port, i); + XAxisScr_RegUpdateDisable(xsw); + XAxisScr_MiPortDisable(xsw, i); + XAxisScr_RegUpdateEnable(xsw); + } + } + } + + /* Reconfigure switch */ + XAxisScr_RegUpdateDisable(xsw); + XAxisScr_MiPortEnable(xsw, mi->port, si->port); + XAxisScr_RegUpdateEnable(xsw); + + /* Reset IPs */ + /*ip_reset(mi); + ip_reset(si);*/ + + debug(8, "FPGA: Switch connected %s (%u) to %s (%u)", mi->name, mi->port, si->name, si->port); + + return 0; +} + +int switch_disconnect(struct fpga_ip *c, struct fpga_ip *mi, struct fpga_ip *si) +{ + struct sw *sw = (struct sw *) c->_vd; + XAxis_Switch *xsw = &sw->inst; + + if (!XAxisScr_IsMiPortEnabled(xsw, mi->port, si->port)) + return -1; + + XAxisScr_MiPortDisable(xsw, mi->port); + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's AXI4-Stream switch", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axis_interconnect", NULL }, + .type = FPGA_IP_TYPE_MISC, + .start = switch_start, + .destroy = switch_destroy, + .parse = switch_parse, + .size = sizeof(struct sw) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/ips/timer.c b/fpga/lib/ips/timer.c new file mode 100644 index 000000000..57eb1a67a --- /dev/null +++ b/fpga/lib/ips/timer.c @@ -0,0 +1,61 @@ +/** Timer related helper functions + * + * These functions present a simpler interface to Xilinx' Timer Counter driver (XTmrCtr_*) + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include "config.h" +#include "plugin.h" + +#include "fpga/ip.h" +#include "fpga/card.h" +#include "fpga/ips/timer.h" + +int timer_start(struct fpga_ip *c) +{ + struct fpga_card *f = c->card; + struct timer *tmr = (struct timer *) c->_vd; + + XTmrCtr *xtmr = &tmr->inst; + XTmrCtr_Config xtmr_cfg = { + .BaseAddress = (uintptr_t) f->map + c->baseaddr, + .SysClockFreqHz = FPGA_AXI_HZ + }; + + XTmrCtr_CfgInitialize(xtmr, &xtmr_cfg, (uintptr_t) f->map + c->baseaddr); + XTmrCtr_InitHw(xtmr); + + return 0; +} + +static struct plugin p = { + .name = "Xilinx's programmable timer / counter", + .description = "", + .type = PLUGIN_TYPE_FPGA_IP, + .ip = { + .vlnv = { "xilinx.com", "ip", "axi_timer", NULL }, + .type = FPGA_IP_TYPE_MISC, + .start = timer_start, + .size = sizeof(struct timer) + } +}; + +REGISTER_PLUGIN(&p) diff --git a/fpga/lib/kernel/kernel.c b/fpga/lib/kernel/kernel.c new file mode 100644 index 000000000..78a60270a --- /dev/null +++ b/fpga/lib/kernel/kernel.c @@ -0,0 +1,282 @@ +/** Linux kernel related functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "utils.h" +#include "config.h" +#include "kernel/kernel.h" + +int kernel_get_cacheline_size() +{ +#ifdef __linux__ + return sysconf(_SC_LEVEL1_ICACHE_LINESIZE); +#else + return 64; /** @todo fixme */ +#endif +} + +#ifdef __linux__ + +int kernel_module_set_param(const char *module, const char *param, const char *value) +{ + FILE *f; + char fn[256]; + + snprintf(fn, sizeof(fn), "%s/module/%s/parameters/%s", SYSFS_PATH, module, param); + f = fopen(fn, "w"); + if (!f) + serror("Failed set parameter %s for kernel module %s to %s", module, param, value); + + debug(LOG_KERNEL | 5, "Set parameter %s of kernel module %s to %s", module, param, value); + fprintf(f, "%s", value); + fclose(f); + + return 0; +} + +int kernel_module_load(const char *module) +{ + int ret; + + ret = kernel_module_loaded(module); + if (!ret) { + debug(LOG_KERNEL | 5, "Kernel module %s already loaded...", module); + return 0; + } + + pid_t pid = fork(); + switch (pid) { + case -1: // error + return -1; + + case 0: // child + execlp("modprobe", "modprobe", module, (char *) 0); + exit(EXIT_FAILURE); // exec never returns + + default: + wait(&ret); + + return kernel_module_loaded(module); + } +} + +int kernel_module_loaded(const char *module) +{ + FILE *f; + int ret = -1; + char *line = NULL; + size_t len = 0; + + f = fopen(PROCFS_PATH "/modules", "r"); + if (!f) + return -1; + + while (getline(&line, &len, f) >= 0) { + if (strstr(line, module) == line) { + ret = 0; + break; + } + } + + free(line); + fclose(f); + + return ret; +} + +int kernel_get_version(struct version *v) +{ + struct utsname uts; + + if (uname(&uts) < 0) + return -1; + + if (version_parse(uts.release, v)) + return -1; + + return 0; +} + +int kernel_get_cmdline_param(const char *param, char *buf, size_t len) +{ + int ret; + char cmdline[512]; + + FILE *f = fopen(PROCFS_PATH "/cmdline", "r"); + if (!f) + return -1; + + if (!fgets(cmdline, sizeof(cmdline), f)) + goto out; + + char *tok = strtok(cmdline, " \t"); + do { + char key[128], value[128]; + + ret = sscanf(tok, "%127[^=]=%127s", key, value); + if (ret >= 1) { + if (ret >= 2) + debug(30, "Found kernel param: %s=%s", key, value); + else + debug(30, "Found kernel param: %s", key); + + if (strcmp(param, key) == 0) { + if (ret >= 2 && buf) + strncpy(buf, value, len); + + return 0; /* found */ + } + } + } while((tok = strtok(NULL, " \t"))); + +out: + fclose(f); + + return -1; /* not found or error */ +} + +int kernel_get_page_size() +{ + return sysconf(_SC_PAGESIZE); +} + +/* There is no sysconf interface to get the hugepage size */ +int kernel_get_hugepage_size() +{ + char *key, *value, *unit, *line = NULL; + int sz = -1; + size_t len = 0; + FILE *f; + + f = fopen(PROCFS_PATH "/meminfo", "r"); + if (!f) + return -1; + + while (getline(&line, &len, f) != -1) { + key = strtok(line, ": "); + value = strtok(NULL, " "); + unit = strtok(NULL, "\n"); + + if (!strcmp(key, "Hugepagesize") && !strcmp(unit, "kB")) { + sz = strtoul(value, NULL, 10) * 1024; + break; + } + } + + free(line); + fclose(f); + + return sz; +} + +int kernel_get_nr_hugepages() +{ + FILE *f; + int nr, ret; + + f = fopen(PROCFS_PATH "/sys/vm/nr_hugepages", "r"); + if (!f) + serror("Failed to open %s", PROCFS_PATH "/sys/vm/nr_hugepages"); + + ret = fscanf(f, "%d", &nr); + if (ret != 1) + nr = -1; + + fclose(f); + + return nr; +} + +int kernel_set_nr_hugepages(int nr) +{ + FILE *f; + + f = fopen(PROCFS_PATH "/sys/vm/nr_hugepages", "w"); + if (!f) + serror("Failed to open %s", PROCFS_PATH "/sys/vm/nr_hugepages"); + + fprintf(f, "%d\n", nr); + fclose(f); + + return 0; +} + +#if 0 +int kernel_has_cap(cap_value_t cap) +{ + int ret; + + cap_t caps; + cap_flag_value_t value; + + caps = cap_get_proc(); + if (caps == NULL) + return -1; + + ret = cap_get_proc(caps); + if (ret == -1) + return -1; + + ret = cap_get_flag(caps, cap, CAP_EFFECTIVE, &value); + if (ret == -1) + return -1; + + ret = cap_free(caps); + if (ret) + return -1; + + return value == CAP_SET ? 0 : -1; +} +#endif + +int kernel_irq_setaffinity(unsigned irq, uintmax_t new, uintmax_t *old) +{ + char fn[64]; + FILE *f; + int ret = 0; + + snprintf(fn, sizeof(fn), "/proc/irq/%u/smp_affinity", irq); + + f = fopen(fn, "w+"); + if (!f) + return -1; /* IRQ does not exist */ + + if (old) + ret = fscanf(f, "%jx", old); + + fprintf(f, "%jx", new); + fclose(f); + + return ret; +} + +#endif /* __linux__ */ diff --git a/fpga/lib/kernel/pci.c b/fpga/lib/kernel/pci.c new file mode 100644 index 000000000..e251f00c8 --- /dev/null +++ b/fpga/lib/kernel/pci.c @@ -0,0 +1,294 @@ +/** Linux PCI helpers + * + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" + +#include "kernel/pci.h" +#include "config.h" + +int pci_init(struct pci *p) +{ + struct dirent *e; + DIR *dp; + FILE *f; + char path[PATH_MAX]; + int ret; + + snprintf(path, sizeof(path), "%s/bus/pci/devices", SYSFS_PATH); + + dp = opendir(path); + if (dp == NULL) { + serror("Failed to detect PCI devices"); + return -1; + } + + while ((e = readdir(dp))) { + struct pci_device *d = (struct pci_device *) alloc(sizeof(struct pci_device)); + + struct { const char *s; int *p; } map[] = { + { "vendor", &d->id.vendor }, + { "device", &d->id.device } + }; + + /* Read vendor & device id */ + for (int i = 0; i < 2; i++) { + snprintf(path, sizeof(path), "%s/bus/pci/devices/%s/%s", SYSFS_PATH, e->d_name, map[i].s); + + f = fopen(path, "r"); + if (!f) + serror("Failed to open '%s'", path); + + ret = fscanf(f, "%x", map[i].p); + if (ret != 1) + error("Failed to parse %s ID from: %s", map[i].s, path); + + fclose(f); + } + + /* Get slot id */ + ret = sscanf(e->d_name, "%4x:%2x:%2x.%u", &d->slot.domain, &d->slot.bus, &d->slot.device, &d->slot.function); + if (ret != 4) + error("Failed to parse PCI slot number: %s", e->d_name); + + list_push(&p->devices, d); + } + + closedir(dp); + + return 0; +} + +int pci_destroy(struct pci *p) +{ + list_destroy(&p->devices, NULL, true); + + return 0; +} + +int pci_device_parse_slot(struct pci_device *f, const char *s, const char **error) +{ + char *str = strdup(s); + char *colon = strrchr(str, ':'); + char *dot = strchr((colon ? colon + 1 : str), '.'); + char *mid = str; + char *e, *bus, *colon2; + + if (colon) { + *colon++ = 0; + mid = colon; + + colon2 = strchr(str, ':'); + if (colon2) { + *colon2++ = 0; + bus = colon2; + + if (str[0] && strcmp(str, "*")) { + long int x = strtol(str, &e, 16); + if ((e && *e) || (x < 0 || x > 0x7fffffff)) { + *error = "Invalid domain number"; + goto fail; + } + + f->slot.domain = x; + } + } + else + bus = str; + + if (bus[0] && strcmp(bus, "*")) { + long int x = strtol(bus, &e, 16); + if ((e && *e) || (x < 0 || x > 0xff)) { + *error = "Invalid bus number"; + goto fail; + } + + f->slot.bus = x; + } + } + + if (dot) + *dot++ = 0; + + if (mid[0] && strcmp(mid, "*")) { + long int x = strtol(mid, &e, 16); + + if ((e && *e) || (x < 0 || x > 0x1f)) { + *error = "Invalid slot number"; + goto fail; + } + + f->slot.device = x; + } + + if (dot && dot[0] && strcmp(dot, "*")) { + long int x = strtol(dot, &e, 16); + + if ((e && *e) || (x < 0 || x > 7)) { + *error = "Invalid function number"; + goto fail; + } + + f->slot.function = x; + } + + free(str); + return 0; + +fail: + free(str); + return -1; +} + +/* ID filter syntax: [vendor]:[device][:class] */ +int pci_device_parse_id(struct pci_device *f, const char *str, const char **error) +{ + char *s, *c, *e; + + if (!*str) + return 0; + + s = strchr(str, ':'); + if (!s) { + *error = "':' expected"; + goto fail; + } + + *s++ = 0; + if (str[0] && strcmp(str, "*")) { + long int x = strtol(str, &e, 16); + + if ((e && *e) || (x < 0 || x > 0xffff)) { + *error = "Invalid vendor ID"; + goto fail; + } + + f->id.vendor = x; + } + + c = strchr(s, ':'); + if (c) + *c++ = 0; + + if (s[0] && strcmp(s, "*")) { + long int x = strtol(s, &e, 16); + if ((e && *e) || (x < 0 || x > 0xffff)) { + *error = "Invalid device ID"; + goto fail; + } + + f->id.device = x; + } + + if (c && c[0] && strcmp(s, "*")) { + long int x = strtol(c, &e, 16); + + if ((e && *e) || (x < 0 || x > 0xffff)) { + *error = "Invalid class code"; + goto fail; + } + + f->id.class = x; + } + + return 0; + +fail: + return -1; +} + +int pci_device_compare(const struct pci_device *d, const struct pci_device *f) +{ + if ((f->slot.domain >= 0 && f->slot.domain != d->slot.domain) || + (f->slot.bus >= 0 && f->slot.bus != d->slot.bus) || + (f->slot.device >= 0 && f->slot.device != d->slot.device) || + (f->slot.function >= 0 && f->slot.function != d->slot.function)) + return 0; + + if (f->id.device >= 0 || f->id.vendor >= 0) { + if ((f->id.device >= 0 && f->id.device != d->id.device) || (f->id.vendor >= 0 && f->id.vendor != d->id.vendor)) + return 0; + } + + if (f->id.class >= 0) { + if (f->id.class != d->id.class) + return 0; + } + + return 1; +} + +struct pci_device * pci_lookup_device(struct pci *p, struct pci_device *f) +{ + return list_search(&p->devices, (cmp_cb_t) pci_device_compare, (void *) f); +} + +int pci_attach_driver(struct pci_device *d, const char *driver) +{ + FILE *f; + char fn[256]; + + /* Add new ID to driver */ + snprintf(fn, sizeof(fn), "%s/bus/pci/drivers/%s/new_id", SYSFS_PATH, driver); + f = fopen(fn, "w"); + if (!f) + serror("Failed to add PCI id to %s driver (%s)", driver, fn); + + debug(5, "Adding ID to %s module: %04x %04x", driver, d->id.vendor, d->id.device); + fprintf(f, "%04x %04x", d->id.vendor, d->id.device); + fclose(f); + + /* Bind to driver */ + snprintf(fn, sizeof(fn), "%s/bus/pci/drivers/%s/bind", SYSFS_PATH, driver); + f = fopen(fn, "w"); + if (!f) + serror("Failed to bind PCI device to %s driver (%s)", driver, fn); + + debug(5, "Bind device to %s driver", driver); + fprintf(f, "%04x:%02x:%02x.%x\n", d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); + fclose(f); + + return 0; +} + +int pci_get_iommu_group(struct pci_device *d) +{ + int ret; + char *group, link[1024], sysfs[1024]; + + snprintf(sysfs, sizeof(sysfs), "%s/bus/pci/devices/%04x:%02x:%02x.%x/iommu_group", SYSFS_PATH, + d->slot.domain, d->slot.bus, d->slot.device, d->slot.function); + + ret = readlink(sysfs, link, sizeof(link)); + if (ret < 0) + return -1; + + group = basename(link); + + return atoi(group); +} diff --git a/fpga/lib/kernel/vfio.c b/fpga/lib/kernel/vfio.c new file mode 100644 index 000000000..018f42dcf --- /dev/null +++ b/fpga/lib/kernel/vfio.c @@ -0,0 +1,641 @@ +/** Virtual Function IO wrapper around kernel API + * + * @author Steffen Vogel + * @copyright 2017, 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 . + *********************************************************************************/ + +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include + +#include +#include + +#include "utils.h" +#include "log.h" + +#include "config.h" + +#include "kernel/kernel.h" +#include "kernel/vfio.h" +#include "kernel/pci.h" + +static const char *vfio_pci_region_names[] = { + "PCI_BAR0", // VFIO_PCI_BAR0_REGION_INDEX, + "PCI_BAR1", // VFIO_PCI_BAR1_REGION_INDEX, + "PCI_BAR2", // VFIO_PCI_BAR2_REGION_INDEX, + "PCI_BAR3", // VFIO_PCI_BAR3_REGION_INDEX, + "PCI_BAR4", // VFIO_PCI_BAR4_REGION_INDEX, + "PCI_BAR5", // VFIO_PCI_BAR5_REGION_INDEX, + "PCI_ROM", // VFIO_PCI_ROM_REGION_INDEX, + "PCI_CONFIG", // VFIO_PCI_CONFIG_REGION_INDEX, + "PCI_VGA" // VFIO_PCI_INTX_IRQ_INDEX, +}; + +static const char *vfio_pci_irq_names[] = { + "PCI_INTX", // VFIO_PCI_INTX_IRQ_INDEX, + "PCI_MSI", // VFIO_PCI_MSI_IRQ_INDEX, + "PCI_MSIX", // VFIO_PCI_MSIX_IRQ_INDEX, + "PCI_ERR", // VFIO_PCI_ERR_IRQ_INDEX, + "PCI_REQ" // VFIO_PCI_REQ_IRQ_INDEX, +}; + +/* Helpers */ +int vfio_get_iommu_name(int index, char *buf, size_t len) +{ + FILE *f; + char fn[256]; + + snprintf(fn, sizeof(fn), "/sys/kernel/iommu_groups/%d/name", index); + + f = fopen(fn, "r"); + if (!f) + return -1; + + int ret = fgets(buf, len, f) == buf ? 0 : -1; + + /* Remove trailing newline */ + char *c = strrchr(buf, '\n'); + if (c) + *c = 0; + + fclose(f); + + return ret; +} + +/* Destructors */ +int vfio_destroy(struct vfio_container *v) +{ + int ret; + + /* Release memory and close fds */ + list_destroy(&v->groups, (dtor_cb_t) vfio_group_destroy, true); + + /* Close container */ + ret = close(v->fd); + if (ret < 0) + return -1; + + debug(5, "VFIO: closed container: fd=%d", v->fd); + + return 0; +} + +int vfio_group_destroy(struct vfio_group *g) +{ + int ret; + + list_destroy(&g->devices, (dtor_cb_t) vfio_device_destroy, false); + + ret = ioctl(g->fd, VFIO_GROUP_UNSET_CONTAINER); + if (ret) + return ret; + + debug(5, "VFIO: released group from container: group=%u", g->index); + + ret = close(g->fd); + if (ret) + return ret; + + debug(5, "VFIO: closed group: group=%u, fd=%d", g->index, g->fd); + + return 0; +} + +int vfio_device_destroy(struct vfio_device *d) +{ + int ret; + + for (int i = 0; i < d->info.num_regions; i++) + vfio_unmap_region(d, i); + + ret = close(d->fd); + if (ret) + return ret; + + debug(5, "VFIO: closed device: name=%s, fd=%d", d->name, d->fd); + + free(d->mappings); + free(d->name); + + return 0; +} + +/* Constructors */ +int vfio_init(struct vfio_container *v) +{ + int ret; + + /* Initialize datastructures */ + memset(v, 0, sizeof(*v)); + + list_init(&v->groups); + + /* Load VFIO kernel module */ + if (kernel_module_load("vfio")) + error("Failed to load kernel module: %s", "vfio"); + + /* Open VFIO API */ + v->fd = open(VFIO_DEV("vfio"), O_RDWR); + if (v->fd < 0) + error("Failed to open VFIO container"); + + /* Check VFIO API version */ + v->version = ioctl(v->fd, VFIO_GET_API_VERSION); + if (v->version < 0 || v->version != VFIO_API_VERSION) + error("Failed to get VFIO version"); + + /* Check available VFIO extensions (IOMMU types) */ + v->extensions = 0; + for (int i = 1; i < VFIO_DMA_CC_IOMMU; i++) { + ret = ioctl(v->fd, VFIO_CHECK_EXTENSION, i); + if (ret < 0) + error("Failed to get VFIO extensions"); + else if (ret > 0) + v->extensions |= (1 << i); + } + + return 0; +} + +int vfio_group_attach(struct vfio_group *g, struct vfio_container *c, int index) +{ + int ret; + char buf[128]; + + g->index = index; + g->container = c; + + list_init(&g->devices); + + /* Open group fd */ + snprintf(buf, sizeof(buf), VFIO_DEV("%u"), g->index); + g->fd = open(buf, O_RDWR); + if (g->fd < 0) + serror("Failed to open VFIO group: %u", g->index); + + /* Claim group ownership */ + ret = ioctl(g->fd, VFIO_GROUP_SET_CONTAINER, &c->fd); + if (ret < 0) + serror("Failed to attach VFIO group to container"); + + /* Set IOMMU type */ + ret = ioctl(c->fd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); + if (ret < 0) + serror("Failed to set IOMMU type of container"); + + /* Check group viability and features */ + g->status.argsz = sizeof(g->status); + + ret = ioctl(g->fd, VFIO_GROUP_GET_STATUS, &g->status); + if (ret < 0) + serror("Failed to get VFIO group status"); + + if (!(g->status.flags & VFIO_GROUP_FLAGS_VIABLE)) + error("VFIO group is not available: bind all devices to the VFIO driver!"); + + list_push(&c->groups, g); + + return 0; +} + +int vfio_pci_attach(struct vfio_device *d, struct vfio_container *c, struct pci_device *pdev) +{ + char name[32]; + int ret; + + /* Load PCI bus driver for VFIO */ + if (kernel_module_load("vfio_pci")) + error("Failed to load kernel driver: %s", "vfio_pci"); + + /* Bind PCI card to vfio-pci driver*/ + ret = pci_attach_driver(pdev, "vfio-pci"); + if (ret) + error("Failed to attach device to driver"); + + /* Get IOMMU group of device */ + int index = pci_get_iommu_group(pdev); + if (index < 0) + error("Failed to get IOMMU group of device"); + + /* VFIO device name consists of PCI BDF */ + snprintf(name, sizeof(name), "%04x:%02x:%02x.%x", pdev->slot.domain, pdev->slot.bus, pdev->slot.device, pdev->slot.function); + + ret = vfio_device_attach(d, c, name, index); + if (ret < 0) + return ret; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) { + vfio_device_destroy(d); + return -1; + } + + d->pci_device = pdev; + + return 0; +} + +int vfio_device_attach(struct vfio_device *d, struct vfio_container *c, const char *name, int index) +{ + int ret; + struct vfio_group *g = NULL; + + /* Check if group already exists */ + for (size_t i = 0; i < list_length(&c->groups); i++) { + struct vfio_group *h = (struct vfio_group *) list_at(&c->groups, i); + + if (h->index == index) + g = h; + } + + if (!g) { + g = alloc(sizeof(struct vfio_group)); + + /* Aquire group ownership */ + ret = vfio_group_attach(g, c, index); + if (ret) + error("Failed to attach to IOMMU group: %u", index); + + info("Attached new group %u to VFIO container", g->index); + } + + d->group = g; + d->name = strdup(name); + + /* Open device fd */ + d->fd = ioctl(g->fd, VFIO_GROUP_GET_DEVICE_FD, d->name); + if (d->fd < 0) + serror("Failed to open VFIO device: %s", d->name); + + /* Get device info */ + d->info.argsz = sizeof(d->info); + + ret = ioctl(d->fd, VFIO_DEVICE_GET_INFO, &d->info); + if (ret < 0) + serror("Failed to get VFIO device info for: %s", d->name); + + d->irqs = alloc(d->info.num_irqs * sizeof(struct vfio_irq_info)); + d->regions = alloc(d->info.num_regions * sizeof(struct vfio_region_info)); + d->mappings = alloc(d->info.num_regions * sizeof(void *)); + + /* Get device regions */ + for (int i = 0; i < d->info.num_regions && i < 8; i++) { + struct vfio_region_info *region = &d->regions[i]; + + region->argsz = sizeof(*region); + region->index = i; + + ret = ioctl(d->fd, VFIO_DEVICE_GET_REGION_INFO, region); + if (ret < 0) + serror("Failed to get regions of VFIO device: %s", d->name); + } + + /* Get device irqs */ + for (int i = 0; i < d->info.num_irqs; i++) { + struct vfio_irq_info *irq = &d->irqs[i]; + + irq->argsz = sizeof(*irq); + irq->index = i; + + ret = ioctl(d->fd, VFIO_DEVICE_GET_IRQ_INFO, irq); + if (ret < 0) + serror("Failed to get IRQs of VFIO device: %s", d->name); + } + + list_push(&d->group->devices, d); + + return 0; +} + +int vfio_pci_reset(struct vfio_device *d) +{ + int ret; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return -1; + + size_t reset_infolen = sizeof(struct vfio_pci_hot_reset_info) + sizeof(struct vfio_pci_dependent_device) * 64; + size_t resetlen = sizeof(struct vfio_pci_hot_reset) + sizeof(int32_t) * 1; + + struct vfio_pci_hot_reset_info *reset_info = (struct vfio_pci_hot_reset_info *) alloc(reset_infolen); + struct vfio_pci_hot_reset *reset = (struct vfio_pci_hot_reset *) alloc(resetlen); + + reset_info->argsz = reset_infolen; + reset->argsz = resetlen; + + ret = ioctl(d->fd, VFIO_DEVICE_GET_PCI_HOT_RESET_INFO, reset_info); + if (ret) + return ret; + + debug(5, "VFIO: dependent devices for hot-reset:"); + for (int i = 0; i < reset_info->count; i++) { INDENT + struct vfio_pci_dependent_device *dd = &reset_info->devices[i]; + debug(5, "%04x:%02x:%02x.%01x: iommu_group=%u", dd->segment, dd->bus, PCI_SLOT(dd->devfn), PCI_FUNC(dd->devfn), dd->group_id); + + if (dd->group_id != d->group->index) + return -3; + } + + reset->count = 1; + reset->group_fds[0] = d->group->fd; + + ret = ioctl(d->fd, VFIO_DEVICE_PCI_HOT_RESET, reset); + + free(reset_info); + + return ret; +} + +int vfio_pci_msi_find(struct vfio_device *d, int nos[32]) +{ + int ret, idx, irq; + char *end, *col, *last, line[1024], name[13]; + FILE *f; + + f = fopen("/proc/interrupts", "r"); + if (!f) + return -1; + + for (int i = 0; i < 32; i++) + nos[i] = -1; + + /* For each line in /proc/interruipts */ + while (fgets(line, sizeof(line), f)) { + col = strtok(line, " "); + + /* IRQ number is in first column */ + irq = strtol(col, &end, 10); + if (col == end) + continue; + + /* Find last column of line */ + do { + last = col; + } while ((col = strtok(NULL, " "))); + + + ret = sscanf(last, "vfio-msi[%u](%12[0-9:])", &idx, name); + if (ret == 2) { + if (strstr(d->name, name) == d->name) + nos[idx] = irq; + } + } + + fclose(f); + + return 0; +} + +int vfio_pci_msi_deinit(struct vfio_device *d, int efds[32]) +{ + int ret, irq_setlen, irq_count = d->irqs[VFIO_PCI_MSI_IRQ_INDEX].count; + struct vfio_irq_set *irq_set; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return -1; + + irq_setlen = sizeof(struct vfio_irq_set) + sizeof(int) * irq_count; + irq_set = alloc(irq_setlen); + + irq_set->argsz = irq_setlen; + irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + irq_set->index = VFIO_PCI_MSI_IRQ_INDEX; + irq_set->count = irq_count; + irq_set->start = 0; + + for (int i = 0; i < irq_count; i++) { + close(efds[i]); + efds[i] = -1; + } + + memcpy(irq_set->data, efds, sizeof(int) * irq_count); + + ret = ioctl(d->fd, VFIO_DEVICE_SET_IRQS, irq_set); + if (ret) + return -4; + + free(irq_set); + + return irq_count; +} + +int vfio_pci_msi_init(struct vfio_device *d, int efds[32]) +{ + int ret, irq_setlen, irq_count = d->irqs[VFIO_PCI_MSI_IRQ_INDEX].count; + struct vfio_irq_set *irq_set; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return -1; + + irq_setlen = sizeof(struct vfio_irq_set) + sizeof(int) * irq_count; + irq_set = alloc(irq_setlen); + + irq_set->argsz = irq_setlen; + irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER; + irq_set->index = VFIO_PCI_MSI_IRQ_INDEX; + irq_set->start = 0; + irq_set->count = irq_count; + + /* Now set the new eventfds */ + for (int i = 0; i < irq_count; i++) { + efds[i] = eventfd(0, 0); + if (efds[i] < 0) + return -3; + } + memcpy(irq_set->data, efds, sizeof(int) * irq_count); + + ret = ioctl(d->fd, VFIO_DEVICE_SET_IRQS, irq_set); + if (ret) + return -4; + + free(irq_set); + + return irq_count; +} + +int vfio_pci_enable(struct vfio_device *d) +{ + int ret; + uint32_t reg; + off_t offset = ((off_t) VFIO_PCI_CONFIG_REGION_INDEX << 40) + PCI_COMMAND; + + /* Check if this is really a vfio-pci device */ + if (!(d->info.flags & VFIO_DEVICE_FLAGS_PCI)) + return -1; + + ret = pread(d->fd, ®, sizeof(reg), offset); + if (ret != sizeof(reg)) + return -1; + + /* Enable memory access and PCI bus mastering which is required for DMA */ + reg |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; + + ret = pwrite(d->fd, ®, sizeof(reg), offset); + if (ret != sizeof(reg)) + return -1; + + return 0; +} + +int vfio_device_reset(struct vfio_device *d) +{ + if (d->info.flags & VFIO_DEVICE_FLAGS_RESET) + return ioctl(d->fd, VFIO_DEVICE_RESET); + else + return -1; /* not supported by this device */ +} + +void vfio_dump(struct vfio_container *v) +{ + info("VFIO Version: %u", v->version); + info("VFIO Extensions: %#x", v->extensions); + + for (size_t i = 0; i < list_length(&v->groups); i++) { + struct vfio_group *g = (struct vfio_group *) list_at(&v->groups, i); + + info("VFIO Group %u, viable=%u, container=%d", g->index, + (g->status.flags & VFIO_GROUP_FLAGS_VIABLE) > 0, + (g->status.flags & VFIO_GROUP_FLAGS_CONTAINER_SET) > 0 + ); + + + for (size_t i = 0; i < list_length(&g->devices); i++) { INDENT + struct vfio_device *d = (struct vfio_device *) list_at(&g->devices, i); + + info("Device %s: regions=%u, irqs=%u, flags=%#x", d->name, + d->info.num_regions, + d->info.num_irqs, + d->info.flags + ); + + for (int i = 0; i < d->info.num_regions && i < 8; i++) { INDENT + struct vfio_region_info *region = &d->regions[i]; + + if (region->size > 0) + info("Region %u %s: size=%#llx, offset=%#llx, flags=%u", + region->index, (d->info.flags & VFIO_DEVICE_FLAGS_PCI) ? vfio_pci_region_names[i] : "", + region->size, + region->offset, + region->flags + ); + } + + for (int i = 0; i < d->info.num_irqs; i++) { INDENT + struct vfio_irq_info *irq = &d->irqs[i]; + + if (irq->count > 0) + info("IRQ %u %s: count=%u, flags=%u", + irq->index, (d->info.flags & VFIO_DEVICE_FLAGS_PCI ) ? vfio_pci_irq_names[i] : "", + irq->count, + irq->flags + ); + } + } + } +} + +void * vfio_map_region(struct vfio_device *d, int idx) +{ + struct vfio_region_info *r = &d->regions[idx]; + + if (!(r->flags & VFIO_REGION_INFO_FLAG_MMAP)) + return MAP_FAILED; + + d->mappings[idx] = mmap(NULL, r->size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_32BIT, d->fd, r->offset); + + return d->mappings[idx]; +} + +int vfio_unmap_region(struct vfio_device *d, int idx) +{ + int ret; + struct vfio_region_info *r = &d->regions[idx]; + + if (!d->mappings[idx]) + return -1; /* was not mapped */ + + debug(3, "VFIO: unmap region %u from device", idx); + + ret = munmap(d->mappings[idx], r->size); + if (ret) + return -2; + + d->mappings[idx] = NULL; + + return 0; +} + +int vfio_map_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len) +{ + int ret; + + if (len & 0xFFF) { + len += 0x1000; + len &= ~0xFFF; + } + + /* Super stupid allocator */ + if (phys == -1) { + phys = c->iova_next; + c->iova_next += len; + } + + struct vfio_iommu_type1_dma_map dma_map = { + .argsz = sizeof(struct vfio_iommu_type1_dma_map), + .vaddr = virt, + .iova = phys, + .size = len, + .flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE + }; + + ret = ioctl(c->fd, VFIO_IOMMU_MAP_DMA, &dma_map); + if (ret) + serror("Failed to create DMA mapping"); + + info("DMA map size=%#llx, iova=%#llx, vaddr=%#llx", dma_map.size, dma_map.iova, dma_map.vaddr); + + return 0; +} + +int vfio_unmap_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len) +{ + int ret; + + struct vfio_iommu_type1_dma_unmap dma_unmap = { + .argsz = sizeof(struct vfio_iommu_type1_dma_unmap), + .flags = 0, + .iova = phys, + .size = len, + }; + + ret = ioctl(c->fd, VFIO_IOMMU_UNMAP_DMA, &dma_unmap); + if (ret) + serror("Failed to unmap DMA mapping"); + + return 0; +} diff --git a/fpga/lib/list.c b/fpga/lib/list.c new file mode 100644 index 000000000..1550ee327 --- /dev/null +++ b/fpga/lib/list.c @@ -0,0 +1,203 @@ +/** A generic linked list + * + * Linked lists a used for several data structures in the code. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include "list.h" +#include "utils.h" + +/* Compare functions */ +static int cmp_lookup(const void *a, const void *b) { + const struct { + char *name; + } *obj = a; + + return strcmp(obj->name, b); +} + +static int cmp_contains(const void *a, const void *b) { + return a == b ? 0 : 1; +} + +static int cmp_sort(const void *a, const void *b, void *thunk) { + cmp_cb_t cmp = (cmp_cb_t) thunk; + + return cmp(*(void **) a, *(void **) b); +} + +int list_init(struct list *l) +{ + assert(l->state == STATE_DESTROYED); + + pthread_mutex_init(&l->lock, NULL); + + l->length = 0; + l->capacity = 0; + l->array = NULL; + + l->state = STATE_INITIALIZED; + + return 0; +} + +int list_destroy(struct list *l, dtor_cb_t destructor, bool release) +{ + pthread_mutex_lock(&l->lock); + + assert(l->state != STATE_DESTROYED); + + for (size_t i = 0; i < list_length(l); i++) { + void *p = list_at(l, i); + + if (destructor) + destructor(p); + if (release) + free(p); + } + + free(l->array); + + l->array = NULL; + + l->length = -1; + l->capacity = 0; + + pthread_mutex_unlock(&l->lock); + pthread_mutex_destroy(&l->lock); + + l->state = STATE_DESTROYED; + + return 0; +} + +void list_push(struct list *l, void *p) +{ + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + /* Resize array if out of capacity */ + if (l->length >= l->capacity) { + l->capacity += LIST_CHUNKSIZE; + l->array = realloc(l->array, l->capacity * sizeof(void *)); + } + + l->array[l->length] = p; + l->length++; + + pthread_mutex_unlock(&l->lock); +} + +void list_remove(struct list *l, void *p) +{ + int removed = 0; + + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + for (size_t i = 0; i < list_length(l); i++) { + if (l->array[i] == p) + removed++; + else + l->array[i - removed] = l->array[i]; + } + + l->length -= removed; + + pthread_mutex_unlock(&l->lock); +} + +void * list_lookup(struct list *l, const char *name) +{ + return list_search(l, cmp_lookup, (void *) name); +} + +int list_contains(struct list *l, void *p) +{ + return list_count(l, cmp_contains, p); +} + +int list_count(struct list *l, cmp_cb_t cmp, void *ctx) +{ + int c = 0; + + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + for (size_t i = 0; i < list_length(l); i++) { + void *e = list_at(l, i); + + if (cmp(e, ctx) == 0) + c++; + } + + pthread_mutex_unlock(&l->lock); + + return c; +} + +void * list_search(struct list *l, cmp_cb_t cmp, void *ctx) +{ + void *e; + + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + for (size_t i = 0; i < list_length(l); i++) { + e = list_at(l, i); + if (cmp(e, ctx) == 0) + goto out; + } + + e = NULL; /* not found */ + +out: pthread_mutex_unlock(&l->lock); + + return e; +} + +void list_sort(struct list *l, cmp_cb_t cmp) +{ + pthread_mutex_lock(&l->lock); + + assert(l->state == STATE_INITIALIZED); + + qsort_r(l->array, l->length, sizeof(void *), cmp_sort, (void *) cmp); + + pthread_mutex_unlock(&l->lock); +} + +int list_set(struct list *l, int index, void *value) +{ + if (index >= l->length) + return -1; + + l->array[index] = value; + + return 0; +} diff --git a/fpga/lib/log.c b/fpga/lib/log.c new file mode 100644 index 000000000..b9af2d1b6 --- /dev/null +++ b/fpga/lib/log.c @@ -0,0 +1,309 @@ +/** Logging and debugging routines + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "log.h" +#include "utils.h" + +#ifdef ENABLE_OPAL_ASYNC +/* Define RTLAB before including OpalPrint.h for messages to be sent + * to the OpalDisplay. Otherwise stdout will be used. */ + #define RTLAB + #include "OpalPrint.h" +#endif + +struct log *global_log; + +/* We register a default log instance */ +__attribute__((constructor)) +void register_default_log() +{ + int ret; + static struct log default_log; + + ret = log_init(&default_log, V, LOG_ALL); + if (ret) + error("Failed to initalize log"); + + ret = log_start(&default_log); + if (ret) + error("Failed to start log"); +} + +/** List of debug facilities as strings */ +static const char *facilities_strs[] = { + "pool", /* LOG_POOL */ + "queue", /* LOG_QUEUE */ + "config", /* LOG_CONFIG */ + "hook", /* LOG_HOOK */ + "path", /* LOG_PATH */ + "node", /* LOG_NODE */ + "mem", /* LOG_MEM */ + "web", /* LOG_WEB */ + "api", /* LOG_API */ + "log", /* LOG_LOG */ + "vfio", /* LOG_VFIO */ + "pci", /* LOG_PCI */ + "xil", /* LOG_XIL */ + "tc", /* LOG_TC */ + "if", /* LOG_IF */ + "advio", /* LOG_ADVIO */ + + /* Node-types */ + "socket", /* LOG_SOCKET */ + "file", /* LOG_FILE */ + "fpga", /* LOG_FPGA */ + "ngsi", /* LOG_NGSI */ + "websocket", /* LOG_WEBSOCKET */ + "opal", /* LOG_OPAL */ +}; + +#ifdef __GNUC__ +/** The current log indention level (per thread!). */ +static __thread int indent = 0; + +int log_indent(int levels) +{ + int old = indent; + indent += levels; + return old; +} + +int log_noindent() +{ + int old = indent; + indent = 0; + return old; +} + +void log_outdent(int *old) +{ + indent = *old; +} +#endif + +static void log_resize(int signal, siginfo_t *sinfo, void *ctx) +{ + int ret; + + ret = ioctl(STDOUT_FILENO, TIOCGWINSZ, &global_log->window); + if (ret) + return; + + global_log->width = global_log->window.ws_col - 25; + if (global_log->prefix) + global_log->width -= strlenp(global_log->prefix); + + debug(LOG_LOG | 15, "New terminal size: %dx%x", global_log->window.ws_row, global_log->window.ws_col); +} + +int log_init(struct log *l, int level, long facilitites) +{ + int ret; + + /* Register this log instance globally */ + global_log = l; + + l->level = level; + l->syslog = 0; + l->facilities = facilitites; + l->file = stderr; + l->path = NULL; + + l->prefix = getenv("VILLAS_LOG_PREFIX"); + + /* Register signal handler which is called whenever the + * terminal size changes. */ + if (l->file == stderr) { + struct sigaction sa_resize = { + .sa_flags = SA_SIGINFO, + .sa_sigaction = log_resize + }; + + sigemptyset(&sa_resize.sa_mask); + + ret = sigaction(SIGWINCH, &sa_resize, NULL); + if (ret) + return ret; + + /* Try to get initial window size */ + ioctl(STDERR_FILENO, TIOCGWINSZ, &global_log->window); + } + else { + l->window.ws_col = LOG_WIDTH; + l->window.ws_row = LOG_HEIGHT; + } + + l->width = l->window.ws_col - 25; + if (l->prefix) + l->width -= strlenp(l->prefix); + + l->state = STATE_INITIALIZED; + + return 0; +} + +int log_start(struct log *l) +{ + if (l->path) { + l->file = fopen(l->path, "a+");; + if (!l->file) { + l->file = stderr; + error("Failed to open log file '%s'", l->path); + } + } + else + l->file = stderr; + + l->state = STATE_STARTED; + + if (l->syslog) { + openlog(NULL, LOG_PID, LOG_DAEMON); + } + + debug(LOG_LOG | 5, "Log sub-system started: level=%d, faciltities=%#lx, path=%s", l->level, l->facilities, l->path); + + return 0; +} + +int log_stop(struct log *l) +{ + if (l->state != STATE_STARTED) + return 0; + + if (l->file != stderr && l->file != stdout) { + fclose(l->file); + } + + if (l->syslog) { + closelog(); + } + + l->state = STATE_STOPPED; + + return 0; +} + +int log_destroy(struct log *l) +{ + default_log.epoch = l->epoch; + + global_log = &default_log; + + l->state = STATE_DESTROYED; + + return 0; +} + +int log_set_facility_expression(struct log *l, const char *expression) +{ + bool negate; + char *copy, *token; + long mask = 0, facilities = 0; + + copy = strdup(expression); + token = strtok(copy, ","); + + while (token != NULL) { + if (token[0] == '!') { + token++; + negate = true; + } + else + negate = false; + + /* Check for some classes */ + if (!strcmp(token, "all")) + mask = LOG_ALL; + else if (!strcmp(token, "nodes")) + mask = LOG_NODES; + else if (!strcmp(token, "kernel")) + mask = LOG_KERNEL; + else { + for (int ind = 0; ind < ARRAY_LEN(facilities_strs); ind++) { + if (!strcmp(token, facilities_strs[ind])) { + mask = (1 << (ind+8)); + goto found; + } + } + + error("Invalid log class '%s'", token); + } + +found: if (negate) + facilities &= ~mask; + else + facilities |= mask; + + token = strtok(NULL, ","); + } + + l->facilities = facilities; + + free(copy); + + return l->facilities; +} + +void log_print(struct log *l, const char *lvl, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_vprint(l, lvl, fmt, ap); + va_end(ap); +} + +void log_vprint(struct log *l, const char *lvl, const char *fmt, va_list ap) +{ + char *buf = alloc(512); + + /* Optional prefix */ + if (l->prefix) + strcatf(&buf, "%s", l->prefix); + + /* Indention */ +#ifdef __GNUC__ + for (int i = 0; i < indent; i++) + strcatf(&buf, "%s ", BOX_UD); + + strcatf(&buf, "%s ", BOX_UDR); +#endif + + /* Format String */ + vstrcatf(&buf, fmt, ap); + + /* Output */ +#ifdef ENABLE_OPAL_ASYNC + OpalPrint("VILLASfpga: %s\n", buf); +#endif + fprintf(l->file ? l->file : stderr, "%s\n", buf); + + free(buf); +} diff --git a/fpga/lib/log_config.c b/fpga/lib/log_config.c new file mode 100644 index 000000000..3b6cf7cc4 --- /dev/null +++ b/fpga/lib/log_config.c @@ -0,0 +1,85 @@ +/** Logging routines that depend on jansson. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include + +#include "config.h" +#include "log.h" +#include "log_config.h" +#include "utils.h" +#include "string.h" + +int log_parse(struct log *l, json_t *cfg) +{ + const char *facilities = NULL; + const char *path = NULL; + int ret; + + json_error_t err; + + ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: s, s?: s, s?: b }", + "level", &l->level, + "file", &path, + "facilities", &facilities, + "syslog", &l->syslog + ); + if (ret) + jerror(&err, "Failed to parse logging configuration"); + + if (path) + l->path = strdup(path); + + if (facilities) + log_set_facility_expression(l, facilities); + + l->state = STATE_PARSED; + + return 0; +} + +void jerror(json_error_t *err, const char *fmt, ...) +{ + va_list ap; + char *buf = NULL; + + struct log *l = global_log ? global_log : &default_log; + + va_start(ap, fmt); + vstrcatf(&buf, fmt, ap); + va_end(ap); + + log_print(l, LOG_LVL_ERROR, "%s:", buf); + { INDENT + log_print(l, LOG_LVL_ERROR, "%s in %s:%d:%d", err->text, err->source, err->line, err->column); + + if (l->syslog) + syslog(LOG_ERR, "%s in %s:%d:%d", err->text, err->source, err->line, err->column); + } + + free(buf); + + killme(SIGABRT); + pause(); +} diff --git a/fpga/lib/log_helper.c b/fpga/lib/log_helper.c new file mode 100644 index 000000000..6f99e3696 --- /dev/null +++ b/fpga/lib/log_helper.c @@ -0,0 +1,138 @@ +/** Logging and debugging routines + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include + +#include "utils.h" +#include "log.h" + +void debug(long class, const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + int lvl = class & 0xFF; + int fac = class & ~0xFF; + + if (((fac == 0) || (fac & l->facilities)) && (lvl <= l->level)) { + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_DEBUG, fmt, ap); + + if (l->syslog) + syslog(LOG_DEBUG, fmt, ap); + + va_end(ap); + } +} + +void info(const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_INFO, fmt, ap); + + if (l->syslog) + syslog(LOG_INFO, fmt, ap); + + va_end(ap); +} + +void warn(const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_WARN, fmt, ap); + + if (l->syslog) + syslog(LOG_WARNING, fmt, ap); + + va_end(ap); +} + +void stats(const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_STATS, fmt, ap); + + if (l->syslog) + syslog(LOG_INFO, fmt, ap); + + va_end(ap); +} + +void error(const char *fmt, ...) +{ + va_list ap; + + struct log *l = global_log; + + va_start(ap, fmt); + + log_vprint(l, LOG_LVL_ERROR, fmt, ap); + + if (l->syslog) + syslog(LOG_ERR, fmt, ap); + + va_end(ap); + + killme(SIGABRT); + pause(); +} + +void serror(const char *fmt, ...) +{ + va_list ap; + char *buf = NULL; + + struct log *l = global_log; + + va_start(ap, fmt); + vstrcatf(&buf, fmt, ap); + va_end(ap); + + log_print(l, LOG_LVL_ERROR, "%s: %m (%u)", buf, errno); + + if (l->syslog) + syslog(LOG_ERR, "%s: %m (%u)", buf, errno); + + free(buf); + + killme(SIGABRT); + pause(); +} diff --git a/fpga/lib/plugin.c b/fpga/lib/plugin.c new file mode 100644 index 000000000..908d8eea4 --- /dev/null +++ b/fpga/lib/plugin.c @@ -0,0 +1,111 @@ +/** Loadable / plugin support. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include "plugin.h" + +/** Global list of all known plugins */ +struct list plugins = { .state = STATE_DESTROYED }; + +LIST_INIT_STATIC(&plugins) + +int plugin_init(struct plugin *p) +{ + assert(p->state == STATE_DESTROYED); + + p->state = STATE_INITIALIZED; + + return 0; +} + +int plugin_parse(struct plugin *p, json_t *cfg) +{ + const char *path; + + path = json_string_value(cfg); + if (!path) + return -1; + + p->path = strdup(path); + + return 0; +} + +int plugin_load(struct plugin *p) +{ + p->handle = dlopen(p->path, RTLD_NOW); + if (!p->path) + return -1; + + p->state = STATE_LOADED; + + return 0; +} + +int plugin_unload(struct plugin *p) +{ + int ret; + + assert(p->state == STATE_LOADED); + + ret = dlclose(p->handle); + if (ret) + return -1; + + p->state = STATE_UNLOADED; + + return 0; +} + +int plugin_destroy(struct plugin *p) +{ + assert(p->state != STATE_DESTROYED && p->state != STATE_LOADED); + + if (p->path) + free(p->path); + + return 0; +} + +struct plugin * plugin_lookup(enum plugin_type type, const char *name) +{ + for (size_t i = 0; i < list_length(&plugins); i++) { + struct plugin *p = (struct plugin *) list_at(&plugins, i); + + if (p->type == type && strcmp(p->name, name) == 0) + return p; + } + + return NULL; +} + +void plugin_dump(enum plugin_type type) +{ + for (size_t i = 0; i < list_length(&plugins); i++) { + struct plugin *p = (struct plugin *) list_at(&plugins, i); + + if (p->type == type) + printf(" - %-13s: %s\n", p->name, p->description); + } +} diff --git a/fpga/lib/utils.c b/fpga/lib/utils.c new file mode 100644 index 000000000..2e295191d --- /dev/null +++ b/fpga/lib/utils.c @@ -0,0 +1,401 @@ +/** General purpose helper functions. + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "utils.h" + +pthread_t main_thread; + +void print_copyright() +{ + printf("VILLASfpga %s (built on %s %s)\n", + CLR_BLU(BUILDID), CLR_MAG(__DATE__), CLR_MAG(__TIME__)); + printf(" Copyright 2014-2017, Institute for Automation of Complex Power Systems, EONERC\n"); + printf(" Steffen Vogel \n"); +} + +void print_version() +{ + printf("%s\n", BUILDID); +} + +int version_parse(const char *s, struct version *v) +{ + return sscanf(s, "%u.%u", &v->major, &v->minor) != 2; +} + +int version_cmp(struct version *a, struct version *b) { + int major = a->major - b->major; + int minor = a->minor - b->minor; + + return major ? major : minor; +} + +double box_muller(float m, float s) +{ + double x1, x2, y1; + static double y2; + static int use_last = 0; + + if (use_last) { /* use value from previous call */ + y1 = y2; + use_last = 0; + } + else { + double w; + do { + x1 = 2.0 * randf() - 1.0; + x2 = 2.0 * randf() - 1.0; + w = x1*x1 + x2*x2; + } while (w >= 1.0); + + w = sqrt(-2.0 * log(w) / w); + y1 = x1 * w; + y2 = x2 * w; + use_last = 1; + } + + return m + y1 * s; +} + +double randf() +{ + return (double) random() / RAND_MAX; +} + +char * strcatf(char **dest, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vstrcatf(dest, fmt, ap); + va_end(ap); + + return *dest; +} + +char * vstrcatf(char **dest, const char *fmt, va_list ap) +{ + char *tmp; + int n = *dest ? strlen(*dest) : 0; + int i = vasprintf(&tmp, fmt, ap); + + *dest = (char *)(realloc(*dest, n + i + 1)); + if (*dest != NULL) + strncpy(*dest+n, tmp, i + 1); + + free(tmp); + + return *dest; +} + +#ifdef __linux__ + +void cpuset_to_integer(cpu_set_t *cset, uintmax_t *set) +{ + *set = 0; + for (int i = 0; i < MIN(sizeof(*set) * 8, CPU_SETSIZE); i++) { + if (CPU_ISSET(i, cset)) + *set |= 1ULL << i; + } +} + +void cpuset_from_integer(uintmax_t set, cpu_set_t *cset) +{ + CPU_ZERO(cset); + for (int i = 0; i < MIN(sizeof(set) * 8, CPU_SETSIZE); i++) { + if (set & (1L << i)) + CPU_SET(i, cset); + } +} + +/* From: https://github.com/mmalecki/util-linux/blob/master/lib/cpuset.c */ +static const char *nexttoken(const char *q, int sep) +{ + if (q) + q = strchr(q, sep); + if (q) + q++; + return q; +} + +int cpulist_parse(const char *str, cpu_set_t *set, int fail) +{ + const char *p, *q; + int r = 0; + + q = str; + CPU_ZERO(set); + + while (p = q, q = nexttoken(q, ','), p) { + unsigned int a; /* beginning of range */ + unsigned int b; /* end of range */ + unsigned int s; /* stride */ + const char *c1, *c2; + char c; + + if ((r = sscanf(p, "%u%c", &a, &c)) < 1) + return 1; + b = a; + s = 1; + + c1 = nexttoken(p, '-'); + c2 = nexttoken(p, ','); + if (c1 != NULL && (c2 == NULL || c1 < c2)) { + if ((r = sscanf(c1, "%u%c", &b, &c)) < 1) + return 1; + c1 = nexttoken(c1, ':'); + if (c1 != NULL && (c2 == NULL || c1 < c2)) { + if ((r = sscanf(c1, "%u%c", &s, &c)) < 1) + return 1; + if (s == 0) + return 1; + } + } + + if (!(a <= b)) + return 1; + while (a <= b) { + if (fail && (a >= CPU_SETSIZE)) + return 2; + CPU_SET(a, set); + a += s; + } + } + + if (r == 2) + return 1; + + return 0; +} + +char *cpulist_create(char *str, size_t len, cpu_set_t *set) +{ + size_t i; + char *ptr = str; + int entry_made = 0; + + for (i = 0; i < CPU_SETSIZE; i++) { + if (CPU_ISSET(i, set)) { + int rlen; + size_t j, run = 0; + entry_made = 1; + for (j = i + 1; j < CPU_SETSIZE; j++) { + if (CPU_ISSET(j, set)) + run++; + else + break; + } + if (!run) + rlen = snprintf(ptr, len, "%zd,", i); + else if (run == 1) { + rlen = snprintf(ptr, len, "%zd,%zd,", i, i + 1); + i++; + } else { + rlen = snprintf(ptr, len, "%zd-%zd,", i, i + run); + i += run; + } + if (rlen < 0 || (size_t) rlen + 1 > len) + return NULL; + ptr += rlen; + if (rlen > 0 && len > (size_t) rlen) + len -= rlen; + else + len = 0; + } + } + ptr -= entry_made; + *ptr = '\0'; + + return str; +} +#endif /* __linux__ */ + +void * alloc(size_t bytes) +{ + void *p = malloc(bytes); + if (!p) + error("Failed to allocate memory"); + + memset(p, 0, bytes); + + return p; +} + +void * memdup(const void *src, size_t bytes) +{ + void *dst = alloc(bytes); + + memcpy(dst, src, bytes); + + return dst; +} + +ssize_t read_random(char *buf, size_t len) +{ + int fd; + ssize_t bytes, total; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + + bytes = 0; + total = 0; + while (total < len) { + bytes = read(fd, buf + total, len - total); + if (bytes < 0) + break; + + total += bytes; + } + + close(fd); + + return bytes; +} + +void rdtsc_sleep(uint64_t nanosecs, uint64_t start) +{ + uint64_t cycles; + + /** @todo Replace the hard coded CPU clock frequency */ + cycles = (double) nanosecs / (1e9 / 3392389000); + + if (start == 0) + start = rdtsc(); + + do { + __asm__("nop"); + } while (rdtsc() - start < cycles); +} + +/* Setup exit handler */ +int signals_init(void (*cb)(int signal, siginfo_t *sinfo, void *ctx)) +{ + int ret; + + info("Initialize signals"); + + struct sigaction sa_quit = { + .sa_flags = SA_SIGINFO | SA_NODEFER, + .sa_sigaction = cb + }; + + struct sigaction sa_chld = { + .sa_flags = 0, + .sa_handler = SIG_IGN + }; + + main_thread = pthread_self(); + + sigemptyset(&sa_quit.sa_mask); + + ret = sigaction(SIGINT, &sa_quit, NULL); + if (ret) + return ret; + + ret = sigaction(SIGTERM, &sa_quit, NULL); + if (ret) + return ret; + + ret = sigaction(SIGALRM, &sa_quit, NULL); + if (ret) + return ret; + + ret = sigaction(SIGCHLD, &sa_chld, NULL); + if (ret) + return ret; + + return 0; +} + +void killme(int sig) +{ + /* Send only to main thread in case the ID was initilized by signals_init() */ + if (main_thread) + pthread_kill(main_thread, sig); + else + kill(0, sig); +} + +pid_t spawn(const char* name, char *const argv[]) +{ + pid_t pid; + + pid = fork(); + switch (pid) { + case -1: return -1; + case 0: return execvp(name, (char * const*) argv); + } + + return pid; +} + +size_t strlenp(const char *str) +{ + size_t sz = 0; + + for (const char *d = str; *d; d++) { + const unsigned char *c = (const unsigned char *) d; + + if (isprint(*c)) + sz++; + else if (c[0] == '\b') + sz--; + else if (c[0] == '\t') + sz += 4; /* tab width == 4 */ + /* CSI sequence */ + else if (c[0] == '\e' && c[1] == '[') { + c += 2; + while (*c && *c != 'm') + c++; + } + /* UTF-8 */ + else if (c[0] >= 0xc2 && c[0] <= 0xdf) { + sz++; + c += 1; + } + else if (c[0] >= 0xe0 && c[0] <= 0xef) { + sz++; + c += 2; + } + else if (c[0] >= 0xf0 && c[0] <= 0xf4) { + sz++; + c += 3; + } + + d = (const char *) c; + } + + return sz; +} diff --git a/fpga/lib/vlnv.c b/fpga/lib/vlnv.c new file mode 100644 index 000000000..e6c693302 --- /dev/null +++ b/fpga/lib/vlnv.c @@ -0,0 +1,64 @@ +/** Vendor, Library, Name, Version (VLNV) tag + * + * @author Steffen Vogel + * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include "fpga/vlnv.h" +#include "fpga/ip.h" + +struct fpga_ip * fpga_vlnv_lookup(struct list *l, struct fpga_vlnv *v) +{ + return (struct fpga_ip *) list_search(l, (cmp_cb_t) fpga_vlnv_cmp, v); +} + +int fpga_vlnv_cmp(struct fpga_vlnv *a, struct fpga_vlnv *b) +{ + return ((!a->vendor || !b->vendor || !strcmp(a->vendor, b->vendor )) && + (!a->library || !b->library || !strcmp(a->library, b->library)) && + (!a->name || !b->name || !strcmp(a->name, b->name )) && + (!a->version || !b->version || !strcmp(a->version, b->version))) ? 0 : 1; +} + +int fpga_vlnv_parse(struct fpga_vlnv *c, const char *vlnv) +{ + char *tmp = strdup(vlnv); + + c->vendor = strdup(strtok(tmp, ":")); + c->library = strdup(strtok(NULL, ":")); + c->name = strdup(strtok(NULL, ":")); + c->version = strdup(strtok(NULL, ":")); + + free(tmp); + + return 0; +} + +int fpga_vlnv_destroy(struct fpga_vlnv *v) +{ + free(v->vendor); + free(v->library); + free(v->name); + free(v->version); + + return 0; +} diff --git a/fpga/scripts/hwdef-parse.py b/fpga/scripts/hwdef-parse.py new file mode 100755 index 000000000..f114d3a34 --- /dev/null +++ b/fpga/scripts/hwdef-parse.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +""" +Author: Daniel Krebs +Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC +License: GNU General Public License (version 3) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + +from lxml import etree +import zipfile +import sys +import re +import json + +whitelist = [ + [ 'xilinx.com', 'ip', 'axi_timer' ], + [ 'xilinx.com', 'ip', 'axis_switch' ], + [ 'xilinx.com', 'ip', 'axi_fifo_mm_s' ], + [ 'xilinx.com', 'ip', 'axi_dma' ], + [ 'acs.eonerc.rwth-aachen.de', 'user', 'axi_pcie_intc' ], + [ 'acs.eonerc.rwth-aachen.de', 'user', 'rtds_axis' ], + [ 'acs.eonerc.rwth-aachen.de', 'hls' ], + [ 'acs.eonerc.rwth-aachen.de', 'sysgen' ], + [ 'xilinx.com', 'ip', 'axi_gpio' ], + [ 'xilinx.com', 'ip', 'axi_bram_ctrl' ] +] + +# List of VLNI ids of AXI4-Stream infrastructure IP cores which do not alter data +# see PG085 (AXI4-Stream Infrastructure IP Suite v2.2) +axi_converter_whitelist = [ + [ 'xilinx.com', 'ip', 'axis_subset_converter' ], + [ 'xilinx.com', 'ip', 'axis_clock_converter' ], + [ 'xilinx.com', 'ip', 'axis_register_slice' ], + [ 'xilinx.com', 'ip', 'axis_data_fifo' ], + [ 'xilinx.com', 'ip', 'axis_dwidth_converter' ], + [ 'xilinx.com', 'ip', 'axis_register_slice' ] +] + +opponent = { + 'MASTER' : ('SLAVE', 'TARGET'), + 'SLAVE' : ('MASTER', 'INITIATOR'), + 'INITIATOR' : ('TARGET', 'SLAVE'), + 'TARGET' : ('INITIATOR', 'MASTER') +} + +def port_trace(root, signame, idx): + pass + +def port_find_driver(root, signame): + pass + +def bus_trace(root, busname, type, whitelist): + module = root.xpath('.//MODULE[.//BUSINTERFACE[@BUSNAME="{}" and (@TYPE="{}" or @TYPE="{}")]]'.format(busname, type[0], type[1])) + + vlnv = module[0].get('VLNV') + instance = module[0].get('INSTANCE') + + if vlnv_match(vlnv, whitelist): + return instance + elif vlnv_match(vlnv, axi_converter_whitelist): + next_bus = module[0].xpath('.//BUSINTERFACE[@TYPE="{}" or @TYPE="{}"]'.format(opponent[type[0]][0], opponent[type[0]][1])) + next_busname = next_bus[0].get('BUSNAME') + + return bus_trace(root, next_busname, type, whitelist) + else: + raise TypeError('Unsupported AXI4-Stream IP core: %s (%s)' % (instance, vlnv)) + +def vlnv_match(vlnv, whitelist): + c = vlnv.split(':') + + for w in whitelist: + if c[:len(w)] == w: + return True + + return False + +if len(sys.argv) < 2: + print('Usage: {} path/to/*.hwdef'.format(sys.argv[0])) + print(' {} path/to/*.xml'.format(sys.argv[0])) + sys.exit(1) + +try: + # read .hwdef which is actually a zip-file + zip = zipfile.ZipFile(sys.argv[1], 'r') + hwh = zip.read('top.hwh') +except: + f = open(sys.argv[1], 'r') + hwh = f.read() + +# parse .hwh file which is actually XML +try: + root = etree.XML(hwh) +except: + print('Bad format of "{}"! Did you choose the right file?'.format(sys.argv[1])) + sys.exit(1) + +ips = {} + +# find all whitelisted modules +modules = root.find('.//MODULES') +for module in modules: + instance = module.get('INSTANCE') + vlnv = module.get('VLNV') + + # Ignroing unkown + if not vlnv_match(vlnv, whitelist): + continue + + ips[instance] = { + 'vlnv' : vlnv, + 'irqs' : { }, + 'ports' : { } + } + +# find PCI-e module to extract memory map +pcie = root.find('.//MODULE[@MODTYPE="axi_pcie"]') +mmap = pcie.find('.//MEMORYMAP') +for mrange in mmap: + instance = mrange.get('INSTANCE') + + if instance in ips: + ips[instance]['baseaddr'] = int(mrange.get('BASEVALUE'), 16); + ips[instance]['highaddr'] = int(mrange.get('HIGHVALUE'), 16); + +# find AXI-Stream switch port mapping +switch = root.find('.//MODULE[@MODTYPE="axis_switch"]') +busifs = switch.find('.//BUSINTERFACES') +for busif in busifs: + if busif.get('VLNV') != 'xilinx.com:interface:axis:1.0': + continue + + busname = busif.get('BUSNAME') + name = busif.get('NAME') + type = busif.get('TYPE') + + r = re.compile('(M|S)([0-9]+)_AXIS') + m = r.search(name) + + port = int(m.group(2)) + + ep = bus_trace(root, busname, opponent[type], whitelist) + + if ep in ips: + ips[ep]['ports'][type.lower()] = port + +# find Interrupt assignments +intc = root.find('.//MODULE[@MODTYPE="axi_pcie_intc"]') +intr = intc.xpath('.//PORT[@NAME="intr" and @DIR="I"]')[0] +concat = root.xpath('.//MODULE[@MODTYPE="xlconcat" and .//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(intr.get('SIGNAME')))[0] +ports = concat.xpath('.//PORT[@DIR="I"]') + +for port in ports: + name = port.get('NAME') + signame = port.get('SIGNAME') + + # Skip unconnected IRQs + if not signame: + continue + + r = re.compile('In([0-9+])') + m = r.search(name) + + irq = int(m.group(1)) + ip = root.xpath('.//MODULE[.//PORT[@SIGNAME="{}" and @DIR="O"]]'.format(signame))[0] + + instance = ip.get('INSTANCE') + vlnv = ip.get('VLNV') + + port = ip.xpath('.//PORT[@SIGNAME="{}" and @DIR="O"]'.format(signame))[0] + irqname = port.get('NAME') + + if instance in ips: + ips[instance]['irqs'][irqname] = irq + +# Find BRAM storage depths (size) +brams = root.xpath('.//MODULE[@MODTYPE="axi_bram_ctrl"]') +for bram in brams: + instance = bram.get('INSTANCE') + + width = bram.find('.//PARAMETER[@NAME="DATA_WIDTH"]').get('VALUE') + depth = bram.find('.//PARAMETER[@NAME="MEM_DEPTH"]').get('VALUE') + + size = int(width) * int(depth) / 8 + + if instance in ips: + ips[instance]['size'] = int(size) + +print(json.dumps(ips, indent=2)) diff --git a/fpga/scripts/hwdef-villas.xml b/fpga/scripts/hwdef-villas.xml new file mode 100644 index 000000000..91bef0b97 --- /dev/null +++ b/fpga/scripts/hwdef-villas.xml @@ -0,0 +1,8119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fpga/scripts/rebind_device.sh b/fpga/scripts/rebind_device.sh new file mode 100755 index 000000000..67fb75bbe --- /dev/null +++ b/fpga/scripts/rebind_device.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# +# Detach and rebind a PCI device to a PCI kernel driver +# +# @author Steffen Vogel +# @copyright 2017, Institute for Automation of Complex Power Systems, EONERC +# @license GNU General Public License (version 3) +# +# VILLASnode +# +# 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 . +################################################################################## + +if [ "$#" -ne 2 ]; then + echo "usage: $0 BUS:DEV:FNC DRIVER" + exit 1 +fi + +BDF=$1 +DRIVER=$2 + +VENDOR=$(cut -b3- /sys/bus/pci/devices/${BDF}/vendor) +DEVICE=$(cut -b3- /sys/bus/pci/devices/${BDF}/device) + +SYSFS_DEVICE=/sys/bus/pci/devices/${BDF} +SYSFS_DRIVER=/sys/bus/pci/drivers/${DRIVER} + +echo "Device: $VENDOR $DEVICE $BDF" + +if [ -L "${SYSFS_DEVICE}/driver" ] && [ -d "${SYSFS_DEVICE}/driver" ]; then + echo ${BDF} > ${SYSFS_DEVICE}/driver/unbind +fi + +echo "${VENDOR} ${DEVICE}" > ${SYSFS_DRIVER}/new_id +echo "${BDF}" > ${SYSFS_DRIVER}/bind +echo "${VENDOR} ${DEVICE}" > ${SYSFS_DRIVER}/remove_id diff --git a/fpga/scripts/reset_pci_device.sh b/fpga/scripts/reset_pci_device.sh new file mode 100755 index 000000000..ae3b14486 --- /dev/null +++ b/fpga/scripts/reset_pci_device.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Reset PCI devices like FPGAs after a reflash +# +# @author Steffen Vogel +# @copyright 2017, Institute for Automation of Complex Power Systems, EONERC +# @license GNU General Public License (version 3) +# +# VILLASnode +# +# 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 . +################################################################################## + +if [ "$#" -ne 1 ]; then + echo "usage: $0 BUS:DEV:FNC" + exit 1 +fi + +BDF=$1 + +echo "1" > /sys/bus/pci/devices/$BDF/remove +echo "1" > /sys/bus/pci/rescan +echo "1" > /sys/bus/pci/devices/$BDF/enable + diff --git a/fpga/tests/CMakeLists.txt b/fpga/tests/CMakeLists.txt new file mode 100644 index 000000000..9cd6d7317 --- /dev/null +++ b/fpga/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +set(SOURCES + main.c + dma.c + fifo.c + hls.c + intc.c + rtds_rtt.c + tmrctr.c + xsg.c +) + +add_executable(unit-tests ${SOURCES}) + +find_package(Criterion REQUIRED) + +target_include_directories(unit-tests PUBLIC + ../include + ${CRITERION_INCLUDE_DIRECTORIES} +) + +target_link_libraries(unit-tests PUBLIC + villas-fpga + ${CRITERION_LIBRARIES} +) diff --git a/fpga/tests/dma.c b/fpga/tests/dma.c new file mode 100644 index 000000000..894ef60e5 --- /dev/null +++ b/fpga/tests/dma.c @@ -0,0 +1,94 @@ +/** DMA unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +extern struct fpga_card *card; + +Test(fpga, dma, .description = "DMA") +{ + int ret = -1; + struct dma_mem mem, src, dst; + + for (size_t i = 0; i < list_length(&card->ips); i++) { INDENT + struct fpga_ip *dm = (struct fpga_ip *) list_at(&card->ips, i); + + if (fpga_vlnv_cmp(&dm->vlnv, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_dma", NULL })) + continue; /* skip non DMA IP cores */ + + struct dma *dma = (struct dma *) dm->_vd; + + /* Simple DMA can only transfer up to 4 kb due to + * PCIe page size burst limitation */ + ssize_t len2, len = dma->inst.HasSg ? 64 << 20 : 1 << 2; + + ret = dma_alloc(dm, &mem, 2 * len, 0); + cr_assert_eq(ret, 0); + + ret = dma_mem_split(&mem, &src, &dst); + cr_assert_eq(ret, 0); + + /* Get new random data */ + len2 = read_random(src.base_virt, len); + if (len2 != len) + serror("Failed to get random data"); + + int irq_mm2s = dm->irq; + int irq_s2mm = dm->irq + 1; + + ret = intc_enable(card->intc, (1 << irq_mm2s) | (1 << irq_s2mm), 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + ret = switch_connect(card->sw, dm, dm); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + /* Start transfer */ + ret = dma_ping_pong(dm, src.base_phys, dst.base_phys, dst.len); + cr_assert_eq(ret, 0, "DMA ping pong failed"); + + ret = memcmp(src.base_virt, dst.base_virt, src.len); + + info("DMA %s (%s): %s", dm->name, dma->inst.HasSg ? "scatter-gather" : "simple", ret ? CLR_RED("failed") : CLR_GRN("passed")); + + ret = switch_disconnect(card->sw, dm, dm); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = intc_disable(card->intc, (1 << irq_mm2s) | (1 << irq_s2mm)); + cr_assert_eq(ret, 0, "Failed to disable interrupt"); + + ret = dma_free(dm, &mem); + cr_assert_eq(ret, 0, "Failed to release DMA memory"); + } + + cr_assert_eq(ret, 0); +} diff --git a/fpga/tests/fifo.c b/fpga/tests/fifo.c new file mode 100644 index 000000000..0c5e70abf --- /dev/null +++ b/fpga/tests/fifo.c @@ -0,0 +1,74 @@ +/** FIFO unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include +#include +#include + +#include + +extern struct fpga_card *card; + +Test(fpga, fifo, .description = "FIFO") +{ + int ret; + ssize_t len; + char src[255], dst[255]; + struct fpga_ip *fifo; + + fifo = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_fifo_mm_s", NULL }); + cr_assert(fifo); + + ret = intc_enable(card->intc, (1 << fifo->irq), 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + ret = switch_connect(card->sw, fifo, fifo); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + /* Get some random data to compare */ + memset(dst, 0, sizeof(dst)); + len = read_random((char *) src, sizeof(src)); + if (len != sizeof(src)) + error("Failed to get random data"); + + len = fifo_write(fifo, (char *) src, sizeof(src)); + if (len != sizeof(src)) + error("Failed to send to FIFO"); + + len = fifo_read(fifo, (char *) dst, sizeof(dst)); + if (len != sizeof(dst)) + error("Failed to read from FIFO"); + + ret = intc_disable(card->intc, (1 << fifo->irq)); + cr_assert_eq(ret, 0, "Failed to disable interrupt"); + + ret = switch_disconnect(card->sw, fifo, fifo); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + /* Compare data */ + cr_assert_eq(memcmp(src, dst, sizeof(src)), 0); +} diff --git a/fpga/tests/hls.c b/fpga/tests/hls.c new file mode 100644 index 000000000..a1ec3bd74 --- /dev/null +++ b/fpga/tests/hls.c @@ -0,0 +1,80 @@ +/** HLS unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include +#include +#include + +extern struct fpga_card *card; + +Test(fpga, hls_dft, .description = "HLS: hls_dft") +{ + int ret; + struct fpga_ip *hls, *rtds; + + rtds = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "acs.eonerc.rwth-aachen.de", "user", "rtds_axis", NULL }); + hls = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { NULL, "hls", "hls_dft", NULL }); + + /* Check if required IP is available on FPGA */ + cr_assert(hls && rtds); + + ret = intc_enable(card->intc, (1 << rtds->irq), 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + ret = switch_connect(card->sw, rtds, hls); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_connect(card->sw, hls, rtds); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + while(1) { + /* Dump RTDS AXI Stream state */ + rtds_axis_dump(rtds); + sleep(1); + } +#if 0 + int len = 2000; + int NSAMPLES = 400; + float src[len], dst[len]; + + for (int i = 0; i < len; i++) { + src[i] = 4 + 5.0 * sin(2.0 * M_PI * 1 * i / NSAMPLES) + + 2.0 * sin(2.0 * M_PI * 2 * i / NSAMPLES) + + 1.0 * sin(2.0 * M_PI * 5 * i / NSAMPLES) + + 0.5 * sin(2.0 * M_PI * 9 * i / NSAMPLES) + + 0.2 * sin(2.0 * M_PI * 15 * i / NSAMPLES); + + fifo_write() + } +#endif + + ret = switch_disconnect(card->sw, rtds, hls); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_disconnect(card->sw, hls, rtds); + cr_assert_eq(ret, 0, "Failed to configure switch"); +} diff --git a/fpga/tests/intc.c b/fpga/tests/intc.c new file mode 100644 index 000000000..1ebdafbda --- /dev/null +++ b/fpga/tests/intc.c @@ -0,0 +1,57 @@ +/** Intc unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include + +#include + +extern struct fpga_card *card; + +Test(fpga, intc, .description = "Interrupt Controller") +{ + int ret; + uint32_t isr; + + cr_assert(card->intc); + + ret = intc_enable(card->intc, 0xFF00, 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + /* Fake IRQs in software by writing to ISR */ + XIntc_Out32((uintptr_t) card->map + card->intc->baseaddr + XIN_ISR_OFFSET, 0xFF00); + + /* Wait for 8 SW triggered IRQs */ + for (int i = 0; i < 8; i++) + intc_wait(card->intc, i+8); + + /* Check ISR if all SW IRQs have been deliverd */ + isr = XIntc_In32((uintptr_t) card->map + card->intc->baseaddr + XIN_ISR_OFFSET); + + ret = intc_disable(card->intc, 0xFF00); + cr_assert_eq(ret, 0, "Failed to disable interrupt"); + + cr_assert_eq(isr & 0xFF00, 0); /* ISR should get cleared by MSI_Grant_signal */ +} diff --git a/fpga/tests/main.c b/fpga/tests/main.c new file mode 100644 index 000000000..866f23131 --- /dev/null +++ b/fpga/tests/main.c @@ -0,0 +1,91 @@ +/** Main Unit Test entry point. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#define TEST_CONFIG "/villas/etc/fpga.conf" +#define TEST_LEN 0x1000 + +#define CPU_HZ 3392389000 +#define FPGA_AXI_HZ 125000000 + +struct list cards; +struct fpga_card *card; +struct pci pci; +struct vfio_container vc; + +static void init() +{ + int ret; + + FILE *f; + json_error_t err; + json_t *json; + + ret = pci_init(&pci); + cr_assert_eq(ret, 0, "Failed to initialize PCI sub-system"); + + ret = vfio_init(&vc); + cr_assert_eq(ret, 0, "Failed to initiliaze VFIO sub-system"); + + /* Parse FPGA configuration */ + f = fopen(TEST_CONFIG, "r"); + cr_assert_not_null(f); + + json = json_loadf(f, 0, &err); + cr_assert_not_null(json); + + fclose(f); + + list_init(&cards); + ret = fpga_card_parse_list(&cards, json); + cr_assert_eq(ret, 0, "Failed to parse FPGA config"); + + json_decref(json); + + card = list_lookup(&cards, "vc707"); + cr_assert(card, "FPGA card not found"); + + if (criterion_options.logging_threshold < CRITERION_IMPORTANT) + fpga_card_dump(card); +} + +static void fini() +{ + int ret; + + ret = fpga_card_destroy(card); + cr_assert_eq(ret, 0, "Failed to de-initilize FPGA"); +} + +TestSuite(fpga, + .init = init, + .fini = fini, + .description = "VILLASfpga" +); diff --git a/fpga/tests/rtds_rtt.c b/fpga/tests/rtds_rtt.c new file mode 100644 index 000000000..8215ec604 --- /dev/null +++ b/fpga/tests/rtds_rtt.c @@ -0,0 +1,81 @@ +/** RTDS AXI-Stream RTT unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include + +#include +#include +#include + +extern struct fpga_card *card; + +Test(fpga, rtds_rtt, .description = "RTDS: tight rtt") +{ + int ret; + struct fpga_ip *ip, *rtds; + struct dma_mem buf; + size_t recvlen; + + /* Get IP cores */ + rtds = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "acs.eonerc.rwth-aachen.de", "user", "rtds_axis", NULL }); + cr_assert(rtds); + + ip = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_dma", NULL }); + cr_assert(ip); + + ret = switch_connect(card->sw, rtds, ip); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_connect(card->sw, ip, rtds); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = dma_alloc(ip, &buf, 0x100, 0); + cr_assert_eq(ret, 0, "Failed to allocate DMA memory"); + + while (1) { + + ret = dma_read(ip, buf.base_phys, buf.len); + cr_assert_eq(ret, 0, "Failed to start DMA read: %d", ret); + + ret = dma_read_complete(ip, NULL, &recvlen); + cr_assert_eq(ret, 0, "Failed to complete DMA read: %d", ret); + + ret = dma_write(ip, buf.base_phys, recvlen); + cr_assert_eq(ret, 0, "Failed to start DMA write: %d", ret); + + ret = dma_write_complete(ip, NULL, NULL); + cr_assert_eq(ret, 0, "Failed to complete DMA write: %d", ret); + } + + ret = switch_disconnect(card->sw, rtds, ip); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_disconnect(card->sw, ip, rtds); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = dma_free(ip, &buf); + cr_assert_eq(ret, 0, "Failed to release DMA memory"); +} diff --git a/fpga/tests/tmrctr.c b/fpga/tests/tmrctr.c new file mode 100644 index 000000000..45ecdab42 --- /dev/null +++ b/fpga/tests/tmrctr.c @@ -0,0 +1,70 @@ +/** Timer/Counter unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include "config.h" + +#include + +#include +#include +#include + +#include + +extern struct fpga_card *card; + +Test(fpga, timer, .description = "Timer Counter") +{ + int ret; + struct fpga_ip *ip; + struct timer *tmr; + + ip = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_timer", NULL }); + cr_assert(ip); + + tmr = (struct timer *) ip->_vd; + + XTmrCtr *xtmr = &tmr->inst; + + ret = intc_enable(card->intc, (1 << ip->irq), 0); + cr_assert_eq(ret, 0, "Failed to enable interrupt"); + + XTmrCtr_SetOptions(xtmr, 0, XTC_EXT_COMPARE_OPTION | XTC_DOWN_COUNT_OPTION); + XTmrCtr_SetResetValue(xtmr, 0, FPGA_AXI_HZ / 125); + XTmrCtr_Start(xtmr, 0); + + uint64_t counter = intc_wait(card->intc, ip->irq); + info("Got IRQ: counter = %ju", counter); + + if (counter == 1) + return; + else + warn("Counter was not 1"); + + intc_disable(card->intc, (1 << ip->irq)); + cr_assert_eq(ret, 0, "Failed to disable interrupt"); + + return; +} diff --git a/fpga/tests/xsg.c b/fpga/tests/xsg.c new file mode 100644 index 000000000..25da8ab1e --- /dev/null +++ b/fpga/tests/xsg.c @@ -0,0 +1,97 @@ +/** System Generator unit test. + * + * @file + * @author Steffen Vogel + * @copyright 2017, Steffen Vogel + * @license GNU General Public License (version 3) + * + * VILLASfpga + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include + +#include +#include +#include + +#include + +extern struct fpga_card *card; + +Test(fpga, xsg, .description = "XSG: multiply_add") +{ + int ret; + double factor, err = 0; + + struct fpga_ip *ip, *dma; + struct model_parameter *p; + struct dma_mem mem; + + ip = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { NULL, "sysgen", "xsg_multiply", NULL }); + cr_assert(ip); + + dma = fpga_vlnv_lookup(&card->ips, &(struct fpga_vlnv) { "xilinx.com", "ip", "axi_dma", NULL }); + cr_assert(dma); + + struct model *model = (struct model *) ip->_vd; + + p = list_lookup(&model->parameters, "factor"); + if (!p) + error("Missing parameter 'factor' for model '%s'", ip->name); + + ret = model_parameter_read(p, &factor); + cr_assert_eq(ret, 0, "Failed to read parameter 'factor' from model '%s'", ip->name); + + info("Model param: factor = %f", factor); + + ret = switch_connect(card->sw, dma, ip); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_connect(card->sw, ip, dma); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = dma_alloc(dma, &mem, 0x1000, 0); + cr_assert_eq(ret, 0, "Failed to allocate DMA memory"); + + float *src = (float *) mem.base_virt; + float *dst = (float *) mem.base_virt + 0x800; + + for (int i = 0; i < 6; i++) + src[i] = 1.1 * (i+1); + + ret = dma_ping_pong(dma, (char *) src, (char *) dst, 6 * sizeof(float)); + cr_assert_eq(ret, 0, "Failed to to ping pong DMA transfer: %d", ret); + + for (int i = 0; i < 6; i++) + err += fabs(factor * src[i] - dst[i]); + + info("Error after FPGA operation: err = %f", err); + + ret = switch_disconnect(card->sw, dma, ip); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = switch_disconnect(card->sw, ip, dma); + cr_assert_eq(ret, 0, "Failed to configure switch"); + + ret = dma_free(dma, &mem); + cr_assert_eq(ret, 0, "Failed to release DMA memory"); + + cr_assert(err < 1e-3); +}