Started implementing an utility to manage PCI passthrough

This commit is contained in:
Alexei KADIR 2024-02-20 23:48:09 +01:00
parent a36277c002
commit 29a129f2b8
8 changed files with 157 additions and 5 deletions

View File

@ -57,7 +57,7 @@ result_t remove_backing(const char* backing);
bool backing_filter(const char* file);
/// @brief Lists the backings in the pool.
/// @param _backings The string pointer array to store the resulting array in. The caller is responsible for freeing the strings and the array.
/// @param _backings The string array pointer to store the resulting array in. The caller is responsible for freeing the strings and the array.
/// @return The result of the operation.
result_t list_backings(char*** _backings);

View File

@ -236,6 +236,7 @@ result_t list_containers(char*** _containers) {
// List the files in the container pool
result = list_files(_containers, pool_path, container_filter);
// Free the path
free(pool_path);
return result;
}

View File

@ -59,7 +59,7 @@ result_t reset_container(const char* container);
bool container_filter(const char* file);
/// @brief Lists the containers in the pool.
/// @param _containers The string pointer array to store the resulting array in. The caller is responsible for freeing the strings and the array.
/// @param _containers The string array pointer to store the resulting array in. The caller is responsible for freeing the strings and the array.
/// @return The result of the operation.
result_t list_containers(char*** _containers);

119
src/pci.c Normal file
View File

@ -0,0 +1,119 @@
#include "pci.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/stat.h>
result_t check_pci_address(const char* pci) {
// Check the length of the string (should be 12 characters, eg. 0000:00:00.0)
if (strlen(pci) != 12)
return failure("Invalid PCI address '%s'. Valid PCI addresses are in the format '0000:00:00.0'.", pci);
// Check the format of the string
for (int i = 0; i < 12; i++)
if (i == 4 || i == 7) {
if (pci[i] != ':')
return failure("Invalid PCI address '%s'. Missing colon at position %d. Valid PCI addresses are in the format '0000:00:00.0'.", pci, i);
} else if (i == 10) {
if (pci[i] != '.')
return failure("Invalid PCI address '%s'. Missing dot at position %d. Valid PCI addresses are in the format '0000:00:00.0'.", pci, i);
} else {
if (!isxdigit(pci[i]))
return failure("Invalid PCI address '%s'. Non-hexadecimal character '%c' at position %d. Valid PCI addresses are in the format '0000:00:00.0'.", pci, pci[i], i);
}
return success();
}
result_t check_pci_exists(const char* pci) {
// Check the PCI address format
result_t result = check_pci_address(pci);
if (result != success())
return result;
// Check if the PCI address exists by checking if the sysfs directory exists
char* path;
result = format(&path, "/sys/bus/pci/devices/%s", pci);
if (result != success())
return result;
// Check that the directory exists
struct stat st;
if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
free(path);
return failure("PCI address '%s' does not exist.", pci);
}
// Free the path
free(path);
return success();
}
result_t get_iommu_group(int* _group, const char* pci) {
// Initialize the output parameter
*_group = -1;
// Check that the PCI address exists
result_t result = check_pci_exists(pci);
if (result != success())
return result;
// Get the IOMMU group if the PCI device has one
char* path;
result = format(&path, "/sys/bus/pci/devices/%s/iommu_group", pci);
if (result != success())
return result;
// Check that the file exists and is a symlink
struct stat st;
if (lstat(path, &st) != 0 || !S_ISLNK(st.st_mode)) {
free(path);
return failure("PCI address '%s' does not have an IOMMU group.", pci);
}
// Read the IOMMU group by getting the path of the symlink, and getting the basename of the path
char iommu_group_path[256]; // 256 should be enough for the path
ssize_t len = readlink(path, iommu_group_path, sizeof(iommu_group_path) - 1);
// Check for errors during the readlink call
if (len == -1) {
free(path);
return failure("Failed to read IOMMU group of PCI address '%s'.", pci);
}
// Null-terminate the path
iommu_group_path[len] = '\0';
// Get the basename of the path, and try to parse it as an integer
*_group = atoi(basename(iommu_group_path));
return success();
}
bool pci_filter(const char* file) {
// Check that the PCI device exists
return check_pci_exists(file) == success();
}
result_t get_iommu_group_devices(char*** _devices, int group) {
// Initialize the output parameters
*_devices = NULL;
// Get the path of the IOMMU group
char* path;
result_t result = format(&path, "/sys/kernel/iommu_groups/%d/devices", group);
if (result != success())
return result;
// List the devices in the IOMMU group
result = list_files(_devices, path, pci_filter);
// Free the path
free(path);
return result;
}

30
src/pci.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include "utils.h"
/// @brief Checks whether the given string is a valid PCI address. If the string is not a valid PCI address, the call will return a failure result with an error message.
/// @param pci The string to check.
/// @return The result of the operation.
result_t check_pci_address(const char* pci);
/// @brief Checks whether the given PCI address exists. If the PCI address does not exist, the call will return a failure result with an error message.
/// @param pci The PCI address to check.
/// @return The result of the operation.
result_t check_pci_exists(const char* pci);
/// @brief Gets the IOMMU group of the given PCI address.
/// @param _group The integer pointer to store the resulting IOMMU group in.
/// @param pci The PCI address to get the IOMMU group of.
/// @return The result of the operation.
result_t get_iommu_group(int* _group, const char* pci);
/// @brief Checks that the given file is a PCI device. This function is used as a filter for listing PCI devices.
/// @param file The file to check.
/// @return Whether the file is a PCI device.
bool pci_filter(const char* file);
/// @brief Get the PCI devices in the given IOMMU group.
/// @param _devices The string array pointer to store the resulting devices in. The caller is responsible for freeing the strings and the array.
/// @param group The IOMMU group to get the devices of.
/// @return The result of the operation.
result_t get_iommu_group_devices(char*** _devices, int group);

View File

@ -9,6 +9,8 @@
#include <pwd.h>
#include <unistd.h>
#include "pci.h"
#define ALIAS(...) \
(const char*[]) { __VA_ARGS__, NULL }

View File

@ -120,8 +120,8 @@ result_t write_file(const char* path, const char* str);
/// @return The result of the operation.
result_t copy_file(const char* src, const char* dst);
/// @brief Lists the files in the given directory, and stores the resulting array in the given string pointer array. A filter can be provided to only include files that match the filter.
/// @param _files The string pointer array to store the resulting array in. The caller is responsible for freeing the strings and the array.
/// @brief Lists the files in the given directory, and stores the resulting array in the given string array pointer. A filter can be provided to only include files that match the filter.
/// @param _files The string array pointer to store the resulting array in. The caller is responsible for freeing the strings and the array.
/// @param path The path to the directory to list the files in.
/// @param filter The filter to use to only include files that match the filter. This parameter can be NULL if no filter is needed.
/// @return The result of the operation.

View File

@ -8,7 +8,7 @@ result_t generate_iso_xml(char** _xml, char* iso_path, int index) {
return failure("Too many ISO images");
// Generate the XML
return foramt(_xml, "<disk type='file' device='cdrom'>"
return format(_xml, "<disk type='file' device='cdrom'>"
" <driver name='qemu' type='raw'/>"
" <source file='%s'/>"
" <target dev='sd%c' bus='sata'/>"