/* 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 #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( "/sys/bus/platform/devices"); std::vector devices = IpDeviceReader().read(PLATFORM_DEVICES_DIRECTORY); // Match devices and ips DeviceIpMatcher matcher(devices, configuredIps); std::vector, IpDevice>> device_ip_pair = matcher.match(); // Bind device to platform driver LinuxDriver driver( std::filesystem::path("/sys/bus/platform/drivers/vfio-platform")); for (auto pair : device_ip_pair) { auto device = pair.second; driver.attach(device); } // VFIO Device Setup std::vector, std::shared_ptr>> vfio_ip_pair; for (auto pair : device_ip_pair) { auto [ip, device] = pair; // 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); // Add return value vfio_ip_pair.push_back({ip, vfio_device}); } // Memory Graph for (auto pair : vfio_ip_pair) { auto [ip, vfio_device] = pair; // Map vfio device memory 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()); // Connect vfio vertex to Reg vertex connect(vfio_device->getName(), ip); // 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); } } } } 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); // TODO: Determine zynq name auto space = mm.findAddressSpace("zynq_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); card->ignored_ip_names = parser.ignored_ip_names; 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; }