aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThanos Makatos <thanos.makatos@nutanix.com>2020-12-08 10:58:29 -0500
committerThanos Makatos <tmakatos@gmail.com>2020-12-11 12:54:21 +0000
commit90211fbd2c7c310df593736b18f1c99f053e2957 (patch)
tree03462a28541581cbf3ea6eb8f9fde05591c3e893
parenta7ecdc3de9f237d600ceaaa44285115a4e790829 (diff)
downloadlibvfio-user-90211fbd2c7c310df593736b18f1c99f053e2957.zip
libvfio-user-90211fbd2c7c310df593736b18f1c99f053e2957.tar.gz
libvfio-user-90211fbd2c7c310df593736b18f1c99f053e2957.tar.bz2
add support PCI vendor-specific capability
The PCI vendor-specific capability is blindly read/written by the library. It is possible that the user might want to intercept accesses to it, in which case we'll have to add callback. The best way to do this to introduce a new function that configures callbacks for the PCI capabilities, e.g. typedef ssize_t (vfu_cap_access_t) (void *pvt, uint8_t id, char *buf, size_t count, loff_t offset, bool is_write); vfu_pci_cap_set_cb(vfu_ctx-T *vfu_ctx, uint8_t cap_id, vfu_cap_access_t *cb); This way the existing API won't have to change. Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com>
-rw-r--r--include/libvfio-user.h12
-rw-r--r--include/pci_caps/common.h9
-rw-r--r--include/pci_caps/pm.h21
-rw-r--r--lib/cap.c23
-rw-r--r--samples/lspci.c9
-rw-r--r--test/unit-tests.c93
6 files changed, 147 insertions, 20 deletions
diff --git a/include/libvfio-user.h b/include/libvfio-user.h
index e5b6474..95c5e7d 100644
--- a/include/libvfio-user.h
+++ b/include/libvfio-user.h
@@ -221,6 +221,7 @@ vfu_ctx_t *
vfu_create_ctx(vfu_trans_t trans, const char *path,
int flags, void *pvt, vfu_dev_type_t dev_type);
+
/**
* Setup logging information.
* @vfu_ctx: the libvfio-user context
@@ -261,15 +262,18 @@ vfu_pci_setup_config_hdr(vfu_ctx_t *vfu_ctx, vfu_pci_hdr_id_t id,
/* FIXME does it have to be packed as well? */
typedef union {
- struct msicap msi;
- struct msixcap msix;
- struct pmcap pm;
- struct pxcap px;
+ struct msicap msi;
+ struct msixcap msix;
+ struct pmcap pm;
+ struct pxcap px;
+ struct vsc vsc;
} vfu_cap_t;
//TODO: Support variable size capabilities.
+
/**
* Setup PCI capabilities.
+ *
* @vfu_ctx: the libvfio-user context
* @caps: array of (vfu_cap_t *)
* @nr_caps: number of elements in @caps
diff --git a/include/pci_caps/common.h b/include/pci_caps/common.h
index 7e158f0..a820f0d 100644
--- a/include/pci_caps/common.h
+++ b/include/pci_caps/common.h
@@ -45,6 +45,15 @@ _Static_assert(sizeof(struct cap_hdr) == 0x2, "bad PCI capability header size");
_Static_assert(offsetof(struct cap_hdr, id) == PCI_CAP_LIST_ID, "bad offset");
_Static_assert(offsetof(struct cap_hdr, next) == PCI_CAP_LIST_NEXT, "bad offset");
+/*
+ * Vendor-specific capability
+ */
+struct vsc {
+ struct cap_hdr hdr;
+ uint8_t size;
+ uint8_t data[];
+} __attribute__ ((packed));
+
#ifdef __cplusplus
}
#endif
diff --git a/include/pci_caps/pm.h b/include/pci_caps/pm.h
index 3098299..6daa812 100644
--- a/include/pci_caps/pm.h
+++ b/include/pci_caps/pm.h
@@ -52,14 +52,19 @@ struct pc {
_Static_assert(sizeof(struct pc) == 0x2, "bad PC size");
struct pmcs {
- unsigned int ps:2;
- unsigned int res1:1;
- unsigned int nsfrst:1;
- unsigned int res2:4;
- unsigned int pmee:1;
- unsigned int dse:4;
- unsigned int dsc:2;
- unsigned int pmes:1;
+ union {
+ uint16_t raw;
+ struct {
+ unsigned int ps:2;
+ unsigned int res1:1;
+ unsigned int nsfrst:1;
+ unsigned int res2:4;
+ unsigned int pmee:1;
+ unsigned int dse:4;
+ unsigned int dsc:2;
+ unsigned int pmes:1;
+ };
+ };
} __attribute__((packed));
_Static_assert(sizeof(struct pc) == 0x2, "bad PMCS size");
diff --git a/lib/cap.c b/lib/cap.c
index 8c72359..74f2d11 100644
--- a/lib/cap.c
+++ b/lib/cap.c
@@ -373,12 +373,23 @@ handle_px_write(vfu_ctx_t *vfu_ctx, uint8_t *cap, char *const buf,
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},
@@ -456,10 +467,14 @@ caps_create(vfu_ctx_t *vfu_ctx, vfu_cap_t **vfu_caps, int nr_caps, int *err)
goto err_out;
}
- size = cap_handlers[id].size;
- if (size == 0) {
- *err = ENOTSUP;
- 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;
diff --git a/samples/lspci.c b/samples/lspci.c
index 20d938a..61861d6 100644
--- a/samples/lspci.c
+++ b/samples/lspci.c
@@ -46,7 +46,8 @@ int main(void)
vfu_pci_hdr_ss_t ss = { 0 };
vfu_pci_hdr_cc_t cc = { { 0 } };
vfu_cap_t pm = { .pm = { .hdr.id = PCI_CAP_ID_PM, .pmcs.nsfrst = 0x1 } };
- vfu_cap_t *caps[1] = { &pm };
+ vfu_cap_t *vsc = alloca(sizeof(*vsc) + 0xd);
+ vfu_cap_t *caps[2] = { &pm, vsc };
vfu_ctx_t *vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "",
LIBVFIO_USER_FLAG_ATTACH_NB, NULL,
VFU_DEV_TYPE_PCI);
@@ -56,13 +57,15 @@ int main(void)
if (vfu_pci_setup_config_hdr(vfu_ctx, id, ss, cc, VFU_PCI_TYPE_CONVENTIONAL, 0) < 0) {
err(EXIT_FAILURE, "failed to setup PCI configuration space header");
}
- if (vfu_pci_setup_caps(vfu_ctx, caps, 1) < 0) {
+ vsc->vsc.hdr.id = PCI_CAP_ID_VNDR;
+ vsc->vsc.size = 0x10;
+ if (vfu_pci_setup_caps(vfu_ctx, caps, 2) < 0) {
err(EXIT_FAILURE, "failed to setup PCI capabilities");
}
if (vfu_realize_ctx(vfu_ctx) < 0) {
err(EXIT_FAILURE, "failed to realize device");
}
- buf = (char*)vfu_pci_get_config_space(vfu_ctx);;
+ 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++) {
printf("%02x:", i * bytes_per_line);
diff --git a/test/unit-tests.c b/test/unit-tests.c
index df683c2..c10ce5a 100644
--- a/test/unit-tests.c
+++ b/test/unit-tests.c
@@ -38,12 +38,14 @@
#include <assert.h>
#include <alloca.h>
#include <string.h>
+#include <linux/pci_regs.h>
#include "dma.h"
#include "libvfio-user.h"
#include "private.h"
#include "migration.h"
#include "tran_sock.h"
+#include "cap.h"
static void
test_dma_map_without_dma(void **state __attribute__((unused)))
@@ -395,6 +397,93 @@ test_run_ctx(UNUSED void **state)
}
/*
+ * FIXME expand and validate
+ */
+static void
+test_vfu_ctx_create(void **state __attribute__((unused)))
+{
+ vfu_ctx_t *vfu_ctx = NULL;
+ vfu_pci_hdr_id_t id = { 0 };
+ vfu_pci_hdr_ss_t ss = { 0 };
+ vfu_pci_hdr_cc_t cc = { 0 };
+ vfu_cap_t pm = {.pm = {.hdr.id = PCI_CAP_ID_PM}};
+ vfu_cap_t *caps[] = { &pm };
+
+ vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "", LIBVFIO_USER_FLAG_ATTACH_NB,
+ NULL, VFU_DEV_TYPE_PCI);
+ assert_non_null(vfu_ctx);
+ assert_int_equal(0,
+ vfu_pci_setup_config_hdr(vfu_ctx, id, ss, cc,
+ VFU_PCI_TYPE_CONVENTIONAL, 0));
+ assert_int_equal(0, vfu_pci_setup_caps(vfu_ctx, caps, 1));
+ assert_int_equal(0, vfu_realize_ctx(vfu_ctx));
+}
+
+static void
+test_pci_caps(void **state __attribute__((unused)))
+{
+ vfu_pci_config_space_t config_space;
+ vfu_ctx_t vfu_ctx = { .pci.config_space = &config_space };
+ vfu_cap_t pm = {.pm = {.hdr.id = PCI_CAP_ID_PM, .pmcs.raw = 0xabcd }};
+ vfu_cap_t *vsc[2] = {
+ alloca(sizeof(struct vsc) + 5),
+ alloca(sizeof(struct vsc) + 13)
+ };
+ vfu_cap_t *vfu_caps[] = { &pm, vsc[0], vsc[1] };
+ struct caps *caps;
+ int err;
+ struct pmcap pmcap = { .pmcs.raw = 0xef01 };
+ off_t off;
+
+ vsc[0]->vsc.hdr.id = PCI_CAP_ID_VNDR;
+ vsc[0]->vsc.size = 8;
+ memcpy(vsc[0]->vsc.data, "abcde", 5);
+
+ vsc[1]->vsc.hdr.id = PCI_CAP_ID_VNDR;
+ vsc[1]->vsc.size = 16;
+ memcpy(vsc[1]->vsc.data, "Hello world.", 12);
+
+ caps = caps_create(&vfu_ctx, vfu_caps, 3, &err);
+ assert_non_null(caps);
+
+ /* check that capability list is placed correctly */
+ assert_int_equal(PCI_CAP_ID_PM,
+ config_space.raw[PCI_STD_HEADER_SIZEOF + PCI_CAP_LIST_ID]);
+ assert_int_equal(PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF,
+ config_space.raw[PCI_STD_HEADER_SIZEOF + PCI_CAP_LIST_NEXT]);
+ assert_int_equal(PCI_CAP_ID_VNDR,
+ config_space.raw[PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + PCI_CAP_LIST_ID]);
+ assert_int_equal(PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + vsc[0]->vsc.size,
+ config_space.raw[PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + PCI_CAP_LIST_NEXT]);
+ assert_int_equal(8,
+ config_space.raw[PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + PCI_CAP_LIST_NEXT + 1]);
+ assert_int_equal(PCI_CAP_ID_VNDR,
+ config_space.raw[PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + vsc[0]->vsc.size]);
+ assert_int_equal(0,
+ config_space.raw[PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + vsc[0]->vsc.size + PCI_CAP_LIST_NEXT]);
+ assert_int_equal(vsc[1]->vsc.size,
+ config_space.raw[PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + vsc[0]->vsc.size + PCI_CAP_LIST_NEXT + 1]);
+
+ /* check writing PMCS */
+ assert_int_equal(0,
+ cap_maybe_access(&vfu_ctx, caps, (char*)&pmcap.pmcs,
+ sizeof(struct pmcs),
+ PCI_STD_HEADER_SIZEOF + offsetof(struct pmcap, pmcs)));
+ assert_memory_equal(
+ &config_space.raw[PCI_STD_HEADER_SIZEOF + offsetof(struct pmcap, pmcs)],
+ &pmcap.pmcs.raw, sizeof(struct pmcs));
+
+ /*
+ * Check that pci_cap_access returns 0 when reading a non-vendor-specific
+ * capability which doesn't have a callback.
+ */
+ off = PCI_STD_HEADER_SIZEOF + PCI_PM_SIZEOF + PCI_CAP_FLAGS + 1;
+ assert_int_equal(5,
+ cap_maybe_access(&vfu_ctx, caps, (char*)vsc[0]->vsc.data, 5, off));
+ assert_memory_equal(&config_space.raw[off], vsc[0]->vsc.data, 5);
+}
+
+/*
* FIXME we shouldn't have to specify a setup function explicitly for each unit
* test, cmocka should provide that. E.g. cmocka_run_group_tests enables us to
* run a function before/after ALL unit tests have finished, we can extend it
@@ -419,7 +508,9 @@ int main(void)
cmocka_unit_test_setup(test_process_command_free_passed_fds, setup),
cmocka_unit_test_setup(test_realize_ctx, setup),
cmocka_unit_test_setup(test_attach_ctx, setup),
- cmocka_unit_test_setup(test_run_ctx, setup)
+ 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),
};
return cmocka_run_group_tests(tests, NULL, NULL);