2024-08-26 13:06:00 +02:00
|
|
|
/* Platform based card
|
|
|
|
*
|
|
|
|
* Author: Pascal Bauer <pascal.bauer@rwth-aachen.de>
|
|
|
|
* Author: Steffen Vogel <post@steffenvogel.de>
|
|
|
|
* Author: Daniel Krebs <github@daniel-krebs.net>
|
|
|
|
*
|
|
|
|
* SPDX-FileCopyrightText: 2023-2024 Pascal Bauer <pascal.bauer@rwth-aachen.de>
|
|
|
|
* SPDX-FileCopyrightText: Steffen Vogel <post@steffenvogel.de>
|
|
|
|
* SPDX-FileCopyrightText: Daniel Krebs <github@daniel-krebs.net>
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <jansson.h>
|
|
|
|
#include <string>
|
|
|
|
#include <villas/fpga/card_parser.hpp>
|
|
|
|
#include <villas/fpga/core.hpp>
|
|
|
|
#include <villas/fpga/node.hpp>
|
|
|
|
#include <villas/fpga/platform_card.hpp>
|
|
|
|
#include <villas/kernel/devices/device_ip_matcher.hpp>
|
|
|
|
#include <villas/kernel/devices/ip_device_reader.hpp>
|
2024-10-21 18:12:20 +02:00
|
|
|
#include <villas/kernel/devices/linux_driver.hpp>
|
2024-08-26 13:06:00 +02:00
|
|
|
#include <villas/kernel/kernel.hpp>
|
|
|
|
#include <villas/memory_manager.hpp>
|
|
|
|
|
2024-09-20 17:12:35 +02:00
|
|
|
#include <villas/fpga/ips/platform_intc.hpp>
|
|
|
|
|
2024-08-26 13:06:00 +02:00
|
|
|
using namespace villas;
|
|
|
|
using namespace villas::fpga;
|
|
|
|
using namespace villas::kernel;
|
|
|
|
using namespace villas::kernel::devices;
|
|
|
|
|
|
|
|
PlatformCard::PlatformCard(
|
|
|
|
std::shared_ptr<kernel::vfio::Container> vfioContainer) {
|
|
|
|
this->vfioContainer = vfioContainer;
|
|
|
|
this->logger = villas::Log::get("PlatformCard");
|
|
|
|
}
|
|
|
|
|
|
|
|
void PlatformCard::connectVFIOtoIps(
|
|
|
|
std::list<std::shared_ptr<ip::Core>> configuredIps) {
|
|
|
|
|
|
|
|
// Read devices from Devicetree
|
2024-08-30 13:58:30 +02:00
|
|
|
const std::filesystem::path PLATFORM_DEVICES_DIRECTORY(
|
|
|
|
"/sys/bus/platform/devices");
|
2024-09-06 13:06:12 +02:00
|
|
|
std::vector<IpDevice> devices =
|
|
|
|
IpDeviceReader().read(PLATFORM_DEVICES_DIRECTORY);
|
2024-08-26 13:06:00 +02:00
|
|
|
|
|
|
|
// Match devices and ips
|
2024-08-30 13:58:30 +02:00
|
|
|
DeviceIpMatcher matcher(devices, configuredIps);
|
2024-08-26 13:06:00 +02:00
|
|
|
std::vector<std::pair<std::shared_ptr<ip::Core>, IpDevice>> device_ip_pair =
|
|
|
|
matcher.match();
|
|
|
|
|
|
|
|
// Bind device to platform driver
|
2024-10-21 18:12:20 +02:00
|
|
|
LinuxDriver driver(
|
2024-08-26 13:11:02 +02:00
|
|
|
std::filesystem::path("/sys/bus/platform/drivers/vfio-platform"));
|
2024-08-26 13:06:00 +02:00
|
|
|
for (auto pair : device_ip_pair) {
|
|
|
|
auto device = pair.second;
|
|
|
|
driver.attach(device);
|
|
|
|
}
|
|
|
|
|
2024-09-06 13:28:11 +02:00
|
|
|
// VFIO Device Setup
|
|
|
|
std::vector<std::pair<std::shared_ptr<ip::Core>,
|
|
|
|
std::shared_ptr<kernel::vfio::Device>>>
|
|
|
|
vfio_ip_pair;
|
2024-08-26 13:06:00 +02:00
|
|
|
for (auto pair : device_ip_pair) {
|
2024-09-06 13:28:11 +02:00
|
|
|
auto [ip, device] = pair;
|
2024-08-26 13:06:00 +02:00
|
|
|
|
|
|
|
// Attach group to container
|
|
|
|
const int iommu_group = device.iommu_group().value();
|
|
|
|
auto vfio_group = vfioContainer->getOrAttachGroup(iommu_group);
|
|
|
|
logger->debug("Device: {}, Iommu: {}", device.name(), iommu_group);
|
|
|
|
|
|
|
|
// Open Vfio Device
|
|
|
|
auto vfio_device = std::make_shared<kernel::vfio::Device>(
|
|
|
|
device.name(), vfio_group->getFileDescriptor());
|
|
|
|
|
|
|
|
// Attach device to group
|
|
|
|
vfio_group->attachDevice(vfio_device);
|
|
|
|
|
|
|
|
// Add as member
|
|
|
|
this->vfio_devices.push_back(vfio_device);
|
|
|
|
|
2024-09-06 13:28:11 +02:00
|
|
|
// Add return value
|
|
|
|
vfio_ip_pair.push_back({ip, vfio_device});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Memory Graph
|
|
|
|
for (auto pair : vfio_ip_pair) {
|
|
|
|
auto [ip, vfio_device] = pair;
|
|
|
|
|
2024-09-06 12:55:52 +02:00
|
|
|
// Map vfio device memory to process
|
2024-08-26 13:06:00 +02:00
|
|
|
const void *mapping = vfio_device->regionMap(0);
|
|
|
|
if (mapping == MAP_FAILED) {
|
|
|
|
logger->error("Failed to mmap() device");
|
|
|
|
}
|
|
|
|
logger->debug("memory mapped: {}", vfio_device->getName());
|
|
|
|
|
|
|
|
// Create mappings from process space to vfio devices
|
|
|
|
auto &mm = MemoryManager::get();
|
|
|
|
size_t srcVertexId = mm.getProcessAddressSpace();
|
|
|
|
const size_t mem_size = vfio_device->regionGetSize(0);
|
|
|
|
size_t targetVertexId = mm.getOrCreateAddressSpace(vfio_device->getName());
|
|
|
|
mm.createMapping(reinterpret_cast<uintptr_t>(mapping), 0, mem_size,
|
|
|
|
"process to vfio", srcVertexId, targetVertexId);
|
|
|
|
logger->debug("create edge from process to {}", vfio_device->getName());
|
|
|
|
|
|
|
|
// Connect vfio vertex to Reg vertex
|
|
|
|
connect(vfio_device->getName(), ip);
|
2024-09-20 17:12:35 +02:00
|
|
|
|
|
|
|
// interrupts
|
|
|
|
if (vfio_device->getNumberIrqs() > 0) {
|
|
|
|
for (int i = 0; i < vfio_device->getNumberIrqs(); i++) {
|
|
|
|
auto intc = new PlatformInterruptController(vfio_device);
|
|
|
|
|
|
|
|
const char *irqName = "iic2intc_irpt";
|
|
|
|
int num = 0;
|
|
|
|
logger->error("Adding Platformintc {}", num);
|
|
|
|
//* dma
|
|
|
|
//* mm2s_introut
|
|
|
|
//* s2mm_introut
|
|
|
|
|
|
|
|
ip->addIrq(irqName, num, intc);
|
|
|
|
}
|
|
|
|
}
|
2024-08-26 13:06:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PlatformCard::connect(std::string device_name,
|
|
|
|
std::shared_ptr<ip::Core> ip) {
|
|
|
|
auto &mm = MemoryManager::get();
|
|
|
|
const size_t ip_mem_size = 65536;
|
|
|
|
|
|
|
|
size_t srcVertexId = mm.getOrCreateAddressSpace(device_name);
|
|
|
|
|
|
|
|
std::string taget_address_space_name =
|
|
|
|
ip->getInstanceName() + "/Reg"; //? TODO: Reg neded?
|
|
|
|
size_t targetVertexId;
|
|
|
|
targetVertexId = mm.getOrCreateAddressSpace(taget_address_space_name);
|
|
|
|
|
|
|
|
mm.createMapping(0, 0, ip_mem_size, "vfio to ip", srcVertexId,
|
|
|
|
targetVertexId);
|
|
|
|
|
|
|
|
logger->debug("Connect {} and {}", mm.getGraph().getVertex(srcVertexId)->name,
|
|
|
|
mm.getGraph().getVertex(targetVertexId)->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PlatformCard::mapMemoryBlock(const std::shared_ptr<MemoryBlock> block) {
|
|
|
|
if (not vfioContainer->isIommuEnabled()) {
|
|
|
|
logger->warn("VFIO mapping not supported without IOMMU");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto &mm = MemoryManager::get();
|
|
|
|
const auto &addrSpaceId = block->getAddrSpaceId();
|
|
|
|
|
|
|
|
if (memoryBlocksMapped.find(addrSpaceId) != memoryBlocksMapped.end())
|
|
|
|
// Block already mapped
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
logger->debug("Create VFIO-Platform mapping for {}", addrSpaceId);
|
|
|
|
|
|
|
|
auto translationFromProcess = mm.getTranslationFromProcess(addrSpaceId);
|
|
|
|
uintptr_t processBaseAddr = translationFromProcess.getLocalAddr(0);
|
|
|
|
uintptr_t iovaAddr =
|
|
|
|
vfioContainer->memoryMap(processBaseAddr, UINTPTR_MAX, block->getSize());
|
|
|
|
|
|
|
|
if (iovaAddr == UINTPTR_MAX) {
|
|
|
|
logger->error("Cannot map memory at {:#x} of size {:#x}", processBaseAddr,
|
|
|
|
block->getSize());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
mm.createMapping(iovaAddr, 0, block->getSize(), "VFIO-D2H",
|
|
|
|
this->addrSpaceIdDeviceToHost, addrSpaceId);
|
|
|
|
|
2024-10-07 00:07:13 +02:00
|
|
|
// TODO: Determine zynq name
|
|
|
|
auto space = mm.findAddressSpace("zynq_zynq_ultra_ps_e_0/HPC1_DDR_LOW");
|
2024-08-26 13:06:00 +02:00
|
|
|
mm.createMapping(iovaAddr, 0, block->getSize(), "VFIO-D2H", space,
|
|
|
|
addrSpaceId);
|
|
|
|
|
|
|
|
// Remember that this block has already been mapped for later
|
|
|
|
memoryBlocksMapped.insert({addrSpaceId, block});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<PlatformCard>
|
|
|
|
PlatformCardFactory::make(json_t *json_card, std::string card_name,
|
|
|
|
std::shared_ptr<kernel::vfio::Container> vc,
|
|
|
|
const std::filesystem::path &searchPath) {
|
|
|
|
auto logger = villas::Log::get("PlatformCardFactory");
|
|
|
|
|
|
|
|
// make sure the vfio container has the required modules
|
|
|
|
kernel::loadModule("vfio_platform");
|
|
|
|
|
|
|
|
CardParser parser(json_card);
|
|
|
|
|
|
|
|
auto card = std::make_shared<fpga::PlatformCard>(vc);
|
|
|
|
card->name = std::string(card_name);
|
|
|
|
card->affinity = parser.affinity;
|
|
|
|
card->doReset = parser.do_reset != 0;
|
|
|
|
card->polling = (parser.polling != 0);
|
2024-09-25 15:32:56 +02:00
|
|
|
card->ignored_ip_names = parser.ignored_ip_names;
|
2024-08-26 13:06:00 +02:00
|
|
|
|
|
|
|
json_t *json_ips = parser.json_ips;
|
|
|
|
json_t *json_paths = parser.json_paths;
|
|
|
|
json_error_t err;
|
|
|
|
|
|
|
|
// Load IPs from a separate json file
|
|
|
|
if (!json_is_string(json_ips)) {
|
|
|
|
logger->debug("FPGA IP cores config item is not a string.");
|
|
|
|
throw ConfigError(json_ips, "node-config-fpga-ips",
|
|
|
|
"FPGA IP cores config item is not a string.");
|
|
|
|
}
|
|
|
|
if (!searchPath.empty()) {
|
|
|
|
std::filesystem::path json_ips_path =
|
|
|
|
searchPath / json_string_value(json_ips);
|
|
|
|
logger->debug("searching for FPGA IP cors config at {}",
|
|
|
|
json_ips_path.string());
|
|
|
|
json_ips = json_load_file(json_ips_path.c_str(), 0, nullptr);
|
|
|
|
}
|
|
|
|
if (json_ips == nullptr) {
|
|
|
|
json_ips = json_load_file(json_string_value(json_ips), 0, nullptr);
|
|
|
|
logger->debug("searching for FPGA IP cors config at {}",
|
|
|
|
json_string_value(json_ips));
|
|
|
|
if (json_ips == nullptr) {
|
|
|
|
throw ConfigError(json_ips, "node-config-fpga-ips",
|
|
|
|
"Failed to find FPGA IP cores config");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (not json_is_object(json_ips))
|
|
|
|
throw ConfigError(json_ips, "node-config-fpga-ips",
|
|
|
|
"FPGA IP core list must be an object!");
|
|
|
|
|
|
|
|
ip::CoreFactory::make(card.get(), json_ips);
|
|
|
|
if (card->ips.empty())
|
|
|
|
throw ConfigError(json_ips, "node-config-fpga-ips",
|
|
|
|
"Cannot initialize IPs of FPGA card {}", card_name);
|
|
|
|
|
|
|
|
// Additional static paths for AXI-Steram switch
|
|
|
|
if (json_paths != nullptr) {
|
|
|
|
if (not json_is_array(json_paths))
|
|
|
|
throw ConfigError(json_paths, err, "",
|
|
|
|
"Switch path configuration must be an array");
|
|
|
|
|
|
|
|
size_t i;
|
|
|
|
json_t *json_path;
|
|
|
|
json_array_foreach(json_paths, i, json_path) {
|
|
|
|
const char *from, *to;
|
|
|
|
int reverse = 0;
|
|
|
|
|
|
|
|
int ret;
|
|
|
|
ret = json_unpack_ex(json_path, &err, 0, "{ s: s, s: s, s?: b }", "from",
|
|
|
|
&from, "to", &to, "reverse", &reverse);
|
|
|
|
if (ret != 0)
|
|
|
|
throw ConfigError(json_path, err, "",
|
|
|
|
"Cannot parse switch path config");
|
|
|
|
|
|
|
|
auto masterIpCore = card->lookupIp(from);
|
|
|
|
if (!masterIpCore)
|
|
|
|
throw ConfigError(json_path, "", "Unknown IP {}", from);
|
|
|
|
|
|
|
|
auto slaveIpCore = card->lookupIp(to);
|
|
|
|
if (!slaveIpCore)
|
|
|
|
throw ConfigError(json_path, "", "Unknown IP {}", to);
|
|
|
|
|
|
|
|
auto masterIpNode = std::dynamic_pointer_cast<ip::Node>(masterIpCore);
|
|
|
|
if (!masterIpNode)
|
|
|
|
throw ConfigError(json_path, "", "IP {} is not a streaming node", from);
|
|
|
|
|
|
|
|
auto slaveIpNode = std::dynamic_pointer_cast<ip::Node>(slaveIpCore);
|
|
|
|
if (!slaveIpNode)
|
|
|
|
throw ConfigError(json_path, "", "IP {} is not a streaming node", to);
|
|
|
|
|
|
|
|
if (not masterIpNode->connect(*slaveIpNode, reverse != 0))
|
|
|
|
throw ConfigError(json_path, "", "Failed to connect node {} to {}",
|
|
|
|
from, to);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Deallocate JSON config
|
|
|
|
json_decref(json_ips);
|
|
|
|
return card;
|
|
|
|
}
|