From 53cc29bc8ca5083b9b6075f151824d65557af6f0 Mon Sep 17 00:00:00 2001 From: John Levon Date: Wed, 20 Jan 2021 10:44:49 +0000 Subject: 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 Reviewed-by: Swapnil Ingle --- .github/workflows/pull_request.yml | 2 +- include/libvfio-user.h | 7 +- include/pci_caps/common.h | 19 ++ include/pci_caps/dsn.h | 60 ++++ lib/libvfio-user.c | 1 + lib/pci.h | 10 +- lib/pci_caps.c | 585 +++++++++++++++++++++++++------------ lib/pci_caps.h | 3 +- lib/private.h | 2 + samples/lspci.c | 36 ++- test/CMakeLists.txt | 1 + test/lspci.expected.out | 29 ++ test/lspci.expected.out.2 | 30 ++ test/test-lspci.sh | 12 + test/unit-tests.c | 183 ++++++++++++ 15 files changed, 777 insertions(+), 203 deletions(-) create mode 100644 include/pci_caps/dsn.h create mode 100644 test/lspci.expected.out create mode 100644 test/lspci.expected.out.2 create mode 100755 test/test-lspci.sh 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 #include -#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 #include #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 + * Swapnil Ingle + * Felipe Franciosi + * + * 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 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 @@ -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- SERR- [disabled] + Region 1: I/O ports at [disabled] + Region 2: I/O ports at [disabled] + Region 3: I/O ports at [disabled] + Region 4: I/O ports at [disabled] + Region 5: I/O ports at [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- SERR- [virtual] + Region 1: I/O ports at [virtual] + Region 2: I/O ports at [virtual] + Region 3: I/O ports at [virtual] + Region 4: I/O ports at [virtual] + Region 5: I/O ports at [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), -- cgit v1.1