From b73c83f1570d6c3a605fa00e3eb298674108f784 Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Thu, 22 Aug 2024 15:22:34 +0200 Subject: [PATCH 1/5] vfntool: only check classcode on known drivers Only check that the target device is an NVMe device when binding to the nvme driver. Signed-off-by: Klaus Jensen --- tools/vfntool/vfntool.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tools/vfntool/vfntool.c b/tools/vfntool/vfntool.c index 34ee43c0..814d7762 100644 --- a/tools/vfntool/vfntool.c +++ b/tools/vfntool/vfntool.c @@ -23,22 +23,23 @@ #include "ccan/opt/opt.h" #include "ccan/str/str.h" -static char *bdf = ""; -static bool show_usage, verbose, reset; +static char *bdf = "", *target = "vfio-pci"; +static bool show_usage, verbose; static struct opt_table opts[] = { OPT_WITHOUT_ARG("-h|--help", opt_set_bool, &show_usage, "show usage"), OPT_WITHOUT_ARG("-v|--verbose", opt_set_bool, &verbose, "verbose"), OPT_WITH_ARG("-d|--device BDF", opt_set_charp, opt_show_charp, &bdf, "pci device"), - OPT_WITHOUT_ARG("-x|--reset", opt_set_bool, &reset, "reset"), + OPT_WITH_ARG("-t|--target DRIVER", opt_set_charp, opt_show_charp, &target, + "bind to DRIVER"), OPT_ENDTABLE, }; -static int do_bind(const char *target) +static int do_bind(void) { - unsigned long long vid, did, classcode; + unsigned long long vid, did; __autofree char *driver = NULL; if (pci_device_info_get_ull(bdf, "vendor", &vid)) @@ -47,11 +48,15 @@ static int do_bind(const char *target) if (pci_device_info_get_ull(bdf, "device", &did)) err(1, "could not get device id"); - if (pci_device_info_get_ull(bdf, "class", &classcode)) - err(1, "could not get device class code"); + if (strcmp("nvme", target) == 0) { + unsigned long long classcode; - if ((classcode & 0xffff00) != 0x010800) - errx(1, "%s is not an NVMe device", bdf); + if (pci_device_info_get_ull(bdf, "class", &classcode)) + err(1, "could not get device class code"); + + if ((classcode & 0xffff00) != 0x010800) + errx(1, "%s is not an NVMe device", bdf); + } driver = pci_get_driver(bdf); if (driver) { @@ -102,8 +107,5 @@ int main(int argc, char **argv) if (streq(bdf, "")) opt_usage_exit_fail("missing --device parameter"); - if (reset) - return do_bind("nvme"); - - return do_bind("vfio-pci"); + return do_bind(); } From fcd63fcf344fca7e480c76546f681f2b90c64514 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 8 Feb 2024 21:16:24 +0100 Subject: [PATCH 2/5] build: add a meson build option for linux headers One might need to point the build to a linux kernel include directory different from the one that is currently installed. To avoid having to constantly install the linux kernel headers on the system, add a meson build option that adds a user defined path to the include paths of the project. Signed-off-by: Joel Granados [k.jensen: refactor] Signed-off-by: Klaus Jensen --- meson.build | 9 +++++++-- meson_options.txt | 3 +++ src/meson.build | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 57528930..4a14e8de 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,8 @@ if get_option('debug') add_project_arguments(['-DDEBUG'], language: ['c', 'cpp']) endif +linux_headers = include_directories(get_option('linux-headers')) + ## ## Programs ## -------- @@ -36,6 +38,7 @@ endif perl = find_program('perl') trace_pl = find_program('scripts/trace.pl') + ## ## Compiler Configuration ## ---------------------- @@ -64,8 +67,9 @@ config_host.set('NVME_AQ_QSIZE', get_option('aq_qsize'), description: 'admin command queue size') config_host.set('HAVE_VFIO_DEVICE_BIND_IOMMUFD', - cc.has_header_symbol('linux/vfio.h', 'VFIO_DEVICE_BIND_IOMMUFD'), - description: 'weather VFIO_DEVICE_BIND_IOMMUFD is defined in linux/vfio.h') + cc.has_header_symbol('linux/vfio.h', 'VFIO_DEVICE_BIND_IOMMUFD', + include_directories: linux_headers), + description: 'if VFIO_DEVICE_BIND_IOMMUFD is defined in linux/vfio.h') subdir('internal') @@ -74,6 +78,7 @@ add_project_arguments([ ], language: 'c', ) + ## ## Optional dependencies ## --------------------- diff --git a/meson_options.txt b/meson_options.txt index c82bc3e4..87199c21 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -12,3 +12,6 @@ option('docs', type: 'feature', value: 'auto', option('trace-events-file', type: 'string', value: 'config/trace-events-all', description: 'trace events file') + +option('linux-headers', type: 'string', value: '', + description: 'path to linux headers') diff --git a/src/meson.build b/src/meson.build index 64a6c6e5..51390301 100644 --- a/src/meson.build +++ b/src/meson.build @@ -33,7 +33,7 @@ thread_dep = dependency('threads') vfn_lib = library('vfn', _vfn_sources, dependencies: [thread_dep], link_with: [ccan_lib], - include_directories: [ccan_inc, core_inc, vfn_inc], + include_directories: [ccan_inc, core_inc, vfn_inc, linux_headers], version: meson.project_version(), install: true, c_args: [ From cf077b22b124589c61b8d64a0c012cc2cef0c9bd Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Fri, 23 Aug 2024 12:06:25 +0200 Subject: [PATCH 3/5] iommufd: keep track of devices Add an internal list to keep track of devices that have been binded to iommufd. Signed-off-by: Klaus Jensen --- src/iommu/iommufd.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/iommu/iommufd.c b/src/iommu/iommufd.c index 7c65c2b3..3f98f8cf 100644 --- a/src/iommu/iommufd.c +++ b/src/iommu/iommufd.c @@ -44,6 +44,17 @@ static int __iommufd = -1; +struct iommufd_device_info { + char *bdf; + + int dev_fd; + int dev_id; + + struct list_node list; +}; + +static LIST_HEAD(iommufd_devices); + struct iommu_ioas { struct iommu_ctx ctx; @@ -99,6 +110,7 @@ static int iommu_ioas_update_iova_ranges(struct iommu_ioas *ioas) static int iommufd_get_device_fd(struct iommu_ctx *ctx, const char *bdf) { struct iommu_ioas *ioas = container_of_var(ctx, ioas, ctx); + struct iommufd_device_info *info; __autofree char *vfio_id = NULL; __autofree char *path = NULL; @@ -148,6 +160,16 @@ static int iommufd_get_device_fd(struct iommu_ctx *ctx, const char *bdf) goto close_dev; } + info = znew_t(struct iommufd_device_info, 1); + + *info = (struct iommufd_device_info) { + .bdf = strdup(bdf), + .dev_fd = devfd, + .dev_id = bind.out_devid, + }; + + list_add(&iommufd_devices, &info->list); + return devfd; close_dev: @@ -157,6 +179,26 @@ static int iommufd_get_device_fd(struct iommu_ctx *ctx, const char *bdf) return -1; } +static int iommufd_put_device_fd(struct iommu_ctx *ctx UNUSED, const char *bdf) +{ + struct iommufd_device_info *info, *next; + + list_for_each_safe(&iommufd_devices, info, next, list) { + if (info->bdf == bdf) { + list_del(&info->list); + + free(info->bdf); + free(info); + + return 0; + } + } + + errno = ENOENT; + + return -1; +} + static int iommu_ioas_do_dma_map(struct iommu_ctx *ctx, void *vaddr, size_t len, uint64_t *iova, unsigned long flags) { @@ -235,6 +277,7 @@ static int iommu_ioas_do_dma_unmap_all(struct iommu_ctx *ctx) static const struct iommu_ctx_ops iommufd_ops = { .get_device_fd = iommufd_get_device_fd, + .put_device_fd = iommufd_put_device_fd, .dma_map = iommu_ioas_do_dma_map, .dma_unmap = iommu_ioas_do_dma_unmap, From 8c87175d1a3309e89e61813b6f4f808acc05684b Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Fri, 23 Aug 2024 11:54:12 +0200 Subject: [PATCH 4/5] iommufd: add fault queue Add support for iommufd based fault queues. Signed-off-by: Joel Granados [k.jensen: refactored] Signed-off-by: Klaus Jensen --- include/vfn/iommu/iommufd.h | 26 ++++++++++++ include/vfn/iommu/meson.build | 1 + meson.build | 6 +++ src/iommu/iommufd.c | 77 +++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 include/vfn/iommu/iommufd.h diff --git a/include/vfn/iommu/iommufd.h b/include/vfn/iommu/iommufd.h new file mode 100644 index 00000000..6e58a815 --- /dev/null +++ b/include/vfn/iommu/iommufd.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later or MIT */ + +/* + * This file is part of libvfn. + * + * Copyright (C) 2022 The libvfn Authors. All Rights Reserved. + * + * This library (libvfn) is dual licensed under the GNU Lesser General + * Public License version 2.1 or later or the MIT license. See the + * COPYING and LICENSE files for more information. + */ + +#ifndef LIBVFN_IOMMU_IOMMUFD_H +#define LIBVFN_IOMMU_IOMMUFD_H + +#ifdef IOMMU_FAULT_QUEUE_ALLOC +struct iommufd_fault_queue { + int fault_id; + int fault_fd; +}; + +int iommufd_alloc_fault_queue(struct iommufd_fault_queue *fq); +int iommufd_set_fault_queue(struct iommu_ctx *ctx, struct iommufd_fault_queue *fq, int devfd); +#endif + +#endif /* LIBVFN_IOMMU_IOMMUFD_H */ diff --git a/include/vfn/iommu/meson.build b/include/vfn/iommu/meson.build index 8daae103..6c65da17 100644 --- a/include/vfn/iommu/meson.build +++ b/include/vfn/iommu/meson.build @@ -1,6 +1,7 @@ vfn_iommu_headers = files([ 'context.h', 'dma.h', + 'iommufd.h', ]) install_headers(vfn_iommu_headers, subdir: 'vfn/iommu') diff --git a/meson.build b/meson.build index 4a14e8de..57d501ce 100644 --- a/meson.build +++ b/meson.build @@ -71,6 +71,11 @@ config_host.set('HAVE_VFIO_DEVICE_BIND_IOMMUFD', include_directories: linux_headers), description: 'if VFIO_DEVICE_BIND_IOMMUFD is defined in linux/vfio.h') +config_host.set('HAVE_IOMMU_FAULT_QUEUE_ALLOC', + cc.has_header_symbol('linux/iommufd.h', 'IOMMU_FAULT_QUEUE_ALLOC', + include_directories: linux_headers), + description: 'if IOMMU_FAULT_QUEUE_ALLOC is defined in linux/iommufd.h') + subdir('internal') add_project_arguments([ @@ -176,4 +181,5 @@ summary_info += {'Debugging': get_option('debug')} summary_info += {'Documentation': build_docs} summary_info += {'Profiling': get_option('profiling')} summary_info += {'iommufd': config_host.get('HAVE_VFIO_DEVICE_BIND_IOMMUFD')} +summary_info += {'iommufd fault queue': config_host.get('HAVE_IOMMU_FAULT_QUEUE_ALLOC')} summary(summary_info, bool_yn: true, section: 'Features') diff --git a/src/iommu/iommufd.c b/src/iommu/iommufd.c index 3f98f8cf..37fda2ee 100644 --- a/src/iommu/iommufd.c +++ b/src/iommu/iommufd.c @@ -35,6 +35,7 @@ #include "vfn/support.h" #include "vfn/pci.h" #include "vfn/iommu.h" +#include "vfn/iommu/iommufd.h" #include "ccan/list/list.h" #include "ccan/compiler/compiler.h" @@ -49,6 +50,7 @@ struct iommufd_device_info { int dev_fd; int dev_id; + int hwpt_id; struct list_node list; }; @@ -107,6 +109,20 @@ static int iommu_ioas_update_iova_ranges(struct iommu_ioas *ioas) return 0; } +static UNNEEDED struct iommufd_device_info *iommufd_get_device_info(int fd) +{ + struct iommufd_device_info *info; + + list_for_each(&iommufd_devices, info, list) { + if (info->dev_fd == fd) + return info; + } + + errno = ENOENT; + + return NULL; +} + static int iommufd_get_device_fd(struct iommu_ctx *ctx, const char *bdf) { struct iommu_ioas *ioas = container_of_var(ctx, ioas, ctx); @@ -166,6 +182,7 @@ static int iommufd_get_device_fd(struct iommu_ctx *ctx, const char *bdf) .bdf = strdup(bdf), .dev_fd = devfd, .dev_id = bind.out_devid, + .hwpt_id = -1, }; list_add(&iommufd_devices, &info->list); @@ -312,6 +329,66 @@ static int iommufd_open(void) return 0; } +#ifdef HAVE_IOMMU_FAULT_QUEUE_ALLOC +int iommufd_alloc_fault_queue(struct iommufd_fault_queue *fq) +{ + struct iommu_fault_alloc fault_alloc_cmd = { + .size = sizeof(fault_alloc_cmd), + }; + + if (ioctl(__iommufd, IOMMU_FAULT_QUEUE_ALLOC, &fault_alloc_cmd)) { + log_debug("could not allocate fault queue\n"); + return -1; + } + + *fq = (struct iommufd_fault_queue) { + .fault_id = fault_alloc_cmd.out_fault_id, + .fault_fd = fault_alloc_cmd.out_fault_fd, + }; + + return 0; +} + +int iommufd_set_fault_queue(struct iommu_ctx *ctx, struct iommufd_fault_queue *fq, int devfd) +{ + struct iommu_ioas *ioas = container_of_var(ctx, ioas, ctx); + struct iommufd_device_info *info; + + struct iommu_hwpt_alloc hwpt_alloc_cmd = { + .size = sizeof(hwpt_alloc_cmd), + .flags = IOMMU_HWPT_FAULT_ID_VALID, + .pt_id = ioas->id, + .fault_id = fq->fault_id, + }; + + struct vfio_device_attach_iommufd_pt attach_cmd = { + .argsz = sizeof(attach_cmd), + }; + + info = iommufd_get_device_info(devfd); + if (!info) + return -1; + + hwpt_alloc_cmd.dev_id = info->dev_id; + + if (ioctl(__iommufd, IOMMU_HWPT_ALLOC, &hwpt_alloc_cmd)) { + log_debug("could not allocate hardware page table\n"); + return -1; + } + + info->hwpt_id = hwpt_alloc_cmd.out_hwpt_id; + + attach_cmd.pt_id = hwpt_alloc_cmd.out_hwpt_id; + + if (ioctl(devfd, VFIO_DEVICE_ATTACH_IOMMUFD_PT, &attach_cmd)) { + log_debug("could not re-attach (replace) page table\n"); + return -1; + } + + return 0; +} +#endif + struct iommu_ctx *iommufd_get_iommu_context(const char *name) { struct iommu_ioas *ioas = znew_t(struct iommu_ioas, 1); From 2b9a073d4767d8692623463ea2968dec96e78b89 Mon Sep 17 00:00:00 2001 From: Klaus Jensen Date: Mon, 29 Jul 2024 14:09:43 +0200 Subject: [PATCH 5/5] examples: add fault queue example Add example using the iommufd fault queue to handle device page faults. This example is intended to be used with the QEMU pcie-ats-testdev device. See QEMU for documentation. Signed-off-by: Klaus Jensen --- examples/iopf.c | 127 +++++++++++++++++++++++++++++++++++++++++++ examples/meson.build | 6 +- 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 examples/iopf.c diff --git a/examples/iopf.c b/examples/iopf.c new file mode 100644 index 00000000..1e3e8f1e --- /dev/null +++ b/examples/iopf.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* + * This file is part of libvfn. + * + * Copyright (C) 2022 The libvfn Authors. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include + +#include + +#include + +#include +#include +#include +#include + +#include "ccan/err/err.h" +#include "ccan/opt/opt.h" +#include "ccan/str/str.h" + +#include "common.h" + +#define REG_ADDR 0x0 +#define REG_CMD 0x8 + +#define IOVA_BASE 0xfef00000 + +static struct opt_table opts[] = { + OPT_SUBTABLE(opts_base, NULL), + OPT_ENDTABLE, +}; + +struct vfio_pci_device pdev; + +int main(int argc, char **argv) +{ + void *bar0; + uint64_t iova = IOVA_BASE; + ssize_t len; + void *vaddr; + + struct iommufd_fault_queue fq; + + struct iommu_hwpt_pgfault pgfault; + struct iommu_hwpt_page_response pgresp = { + .code = IOMMUFD_PAGE_RESP_SUCCESS, + }; + + opt_register_table(opts, NULL); + opt_parse(&argc, argv, opt_log_stderr_exit); + + if (show_usage) + opt_usage_and_exit(NULL); + + if (streq(bdf, "")) + opt_usage_exit_fail("missing --device parameter"); + + opt_free_table(); + + if (vfio_pci_open(&pdev, bdf)) + err(1, "failed to open pci device"); + + if (iommufd_alloc_fault_queue(&fq)) + err(1, "could not allocate fault queue"); + + if (iommufd_set_fault_queue(pdev.dev.ctx, &fq, pdev.dev.fd)) + err(1, "could not associate fault queue with device/ioas"); + + bar0 = vfio_pci_map_bar(&pdev, 0, 0x1000, 0, PROT_READ | PROT_WRITE); + if (!bar0) + err(1, "failed to map bar"); + + len = pgmap(&vaddr, 0x1000); + if (len < 0) + err(1, "could not allocate aligned memory"); + + memset(vaddr, 0x42, 0x1000); + + mmio_lh_write64(bar0 + REG_ADDR, iova); + mmio_write32(bar0 + REG_CMD, 0x3); + + /* wait for page fault */ + while (read(fq.fault_fd, &pgfault, sizeof(pgfault)) == 0) + ; + + printf("handling page fault on addr 0x%llx\n", pgfault.addr); + + if (iommu_map_vaddr(pdev.dev.ctx, vaddr, 0x1000, &iova, IOMMU_MAP_FIXED_IOVA)) + err(1, "failed to map page"); + + pgresp.cookie = pgfault.cookie; + + if (write(fq.fault_fd, &pgresp, sizeof(pgresp)) < 0) + err(1, "failed to write page response"); + + while (mmio_read32(bar0 + REG_CMD) & 0x1) + ; + + memset(vaddr, 0x0, 0x1000); + + mmio_write32(bar0 + REG_CMD, 0x1); + + while (mmio_read32(bar0 + REG_CMD) & 0x1) + ; + + for (int i = 0; i < 0x1000; i++) { + uint8_t byte = *(uint8_t *)(vaddr + i); + + if (byte != 0x42) + errx(1, "unexpected byte 0x%"PRIx8, byte); + } + + return 0; +} diff --git a/examples/meson.build b/examples/meson.build index 0589e357..8243778c 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -16,10 +16,14 @@ examples = { 'regs': ['regs.c'], } +if config_host.get('HAVE_IOMMU_FAULT_QUEUE_ALLOC', false) + examples += {'iopf': ['iopf.c']} +endif + foreach example, sources : examples executable(example, [example_sources, sources], dependencies: [libnvme], link_with: [ccan_lib, vfn_lib], - include_directories: [ccan_inc, vfn_inc], + include_directories: [ccan_inc, vfn_inc, linux_headers], ) endforeach