aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Levon <john.levon@nutanix.com>2021-01-20 10:44:49 +0000
committerGitHub <noreply@github.com>2021-01-20 10:44:49 +0000
commit53cc29bc8ca5083b9b6075f151824d65557af6f0 (patch)
treee7ae6ef44eb87c81f74f1740d0da69614606d247
parentfa5104150bca4182f8a38d39fa50f7e61982568e (diff)
downloadlibvfio-user-53cc29bc8ca5083b9b6075f151824d65557af6f0.zip
libvfio-user-53cc29bc8ca5083b9b6075f151824d65557af6f0.tar.gz
libvfio-user-53cc29bc8ca5083b9b6075f151824d65557af6f0.tar.bz2
support extended capabilities (#226)
Provide initial support for extended capabilities, and implement handlers for the Device Serial Number and Vendor-Specific capabilities. Signed-off-by: John Levon <john.levon@nutanix.com> Reviewed-by: Swapnil Ingle <swapnil.ingle@nutanix.com>
-rw-r--r--.github/workflows/pull_request.yml2
-rw-r--r--include/libvfio-user.h7
-rw-r--r--include/pci_caps/common.h19
-rw-r--r--include/pci_caps/dsn.h60
-rw-r--r--lib/libvfio-user.c1
-rw-r--r--lib/pci.h10
-rw-r--r--lib/pci_caps.c585
-rw-r--r--lib/pci_caps.h3
-rw-r--r--lib/private.h2
-rw-r--r--samples/lspci.c36
-rw-r--r--test/CMakeLists.txt1
-rw-r--r--test/lspci.expected.out29
-rw-r--r--test/lspci.expected.out.230
-rwxr-xr-xtest/test-lspci.sh12
-rw-r--r--test/unit-tests.c183
15 files changed, 777 insertions, 203 deletions
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 1f661a5..024a77c 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -24,6 +24,6 @@ jobs:
- uses: actions/checkout@v2
- name: pre-push
run: |
- yum -y install make gcc-4.8.5 epel-release
+ yum -y install make gcc-4.8.5 epel-release pciutils
yum -y install clang cmake json-c-devel libcmocka-devel openssl-devel
make pre-push VERBOSE=1
diff --git a/include/libvfio-user.h b/include/libvfio-user.h
index 0b5ac02..d7d54a7 100644
--- a/include/libvfio-user.h
+++ b/include/libvfio-user.h
@@ -43,11 +43,12 @@
#include <unistd.h>
#include <syslog.h>
-#include "pci_defs.h"
-#include "pci_caps/pm.h"
-#include "pci_caps/px.h"
+#include "pci_caps/dsn.h"
#include "pci_caps/msi.h"
#include "pci_caps/msix.h"
+#include "pci_caps/pm.h"
+#include "pci_caps/px.h"
+#include "pci_defs.h"
#include "vfio-user.h"
#ifdef __cplusplus
diff --git a/include/pci_caps/common.h b/include/pci_caps/common.h
index a820f0d..b532e15 100644
--- a/include/pci_caps/common.h
+++ b/include/pci_caps/common.h
@@ -31,6 +31,7 @@
#ifndef LIB_VFIO_USER_PCI_CAPS_COMMON_H
#define LIB_VFIO_USER_PCI_CAPS_COMMON_H
+#include <linux/pci_regs.h>
#include <stddef.h>
#ifdef __cplusplus
@@ -54,6 +55,24 @@ struct vsc {
uint8_t data[];
} __attribute__ ((packed));
+/*
+ * PCI Express extended capability header.
+ */
+struct pcie_ext_cap_hdr {
+ unsigned int id:16;
+ unsigned int version:4;
+ unsigned int next:12;
+} __attribute__((packed));
+
+/* PCI Express vendor-specific capability header (PCIE 7.19) */
+struct pcie_ext_cap_vsc_hdr {
+ struct pcie_ext_cap_hdr hdr;
+ unsigned int id:16;
+ unsigned int rev:4;
+ unsigned int len:12;
+ uint8_t data[];
+} __attribute__((packed));
+
#ifdef __cplusplus
}
#endif
diff --git a/include/pci_caps/dsn.h b/include/pci_caps/dsn.h
new file mode 100644
index 0000000..3894f5e
--- /dev/null
+++ b/include/pci_caps/dsn.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ *
+ */
+
+/*
+ * Device Serial Number (PCIE 7.12).
+ */
+
+#ifndef LIB_VFIO_USER_PCI_CAPS_DSN_H
+#define LIB_VFIO_USER_PCI_CAPS_DSN_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct dsncap {
+ struct pcie_ext_cap_hdr hdr;
+ uint32_t sn_lo;
+ uint32_t sn_hi;
+} __attribute__((packed));
+_Static_assert(sizeof (struct dsncap) == PCI_EXT_CAP_DSN_SIZEOF,
+ "bad DSN Capability size");
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIB_VFIO_USER_PCI_CAPS_DSN_H */
+
+/* ex: set tabstop=4 shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/lib/libvfio-user.c b/lib/libvfio-user.c
index 8bd704a..be310a3 100644
--- a/lib/libvfio-user.c
+++ b/lib/libvfio-user.c
@@ -1033,6 +1033,7 @@ vfu_realize_ctx(vfu_ctx_t *vfu_ctx)
}
}
+ // FIXME: verify we don't need this for ext caps
if (vfu_ctx->pci.nr_caps != 0) {
vfu_ctx->pci.config_space->hdr.sts.cl = 0x1;
}
diff --git a/lib/pci.h b/lib/pci.h
index faea75c..4b9f056 100644
--- a/lib/pci.h
+++ b/lib/pci.h
@@ -45,11 +45,17 @@ pci_config_space_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
loff_t pos, bool is_write);
+static inline size_t
+pci_config_space_size(vfu_ctx_t *vfu_ctx)
+{
+ return vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size;
+}
+
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];
+ assert((size_t)offset < pci_config_space_size(vfu_ctx));
+ return (uint8_t *)vfu_ctx->pci.config_space + offset;
}
#endif /* LIB_VFIO_USER_PCI_H */
diff --git a/lib/pci_caps.c b/lib/pci_caps.c
index 8bc0f2d..991c70e 100644
--- a/lib/pci_caps.c
+++ b/lib/pci_caps.c
@@ -47,6 +47,11 @@
* - 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.
+ *
+ * Extended capabilities live in extended space (after the first 256 bytes), so
+ * can never clash with a standard capability. An empty capability list is
+ * signalled by a zeroed header at offset 256 (which the config space has by
+ * default).
*/
#include <assert.h>
@@ -62,6 +67,9 @@
#include "pci.h"
#include "private.h"
+/* All capabilities must be dword-aligned. */
+#define CAP_ROUND (4)
+
static void *
cap_data(vfu_ctx_t *vfu_ctx, struct pci_cap *cap)
{
@@ -69,19 +77,36 @@ cap_data(vfu_ctx_t *vfu_ctx, struct pci_cap *cap)
}
static size_t
-cap_size(uint8_t id, uint8_t *data)
+cap_size(vfu_ctx_t *vfu_ctx, void *data, bool extended)
{
- 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;
+ if (extended) {
+ uint16_t id = ((struct pcie_ext_cap_hdr *)data)->id;
+
+ switch (id) {
+ case PCI_EXT_CAP_ID_DSN:
+ return PCI_EXT_CAP_DSN_SIZEOF;
+ case PCI_EXT_CAP_ID_VNDR:
+ return ((struct pcie_ext_cap_vsc_hdr *)data)->len;
+ default:
+ vfu_log(vfu_ctx, LOG_ERR, "invalid cap id %u\n", id);
+ abort();
+ }
+ } else {
+ uint8_t id = ((struct cap_hdr *)data)->id;
+
+ 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:
+ vfu_log(vfu_ctx, LOG_ERR, "invalid cap id %u\n", id);
+ abort();
+ }
}
}
@@ -89,20 +114,20 @@ 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;
+ 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
@@ -111,47 +136,47 @@ cap_write_pm(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char * buf,
{
struct pmcap *pm = cap_data(vfu_ctx, cap);
- switch (offset - cap->off) {
- case offsetof(struct pmcap, pc):
- if (count != sizeof (struct pc)) {
- return -EINVAL;
- }
+ 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);
+ 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;
+ }
+ 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);
+ assert(msix != NULL);
+ assert(mxc != NULL);
- if (mxc->mxe != msix->mxc.mxe) {
- vfu_log(vfu_ctx, LOG_DEBUG, "%s MSI-X\n",
+ 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,
+ 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;
- }
+ }
+ msix->mxc.fm = mxc->fm;
+ }
- return sizeof(struct mxc);
+ return sizeof(struct mxc);
}
static ssize_t
@@ -160,101 +185,101 @@ cap_write_msix(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char *buf,
{
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,
+ 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;
+ 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);
+ 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->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",
+ 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",
+ }
+
+ 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->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->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");
- }
+ if (p->iflr) {
+ vfu_log(vfu_ctx, LOG_DEBUG,
+ "initiate function level reset\n");
+ }
- return 0;
+ 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;
+ switch (off) {
+ case offsetof(struct pxcap, pxdc):
+ return handle_px_pxdc_write(vfu_ctx, px, (union pxdc *)buf);
+ }
+ return -EINVAL;
}
static ssize_t
@@ -263,16 +288,16 @@ cap_write_px(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char *buf,
{
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;
+ 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
@@ -283,6 +308,22 @@ cap_write_vendor(vfu_ctx_t *vfu_ctx, struct pci_cap *cap UNUSED, char *buf,
return count;
}
+static ssize_t
+ext_cap_write_dsn(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char *buf UNUSED,
+ size_t count UNUSED, loff_t offset UNUSED)
+{
+ vfu_log(vfu_ctx, LOG_ERR, "%s capability is read-only\n", cap->name);
+ return -EPERM;
+}
+
+static ssize_t
+ext_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)
{
@@ -301,6 +342,12 @@ cap_find_by_offset(vfu_ctx_t *vfu_ctx, loff_t offset, size_t count)
}
}
+ for (i = 0; i < vfu_ctx->pci.nr_ext_caps; i++) {
+ struct pci_cap *cap = &vfu_ctx->pci.ext_caps[i];
+ if (ranges_intersect(offset, count, cap->off, cap->size)) {
+ return cap;
+ }
+ }
return NULL;
}
@@ -352,7 +399,7 @@ 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;
+ uint8_t *prevp = NULL;
size_t offset;
config_space = vfu_pci_get_config_space(vfu_ctx);
@@ -379,22 +426,20 @@ cap_place(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, void *data)
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));
+ size = cap_size(vfu_ctx, pci_config_space_ptr(vfu_ctx, offset),
+ false);
cap->off = ROUND_UP(offset + size, 4);
break;
}
}
}
- if (cap->off + cap->size >
- vfu_ctx->reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size) {
+ if (cap->off + cap->size > pci_config_space_size(vfu_ctx)) {
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);
@@ -409,11 +454,81 @@ cap_place(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, void *data)
return 0;
}
+/*
+ * Place the new extended capability after the previous (or at the beginning of
+ * extended config space, replacing the initial zeroed capability).
+ *
+ * If cap->off is already provided, place it directly, but first check it
+ * doesn't overlap an existing extended capability, and that the first one
+ * replaces the initial zeroed capability. We also still need to link it into
+ * the list.
+ */
+static int
+ext_cap_place(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, void *data)
+{
+ struct pcie_ext_cap_hdr *hdr = NULL;
+
+ hdr = (void *)pci_config_space_ptr(vfu_ctx, PCI_CFG_SPACE_SIZE);
+
+ if (cap->off != 0) {
+ if (cap->off < PCI_CFG_SPACE_SIZE) {
+ 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;
+ }
+
+ if (hdr->id == 0x0 && cap->off != PCI_CFG_SPACE_SIZE) {
+ vfu_log(vfu_ctx, LOG_ERR, "first extended capability must be at "
+ "%#x\n", PCI_CFG_SPACE_SIZE);
+ return EINVAL;
+ }
+
+ while (hdr->next != 0) {
+ hdr = (void *)pci_config_space_ptr(vfu_ctx, hdr->next);
+ }
+ } else if (hdr->id == 0x0) {
+ hdr = NULL;
+ cap->off = PCI_CFG_SPACE_SIZE;
+ } else {
+ while (hdr->next != 0) {
+ hdr = (void *)pci_config_space_ptr(vfu_ctx, hdr->next);
+ }
+
+ cap->off = ROUND_UP((uint8_t *)hdr + cap_size(vfu_ctx, hdr, true) -
+ pci_config_space_ptr(vfu_ctx, 0), CAP_ROUND);
+ }
+
+ if (cap->off + cap->size > pci_config_space_size(vfu_ctx)) {
+ 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 next points to us. */
+ if (hdr != NULL) {
+ assert((cap->off & 0x3) == 0);
+ hdr->next = cap->off;
+ }
+
+ hdr = (void *)pci_config_space_ptr(vfu_ctx, cap->off);
+ hdr->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;
+ bool extended = (flags & VFU_CAP_FLAG_EXTENDED);
+ struct pci_cap cap = { 0 };
int ret;
assert(vfu_ctx != NULL);
@@ -428,82 +543,164 @@ vfu_pci_add_capability(vfu_ctx_t *vfu_ctx, size_t pos, int flags, void *data)
return ERROR(EINVAL);
}
- if ((flags & VFU_CAP_FLAG_EXTENDED)) {
- return ERROR(ENOTSUP);
- }
+ cap.off = pos;
+ cap.flags = flags;
+ cap.extended = extended;
- if (vfu_ctx->pci.nr_caps == VFU_MAX_CAPS) {
- return ERROR(ENOSPC);
- }
+ if (extended) {
+ switch (vfu_ctx->pci.type) {
+ case VFU_PCI_TYPE_PCI_X_2:
+ case VFU_PCI_TYPE_EXPRESS:
+ break;
+ default:
+ return ERROR(EINVAL);
+ }
- cap.id = ((struct cap_hdr *)data)->id;
- cap.hdr_size = sizeof (struct cap_hdr);
- cap.flags = flags;
- cap.off = pos;
+ if (vfu_ctx->pci.nr_ext_caps == VFU_MAX_CAPS) {
+ return ERROR(ENOSPC);
+ }
- 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.id = ((struct pcie_ext_cap_hdr *)data)->id;
+ cap.hdr_size = sizeof (struct pcie_ext_cap_hdr);
+
+ switch (cap.id) {
+ case PCI_EXT_CAP_ID_DSN:
+ cap.name = "Device Serial Number";
+ cap.cb = ext_cap_write_dsn;
+ break;
+ case PCI_EXT_CAP_ID_VNDR:
+ cap.name = "Vendor-Specific";
+ cap.cb = ext_cap_write_vendor;
+ cap.hdr_size = sizeof (struct pcie_ext_cap_vsc_hdr);
+ break;
+ default:
+ vfu_log(vfu_ctx, LOG_ERR, "unsupported capability %#x\n", cap.id);
+ return ERROR(ENOTSUP);
+ }
- cap.size = cap_size(cap.id, data);
+ cap.size = cap_size(vfu_ctx, data, extended);
- if (cap.off + cap.size >= space_size) {
- return ERROR(EINVAL);
- }
+ if (cap.off + cap.size >= pci_config_space_size(vfu_ctx)) {
+ return ERROR(EINVAL);
+ }
+
+ ret = ext_cap_place(vfu_ctx, &cap, data);
+
+ } else {
+ if (vfu_ctx->pci.nr_caps == VFU_MAX_CAPS) {
+ return ERROR(ENOSPC);
+ }
- ret = cap_place(vfu_ctx, &cap, data);
+ cap.id = ((struct cap_hdr *)data)->id;
+ cap.hdr_size = sizeof (struct cap_hdr);
+
+ switch (cap.id) {
+ case PCI_CAP_ID_PM:
+ cap.name = "Power Management";
+ 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(vfu_ctx, data, extended);
+
+ if (cap.off + cap.size >= pci_config_space_size(vfu_ctx)) {
+ 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++;
+ if (extended) {
+ memcpy(&vfu_ctx->pci.ext_caps[vfu_ctx->pci.nr_ext_caps],
+ &cap, sizeof (cap));
+ vfu_ctx->pci.nr_ext_caps++;
+ } else {
+ memcpy(&vfu_ctx->pci.caps[vfu_ctx->pci.nr_caps], &cap, sizeof (cap));
+ vfu_ctx->pci.nr_caps++;
+ }
+
return cap.off;
}
+static size_t
+vfu_pci_find_next_ext_capability(vfu_ctx_t *vfu_ctx, size_t offset, int cap_id)
+{
+ struct pcie_ext_cap_hdr *hdr = NULL;
+
+ if (offset + sizeof (*hdr) >= pci_config_space_size(vfu_ctx)) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ if (offset == 0) {
+ offset = PCI_CFG_SPACE_SIZE;
+ hdr = (void *)pci_config_space_ptr(vfu_ctx, offset);
+ } else {
+ hdr = (void *)pci_config_space_ptr(vfu_ctx, offset);
+ hdr = (void *)pci_config_space_ptr(vfu_ctx, hdr->next);
+ }
+
+ for (;;) {
+ offset = (uint8_t *)hdr - pci_config_space_ptr(vfu_ctx, 0);
+
+ if (offset + sizeof (*hdr) >= pci_config_space_size(vfu_ctx)) {
+ errno = EINVAL;
+ return 0;
+ }
+
+ if (hdr->id == cap_id) {
+ return offset;
+ }
+
+ if (hdr->next == 0) {
+ break;
+ }
+
+ hdr = (void *)pci_config_space_ptr(vfu_ctx, hdr->next);
+ }
+
+ errno = ENOENT;
+ return 0;
+}
+
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;
+ return vfu_pci_find_next_ext_capability(vfu_ctx, offset, cap_id);
}
- if (offset + PCI_CAP_LIST_NEXT >= space_size) {
+ if (offset + PCI_CAP_LIST_NEXT >= pci_config_space_size(vfu_ctx)) {
errno = EINVAL;
return 0;
}
- config_space = vfu_pci_get_config_space(vfu_ctx);
-
if (offset == 0) {
- offset = config_space->hdr.cap;
+ offset = vfu_pci_get_config_space(vfu_ctx)->hdr.cap;
} else {
offset = *pci_config_space_ptr(vfu_ctx, offset + PCI_CAP_LIST_NEXT);
}
@@ -517,7 +714,7 @@ vfu_pci_find_next_capability(vfu_ctx_t *vfu_ctx, bool extended,
uint8_t id, next;
/* Sanity check. */
- if (offset + PCI_CAP_LIST_NEXT >= space_size) {
+ if (offset + PCI_CAP_LIST_NEXT >= pci_config_space_size(vfu_ctx)) {
errno = EINVAL;
return 0;
}
diff --git a/lib/pci_caps.h b/lib/pci_caps.h
index 86448de..cd72f56 100644
--- a/lib/pci_caps.h
+++ b/lib/pci_caps.h
@@ -48,7 +48,8 @@ typedef ssize_t (cap_write_cb_t)(vfu_ctx_t *vfu_ctx, struct pci_cap *cap,
struct pci_cap {
const char *name;
- uint8_t id;
+ bool extended;
+ uint16_t id;
size_t off;
size_t hdr_size;
size_t size;
diff --git a/lib/private.h b/lib/private.h
index 84caad5..c7a4b68 100644
--- a/lib/private.h
+++ b/lib/private.h
@@ -87,6 +87,8 @@ struct pci_dev {
vfu_pci_config_space_t *config_space;
struct pci_cap caps[VFU_MAX_CAPS];
size_t nr_caps;
+ struct pci_cap ext_caps[VFU_MAX_CAPS];
+ size_t nr_ext_caps;
};
struct vfu_ctx {
diff --git a/samples/lspci.c b/samples/lspci.c
index 9484042..358fefe 100644
--- a/samples/lspci.c
+++ b/samples/lspci.c
@@ -44,7 +44,13 @@ int main(void)
char *buf;
const int bytes_per_line = 0x10;
struct vsc *vsc = alloca(sizeof(*vsc) + 0xd);
+ struct pcie_ext_cap_vsc_hdr *evsc = alloca(sizeof(*evsc) + 0xd);
+ struct dsncap dsn = { .hdr.id = PCI_EXT_CAP_ID_DSN,
+ .sn_lo = 0xdeadbeef,
+ .sn_hi = 0xcafebabe };
struct pmcap pm = { .hdr.id = PCI_CAP_ID_PM, .pmcs.nsfrst = 0x1 };
+ /* Required for lspci to report extended caps. */
+ struct pxcap px = { .hdr.id = PCI_CAP_ID_EXP };
vfu_ctx_t *vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "",
LIBVFIO_USER_FLAG_ATTACH_NB, NULL,
@@ -52,7 +58,7 @@ int main(void)
if (vfu_ctx == NULL) {
err(EXIT_FAILURE, "failed to create libvfio-user context");
}
- if (vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_CONVENTIONAL,
+ if (vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_EXPRESS,
PCI_HEADER_TYPE_NORMAL, 0) < 0) {
err(EXIT_FAILURE, "vfu_pci_init() failed");
}
@@ -70,12 +76,38 @@ int main(void)
err(EXIT_FAILURE, "vfu_pci_add_capability() failed");
}
+ if (vfu_pci_add_capability(vfu_ctx, 0, 0, &px) < 0) {
+ err(EXIT_FAILURE, "vfu_pci_add_capability() failed");
+ }
+
+ if (vfu_pci_add_capability(vfu_ctx, 0, VFU_CAP_FLAG_EXTENDED, &dsn) < 0) {
+ err(EXIT_FAILURE, "vfu_pci_add_capability() failed");
+ }
+
+ memset(evsc, 0, sizeof(*evsc) + 0xd);
+ evsc->hdr.id = PCI_EXT_CAP_ID_VNDR;
+ evsc->id = 1;
+ evsc->rev = 1;
+ evsc->len = sizeof(*evsc) + 0xd;
+
+ if (vfu_pci_add_capability(vfu_ctx, 0, VFU_CAP_FLAG_EXTENDED, evsc) < 0) {
+ err(EXIT_FAILURE, "vfu_pci_add_capability() failed");
+ }
+
+ evsc->id = 2;
+ evsc->rev = 2;
+
+ if (vfu_pci_add_capability(vfu_ctx, 0x400,
+ VFU_CAP_FLAG_EXTENDED, evsc) < 0) {
+ err(EXIT_FAILURE, "vfu_pci_add_capability() failed");
+ }
+
if (vfu_realize_ctx(vfu_ctx) < 0) {
err(EXIT_FAILURE, "failed to realize device");
}
buf = (char*)vfu_pci_get_config_space(vfu_ctx);
printf("00:00.0 bogus PCI device\n");
- for (i = 0; i < PCI_CFG_SPACE_SIZE / bytes_per_line; i++) {
+ for (i = 0; i < PCI_CFG_SPACE_EXP_SIZE / bytes_per_line; i++) {
printf("%02x:", i * bytes_per_line);
for (j = 0; j < bytes_per_line; j++) {
printf(" %02x", buf[i * bytes_per_line + j] & 0xff);
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 50f58f2..9c51d4d 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -56,3 +56,4 @@ target_link_libraries(unit-tests PUBLIC "-Wl,--wrap=process_request")
enable_testing()
add_test(NAME unit-tests COMMAND unit-tests)
+add_test(NAME lspci COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test-lspci.sh)
diff --git a/test/lspci.expected.out b/test/lspci.expected.out
new file mode 100644
index 0000000..b01456a
--- /dev/null
+++ b/test/lspci.expected.out
@@ -0,0 +1,29 @@
+00:00.0 Non-VGA unclassified device: Device 0000:0000
+ Control: I/O- Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
+ Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
+ Region 0: I/O ports at <unassigned> [disabled]
+ Region 1: I/O ports at <unassigned> [disabled]
+ Region 2: I/O ports at <unassigned> [disabled]
+ Region 3: I/O ports at <unassigned> [disabled]
+ Region 4: I/O ports at <unassigned> [disabled]
+ Region 5: I/O ports at <unassigned> [disabled]
+ Capabilities: [40] Power Management version 0
+ Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0-,D1-,D2-,D3hot-,D3cold-)
+ Status: D0 NoSoftRst+ PME-Enable- DSel=0 DScale=0 PME-
+ Capabilities: [48] Vendor Specific Information: Len=10 <?>
+ Capabilities: [58] Express (v0) Endpoint, MSI 00
+ DevCap: MaxPayload 128 bytes, PhantFunc 0, Latency L0s <64ns, L1 <1us
+ ExtTag- AttnBtn- AttnInd- PwrInd- RBE- FLReset- SlotPowerLimit 0.000W
+ DevCtl: Report errors: Correctable- Non-Fatal- Fatal- Unsupported-
+ RlxdOrd- ExtTag- PhantFunc- AuxPwr- NoSnoop-
+ MaxPayload 128 bytes, MaxReadReq 128 bytes
+ DevSta: CorrErr- UncorrErr- FatalErr- UnsuppReq- AuxPwr- TransPend-
+ LnkCap: Port #0, Speed unknown, Width x0, ASPM not supported, Exit Latency L0s <64ns, L1 <1us
+ ClockPM- Surprise- LLActRep- BwNot- ASPMOptComp-
+ LnkCtl: ASPM Disabled; RCB 64 bytes Disabled- CommClk-
+ ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt-
+ LnkSta: Speed unknown, Width x0, TrErr- Train- SlotClk- DLActive- BWMgmt- ABWMgmt-
+ Capabilities: [100 v0] Device Serial Number ca-fe-ba-be-de-ad-be-ef
+ Capabilities: [10c v0] Vendor Specific Information: ID=0001 Rev=1 Len=015 <?>
+ Capabilities: [400 v0] Vendor Specific Information: ID=0002 Rev=2 Len=015 <?>
+
diff --git a/test/lspci.expected.out.2 b/test/lspci.expected.out.2
new file mode 100644
index 0000000..fc1abf6
--- /dev/null
+++ b/test/lspci.expected.out.2
@@ -0,0 +1,30 @@
+00:00.0 Non-VGA unclassified device: Device 0000:0000
+ Control: I/O- Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
+ Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
+ Region 0: I/O ports at <unassigned> [virtual]
+ Region 1: I/O ports at <unassigned> [virtual]
+ Region 2: I/O ports at <unassigned> [virtual]
+ Region 3: I/O ports at <unassigned> [virtual]
+ Region 4: I/O ports at <unassigned> [virtual]
+ Region 5: I/O ports at <unassigned> [virtual]
+ Capabilities: [40] Power Management version 0
+ Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0-,D1-,D2-,D3hot-,D3cold-)
+ Status: D0 NoSoftRst+ PME-Enable- DSel=0 DScale=0 PME-
+ Capabilities: [48] Vendor Specific Information: Len=10 <?>
+ Capabilities: [58] Express (v0) Endpoint, MSI 00
+ DevCap: MaxPayload 128 bytes, PhantFunc 0, Latency L0s <64ns, L1 <1us
+ ExtTag- AttnBtn- AttnInd- PwrInd- RBE- FLReset- SlotPowerLimit 0.000W
+ DevCtl: CorrErr- NonFatalErr- FatalErr- UnsupReq-
+ RlxdOrd- ExtTag- PhantFunc- AuxPwr- NoSnoop-
+ MaxPayload 128 bytes, MaxReadReq 128 bytes
+ DevSta: CorrErr- NonFatalErr- FatalErr- UnsupReq- AuxPwr- TransPend-
+ LnkCap: Port #0, Speed unknown, Width x0, ASPM not supported
+ ClockPM- Surprise- LLActRep- BwNot- ASPMOptComp-
+ LnkCtl: ASPM Disabled; RCB 64 bytes Disabled- CommClk-
+ ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt-
+ LnkSta: Speed unknown (ok), Width x0 (ok)
+ TrErr- Train- SlotClk- DLActive- BWMgmt- ABWMgmt-
+ Capabilities: [100 v0] Device Serial Number ca-fe-ba-be-de-ad-be-ef
+ Capabilities: [10c v0] Vendor Specific Information: ID=0001 Rev=1 Len=015 <?>
+ Capabilities: [400 v0] Vendor Specific Information: ID=0002 Rev=2 Len=015 <?>
+
diff --git a/test/test-lspci.sh b/test/test-lspci.sh
new file mode 100755
index 0000000..04acabd
--- /dev/null
+++ b/test/test-lspci.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+#
+# There are two different potential outputs on the distributions we test for;
+# accept either.
+#
+
+../samples/lspci | lspci -vv -F /dev/stdin >lspci.out
+diff lspci.out $(dirname $0)/lspci.expected.out || {
+ diff lspci.out $(dirname $0)/lspci.expected.out.2
+}
+exit $?
diff --git a/test/unit-tests.c b/test/unit-tests.c
index 1af604e..402b7d0 100644
--- a/test/unit-tests.c
+++ b/test/unit-tests.c
@@ -390,6 +390,7 @@ test_realize_ctx(void **state __attribute__((unused)))
assert_non_null(vfu_ctx.pci.config_space);
assert_non_null(vfu_ctx.irqs);
assert_int_equal(0, vfu_ctx.pci.nr_caps);
+ assert_int_equal(0, vfu_ctx.pci.nr_ext_caps);
}
static int
@@ -735,6 +736,187 @@ test_pci_caps(void **state __attribute__((unused)))
"Bye world.", 10);
}
+static ssize_t
+test_pci_ext_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_CFG_SPACE_SIZE + sizeof (struct dsncap) +
+ sizeof (struct pcie_ext_cap_vsc_hdr) + 8 +
+ sizeof (struct pcie_ext_cap_vsc_hdr));
+ assert_int_equal(count, 10);
+ assert_memory_equal(ptr, "Hello world.", 10);
+ memcpy(ptr, buf, count);
+ return count;
+}
+
+static void
+test_pci_ext_caps(void **state __attribute__((unused)))
+{
+ uint8_t config_space[PCI_CFG_SPACE_EXP_SIZE] = { 0, };
+ vfu_reg_info_t reg_info[VFU_PCI_DEV_NUM_REGIONS] = {
+ [VFU_PCI_DEV_CFG_REGION_IDX] = { .size = PCI_CFG_SPACE_EXP_SIZE },
+ };
+ vfu_ctx_t vfu_ctx = { .pci.config_space = (void *)&config_space,
+ .reg_info = reg_info,
+ };
+
+ struct pcie_ext_cap_vsc_hdr *vsc1 = alloca(sizeof (*vsc1) + 5);
+ struct pcie_ext_cap_vsc_hdr *vsc2 = alloca(sizeof (*vsc2) + 13);
+ struct pcie_ext_cap_vsc_hdr *vsc3 = alloca(sizeof (*vsc3) + 13);
+ struct pcie_ext_cap_vsc_hdr *vsc4 = alloca(sizeof (*vsc4) + 13);
+ struct pcie_ext_cap_hdr *hdr;
+ size_t expoffsets[] = {
+ PCI_CFG_SPACE_SIZE,
+ PCI_CFG_SPACE_SIZE + sizeof (struct dsncap),
+ PCI_CFG_SPACE_SIZE + sizeof (struct dsncap) + sizeof (*vsc1) + 8,
+ 512,
+ 600
+ };
+ struct dsncap dsn;
+ size_t offset;
+ ssize_t ret;
+
+ vfu_ctx.pci.type = VFU_PCI_TYPE_EXPRESS;
+
+ vfu_ctx.reg_info = calloc(VFU_PCI_DEV_NUM_REGIONS,
+ sizeof (*vfu_ctx.reg_info));
+
+ vfu_ctx.reg_info[VFU_PCI_DEV_CFG_REGION_IDX].cb = test_pci_ext_caps_region_cb;
+ vfu_ctx.reg_info[VFU_PCI_DEV_CFG_REGION_IDX].size = PCI_CFG_SPACE_EXP_SIZE;
+
+ dsn.hdr.id = PCI_EXT_CAP_ID_DSN;
+ dsn.sn_lo = 0x4;
+ dsn.sn_hi = 0x8;
+
+ vsc1->hdr.id = PCI_EXT_CAP_ID_VNDR;
+ vsc1->len = sizeof (*vsc1) + 5;
+ memcpy(vsc1->data, "abcde", 5);
+ vsc2->hdr.id = PCI_EXT_CAP_ID_VNDR;
+ vsc2->len = sizeof (*vsc2) + 13;
+ memcpy(vsc2->data, "Hello world.", 12);
+ vsc3->hdr.id = PCI_EXT_CAP_ID_VNDR;
+ vsc3->len = sizeof (*vsc3) + 13;
+ memcpy(vsc3->data, "Hello world.", 12);
+ vsc4->hdr.id = PCI_EXT_CAP_ID_VNDR;
+ vsc4->len = sizeof (*vsc4) + 13;
+ memcpy(vsc4->data, "Hello world.", 12);
+
+ offset = vfu_pci_add_capability(&vfu_ctx, 4096, VFU_CAP_FLAG_EXTENDED, &dsn);
+ assert_int_equal(-1, offset);
+ assert_int_equal(EINVAL, errno);
+
+ /* First cap must be at 256 */
+ offset = vfu_pci_add_capability(&vfu_ctx, 512, VFU_CAP_FLAG_EXTENDED, &dsn);
+ assert_int_equal(-1, offset);
+ assert_int_equal(EINVAL, errno);
+
+ offset = vfu_pci_add_capability(&vfu_ctx, 0, VFU_CAP_FLAG_EXTENDED, &dsn);
+ assert_int_equal(expoffsets[0], offset);
+ offset = vfu_pci_add_capability(&vfu_ctx, 0, VFU_CAP_FLAG_EXTENDED |
+ VFU_CAP_FLAG_READONLY, vsc1);
+ assert_int_equal(expoffsets[1], offset);
+ offset = vfu_pci_add_capability(&vfu_ctx, 0, VFU_CAP_FLAG_EXTENDED |
+ VFU_CAP_FLAG_CALLBACK, vsc2);
+ assert_int_equal(expoffsets[2], offset);
+ offset = vfu_pci_add_capability(&vfu_ctx, expoffsets[3],
+ VFU_CAP_FLAG_EXTENDED, vsc3);
+ assert_int_equal(expoffsets[3], offset);
+ offset = vfu_pci_add_capability(&vfu_ctx, expoffsets[4],
+ VFU_CAP_FLAG_EXTENDED, vsc4);
+ assert_int_equal(expoffsets[4], offset);
+
+ offset = vfu_pci_find_capability(&vfu_ctx, true, PCI_EXT_CAP_ID_DSN);
+ assert_int_equal(expoffsets[0], offset);
+ hdr = (struct pcie_ext_cap_hdr *)&config_space[offset];
+ assert_int_equal(PCI_EXT_CAP_ID_DSN, hdr->id);
+ assert_int_equal(expoffsets[1], hdr->next);
+
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true, offset,
+ PCI_EXT_CAP_ID_DSN);
+ assert_int_equal(0, offset);
+
+ offset = vfu_pci_find_capability(&vfu_ctx, true, PCI_EXT_CAP_ID_VNDR);
+ assert_int_equal(expoffsets[1], offset);
+ hdr = (struct pcie_ext_cap_hdr *)&config_space[offset];
+ assert_int_equal(PCI_EXT_CAP_ID_VNDR, hdr->id);
+ assert_int_equal(expoffsets[2], hdr->next);
+
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true, offset,
+ PCI_EXT_CAP_ID_DSN);
+ assert_int_equal(0, offset);
+
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true, 0, PCI_EXT_CAP_ID_VNDR);
+ assert_int_equal(expoffsets[1], offset);
+ hdr = (struct pcie_ext_cap_hdr *)&config_space[offset];
+ assert_int_equal(PCI_EXT_CAP_ID_VNDR, hdr->id);
+ assert_int_equal(expoffsets[2], hdr->next);
+
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true,
+ offset, PCI_EXT_CAP_ID_VNDR);
+ assert_int_equal(expoffsets[2], offset);
+ hdr = (struct pcie_ext_cap_hdr *)&config_space[offset];
+ assert_int_equal(PCI_EXT_CAP_ID_VNDR, hdr->id);
+ assert_int_equal(expoffsets[3], hdr->next);
+
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true,
+ offset, PCI_EXT_CAP_ID_VNDR);
+ assert_int_equal(expoffsets[3], offset);
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true,
+ offset, PCI_EXT_CAP_ID_VNDR);
+ assert_int_equal(expoffsets[4], offset);
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true,
+ offset, PCI_EXT_CAP_ID_VNDR);
+ assert_int_equal(0, offset);
+
+ /* check for invalid offsets */
+
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true, 8192,
+ PCI_EXT_CAP_ID_DSN);
+ assert_int_equal(0, offset);
+ assert_int_equal(EINVAL, errno);
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true, 4096,
+ PCI_EXT_CAP_ID_DSN);
+ assert_int_equal(0, offset);
+ assert_int_equal(EINVAL, errno);
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true, 4095,
+ PCI_EXT_CAP_ID_DSN);
+ assert_int_equal(0, offset);
+ assert_int_equal(EINVAL, errno);
+
+ offset = vfu_pci_find_next_capability(&vfu_ctx, true,
+ expoffsets[1] + 1,
+ PCI_EXT_CAP_ID_DSN);
+ assert_int_equal(0, offset);
+ assert_int_equal(ENOENT, errno);
+
+ /* check read only capability */
+
+ ret = pci_config_space_access(&vfu_ctx, (char *)vsc1->data, 5,
+ expoffsets[1] + offsetof(struct pcie_ext_cap_vsc_hdr, data),
+ false);
+ assert_int_equal(ret, 5);
+ assert_memory_equal(vsc1->data, "abcde", 5);
+
+ ret = pci_config_space_access(&vfu_ctx, "ced", 3,
+ expoffsets[1] + offsetof(struct pcie_ext_cap_vsc_hdr, data),
+ true);
+ assert_int_equal(ret, -EPERM);
+
+ /* check capability callback */
+
+ ret = pci_config_space_access(&vfu_ctx, "Bye world.", 10,
+ expoffsets[2] + offsetof(struct pcie_ext_cap_vsc_hdr, data),
+ true);
+
+ assert_int_equal(ret, 10);
+ assert_memory_equal(pci_config_space_ptr(&vfu_ctx,
+ expoffsets[2] + offsetof(struct pcie_ext_cap_vsc_hdr, data)),
+ "Bye world.", 10);
+}
+
static void
test_device_get_info(void **state __attribute__((unused)))
{
@@ -912,6 +1094,7 @@ int main(void)
cmocka_unit_test_setup(test_run_ctx, setup),
cmocka_unit_test_setup(test_vfu_ctx_create, setup),
cmocka_unit_test_setup(test_pci_caps, setup),
+ cmocka_unit_test_setup(test_pci_ext_caps, setup),
cmocka_unit_test_setup(test_device_get_info, setup),
cmocka_unit_test_setup(test_device_get_info_compat, setup),
cmocka_unit_test_setup(test_get_region_info, setup),