aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/libvfio-user.h76
-rw-r--r--lib/CMakeLists.txt4
-rw-r--r--lib/cap.c529
-rw-r--r--lib/libvfio-user.c6
-rw-r--r--lib/pci.c183
-rw-r--r--lib/pci.h12
-rw-r--r--lib/pci_caps.c547
-rw-r--r--lib/pci_caps.h (renamed from lib/cap.h)47
-rw-r--r--lib/private.h26
-rw-r--r--samples/lspci.c28
-rw-r--r--test/CMakeLists.txt2
-rw-r--r--test/unit-tests.c186
12 files changed, 908 insertions, 738 deletions
diff --git a/include/libvfio-user.h b/include/libvfio-user.h
index 2bf0cd1..8e48a7d 100644
--- a/include/libvfio-user.h
+++ b/include/libvfio-user.h
@@ -233,7 +233,8 @@ typedef ssize_t (vfu_region_access_cb_t)(vfu_ctx_t *vfu_ctx, char *buf,
* by the relevant PCI specification
* - all accesses to the standard PCI header (i.e. the first 64 bytes of the
* region) are handled by the library
- * - all accesses to known PCI capabilities are handled by the library
+ * - all accesses to known PCI capabilities (see vfu_pci_add_capability())
+ * are handled by the library
* - if no callback is provided, reads to other areas are a simple memcpy(),
* and writes are an error
* - otherwise, the callback is expected to handle the access
@@ -522,8 +523,6 @@ vfu_dma_write(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, void *data);
/*
* Supported PCI regions.
- *
- * FIXME: update with CFG behaviour etc.
*/
enum {
VFU_PCI_DEV_BAR0_REGION_IDX,
@@ -594,26 +593,61 @@ vfu_pci_set_class(vfu_ctx_t *vfu_ctx, uint8_t base, uint8_t sub, uint8_t pi);
vfu_pci_config_space_t *
vfu_pci_get_config_space(vfu_ctx_t *vfu_ctx);
-/* FIXME does it have to be packed as well? */
-typedef union {
- struct msicap msi;
- struct msixcap msix;
- struct pmcap pm;
- struct pxcap px;
- struct vsc vsc;
-} vfu_cap_t;
-
-//TODO: Support variable size capabilities.
+#define VFU_CAP_FLAG_EXTENDED (1 << 0)
+#define VFU_CAP_FLAG_CALLBACK (1 << 1)
+#define VFU_CAP_FLAG_READONLY (1 << 2)
/**
- * Setup PCI capabilities.
+ * Add a PCI capability to PCI config space.
+ *
+ * Certain standard capabilities are handled entirely within the library:
+ *
+ * PCI_CAP_ID_EXP (pxcap)
+ * PCI_CAP_ID_MSIX (msixcap)
+ * PCI_CAP_ID_PM (pmcap)
+ *
+ * However, they must still be explicitly initialized and added here.
+ *
+ * The contents of @data are copied in. It must start with either a struct
+ * cap_hdr or a struct ext_cap_hdr, with the ID field set; the 'next' field is
+ * ignored. For PCI_CAP_ID_VNDR or PCI_EXT_CAP_ID_VNDR, the embedded size field
+ * must also be set; in general, any non-fixed-size capability must be
+ * initialized such that the size can be derived at this point.
+ *
+ * If @pos is non-zero, the capability will be placed at the given offset within
+ * configuration space. It must not overlap the PCI standard header, or any
+ * existing capability. Note that if a capability is added "out of order" in
+ * terms of the offset, there is no re-ordering of the capability list written
+ * in configuration space.
+ *
+ * If @pos is zero, the capability will be placed at a suitable offset
+ * automatically.
+ *
+ * The @flags field can be set as follows:
+ *
+ * VFU_CAP_FLAG_EXTENDED: this is an extended capability; supported if device is
+ * of PCI type VFU_PCI_TYPE_{PCI_X_2,EXPRESS}.
+ *
+ * VFU_CAP_FLAG_CALLBACK: all accesses to the capability are delegated to the
+ * callback for the region VFU_PCI_DEV_CFG_REGION_IDX. The callback should copy
+ * data into and out of the capability as needed (this could be directly on the
+ * config space area from vfu_pci_get_config_space()). It is not supported to
+ * allow writes to the initial capability header (ID/next fields).
+ *
+ * VFU_CAP_FLAG_READONLY: this prevents clients from writing to the capability.
+ * By default, clients are allowed to write to any part of the capability,
+ * excluding the initial header.
+ *
+ * Returns the offset of the capability in config space, or -1 on error, with
+ * errno set.
*
* @vfu_ctx: the libvfio-user context
- * @caps: array of (vfu_cap_t *)
- * @nr_caps: number of elements in @caps
+ * @pos: specific offset for the capability, or 0.
+ * @flags: VFU_CAP_FLAG_*
+ * @data: capability data, including the header
*/
-int
-vfu_pci_setup_caps(vfu_ctx_t *vfu_ctx, vfu_cap_t **caps, int nr_caps);
+ssize_t
+vfu_pci_add_capability(vfu_ctx_t *vfu_ctx, size_t pos, int flags, void *data);
/**
* Find the offset within config space of a given capability (if there are
@@ -630,14 +664,14 @@ vfu_pci_find_capability(vfu_ctx_t *vfu_ctx, bool extended, int cap_id);
/**
* Find the offset within config space of the given capability, starting from
- * @pos. This can be used to iterate through multiple capabilities with the
- * same ID.
+ * @pos, which must be the valid offset of an existing capability. This can be
+ * used to iterate through multiple capabilities with the same ID.
*
* Returns 0 if no more matching capabilities were found, with errno set.
*
* @vfu_ctx: the libvfio-user context
- * @pos: offset within config space to start looking
* @extended whether capability is an extended one or not
+ * @pos: offset within config space to start looking
* @id: capability id (PCI_CAP_ID_*)
*/
size_t
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 8886982..e5fd57d 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -38,7 +38,7 @@ set(LIBMINOR 0)
set(LIBPATCH 1)
set(LIBOBJS
- $<TARGET_OBJECTS:cap>
+ $<TARGET_OBJECTS:pci_caps>
$<TARGET_OBJECTS:dma>
$<TARGET_OBJECTS:irq>
$<TARGET_OBJECTS:libvfio-user>
@@ -76,7 +76,7 @@ function(add_library_ut lib)
set_target_properties(${lib_ut} PROPERTIES LINK_FLAGS ${UT_LFLAGS})
endfunction(add_library_ut)
-add_library_ut(cap cap.c)
+add_library_ut(pci_caps pci_caps.c)
add_library_ut(dma dma.c)
add_library_ut(irq irq.c)
add_library_ut(libvfio-user libvfio-user.c)
diff --git a/lib/cap.c b/lib/cap.c
deleted file mode 100644
index d42b5d4..0000000
--- a/lib/cap.c
+++ /dev/null
@@ -1,529 +0,0 @@
-/*
- * Copyright (c) 2019 Nutanix Inc. All rights reserved.
- *
- * Authors: Thanos Makatos <thanos@nutanix.com>
- * Swapnil Ingle <swapnil.ingle@nutanix.com>
- * Felipe Franciosi <felipe@nutanix.com>
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of Nutanix nor the names of its contributors may be
- * used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- *
- */
-
-#include <assert.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "cap.h"
-#include "common.h"
-#include "libvfio-user.h"
-#include "private.h"
-
-#define VFU_MAX_CAPS \
- ((PCI_CFG_SPACE_SIZE - PCI_STD_HEADER_SIZEOF) / PCI_CAP_SIZEOF)
-
-/*
- * PCI capabilities are stored after the the PCI configuration space header
- * (vfu_ctx->config_space), as they would in an actual PCI device. We also
- * maintain an array that points to the beginning and end of each capability
- * (struct caps) to easily tell which capability is accessed and whether the
- * access is valid, e.g. it doesn't span multiple capabilities.
- */
-struct cap {
- uint8_t start;
- uint8_t end;
-};
-
-struct caps {
- struct cap caps[VFU_MAX_CAPS]; /* FIXME only needs to be as big as nr_caps */
- unsigned int nr_caps;
-};
-
-/*
- * Tells whether a capability is being accessed.
- */
-static bool
-cap_is_accessed(struct cap *caps, int nr_caps, size_t count, loff_t offset)
-{
- if (nr_caps == 0) {
- return false;
- }
-
- assert(caps != NULL);
-
- if (offset < caps[0].start) {
- /* write starts before first capability */
-
- if (offset + count <= caps[0].start) {
- /* write ends before first capability */
- return false;
- }
-
- /*
- * FIXME write starts before capabilities but extends into them.
- */
- assert(false);
- } else if (offset > caps[nr_caps - 1].end) {
- /* write starts after last capability */
- return false;
- }
-
- if (offset + count > (size_t)(caps[nr_caps - 1].end + 1)) {
- /*
- * FIXME write starts within capabilities but extends past them, I think
- * that this _is_ possible, e.g. MSI-X is 12 bytes (PCI_CAP_MSIX_SIZEOF)
- * and the host writes to first 8 bytes and then writes 8 more.
- */
- assert(false);
- }
- return true;
-}
-
-/*
- * Returns the PCI capability that is contained within the specified region
- * (offset + count).
- */
-static uint8_t *
-cap_find(vfu_pci_config_space_t *config_space, struct caps *caps, loff_t offset,
- size_t count)
-{
- struct cap *cap;
-
- assert(config_space != NULL);
- assert(caps != NULL);
-
- cap = caps->caps;
- while (cap < caps->caps + caps->nr_caps) {
- /*
- * FIXME ensure that at most one capability is written to. It might
- * legitimate to write to two capabilities at the same time.
- */
- if (offset >= cap->start && offset <= cap->end) {
- if (offset + count - 1 > cap->end) {
- assert(false);
- }
- return config_space->raw + cap->start;
- }
- cap++;
- }
- return NULL;
-}
-
-/*
- * Tells whether the header of a PCI capability is accessed.
- */
-static bool
-cap_header_is_accessed(uint8_t cap_offset, loff_t offset)
-{
- return offset - cap_offset <= 1;
-}
-
-typedef ssize_t (cap_access) (vfu_ctx_t *vfu_ctx, uint8_t *cap, char *buf,
- size_t count, loff_t offset);
-
-static ssize_t
-handle_pmcs_write(vfu_ctx_t *vfu_ctx, struct pmcap *pm,
- const struct pmcs *const pmcs)
-{
-
- if (pm->pmcs.ps != pmcs->ps) {
- vfu_log(vfu_ctx, LOG_DEBUG, "power state set to %#x\n", pmcs->ps);
- }
- if (pm->pmcs.pmee != pmcs->pmee) {
- vfu_log(vfu_ctx, LOG_DEBUG, "PME enable set to %#x\n", pmcs->pmee);
- }
- if (pm->pmcs.dse != pmcs->dse) {
- vfu_log(vfu_ctx, LOG_DEBUG, "data select set to %#x\n", pmcs->dse);
- }
- if (pm->pmcs.pmes != pmcs->pmes) {
- vfu_log(vfu_ctx, LOG_DEBUG, "PME status set to %#x\n", pmcs->pmes);
- }
- pm->pmcs = *pmcs;
- return 0;
-}
-
-static ssize_t
-handle_pm_write(vfu_ctx_t *vfu_ctx, uint8_t *cap, char *const buf,
- const size_t count, const loff_t offset)
-{
- struct pmcap *pm = (struct pmcap *)cap;
-
- switch (offset) {
- case offsetof(struct pmcap, pc):
- if (count != sizeof(struct pc)) {
- return -EINVAL;
- }
- assert(false); /* FIXME implement */
- break;
- case offsetof(struct pmcap, pmcs):
- if (count != sizeof(struct pmcs)) {
- return -EINVAL;
- }
- return handle_pmcs_write(vfu_ctx, pm, (struct pmcs *)buf);
- }
- return -EINVAL;
-}
-
-static ssize_t
-handle_mxc_write(vfu_ctx_t *vfu_ctx, struct msixcap *msix,
- const struct mxc *const mxc)
-{
- assert(msix != NULL);
- assert(mxc != NULL);
-
- if (mxc->mxe != msix->mxc.mxe) {
- vfu_log(vfu_ctx, LOG_DEBUG, "%s MSI-X\n",
- mxc->mxe ? "enable" : "disable");
- msix->mxc.mxe = mxc->mxe;
- }
-
- if (mxc->fm != msix->mxc.fm) {
- if (mxc->fm) {
- vfu_log(vfu_ctx, LOG_DEBUG, "all MSI-X vectors masked\n");
- } else {
- vfu_log(vfu_ctx, LOG_DEBUG,
- "vector's mask bit determines whether vector is masked\n");
- }
- msix->mxc.fm = mxc->fm;
- }
-
- return sizeof(struct mxc);
-}
-
-static ssize_t
-handle_msix_write(vfu_ctx_t *vfu_ctx, uint8_t *cap, char *const buf,
- const size_t count, const loff_t offset)
-{
- struct msixcap *msix = (struct msixcap *)cap;
-
- if (count == sizeof(struct mxc)) {
- switch (offset) {
- case offsetof(struct msixcap, mxc):
- return handle_mxc_write(vfu_ctx, msix, (struct mxc *)buf);
- default:
- vfu_log(vfu_ctx, LOG_ERR,
- "invalid MSI-X write offset %ld\n", offset);
- return -EINVAL;
- }
- }
- vfu_log(vfu_ctx, LOG_ERR, "invalid MSI-X write size %lu\n", count);
- return -EINVAL;
-}
-
-static int
-handle_px_pxdc_write(vfu_ctx_t *vfu_ctx, struct pxcap *px,
- const union pxdc *const p)
-{
- assert(px != NULL);
- assert(p != NULL);
-
- if (p->cere != px->pxdc.cere) {
- px->pxdc.cere = p->cere;
- vfu_log(vfu_ctx, LOG_DEBUG, "CERE %s\n", p->cere ? "enable" : "disable");
- }
-
- if (p->nfere != px->pxdc.nfere) {
- px->pxdc.nfere = p->nfere;
- vfu_log(vfu_ctx, LOG_DEBUG, "NFERE %s\n",
- p->nfere ? "enable" : "disable");
- }
-
- if (p->fere != px->pxdc.fere) {
- px->pxdc.fere = p->fere;
- vfu_log(vfu_ctx, LOG_DEBUG, "FERE %s\n", p->fere ? "enable" : "disable");
- }
-
- if (p->urre != px->pxdc.urre) {
- px->pxdc.urre = p->urre;
- vfu_log(vfu_ctx, LOG_DEBUG, "URRE %s\n", p->urre ? "enable" : "disable");
- }
-
- if (p->ero != px->pxdc.ero) {
- px->pxdc.ero = p->ero;
- vfu_log(vfu_ctx, LOG_DEBUG, "ERO %s\n", p->ero ? "enable" : "disable");
- }
-
- if (p->mps != px->pxdc.mps) {
- px->pxdc.mps = p->mps;
- vfu_log(vfu_ctx, LOG_DEBUG, "MPS set to %d\n", p->mps);
- }
-
- if (p->ete != px->pxdc.ete) {
- px->pxdc.ete = p->ete;
- vfu_log(vfu_ctx, LOG_DEBUG, "ETE %s\n", p->ete ? "enable" : "disable");
- }
-
- if (p->pfe != px->pxdc.pfe) {
- px->pxdc.pfe = p->pfe;
- vfu_log(vfu_ctx, LOG_DEBUG, "PFE %s\n", p->pfe ? "enable" : "disable");
- }
-
- if (p->appme != px->pxdc.appme) {
- px->pxdc.appme = p->appme;
- vfu_log(vfu_ctx, LOG_DEBUG, "APPME %s\n",
- p->appme ? "enable" : "disable");
- }
-
- if (p->ens != px->pxdc.ens) {
- px->pxdc.ens = p->ens;
- vfu_log(vfu_ctx, LOG_DEBUG, "ENS %s\n", p->ens ? "enable" : "disable");
- }
-
- if (p->mrrs != px->pxdc.mrrs) {
- px->pxdc.mrrs = p->mrrs;
- vfu_log(vfu_ctx, LOG_DEBUG, "MRRS set to %d\n", p->mrrs);
- }
-
- if (p->iflr) {
- vfu_log(vfu_ctx, LOG_DEBUG,
- "initiate function level reset\n");
- }
-
- return 0;
-}
-
-static int
-handle_px_write_2_bytes(vfu_ctx_t *vfu_ctx, struct pxcap *px, char *const buf,
- loff_t off)
-{
- switch (off) {
- case offsetof(struct pxcap, pxdc):
- return handle_px_pxdc_write(vfu_ctx, px, (union pxdc *)buf);
- }
- return -EINVAL;
-}
-
-static ssize_t
-handle_px_write(vfu_ctx_t *vfu_ctx, uint8_t *cap, char *const buf,
- size_t count, loff_t offset)
-{
- struct pxcap *px = (struct pxcap *)cap;
-
- int err = -EINVAL;
- switch (count) {
- case 2:
- err = handle_px_write_2_bytes(vfu_ctx, px, buf, offset);
- break;
- }
- if (err != 0) {
- return err;
- }
- return count;
-}
-
-static ssize_t
-handle_vsc_write(vfu_ctx_t *vfu_ctx __attribute__ ((unused)), uint8_t *cap,
- char *const buf, const size_t count, const loff_t offset)
-{
- /* FIXME we shouldn't allow then length field to be modified, right? */
-
- memcpy(cap + offset, buf, count);
- return count;
-}
-
-static const struct cap_handler {
- char *name;
- size_t size;
- cap_access *fn;
-} cap_handlers[PCI_CAP_ID_MAX + 1] = {
- [PCI_CAP_ID_PM] = {"PM", PCI_PM_SIZEOF, handle_pm_write},
- [PCI_CAP_ID_VNDR] = {"Vendor-Specific", 0, handle_vsc_write},
- [PCI_CAP_ID_EXP] = {"PCI Express", PCI_CAP_EXP_ENDPOINT_SIZEOF_V2,
- handle_px_write},
- [PCI_CAP_ID_MSIX] = {"MSI-X", PCI_CAP_MSIX_SIZEOF, handle_msix_write},
-};
-
-ssize_t
-cap_maybe_access(vfu_ctx_t *vfu_ctx, struct caps *caps, char *buf, size_t count,
- loff_t offset)
-{
- vfu_pci_config_space_t *config_space;
- uint8_t *cap;
-
- if (caps == NULL) {
- return 0;
- }
-
- if (count == 0) {
- return 0;
- }
-
- if (!cap_is_accessed(caps->caps, caps->nr_caps, count, offset)) {
- return 0;
- }
-
- /* we're now guaranteed that the access is within some capability */
- config_space = vfu_pci_get_config_space(vfu_ctx);
- cap = cap_find(config_space, caps, offset, count);
- assert(cap != NULL); /* FIXME */
-
- if (cap_header_is_accessed(cap - config_space->raw, offset)) {
- /* FIXME how to deal with writes to capability header? */
- assert(false);
- }
- return cap_handlers[cap[PCI_CAP_LIST_ID]].fn(vfu_ctx, cap, buf, count,
- offset - (loff_t)(cap - config_space->raw));
-}
-
-static bool
-cap_is_valid(uint8_t id)
-{
- /* TODO 0 is a valid capability ID (Null Capability), check
- * https://pcisig.com/sites/default/files/files/PCI_Code-ID_r_1_11__v24_Jan_2019.pdf:
- *
- */
- return id >= PCI_CAP_ID_PM && id <= PCI_CAP_ID_MAX;
-}
-
-struct caps *
-caps_create(vfu_ctx_t *vfu_ctx, vfu_cap_t **vfu_caps, int nr_caps, int *err)
-{
- int i;
- uint8_t *prev;
- uint8_t next;
- vfu_pci_config_space_t *config_space;
- struct caps *caps = NULL;
-
- *err = 0;
- if (nr_caps <= 0 || nr_caps >= VFU_MAX_CAPS) {
- *err = EINVAL;
- return NULL;
- }
-
- assert(vfu_caps != NULL);
-
- caps = calloc(1, sizeof *caps);
- if (caps == NULL) {
- *err = ENOMEM;
- goto err_out;
- }
-
- config_space = vfu_pci_get_config_space(vfu_ctx);
- /* points to the next field of the previous capability */
- prev = &config_space->hdr.cap;
-
- /* relative offset that points where the next capability should be placed */
- next = PCI_STD_HEADER_SIZEOF;
-
- for (i = 0; i < nr_caps; i++) {
- uint8_t *cap = (uint8_t*)vfu_caps[i];
- uint8_t id = cap[PCI_CAP_LIST_ID];
- size_t size;
-
- if (!cap_is_valid(id)) {
- *err = EINVAL;
- goto err_out;
- }
-
- if (id == PCI_CAP_ID_VNDR) {
- size = cap[PCI_CAP_LIST_NEXT + 1];
- } else {
- size = cap_handlers[id].size;
- if (size == 0) {
- *err = ENOTSUP;
- goto err_out;
- }
- }
-
- caps->caps[i].start = next;
- caps->caps[i].end = next + size - 1;
-
- memcpy(&config_space->raw[next], cap, size);
- *prev = next;
- prev = &config_space->raw[next + PCI_CAP_LIST_NEXT];
- *prev = 0;
- next += size;
- assert(next % 4 == 0); /* FIXME */
-
- vfu_log(vfu_ctx, LOG_DEBUG, "initialized PCI capability %s %#x-%#x",
- cap_handlers[id].name, caps->caps[i].start, caps->caps[i].end);
- }
- caps->nr_caps = nr_caps;
-
- return caps;
-
-err_out:
- free(caps);
- return NULL;
-}
-
-size_t
-vfu_pci_find_next_capability(vfu_ctx_t *vfu_ctx, bool extended,
- size_t offset, int cap_id)
-{
- size_t space_size = vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size;
- vfu_pci_config_space_t *config_space;
-
- if (extended) {
- errno = ENOTSUP;
- return 0;
- }
-
- if (offset + PCI_CAP_LIST_NEXT >= space_size) {
- errno = EINVAL;
- return 0;
- }
-
- config_space = vfu_pci_get_config_space(vfu_ctx);
-
- if (offset == 0) {
- offset = config_space->hdr.cap;
- } else {
- offset = config_space->raw[offset + PCI_CAP_LIST_NEXT];
- }
-
- if (offset == 0) {
- errno = ENOENT;
- return 0;
- }
-
- for (;;) {
- /* Sanity check. */
- if (offset + PCI_CAP_LIST_NEXT >= space_size) {
- errno = EINVAL;
- return 0;
- }
-
- if (config_space->raw[offset + PCI_CAP_LIST_ID] == cap_id) {
- return offset;
- }
-
- offset = config_space->raw[offset + PCI_CAP_LIST_NEXT];
-
- if (offset == 0) {
- errno = ENOENT;
- return 0;
- }
- }
-}
-
-size_t
-vfu_pci_find_capability(vfu_ctx_t *vfu_ctx, bool extended, int cap_id)
-{
- return vfu_pci_find_next_capability(vfu_ctx, extended, 0, cap_id);
-}
-
-/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/lib/libvfio-user.c b/lib/libvfio-user.c
index c528070..b56a490 100644
--- a/lib/libvfio-user.c
+++ b/lib/libvfio-user.c
@@ -49,7 +49,6 @@
#include <sys/socket.h>
#include <sys/stat.h>
-#include "cap.h"
#include "dma.h"
#include "irq.h"
#include "libvfio-user.h"
@@ -1035,10 +1034,10 @@ vfu_realize_ctx(vfu_ctx_t *vfu_ctx)
}
}
- if (vfu_ctx->pci.caps != NULL) {
+ if (vfu_ctx->pci.nr_caps != 0) {
vfu_ctx->pci.config_space->hdr.sts.cl = 0x1;
- vfu_ctx->pci.config_space->hdr.cap = PCI_STD_HEADER_SIZEOF;
}
+
vfu_ctx->realized = true;
return 0;
@@ -1094,7 +1093,6 @@ vfu_destroy_ctx(vfu_ctx_t *vfu_ctx)
}
free_sparse_mmap_areas(vfu_ctx);
free(vfu_ctx->reg_info);
- free(vfu_ctx->pci.caps);
free(vfu_ctx->migration);
free(vfu_ctx->irqs);
free(vfu_ctx);
diff --git a/lib/pci.c b/lib/pci.c
index 41a6f9a..78ae459 100644
--- a/lib/pci.c
+++ b/lib/pci.c
@@ -36,7 +36,7 @@
#include <string.h>
#include <sys/param.h>
-#include "cap.h"
+#include "pci_caps.h"
#include "common.h"
#include "libvfio-user.h"
#include "pci.h"
@@ -217,14 +217,16 @@ handle_erom_write(vfu_ctx_t *ctx, vfu_pci_config_space_t *pci,
}
static int
-pci_hdr_write(vfu_ctx_t *vfu_ctx, vfu_pci_config_space_t *cfg_space,
- const char *buf, size_t count, loff_t offset)
+pci_hdr_write(vfu_ctx_t *vfu_ctx, const char *buf, size_t count, loff_t offset)
{
+ vfu_pci_config_space_t *cfg_space;
int ret = 0;
assert(vfu_ctx != NULL);
assert(buf != NULL);
+ cfg_space = vfu_pci_get_config_space(vfu_ctx);
+
switch (offset) {
case PCI_COMMAND:
ret = handle_command_write(vfu_ctx, cfg_space, buf, count);
@@ -269,29 +271,92 @@ pci_hdr_write(vfu_ctx_t *vfu_ctx, vfu_pci_config_space_t *cfg_space,
return ret;
}
-static int
-pci_hdr_access(vfu_ctx_t *vfu_ctx, char *buf, size_t *countp,
- loff_t *offsetp, bool is_write)
+/*
+ * Access to the standard PCI header at the given offset.
+ */
+static ssize_t
+pci_hdr_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
+ loff_t offset, bool is_write)
{
- vfu_pci_config_space_t *cfg_space = vfu_pci_get_config_space(vfu_ctx);
- size_t count;
- int err = 0;
-
- assert(countp != NULL);
- assert(offsetp != NULL);
+ ssize_t ret;
- count = MIN(*countp, (size_t)(PCI_STD_HEADER_SIZEOF - *offsetp));
+ assert(count <= PCI_STD_HEADER_SIZEOF);
if (is_write) {
- err = pci_hdr_write(vfu_ctx, cfg_space, buf, count, *offsetp);
+ ret = pci_hdr_write(vfu_ctx, buf, count, offset);
+ if (ret < 0) {
+ vfu_log(vfu_ctx, LOG_ERR, "failed to write to PCI header: %s",
+ strerror(-ret));
+ } else {
+ dump_buffer("buffer write", buf, count);
+ ret = count;
+ }
} else {
- memcpy(buf, cfg_space->hdr.raw + *offsetp, count);
+ memcpy(buf, pci_config_space_ptr(vfu_ctx, offset), count);
+ ret = count;
+ }
+
+ return ret;
+}
+
+/*
+ * Access to the PCI config space that isn't handled by pci_hdr_access() or a
+ * capability handler.
+ */
+ssize_t
+pci_nonstd_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
+ loff_t offset, bool is_write)
+{
+ vfu_region_access_cb_t *cb =
+ vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].cb;
+
+ if (cb != NULL) {
+ return cb(vfu_ctx, buf, count, offset, is_write);
}
- *countp -= count;
- *offsetp += count;
+ if (is_write) {
+ vfu_log(vfu_ctx, LOG_ERR, "no callback for write to config space "
+ "offset %u size %zu\n", offset, count);
+ return -EINVAL;
+ }
- return err;
+ memcpy(buf, pci_config_space_ptr(vfu_ctx, offset), count);
+ return count;
+}
+
+/*
+ * Returns the size of the next segment to access, which may be less than
+ * @count: we might need to split up an access that straddles capabilities and
+ * normal config space, for example.
+ *
+ * @cb is set to the callback to use for accessing the segment.
+ */
+static size_t
+pci_config_space_next_segment(vfu_ctx_t *ctx, size_t count, loff_t offset,
+ vfu_region_access_cb_t **cb)
+{
+ struct pci_cap *cap;
+
+ if (offset < PCI_STD_HEADER_SIZEOF) {
+ *cb = pci_hdr_access;
+ return MIN(count, (size_t)(PCI_STD_HEADER_SIZEOF - offset));
+ }
+
+ cap = cap_find_by_offset(ctx, offset, count);
+
+ if (cap == NULL) {
+ *cb = pci_nonstd_access;
+ return count;
+ }
+
+ /* If we have config space before the capability. */
+ if (offset < (loff_t)cap->off) {
+ *cb = pci_nonstd_access;
+ return cap->off - offset;
+ }
+
+ *cb = pci_cap_access;
+ return MIN(count, cap->size);
}
/*
@@ -308,59 +373,26 @@ ssize_t
pci_config_space_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
loff_t offset, bool is_write)
{
- vfu_region_access_cb_t *cb;
loff_t start = offset;
- ssize_t ret;
- int rc;
+ ssize_t ret = 0;
assert(vfu_ctx != NULL);
- cb = vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].cb;
+ while (count > 0) {
+ vfu_region_access_cb_t *cb;
+ size_t size;
- if (offset < PCI_STD_HEADER_SIZEOF) {
- rc = pci_hdr_access(vfu_ctx, buf, &count, &offset, is_write);
- if (rc < 0) {
- vfu_log(vfu_ctx, LOG_ERR, "failed to access PCI header: %s",
- strerror(-rc));
- dump_buffer("buffer write", buf, count);
- return rc;
- }
- }
+ size = pci_config_space_next_segment(vfu_ctx, count, offset, &cb);
- if (count == 0) {
- return offset - start;
- }
+ ret = cb(vfu_ctx, buf, size, offset, is_write);
- if (is_write) {
- rc = cap_maybe_access(vfu_ctx, vfu_ctx->pci.caps, buf, count, offset);
-
- if (rc < 0) {
- vfu_log(vfu_ctx, LOG_ERR, "bad access to capabilities %#lx-%#lx\n",
- offset, offset + count);
- return rc;
- } else if (rc > 0) {
- offset += count;
- return offset - start;
- }
-
- if (cb == NULL) {
- vfu_log(vfu_ctx, LOG_ERR, "config space write: no callback");
- return -EINVAL;
- }
-
- } else {
- if (cb == NULL) {
- memcpy(buf, vfu_ctx->pci.config_space->raw + offset, count);
- offset += count;
- return offset - start;
+ // FIXME: partial reads, still return an error?
+ if (ret < 0) {
+ return ret;
}
- }
-
- ret = cb(vfu_ctx, buf, count, offset, is_write);
- offset += ret;
- if (ret < 0) {
- return ret;
+ offset += ret;
+ count -= ret;
}
return offset - start;
@@ -435,33 +467,6 @@ vfu_pci_set_class(vfu_ctx_t *vfu_ctx, uint8_t base, uint8_t sub, uint8_t pi)
vfu_ctx->pci.config_space->hdr.cc.pi = pi;
}
-int
-vfu_pci_setup_caps(vfu_ctx_t *vfu_ctx, vfu_cap_t **caps, int nr_caps)
-{
- int ret;
-
- assert(vfu_ctx != NULL);
-
- if (vfu_ctx->pci.caps != NULL) {
- vfu_log(vfu_ctx, LOG_ERR, "capabilities are already setup");
- return ERROR(EEXIST);
- }
-
- if (caps == NULL || nr_caps == 0) {
- vfu_log(vfu_ctx, LOG_ERR, "Invalid args passed");
- return ERROR(EINVAL);
- }
-
- vfu_ctx->pci.caps = caps_create(vfu_ctx, caps, nr_caps, &ret);
- if (vfu_ctx->pci.caps == NULL) {
- vfu_log(vfu_ctx, LOG_ERR, "failed to create PCI capabilities: %s",
- strerror(ret));
- return ERROR(ret);
- }
-
- return 0;
-}
-
inline vfu_pci_config_space_t *
vfu_pci_get_config_space(vfu_ctx_t *vfu_ctx)
{
diff --git a/lib/pci.h b/lib/pci.h
index b38b12b..faea75c 100644
--- a/lib/pci.h
+++ b/lib/pci.h
@@ -37,9 +37,21 @@
#include "private.h"
ssize_t
+pci_nonstd_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
+ loff_t offset, bool is_write);
+
+ssize_t
pci_config_space_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
loff_t pos, bool is_write);
+
+static inline uint8_t *
+pci_config_space_ptr(vfu_ctx_t *vfu_ctx, loff_t offset)
+{
+ assert(offset < vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size);
+ return &vfu_ctx->pci.config_space->raw[offset];
+}
+
#endif /* LIB_VFIO_USER_PCI_H */
/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/lib/pci_caps.c b/lib/pci_caps.c
new file mode 100644
index 0000000..8bc0f2d
--- /dev/null
+++ b/lib/pci_caps.c
@@ -0,0 +1,547 @@
+/*
+ * Copyright (c) 2021 Nutanix Inc. All rights reserved.
+ *
+ * Authors: Thanos Makatos <thanos@nutanix.com>
+ * Swapnil Ingle <swapnil.ingle@nutanix.com>
+ * Felipe Franciosi <felipe@nutanix.com>
+ * John Levon <john.levon@nutanix.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Nutanix nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ */
+
+/*
+ * Capability handling. We handle reads and writes to standard capabilities
+ * ourselves, and optionally for vendor capabilities too. For each access (via
+ * pci_config_space_access() -> pci_cap_access()), if we find that we're
+ * reading from a particular capability offset:
+ *
+ * - if VFU_CAP_FLAG_CALLBACK is set, we call the config space region callback
+ * given by the user
+ * - else we memcpy() the capability data back out to the client
+ *
+ * For writes:
+ *
+ * - if VFU_CAP_FLAG_READONLY is set, we fail the write
+ * - if VFU_CAP_FLAG_CALLBACK is set, we call the config space region callback
+ * given by the user
+ * - else we call the cap-specific callback to handle the write.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "common.h"
+#include "libvfio-user.h"
+#include "pci_caps.h"
+#include "pci.h"
+#include "private.h"
+
+static void *
+cap_data(vfu_ctx_t *vfu_ctx, struct pci_cap *cap)
+{
+ return (void *)pci_config_space_ptr(vfu_ctx, cap->off);
+}
+
+static size_t
+cap_size(uint8_t id, uint8_t *data)
+{
+ switch (id) {
+ case PCI_CAP_ID_PM:
+ return PCI_PM_SIZEOF;
+ case PCI_CAP_ID_EXP:
+ return PCI_CAP_EXP_ENDPOINT_SIZEOF_V2;
+ case PCI_CAP_ID_MSIX:
+ return PCI_CAP_MSIX_SIZEOF;
+ case PCI_CAP_ID_VNDR:
+ return ((struct vsc *)data)->size;
+ default:
+ return 0;
+ }
+}
+
+static ssize_t
+handle_pmcs_write(vfu_ctx_t *vfu_ctx, struct pmcap *pm,
+ const struct pmcs *const pmcs)
+{
+ if (pm->pmcs.ps != pmcs->ps) {
+ vfu_log(vfu_ctx, LOG_DEBUG, "power state set to %#x\n", pmcs->ps);
+ }
+ if (pm->pmcs.pmee != pmcs->pmee) {
+ vfu_log(vfu_ctx, LOG_DEBUG, "PME enable set to %#x\n", pmcs->pmee);
+ }
+ if (pm->pmcs.dse != pmcs->dse) {
+ vfu_log(vfu_ctx, LOG_DEBUG, "data select set to %#x\n", pmcs->dse);
+ }
+ if (pm->pmcs.pmes != pmcs->pmes) {
+ vfu_log(vfu_ctx, LOG_DEBUG, "PME status set to %#x\n", pmcs->pmes);
+ }
+ pm->pmcs = *pmcs;
+ return 0;
+}
+
+static ssize_t
+cap_write_pm(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char * buf,
+ size_t count, loff_t offset)
+{
+ struct pmcap *pm = cap_data(vfu_ctx, cap);
+
+ switch (offset - cap->off) {
+ case offsetof(struct pmcap, pc):
+ if (count != sizeof (struct pc)) {
+ return -EINVAL;
+ }
+ assert(false); /* FIXME implement */
+ break;
+ case offsetof(struct pmcap, pmcs):
+ if (count != sizeof (struct pmcs)) {
+ return -EINVAL;
+ }
+ handle_pmcs_write(vfu_ctx, pm, (struct pmcs *)buf);
+ return sizeof (struct pmcs);
+ }
+ return -EINVAL;
+}
+
+static ssize_t
+handle_mxc_write(vfu_ctx_t *vfu_ctx, struct msixcap *msix,
+ const struct mxc *const mxc)
+{
+ assert(msix != NULL);
+ assert(mxc != NULL);
+
+ if (mxc->mxe != msix->mxc.mxe) {
+ vfu_log(vfu_ctx, LOG_DEBUG, "%s MSI-X\n",
+ mxc->mxe ? "enable" : "disable");
+ msix->mxc.mxe = mxc->mxe;
+ }
+
+ if (mxc->fm != msix->mxc.fm) {
+ if (mxc->fm) {
+ vfu_log(vfu_ctx, LOG_DEBUG, "all MSI-X vectors masked\n");
+ } else {
+ vfu_log(vfu_ctx, LOG_DEBUG,
+ "vector's mask bit determines whether vector is masked\n");
+ }
+ msix->mxc.fm = mxc->fm;
+ }
+
+ return sizeof(struct mxc);
+}
+
+static ssize_t
+cap_write_msix(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char *buf,
+ size_t count, loff_t offset)
+{
+ struct msixcap *msix = cap_data(vfu_ctx, cap);
+
+ if (count == sizeof(struct mxc)) {
+ switch (offset - cap->off) {
+ case offsetof(struct msixcap, mxc):
+ return handle_mxc_write(vfu_ctx, msix, (struct mxc *)buf);
+ default:
+ vfu_log(vfu_ctx, LOG_ERR,
+ "invalid MSI-X write offset %ld\n", offset);
+ return -EINVAL;
+ }
+ }
+ vfu_log(vfu_ctx, LOG_ERR, "invalid MSI-X write size %lu\n", count);
+ return -EINVAL;
+}
+
+static int
+handle_px_pxdc_write(vfu_ctx_t *vfu_ctx, struct pxcap *px,
+ const union pxdc *const p)
+{
+ assert(px != NULL);
+ assert(p != NULL);
+
+ if (p->cere != px->pxdc.cere) {
+ px->pxdc.cere = p->cere;
+ vfu_log(vfu_ctx, LOG_DEBUG, "CERE %s\n", p->cere ? "enable" : "disable");
+ }
+
+ if (p->nfere != px->pxdc.nfere) {
+ px->pxdc.nfere = p->nfere;
+ vfu_log(vfu_ctx, LOG_DEBUG, "NFERE %s\n",
+ p->nfere ? "enable" : "disable");
+ }
+
+ if (p->fere != px->pxdc.fere) {
+ px->pxdc.fere = p->fere;
+ vfu_log(vfu_ctx, LOG_DEBUG, "FERE %s\n", p->fere ? "enable" : "disable");
+ }
+
+ if (p->urre != px->pxdc.urre) {
+ px->pxdc.urre = p->urre;
+ vfu_log(vfu_ctx, LOG_DEBUG, "URRE %s\n", p->urre ? "enable" : "disable");
+ }
+
+ if (p->ero != px->pxdc.ero) {
+ px->pxdc.ero = p->ero;
+ vfu_log(vfu_ctx, LOG_DEBUG, "ERO %s\n", p->ero ? "enable" : "disable");
+ }
+
+ if (p->mps != px->pxdc.mps) {
+ px->pxdc.mps = p->mps;
+ vfu_log(vfu_ctx, LOG_DEBUG, "MPS set to %d\n", p->mps);
+ }
+
+ if (p->ete != px->pxdc.ete) {
+ px->pxdc.ete = p->ete;
+ vfu_log(vfu_ctx, LOG_DEBUG, "ETE %s\n", p->ete ? "enable" : "disable");
+ }
+
+ if (p->pfe != px->pxdc.pfe) {
+ px->pxdc.pfe = p->pfe;
+ vfu_log(vfu_ctx, LOG_DEBUG, "PFE %s\n", p->pfe ? "enable" : "disable");
+ }
+
+ if (p->appme != px->pxdc.appme) {
+ px->pxdc.appme = p->appme;
+ vfu_log(vfu_ctx, LOG_DEBUG, "APPME %s\n",
+ p->appme ? "enable" : "disable");
+ }
+
+ if (p->ens != px->pxdc.ens) {
+ px->pxdc.ens = p->ens;
+ vfu_log(vfu_ctx, LOG_DEBUG, "ENS %s\n", p->ens ? "enable" : "disable");
+ }
+
+ if (p->mrrs != px->pxdc.mrrs) {
+ px->pxdc.mrrs = p->mrrs;
+ vfu_log(vfu_ctx, LOG_DEBUG, "MRRS set to %d\n", p->mrrs);
+ }
+
+ if (p->iflr) {
+ vfu_log(vfu_ctx, LOG_DEBUG,
+ "initiate function level reset\n");
+ }
+
+ return 0;
+}
+
+static int
+handle_px_write_2_bytes(vfu_ctx_t *vfu_ctx, struct pxcap *px, char *buf,
+ loff_t off)
+{
+ switch (off) {
+ case offsetof(struct pxcap, pxdc):
+ return handle_px_pxdc_write(vfu_ctx, px, (union pxdc *)buf);
+ }
+ return -EINVAL;
+}
+
+static ssize_t
+cap_write_px(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char *buf,
+ size_t count, loff_t offset)
+{
+ struct pxcap *px = cap_data(vfu_ctx, cap);
+
+ int err = -EINVAL;
+ switch (count) {
+ case 2:
+ err = handle_px_write_2_bytes(vfu_ctx, px, buf, offset - cap->off);
+ break;
+ }
+ if (err != 0) {
+ return err;
+ }
+ return count;
+}
+
+static ssize_t
+cap_write_vendor(vfu_ctx_t *vfu_ctx, struct pci_cap *cap UNUSED, char *buf,
+ size_t count, loff_t offset)
+{
+ memcpy(pci_config_space_ptr(vfu_ctx, offset), buf, count);
+ return count;
+}
+
+static bool
+ranges_intersect(size_t off1, size_t size1, size_t off2, size_t size2)
+{
+ return (off1 < (off2 + size2) && (off1 + size1) >= off2);
+}
+
+struct pci_cap *
+cap_find_by_offset(vfu_ctx_t *vfu_ctx, loff_t offset, size_t count)
+{
+ size_t i;
+
+ for (i = 0; i < vfu_ctx->pci.nr_caps; i++) {
+ struct pci_cap *cap = &vfu_ctx->pci.caps[i];
+ if (ranges_intersect(offset, count, cap->off, cap->size)) {
+ return cap;
+ }
+ }
+
+ return NULL;
+}
+
+ssize_t
+pci_cap_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count, loff_t offset,
+ bool is_write)
+{
+ struct pci_cap *cap = cap_find_by_offset(vfu_ctx, offset, count);
+
+ assert(cap != NULL);
+ assert((size_t)offset >= cap->off);
+ assert(count <= cap->size);
+
+ if (is_write && (cap->flags & VFU_CAP_FLAG_READONLY)) {
+ vfu_log(vfu_ctx, LOG_ERR, "write of %zu bytes to read-only capability "
+ "%u (%s)\n", count, cap->id, cap->name);
+ return -EPERM;
+ }
+
+ if (cap->flags & VFU_CAP_FLAG_CALLBACK) {
+ return pci_nonstd_access(vfu_ctx, buf, count, offset, is_write);
+ }
+
+ if (!is_write) {
+ memcpy(buf, pci_config_space_ptr(vfu_ctx, offset), count);
+ return count;
+ }
+
+ if (offset - cap->off < cap->hdr_size) {
+ vfu_log(vfu_ctx, LOG_ERR,
+ "disallowed write to header for cap %d (%s)\n",
+ cap->id, cap->name);
+ return -EPERM;
+ }
+
+ return cap->cb(vfu_ctx, cap, buf, count, offset);
+}
+
+/*
+ * Place the new capability after the previous (or after the standard header if
+ * this is the first capability).
+ *
+ * If cap->off is already provided, place it directly, but first check it
+ * doesn't overlap an existing capability, or the PCI header. We still also need
+ * to link it into the list. There's no guarantee that the list is ordered by
+ * offset after doing so.
+ */
+static int
+cap_place(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, void *data)
+{
+ vfu_pci_config_space_t *config_space;
+ uint8_t *prevp;
+ size_t offset;
+
+ config_space = vfu_pci_get_config_space(vfu_ctx);
+
+ prevp = &config_space->hdr.cap;
+
+ if (cap->off != 0) {
+ if (cap->off < PCI_STD_HEADER_SIZEOF) {
+ vfu_log(vfu_ctx, LOG_ERR, "invalid offset %#lx for capability "
+ "%u (%s)\n", cap->off, cap->id, cap->name);
+ return EINVAL;
+ }
+
+ if (cap_find_by_offset(vfu_ctx, cap->off, cap->size) != NULL) {
+ vfu_log(vfu_ctx, LOG_ERR, "overlap found for capability "
+ "%u (%s)\n", cap->id, cap->name);
+ return EINVAL;
+ }
+
+ while (*prevp != 0) {
+ prevp = pci_config_space_ptr(vfu_ctx, *prevp + PCI_CAP_LIST_NEXT);
+ }
+ } else if (*prevp == 0) {
+ cap->off = PCI_STD_HEADER_SIZEOF;
+ } else {
+ for (offset = *prevp; offset != 0; offset = *prevp) {
+ uint8_t id;
+ size_t size;
+
+ id = *pci_config_space_ptr(vfu_ctx, offset + PCI_CAP_LIST_ID);
+ prevp = pci_config_space_ptr(vfu_ctx, offset + PCI_CAP_LIST_NEXT);
+
+ if (*prevp == 0) {
+ size = cap_size(id, pci_config_space_ptr(vfu_ctx, offset));
+ cap->off = ROUND_UP(offset + size, 4);
+ break;
+ }
+ }
+ }
+
+ if (cap->off + cap->size >
+ vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size) {
+ vfu_log(vfu_ctx, LOG_ERR, "no config space left for capability "
+ "%u (%s) of size %zu bytes at offset %#lx\n", cap->id,
+ cap->name, cap->size, cap->off);
+ return ENOSPC;
+ }
+
+ memcpy(cap_data(vfu_ctx, cap), data, cap->size);
+ /* Make sure the previous cap's PCI_CAP_LIST_NEXT points to us. */
+ *prevp = cap->off;
+ /* Make sure our PCI_CAP_LIST_NEXT is zeroed. */
+ *pci_config_space_ptr(vfu_ctx, cap->off + PCI_CAP_LIST_NEXT) = 0;
+ return 0;
+}
+
+ssize_t
+vfu_pci_add_capability(vfu_ctx_t *vfu_ctx, size_t pos, int flags, void *data)
+{
+ size_t space_size = vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size;
+ struct pci_cap cap;
+ int ret;
+
+ assert(vfu_ctx != NULL);
+
+ if (flags & ~(VFU_CAP_FLAG_EXTENDED | VFU_CAP_FLAG_CALLBACK |
+ VFU_CAP_FLAG_READONLY)) {
+ return ERROR(EINVAL);
+ }
+
+ if ((flags & VFU_CAP_FLAG_CALLBACK) &&
+ vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].cb == NULL) {
+ return ERROR(EINVAL);
+ }
+
+ if ((flags & VFU_CAP_FLAG_EXTENDED)) {
+ return ERROR(ENOTSUP);
+ }
+
+ if (vfu_ctx->pci.nr_caps == VFU_MAX_CAPS) {
+ return ERROR(ENOSPC);
+ }
+
+ cap.id = ((struct cap_hdr *)data)->id;
+ cap.hdr_size = sizeof (struct cap_hdr);
+ cap.flags = flags;
+ cap.off = pos;
+
+ switch (cap.id) {
+ case PCI_CAP_ID_PM:
+ cap.name = "PM";
+ cap.cb = cap_write_pm;
+ break;
+ case PCI_CAP_ID_EXP:
+ cap.name = "PCI Express";
+ cap.cb = cap_write_px;
+ break;
+ case PCI_CAP_ID_MSIX:
+ cap.name = "MSI-X";
+ cap.cb = cap_write_msix;
+ break;
+ case PCI_CAP_ID_VNDR:
+ cap.name = "Vendor Specific";
+ cap.cb = cap_write_vendor;
+ cap.hdr_size = sizeof (struct vsc);
+ break;
+ default:
+ vfu_log(vfu_ctx, LOG_ERR, "unsupported capability %#x\n", cap.id);
+ return ERROR(ENOTSUP);
+ }
+
+ cap.size = cap_size(cap.id, data);
+
+ if (cap.off + cap.size >= space_size) {
+ return ERROR(EINVAL);
+ }
+
+ ret = cap_place(vfu_ctx, &cap, data);
+
+ if (ret != 0) {
+ return ERROR(ret);
+ }
+
+ memcpy(&vfu_ctx->pci.caps[vfu_ctx->pci.nr_caps], &cap, sizeof (cap));
+ vfu_ctx->pci.nr_caps++;
+ return cap.off;
+}
+
+size_t
+vfu_pci_find_next_capability(vfu_ctx_t *vfu_ctx, bool extended,
+ size_t offset, int cap_id)
+{
+ size_t space_size = vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size;
+ vfu_pci_config_space_t *config_space;
+
+ assert(vfu_ctx != NULL);
+
+ if (extended) {
+ errno = ENOTSUP;
+ return 0;
+ }
+
+ if (offset + PCI_CAP_LIST_NEXT >= space_size) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ config_space = vfu_pci_get_config_space(vfu_ctx);
+
+ if (offset == 0) {
+ offset = config_space->hdr.cap;
+ } else {
+ offset = *pci_config_space_ptr(vfu_ctx, offset + PCI_CAP_LIST_NEXT);
+ }
+
+ if (offset == 0) {
+ errno = ENOENT;
+ return 0;
+ }
+
+ for (;;) {
+ uint8_t id, next;
+
+ /* Sanity check. */
+ if (offset + PCI_CAP_LIST_NEXT >= space_size) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ id = *pci_config_space_ptr(vfu_ctx, offset + PCI_CAP_LIST_ID);
+ next = *pci_config_space_ptr(vfu_ctx, offset + PCI_CAP_LIST_NEXT);
+
+ if (id == cap_id) {
+ return offset;
+ }
+
+ offset = next;
+
+ if (offset == 0) {
+ errno = ENOENT;
+ return 0;
+ }
+ }
+}
+
+size_t
+vfu_pci_find_capability(vfu_ctx_t *vfu_ctx, bool extended, int cap_id)
+{
+ return vfu_pci_find_next_capability(vfu_ctx, extended, 0, cap_id);
+}
+
+/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/lib/cap.h b/lib/pci_caps.h
index acd63e2..86448de 100644
--- a/lib/cap.h
+++ b/lib/pci_caps.h
@@ -30,30 +30,47 @@
*
*/
-#ifndef LIB_VFIO_USER_CAP_H
-#define LIB_VFIO_USER_CAP_H
+#ifndef LIB_VFIO_USER_PCI_CAPS_H
+#define LIB_VFIO_USER_PCI_CAPS_H
#include "libvfio-user.h"
-struct caps;
+/*
+ * This is an arbitrary value, but more than enough for max caps in extended
+ * config space.
+ */
+#define VFU_MAX_CAPS (1024)
+
+struct pci_cap;
-/**
- * Initializes PCI capabilities.
+typedef ssize_t (cap_write_cb_t)(vfu_ctx_t *vfu_ctx, struct pci_cap *cap,
+ char *buf, size_t count, loff_t offset);
+
+struct pci_cap {
+ const char *name;
+ uint8_t id;
+ size_t off;
+ size_t hdr_size;
+ size_t size;
+ unsigned int flags;
+ cap_write_cb_t *cb;
+};
+
+/*
+ * Return the first cap (if any) that intersects with the [off, off+count)
+ * interval.
*/
-struct caps *
-caps_create(vfu_ctx_t *vfu_ctx, vfu_cap_t **caps, int nr_caps, int *err);
+struct pci_cap *
+cap_find_by_offset(vfu_ctx_t *ctx, loff_t off, size_t count);
/*
- * Conditionally accesses the PCI capabilities. Returns:
- * 0: if no PCI capabilities are accessed,
- * >0: if a PCI capability was accessed, with the return value indicating the
- number of bytes accessed, and
- * <0: negative error code on error.
+ * Handle an access to a capability. The access is guaranteed to be entirely
+ * within a capability.
*/
ssize_t
-cap_maybe_access(vfu_ctx_t *vfu_ctx, struct caps *caps, char *buf, size_t count,
- loff_t offset);
+pci_cap_access(vfu_ctx_t *ctx, char *buf, size_t count, loff_t offset,
+ bool is_write);
-#endif /* LIB_VFIO_USER_CAP_H */
+#endif /* LIB_VFIO_USER_PCI_CAPS_H */
/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/lib/private.h b/lib/private.h
index a3b90f5..1674271 100644
--- a/lib/private.h
+++ b/lib/private.h
@@ -33,6 +33,7 @@
#ifndef LIB_VFIO_USER_PRIVATE_H
#define LIB_VFIO_USER_PRIVATE_H
+#include "pci_caps.h"
#include "dma.h"
static inline int
@@ -73,32 +74,23 @@ struct vfu_sparse_mmap_areas {
};
typedef struct {
-
- /*
- * Region flags, see VFU_REGION_FLAG_READ and friends.
- */
+ /* Region flags, see VFU_REGION_FLAG_READ and friends. */
uint32_t flags;
-
- /*
- * Size of the region.
- */
+ /* Size of the region. */
uint32_t size;
-
- /*
- * Callback function that is called when the region is read or written.
- * Note that the memory of the region is owned by the user, except for the
- * standard header (first 64 bytes) of the PCI configuration space.
- */
+ /* Callback that is called when the region is read or written. */
vfu_region_access_cb_t *cb;
-
- struct vfu_sparse_mmap_areas *mmap_areas; /* sparse mmap areas */
+ /* Sparse mmap areas if set. */
+ struct vfu_sparse_mmap_areas *mmap_areas;
+ /* fd for a mappable region, or -1. */
int fd;
} vfu_reg_info_t;
struct pci_dev {
vfu_pci_type_t type;
vfu_pci_config_space_t *config_space;
- struct caps *caps;
+ struct pci_cap caps[VFU_MAX_CAPS];
+ size_t nr_caps;
};
struct vfu_ctx {
diff --git a/samples/lspci.c b/samples/lspci.c
index e2fa6fc..9484042 100644
--- a/samples/lspci.c
+++ b/samples/lspci.c
@@ -30,10 +30,11 @@
*
*/
-#include <stdio.h>
#include <err.h>
-#include <stdlib.h>
#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include "libvfio-user.h"
@@ -42,9 +43,9 @@ int main(void)
int i, j;
char *buf;
const int bytes_per_line = 0x10;
- vfu_cap_t pm = { .pm = { .hdr.id = PCI_CAP_ID_PM, .pmcs.nsfrst = 0x1 } };
- vfu_cap_t *vsc = alloca(sizeof(*vsc) + 0xd);
- vfu_cap_t *caps[2] = { &pm, vsc };
+ struct vsc *vsc = alloca(sizeof(*vsc) + 0xd);
+ struct pmcap pm = { .hdr.id = PCI_CAP_ID_PM, .pmcs.nsfrst = 0x1 };
+
vfu_ctx_t *vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "",
LIBVFIO_USER_FLAG_ATTACH_NB, NULL,
VFU_DEV_TYPE_PCI);
@@ -55,11 +56,20 @@ int main(void)
PCI_HEADER_TYPE_NORMAL, 0) < 0) {
err(EXIT_FAILURE, "vfu_pci_init() failed");
}
- vsc->vsc.hdr.id = PCI_CAP_ID_VNDR;
- vsc->vsc.size = 0x10;
- if (vfu_pci_setup_caps(vfu_ctx, caps, 2) < 0) {
- err(EXIT_FAILURE, "failed to setup PCI capabilities");
+
+ if (vfu_pci_add_capability(vfu_ctx, 0, 0, &pm) < 0) {
+ err(EXIT_FAILURE, "vfu_pci_add_capability() failed");
}
+
+ memset(vsc, 0, 0x10);
+ vsc->hdr.id = PCI_CAP_ID_VNDR;
+ vsc->size = 0x10;
+ memcpy(vsc->data, "abcdefgh", 8);
+
+ if (vfu_pci_add_capability(vfu_ctx, 0, 0, vsc) < 0) {
+ err(EXIT_FAILURE, "vfu_pci_add_capability() failed");
+ }
+
if (vfu_realize_ctx(vfu_ctx) < 0) {
err(EXIT_FAILURE, "failed to realize device");
}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index cf7c5e3..50f58f2 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -27,12 +27,12 @@
#
add_executable(unit-tests unit-tests.c mocks.c
- ../lib/cap.c
../lib/dma.c
../lib/irq.c
../lib/libvfio-user.c
../lib/migration.c
../lib/pci.c
+ ../lib/pci_caps.c
../lib/tran_sock.c)
target_link_libraries(unit-tests PUBLIC cmocka json-c)
diff --git a/test/unit-tests.c b/test/unit-tests.c
index 2a62a3a..c03a37a 100644
--- a/test/unit-tests.c
+++ b/test/unit-tests.c
@@ -42,10 +42,10 @@
#include "dma.h"
#include "libvfio-user.h"
+#include "pci.h"
#include "private.h"
#include "migration.h"
#include "tran_sock.h"
-#include "cap.h"
static void
test_dma_map_without_dma(void **state __attribute__((unused)))
@@ -389,7 +389,7 @@ test_realize_ctx(void **state __attribute__((unused)))
assert_int_equal(PCI_CFG_SPACE_SIZE, cfg_reg->size);
assert_non_null(vfu_ctx.pci.config_space);
assert_non_null(vfu_ctx.irqs);
- assert_null(vfu_ctx.pci.caps);
+ assert_int_equal(0, vfu_ctx.pci.nr_caps);
}
static int
@@ -531,8 +531,10 @@ static void
test_vfu_ctx_create(void **state __attribute__((unused)))
{
vfu_ctx_t *vfu_ctx = NULL;
- vfu_cap_t pm = {.pm = {.hdr.id = PCI_CAP_ID_PM}};
- vfu_cap_t *caps[] = { &pm };
+ struct pmcap pm = { { 0 } };
+
+ pm.hdr.id = PCI_CAP_ID_PM;
+ pm.pmcs.nsfrst = 0x1;
vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "", LIBVFIO_USER_FLAG_ATTACH_NB,
NULL, VFU_DEV_TYPE_PCI);
@@ -541,10 +543,26 @@ test_vfu_ctx_create(void **state __attribute__((unused)))
assert_int_equal(1, vfu_ctx->irq_count[VFU_DEV_REQ_IRQ]);
assert_int_equal(0, vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_CONVENTIONAL,
PCI_HEADER_TYPE_NORMAL, 0));
- assert_int_equal(0, vfu_pci_setup_caps(vfu_ctx, caps, 1));
+ assert_int_equal(PCI_STD_HEADER_SIZEOF,
+ vfu_pci_add_capability(vfu_ctx, 0, 0, &pm));
assert_int_equal(0, vfu_realize_ctx(vfu_ctx));
}
+static ssize_t
+test_pci_caps_region_cb(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
+ loff_t offset, bool is_write)
+{
+ uint8_t *ptr = pci_config_space_ptr(vfu_ctx, offset);
+
+ assert_int_equal(is_write, true);
+ assert_int_equal(offset, PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + 8 +
+ offsetof(struct vsc, data));
+ assert_int_equal(count, 10);
+ assert_memory_equal(ptr, "Hello world.", 10);
+ memcpy(ptr, buf, count);
+ return count;
+}
+
static void
test_pci_caps(void **state __attribute__((unused)))
{
@@ -555,35 +573,72 @@ test_pci_caps(void **state __attribute__((unused)))
vfu_ctx_t vfu_ctx = { .pci.config_space = &config_space,
.reg_info = reg_info,
};
- vfu_cap_t pm = {.pm = {.hdr.id = PCI_CAP_ID_PM, .pmcs.raw = 0xabcd }};
- vfu_cap_t *vsc[2] = {
- alloca(sizeof(struct vsc) + 5),
- alloca(sizeof(struct vsc) + 13)
+ struct vsc *vsc1 = alloca(sizeof (*vsc1) + 3);
+ struct vsc *vsc2 = alloca(sizeof (*vsc2) + 13);
+ struct vsc *vsc3 = alloca(sizeof (*vsc3) + 13);
+ struct vsc *vsc4 = alloca(sizeof (*vsc4) + 13);
+ struct pmcap pm = { { 0 } };
+ size_t expoffsets[] = {
+ PCI_STD_HEADER_SIZEOF,
+ PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF,
+ /* NB: note 4-byte alignment of vsc2. */
+ PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + 8,
+ 0x80,
+ 0x90
};
- vfu_cap_t *vfu_caps[] = { &pm, vsc[0], vsc[1] };
- struct caps *caps;
- int err;
- struct pmcap pmcap = { .pmcs.raw = 0xef01 };
size_t offset;
- off_t off;
+ ssize_t ret;
+
+ memset(&config_space, 0, sizeof (config_space));
+
+ vfu_ctx.reg_info = calloc(VFU_PCI_DEV_NUM_REGIONS,
+ sizeof (*vfu_ctx.reg_info));
+
+ pm.hdr.id = PCI_CAP_ID_PM;
+ pm.pmcs.raw = 0xef01;
+
+ vsc1->hdr.id = PCI_CAP_ID_VNDR;
+ vsc1->size = 6;
+ memcpy(vsc1->data, "abc", 3);
+
+ vsc2->hdr.id = PCI_CAP_ID_VNDR;
+ vsc2->size = 16;
+ memcpy(vsc2->data, "Hello world.", 12);
- vsc[0]->vsc.hdr.id = PCI_CAP_ID_VNDR;
- vsc[0]->vsc.size = 8;
- memcpy(vsc[0]->vsc.data, "abcde", 5);
+ vsc3->hdr.id = PCI_CAP_ID_VNDR;
+ vsc3->size = 16;
+ memcpy(vsc3->data, "Hello world.", 12);
- vsc[1]->vsc.hdr.id = PCI_CAP_ID_VNDR;
- vsc[1]->vsc.size = 16;
- memcpy(vsc[1]->vsc.data, "Hello world.", 12);
+ vsc4->hdr.id = PCI_CAP_ID_VNDR;
+ vsc4->size = 16;
+ memcpy(vsc4->data, "Hello world.", 12);
- caps = caps_create(&vfu_ctx, vfu_caps, 3, &err);
- assert_non_null(caps);
+ offset = vfu_pci_add_capability(&vfu_ctx, 0, VFU_CAP_FLAG_CALLBACK, &pm);
+ assert_int_equal(-1, offset);
+ assert_int_equal(EINVAL, errno);
+
+ offset = vfu_pci_add_capability(&vfu_ctx, 256, 0, &pm);
+ assert_int_equal(-1, offset);
+ assert_int_equal(EINVAL, errno);
- /* check that capability list is placed correctly */
+ vfu_ctx.reg_info[VFU_PCI_DEV_CFG_REGION_IDX].cb = test_pci_caps_region_cb;
+ vfu_ctx.reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size = PCI_CFG_SPACE_SIZE;
+
+ offset = vfu_pci_add_capability(&vfu_ctx, 0, 0, &pm);
+ assert_int_equal(expoffsets[0], offset);
+ offset = vfu_pci_add_capability(&vfu_ctx, 0, VFU_CAP_FLAG_READONLY, vsc1);
+ assert_int_equal(expoffsets[1], offset);
+ offset = vfu_pci_add_capability(&vfu_ctx, 0, VFU_CAP_FLAG_CALLBACK, vsc2);
+ assert_int_equal(expoffsets[2], offset);
+ offset = vfu_pci_add_capability(&vfu_ctx, expoffsets[3], 0, vsc3);
+ assert_int_equal(expoffsets[3], offset);
+ offset = vfu_pci_add_capability(&vfu_ctx, expoffsets[4], 0, vsc4);
+ assert_int_equal(expoffsets[4], offset);
offset = vfu_pci_find_capability(&vfu_ctx, false, PCI_CAP_ID_PM);
- assert_int_equal(PCI_STD_HEADER_SIZEOF, offset);
+ assert_int_equal(expoffsets[0], offset);
assert_int_equal(PCI_CAP_ID_PM, config_space.raw[offset]);
- assert_int_equal(PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF,
+ assert_int_equal(expoffsets[1],
config_space.raw[offset + PCI_CAP_LIST_NEXT]);
offset = vfu_pci_find_next_capability(&vfu_ctx, false, offset,
@@ -591,31 +646,40 @@ test_pci_caps(void **state __attribute__((unused)))
assert_int_equal(0, offset);
offset = vfu_pci_find_capability(&vfu_ctx, false, PCI_CAP_ID_VNDR);
- assert_int_equal(PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF, offset);
+ assert_int_equal(expoffsets[1], offset);
assert_int_equal(PCI_CAP_ID_VNDR, config_space.raw[offset]);
+ assert_int_equal(expoffsets[2],
+ config_space.raw[offset + PCI_CAP_LIST_NEXT]);
offset = vfu_pci_find_next_capability(&vfu_ctx, false, offset,
PCI_CAP_ID_PM);
assert_int_equal(0, offset);
offset = vfu_pci_find_next_capability(&vfu_ctx, false, 0, PCI_CAP_ID_VNDR);
- assert_int_equal(PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF, offset);
+ assert_int_equal(expoffsets[1], offset);
assert_int_equal(PCI_CAP_ID_VNDR, config_space.raw[offset]);
- assert_int_equal(PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + vsc[0]->vsc.size,
+ assert_int_equal(expoffsets[2],
config_space.raw[offset + PCI_CAP_LIST_NEXT]);
- offset = vfu_pci_find_next_capability(&vfu_ctx, false, offset,
- PCI_CAP_ID_VNDR);
- assert_int_equal(PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + vsc[0]->vsc.size,
- offset);
+ offset = vfu_pci_find_next_capability(&vfu_ctx, false,
+ offset, PCI_CAP_ID_VNDR);
+ assert_int_equal(expoffsets[2], offset);
assert_int_equal(PCI_CAP_ID_VNDR, config_space.raw[offset]);
- assert_int_equal(0, config_space.raw[offset + PCI_CAP_LIST_NEXT]);
+ assert_int_equal(expoffsets[3],
+ config_space.raw[offset + PCI_CAP_LIST_NEXT]);
- offset = vfu_pci_find_next_capability(&vfu_ctx, false, offset,
- PCI_CAP_ID_VNDR);
+ offset = vfu_pci_find_next_capability(&vfu_ctx, false,
+ offset, PCI_CAP_ID_VNDR);
+ assert_int_equal(expoffsets[3], offset);
+ offset = vfu_pci_find_next_capability(&vfu_ctx, false,
+ offset, PCI_CAP_ID_VNDR);
+ assert_int_equal(expoffsets[4], offset);
+ offset = vfu_pci_find_next_capability(&vfu_ctx, false,
+ offset, PCI_CAP_ID_VNDR);
assert_int_equal(0, offset);
/* check for invalid offsets */
+
offset = vfu_pci_find_next_capability(&vfu_ctx, false, 8192, PCI_CAP_ID_PM);
assert_int_equal(0, offset);
assert_int_equal(EINVAL, errno);
@@ -634,22 +698,42 @@ test_pci_caps(void **state __attribute__((unused)))
assert_int_equal(ENOENT, errno);
/* check writing PMCS */
- assert_int_equal(0,
- cap_maybe_access(&vfu_ctx, caps, (char*)&pmcap.pmcs,
- sizeof(struct pmcs),
- PCI_STD_HEADER_SIZEOF + offsetof(struct pmcap, pmcs)));
- assert_memory_equal(
- &config_space.raw[PCI_STD_HEADER_SIZEOF + offsetof(struct pmcap, pmcs)],
- &pmcap.pmcs.raw, sizeof(struct pmcs));
-
- /*
- * Check that pci_cap_access returns 0 when reading a non-vendor-specific
- * capability which doesn't have a callback.
- */
- off = PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + PCI_CAP_FLAGS + 1;
- assert_int_equal(5,
- cap_maybe_access(&vfu_ctx, caps, (char*)vsc[0]->vsc.data, 5, off));
- assert_memory_equal(&config_space.raw[off], vsc[0]->vsc.data, 5);
+
+ pm.pmcs.raw = 0xffff;
+
+ ret = pci_config_space_access(&vfu_ctx, (char *)&pm.pmcs,
+ sizeof (struct pmcs), expoffsets[0] +
+ offsetof(struct pmcap, pmcs), true);
+
+ assert_int_equal(sizeof (struct pmcs), ret);
+
+ assert_memory_equal(pci_config_space_ptr(&vfu_ctx, expoffsets[0] +
+ offsetof(struct pmcap, pmcs)),
+ &pm.pmcs, sizeof (struct pmcs));
+
+ /* check read only capability */
+
+ ret = pci_config_space_access(&vfu_ctx, (char *)vsc1->data, 3,
+ expoffsets[1] + offsetof(struct vsc, data),
+ false);
+ assert_int_equal(ret, 3);
+ assert_memory_equal(vsc1->data, "abc", 3);
+
+ ret = pci_config_space_access(&vfu_ctx, "ced", 3,
+ expoffsets[1] + offsetof(struct vsc, data),
+ true);
+ assert_int_equal(ret, -EPERM);
+
+ /* check capability callback */
+
+ ret = pci_config_space_access(&vfu_ctx, "Bye world.", 10,
+ expoffsets[2] + offsetof(struct vsc, data),
+ true);
+
+ assert_int_equal(ret, 10);
+ assert_memory_equal(pci_config_space_ptr(&vfu_ctx,
+ expoffsets[2] + offsetof(struct vsc, data)),
+ "Bye world.", 10);
}
static void