diff --git a/fpga/include/villas/fpga/platform_card.hpp b/fpga/include/villas/fpga/platform_card.hpp new file mode 100644 index 000000000..84b55480a --- /dev/null +++ b/fpga/include/villas/fpga/platform_card.hpp @@ -0,0 +1,46 @@ +/* Platform based card + * + * Author: Pascal Bauer + * Author: Steffen Vogel + * Author: Daniel Krebs + * + * SPDX-FileCopyrightText: 2023-2024 Pascal Bauer + * SPDX-FileCopyrightText: Steffen Vogel + * SPDX-FileCopyrightText: Daniel Krebs + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +namespace villas { +namespace fpga { + +class PlatformCard : public Card { +public: + PlatformCard(std::shared_ptr vfioContainer); + + ~PlatformCard(){}; + + std::vector> vfio_devices; + + void connectVFIOtoIps(std::list> configuredIps) override; + bool mapMemoryBlock(const std::shared_ptr block) override; + +private: + void connect(std::string device_name, std::shared_ptr ip); +}; + +class PlatformCardFactory { +public: + static std::shared_ptr + make(json_t *json_card, std::string card_name, + std::shared_ptr vc, + const std::filesystem::path &searchPath); +}; + +} /* namespace fpga */ +} /* namespace villas */ diff --git a/fpga/lib/platform_card.cpp b/fpga/lib/platform_card.cpp new file mode 100644 index 000000000..90ed66aa3 --- /dev/null +++ b/fpga/lib/platform_card.cpp @@ -0,0 +1,253 @@ +/* Platform based card + * + * Author: Pascal Bauer + * Author: Steffen Vogel + * Author: Daniel Krebs + * + * SPDX-FileCopyrightText: 2023-2024 Pascal Bauer + * SPDX-FileCopyrightText: Steffen Vogel + * SPDX-FileCopyrightText: Daniel Krebs + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace villas; +using namespace villas::fpga; +using namespace villas::kernel; +using namespace villas::kernel::devices; + +PlatformCard::PlatformCard( + std::shared_ptr vfioContainer) { + this->vfioContainer = vfioContainer; + this->logger = villas::Log::get("PlatformCard"); +} + +void PlatformCard::connectVFIOtoIps( + std::list> configuredIps) { + + // Read devices from Devicetree + const std::filesystem::path PLATFORM_DEVICES_DIRECTORY = + std::filesystem::path("/sys/bus/platform/devices"); + auto dtr = IpDeviceReader(PLATFORM_DEVICES_DIRECTORY); + std::vector devices = dtr.devices; + + // Match devices and ips + auto matcher = DeviceIpMatcher(devices, configuredIps); + std::vector, IpDevice>> device_ip_pair = + matcher.match(); + + // Bind device to platform driver + auto driver = + Driver(std::filesystem::path("/sys/bus/platform/drivers/vfio-platform")); + for (auto pair : device_ip_pair) { + auto device = pair.second; + driver.attach(device); + } + + // VFIO Setup + for (auto pair : device_ip_pair) { + auto device = pair.second; + + // 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( + device.name(), vfio_group->getFileDescriptor()); + + // Attach device to group + vfio_group->attachDevice(vfio_device); + + // Add as member + this->vfio_devices.push_back(vfio_device); + + // Map vfio device to process + 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(mapping), 0, mem_size, + "process to vfio", srcVertexId, targetVertexId); + logger->debug("create edge from process to {}", vfio_device->getName()); + + auto ip = pair.first; + // Connect vfio vertex to Reg vertex + connect(vfio_device->getName(), ip); + } +} + +void PlatformCard::connect(std::string device_name, + std::shared_ptr 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 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); + + auto space = mm.findAddressSpace("zynq_ultra_ps_e_0/HPC1_DDR_LOW"); + 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 +PlatformCardFactory::make(json_t *json_card, std::string card_name, + std::shared_ptr 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(vc); + card->name = std::string(card_name); + card->affinity = parser.affinity; + card->doReset = parser.do_reset != 0; + card->polling = (parser.polling != 0); + + 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(masterIpCore); + if (!masterIpNode) + throw ConfigError(json_path, "", "IP {} is not a streaming node", from); + + auto slaveIpNode = std::dynamic_pointer_cast(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; +}