1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00
VILLASnode/common/lib/kernel/vfio_container.cpp
Pascal Bauer 83e95f88a5 Refactor: change namespace pci to devices
Signed-off-by: Pascal Bauer <pascal.bauer@rwth-aachen.de>
2024-08-30 12:23:02 +02:00

312 lines
9.2 KiB
C++

/* Virtual Function IO wrapper around kernel API.
*
* Author: Steffen Vogel <post@steffenvogel.de>
* Author: Daniel Krebs <github@daniel-krebs.net>
* Author: Niklas Eiling <niklas.eiling@eonerc.rwth-aachen.de>
* SPDX-FileCopyrightText: 2014-2021 Steffen Vogel <post@steffenvogel.de>
* SPDX-FileCopyrightText: 2018 Daniel Krebs <github@daniel-krebs.net>
* SPDX-FileCopyrightText: 2022-2023 Niklas Eiling <niklas.eiling@eonerc.rwth-aachen.de>
* SPDX-License-Identifier: Apache-2.0
*/
#define _DEFAULT_SOURCE
#if defined(__arm__) || defined(__aarch64__)
#define _LARGEFILE64_SOURCE 1
#define _FILE_OFFSET_BITS 64
#endif
#include <algorithm>
#include <array>
#include <limits>
#include <sstream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <fcntl.h>
#include <linux/pci_regs.h>
#include <sys/eventfd.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <villas/exceptions.hpp>
#include <villas/kernel/kernel.hpp>
#include <villas/kernel/vfio_container.hpp>
#include <villas/log.hpp>
using namespace villas::kernel::vfio;
#ifndef VFIO_NOIOMMU_IOMMU
#define VFIO_NOIOMMU_IOMMU 8
#endif
static std::array<std::string, EXTENSION_SIZE> construct_vfio_extension_str() {
std::array<std::string, EXTENSION_SIZE> ret;
ret[VFIO_TYPE1_IOMMU] = "Type 1";
ret[VFIO_SPAPR_TCE_IOMMU] = "SPAPR TCE";
ret[VFIO_TYPE1v2_IOMMU] = "Type 1 v2";
ret[VFIO_DMA_CC_IOMMU] = "DMA CC";
ret[VFIO_EEH] = "EEH";
ret[VFIO_TYPE1_NESTING_IOMMU] = "Type 1 Nesting";
ret[VFIO_SPAPR_TCE_v2_IOMMU] = "SPAPR TCE v2";
ret[VFIO_NOIOMMU_IOMMU] = "No IOMMU";
// Backwards compatability with older kernels
#ifdef VFIO_UNMAP_ALL
ret[VFIO_UNMAP_ALL] = "Unmap all";
#endif
#ifdef VFIO_UPDATE_VADDR
ret[VFIO_UPDATE_VADDR] = "Update vaddr";
#endif
return ret;
}
static std::array<std::string, EXTENSION_SIZE> VFIO_EXTENSION_STR =
construct_vfio_extension_str();
Container::Container(std::vector<std::string> required_modules)
: fd(-1), version(0), extensions(), iova_next(0), hasIommu(false), groups(),
log(Log::get("kernel:vfio:container")) {
for (auto module : required_modules) {
if (kernel::loadModule(module.c_str()) != 0) {
throw RuntimeError("Kernel module '{}' required but could not be loaded. "
"Please load manually!",
module);
}
}
// Create a VFIO Container
fd = open(VFIO_DEV, O_RDWR);
if (fd < 0)
throw RuntimeError("Failed to open VFIO container");
// Check VFIO API version
version = ioctl(fd, VFIO_GET_API_VERSION);
if (version < 0 || version != VFIO_API_VERSION)
throw RuntimeError("Unknown API version: {}", version);
// Check available VFIO extensions (IOMMU types)
for (unsigned int i = VFIO_TYPE1_IOMMU; i < EXTENSION_SIZE; i++) {
int ret = ioctl(fd, VFIO_CHECK_EXTENSION, i);
extensions[i] = ret != 0;
log->debug("VFIO extension {} is {} ({})", i,
extensions[i] ? "available" : "not available",
VFIO_EXTENSION_STR[i]);
}
if (extensions[VFIO_TYPE1_IOMMU]) {
log->debug("Using VFIO type {} ({})", VFIO_TYPE1_IOMMU,
VFIO_EXTENSION_STR[VFIO_TYPE1_IOMMU]);
hasIommu = true;
} else if (extensions[VFIO_NOIOMMU_IOMMU]) {
log->debug("Using VFIO type {} ({})", VFIO_NOIOMMU_IOMMU,
VFIO_EXTENSION_STR[VFIO_NOIOMMU_IOMMU]);
hasIommu = false;
} else
throw RuntimeError("No supported IOMMU type available");
log->debug("Version: {:#x}", version);
log->debug("IOMMU: {}", hasIommu ? "yes" : "no");
}
Container::~Container() {
// Release memory and close fds
groups.clear();
log->debug("Cleaning up container with fd {}", fd);
// Close container
int ret = close(fd);
if (ret < 0)
log->error("Error closing vfio container fd {}: {}", fd, ret);
}
void Container::attachGroup(std::shared_ptr<Group> group) {
if (group->isAttachedToContainer())
throw RuntimeError("Group is already attached to a container");
// Claim group ownership
int ret = ioctl(group->getFileDescriptor(), VFIO_GROUP_SET_CONTAINER, &fd);
if (ret < 0)
throw SystemError("Failed to attach VFIO group {} to container fd {}",
group->getIndex(), fd);
// Set IOMMU type
int iommu_type = isIommuEnabled() ? VFIO_TYPE1_IOMMU : VFIO_NOIOMMU_IOMMU;
ret = ioctl(fd, VFIO_SET_IOMMU, iommu_type);
if (ret < 0)
throw SystemError("Failed to set IOMMU type of container");
group->setAttachedToContainer();
log->debug("Attached new group {} to VFIO container with fd {}",
group->getIndex(), fd);
// Push to our list
groups.push_back(std::move(group));
}
std::shared_ptr<Group> Container::getOrAttachGroup(int index) {
// Search if group with index already exists
for (auto &group : groups) {
if (group->getIndex() == index)
return group;
}
// Group not yet part of this container, so acquire ownership
auto group = std::make_shared<Group>(index, isIommuEnabled());
attachGroup(group);
return group;
}
void Container::dump() {
log->info("File descriptor: {}", fd);
log->info("Version: {}", version);
// Check available VFIO extensions (IOMMU types)
for (size_t i = 0; i < extensions.size(); i++)
log->debug("VFIO extension {} ({}) is {}", extensions[i],
VFIO_EXTENSION_STR[i],
extensions[i] ? "available" : "not available");
for (auto &group : groups)
group->dump();
}
std::shared_ptr<Device> Container::attachDevice(const std::string &name,
int index) {
auto group = getOrAttachGroup(index);
auto device = group->attachDevice(name);
return device;
}
std::shared_ptr<Device> Container::attachDevice(devices::PciDevice &pdev) {
int ret;
char name[32], iommu_state[4];
static constexpr const char *kernelDriver = "vfio-pci";
// Load PCI bus driver for VFIO
if (kernel::loadModule("vfio_pci"))
throw RuntimeError("Failed to load kernel driver: vfio_pci");
// Bind PCI card to vfio-pci driver if not already bound
if (pdev.getDriver() != kernelDriver) {
log->debug("Bind PCI card to kernel driver '{}'", kernelDriver);
pdev.attachDriver(kernelDriver);
}
try {
pdev.rewriteBar();
} catch (std::exception &e) {
throw RuntimeError(
e.what() +
std::string(
"\nBAR of device is in inconsistent state. Rewriting the BAR "
"failed. Please remove, rescan and reset the device and "
"try again."));
}
// Get IOMMU group of device
int index = isIommuEnabled() ? pdev.getIommuGroup() : 0;
if (index < 0) {
ret = kernel::getCmdlineParam("intel_iommu", iommu_state,
sizeof(iommu_state));
if (ret != 0 || strcmp("on", iommu_state) != 0)
log->warn("Kernel booted without command line parameter "
"'intel_iommu' set to 'on'. Please check documentation "
"(https://villas.fein-aachen.org/doc/fpga-setup.html) "
"for help with troubleshooting.");
throw RuntimeError("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);
log->info("Attach to device {} with index {}", std::string(name), index);
auto group = getOrAttachGroup(index);
auto device = group->attachDevice(name, &pdev);
// Check if this is really a vfio-pci device
if (!device->isVfioPciDevice())
throw RuntimeError("Device is not a vfio-pci device");
return device;
}
uintptr_t Container::memoryMap(uintptr_t virt, uintptr_t phys, size_t length) {
int ret;
if (not hasIommu) {
log->error("DMA mapping not supported without IOMMU");
return UINTPTR_MAX;
}
if (length & 0xFFF) {
length += 0x1000;
length &= ~0xFFF;
}
// Super stupid allocator
size_t iovaIncrement = 0;
if (phys == UINTPTR_MAX) {
phys = this->iova_next;
iovaIncrement = length;
}
struct vfio_iommu_type1_dma_map dmaMap;
memset(&dmaMap, 0, sizeof(dmaMap));
dmaMap.argsz = sizeof(dmaMap);
dmaMap.vaddr = virt;
dmaMap.iova = phys;
dmaMap.size = length;
dmaMap.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;
ret = ioctl(this->fd, VFIO_IOMMU_MAP_DMA, &dmaMap);
if (ret) {
log->error("Failed to create DMA mapping: {}", ret);
return UINTPTR_MAX;
}
log->debug("DMA map size={:#x}, iova={:#x}, vaddr={:#x}", dmaMap.size,
dmaMap.iova, dmaMap.vaddr);
// Mapping successful, advance IOVA allocator
this->iova_next += iovaIncrement;
// We intentionally don't return the actual mapped length, the users are
// only guaranteed to have their demanded memory mapped correctly
return dmaMap.iova;
}
bool Container::memoryUnmap(uintptr_t phys, size_t length) {
int ret;
if (not hasIommu)
return true;
struct vfio_iommu_type1_dma_unmap dmaUnmap;
dmaUnmap.argsz = sizeof(struct vfio_iommu_type1_dma_unmap);
dmaUnmap.flags = 0;
dmaUnmap.iova = phys;
dmaUnmap.size = length;
ret = ioctl(this->fd, VFIO_IOMMU_UNMAP_DMA, &dmaUnmap);
if (ret) {
log->error("Failed to unmap DMA mapping");
return false;
}
log->debug("DMA unmap size={:#x}, iova={:#x}", dmaUnmap.size, dmaUnmap.iova);
return true;
}