diff options
author | John Levon <john.levon@nutanix.com> | 2021-01-20 09:46:32 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-20 09:46:32 +0000 |
commit | 5aa56c58446c43f085da7ff6ca339d7361b8302d (patch) | |
tree | 1561f51430830157615fcaf79ed5e92d37ce95d5 /lib | |
parent | 7a9335eb438d29bf5be8caa67bf5252bb891076f (diff) | |
download | libvfio-user-5aa56c58446c43f085da7ff6ca339d7361b8302d.zip libvfio-user-5aa56c58446c43f085da7ff6ca339d7361b8302d.tar.gz libvfio-user-5aa56c58446c43f085da7ff6ca339d7361b8302d.tar.bz2 |
re-work API for adding capabilities (#200)
Allow to add capabilities individually, including extended capabilities, and
those to be handled via the region callback.
As a side effect, rework config space accesses to handle reads that straddle
capabilities and non-standard areas and use callbacks as needed.
Signed-off-by: John Levon <john.levon@nutanix.com>
Reviewed-by: Swapnil Ingle <swapnil.ingle@nutanix.com>
Reviewed-by: Thanos Makatos <thanos.makatos@nutanix.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/CMakeLists.txt | 4 | ||||
-rw-r--r-- | lib/cap.c | 529 | ||||
-rw-r--r-- | lib/libvfio-user.c | 6 | ||||
-rw-r--r-- | lib/pci.c | 183 | ||||
-rw-r--r-- | lib/pci.h | 12 | ||||
-rw-r--r-- | lib/pci_caps.c | 547 | ||||
-rw-r--r-- | lib/pci_caps.h (renamed from lib/cap.h) | 47 | ||||
-rw-r--r-- | lib/private.h | 26 |
8 files changed, 698 insertions, 656 deletions
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); @@ -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) { @@ -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 { |