tools/tests/regression/downloads/*
tools/tests/mem-sharing/memshrtool
tools/tests/mce-test/tools/xen-mceinj
+tools/tests/vpci/list.h
+tools/tests/vpci/vpci.[hc]
+tools/tests/vpci/test_vpci
tools/xcutils/lsevtchn
tools/xcutils/readnotes
tools/xenbackendd/_paths.h
{
switch(d_config->c_info.type) {
case LIBXL_DOMAIN_TYPE_HVM:
- xc_config->emulation_flags = XEN_X86_EMU_ALL;
+ xc_config->emulation_flags = (XEN_X86_EMU_ALL & ~XEN_X86_EMU_VPCI);
break;
case LIBXL_DOMAIN_TYPE_PVH:
xc_config->emulation_flags = XEN_X86_EMU_LAPIC;
SUBDIRS-$(CONFIG_X86) += x86_emulator
SUBDIRS-y += xen-access
SUBDIRS-y += xenstore
+SUBDIRS-$(CONFIG_HAS_PCI) += vpci
.PHONY: all clean install distclean uninstall
all clean distclean: %: subdirs-%
--- /dev/null
+XEN_ROOT=$(CURDIR)/../../..
+include $(XEN_ROOT)/tools/Rules.mk
+
+TARGET := test_vpci
+
+.PHONY: all
+all: $(TARGET)
+
+.PHONY: run
+run: $(TARGET)
+ ./$(TARGET)
+
+$(TARGET): vpci.c vpci.h list.h main.c emul.h
+ $(HOSTCC) -g -o $@ vpci.c main.c
+
+.PHONY: clean
+clean:
+ rm -rf $(TARGET) *.o *~ vpci.h vpci.c list.h
+
+.PHONY: distclean
+distclean: clean
+
+.PHONY: install
+install:
+
+vpci.c: $(XEN_ROOT)/xen/drivers/vpci/vpci.c
+ # Trick the compiler so it doesn't complain about missing symbols
+ sed -e '/#include/d' \
+ -e '1s;^;#include "emul.h"\
+ vpci_register_init_t *const __start_vpci_array[1]\;\
+ vpci_register_init_t *const __end_vpci_array[1]\;\
+ ;' <$< >$@
+
+list.h: $(XEN_ROOT)/xen/include/xen/list.h
+vpci.h: $(XEN_ROOT)/xen/include/xen/vpci.h
+list.h vpci.h:
+ sed -e '/#include/d' <$< >$@
--- /dev/null
+/*
+ * Unit tests for the generic vPCI handler code.
+ *
+ * Copyright (C) 2017 Citrix Systems R&D
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms and conditions of the GNU General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TEST_VPCI_
+#define _TEST_VPCI_
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define container_of(ptr, type, member) ({ \
+ typeof(((type *)0)->member) *mptr = (ptr); \
+ \
+ (type *)((char *)mptr - offsetof(type, member)); \
+})
+
+#define smp_wmb()
+#define prefetch(x) __builtin_prefetch(x)
+#define ASSERT(x) assert(x)
+#define __must_check __attribute__((__warn_unused_result__))
+
+#include "list.h"
+
+struct domain {
+};
+
+struct pci_dev {
+ struct vpci *vpci;
+};
+
+struct vcpu
+{
+ const struct domain *domain;
+};
+
+extern const struct vcpu *current;
+extern const struct pci_dev test_pdev;
+
+typedef bool spinlock_t;
+#define spin_lock_init(l) (*(l) = false)
+#define spin_lock(l) (*(l) = true)
+#define spin_unlock(l) (*(l) = false)
+
+typedef union {
+ uint32_t sbdf;
+ struct {
+ union {
+ uint16_t bdf;
+ struct {
+ union {
+ struct {
+ uint8_t func : 3,
+ dev : 5;
+ };
+ uint8_t extfunc;
+ };
+ uint8_t bus;
+ };
+ };
+ uint16_t seg;
+ };
+} pci_sbdf_t;
+
+#include "vpci.h"
+
+#define __hwdom_init
+
+#define has_vpci(d) true
+
+#define xzalloc(type) ((type *)calloc(1, sizeof(type)))
+#define xmalloc(type) ((type *)malloc(sizeof(type)))
+#define xfree(p) free(p)
+
+#define pci_get_pdev_by_domain(...) &test_pdev
+
+/* Dummy native helpers. Writes are ignored, reads return 1's. */
+#define pci_conf_read8(...) 0xff
+#define pci_conf_read16(...) 0xffff
+#define pci_conf_read32(...) 0xffffffff
+#define pci_conf_write8(...)
+#define pci_conf_write16(...)
+#define pci_conf_write32(...)
+
+#define PCI_CFG_SPACE_EXP_SIZE 4096
+
+#define BUG() assert(0)
+#define ASSERT_UNREACHABLE() assert(0)
+
+#define min(x, y) ({ \
+ const typeof(x) tx = (x); \
+ const typeof(y) ty = (y); \
+ \
+ (void) (&tx == &ty); \
+ tx < ty ? tx : ty; \
+})
+
+#define max(x, y) ({ \
+ const typeof(x) tx = (x); \
+ const typeof(y) ty = (y); \
+ \
+ (void) (&tx == &ty); \
+ tx > ty ? tx : ty; \
+})
+
+#endif
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
--- /dev/null
+/*
+ * Unit tests for the generic vPCI handler code.
+ *
+ * Copyright (C) 2017 Citrix Systems R&D
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms and conditions of the GNU General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "emul.h"
+
+/* Single vcpu (current), and single domain with a single PCI device. */
+static struct vpci vpci;
+
+const static struct domain d;
+
+const struct pci_dev test_pdev = {
+ .vpci = &vpci,
+};
+
+const static struct vcpu v = {
+ .domain = &d
+};
+
+const struct vcpu *current = &v;
+
+/* Dummy hooks, write stores data, read fetches it. */
+static uint32_t vpci_read8(const struct pci_dev *pdev, unsigned int reg,
+ void *data)
+{
+ return *(uint8_t *)data;
+}
+
+static void vpci_write8(const struct pci_dev *pdev, unsigned int reg,
+ uint32_t val, void *data)
+{
+ *(uint8_t *)data = val;
+}
+
+static uint32_t vpci_read16(const struct pci_dev *pdev, unsigned int reg,
+ void *data)
+{
+ return *(uint16_t *)data;
+}
+
+static void vpci_write16(const struct pci_dev *pdev, unsigned int reg,
+ uint32_t val, void *data)
+{
+ *(uint16_t *)data = val;
+}
+
+static uint32_t vpci_read32(const struct pci_dev *pdev, unsigned int reg,
+ void *data)
+{
+ return *(uint32_t *)data;
+}
+
+static void vpci_write32(const struct pci_dev *pdev, unsigned int reg,
+ uint32_t val, void *data)
+{
+ *(uint32_t *)data = val;
+}
+
+#define VPCI_READ(reg, size, data) ({ \
+ data = vpci_read((pci_sbdf_t){ .sbdf = 0 }, reg, size); \
+})
+
+#define VPCI_READ_CHECK(reg, size, expected) ({ \
+ uint32_t rd; \
+ \
+ VPCI_READ(reg, size, rd); \
+ assert(rd == (expected)); \
+})
+
+#define VPCI_WRITE(reg, size, data) ({ \
+ vpci_write((pci_sbdf_t){ .sbdf = 0 }, reg, size, data); \
+})
+
+#define VPCI_WRITE_CHECK(reg, size, data) ({ \
+ VPCI_WRITE(reg, size, data); \
+ VPCI_READ_CHECK(reg, size, data); \
+})
+
+#define VPCI_ADD_REG(fread, fwrite, off, size, store) \
+ assert(!vpci_add_register(test_pdev.vpci, fread, fwrite, off, size, \
+ &store))
+
+#define VPCI_ADD_INVALID_REG(fread, fwrite, off, size) \
+ assert(vpci_add_register(test_pdev.vpci, fread, fwrite, off, size, NULL))
+
+#define VPCI_REMOVE_REG(off, size) \
+ assert(!vpci_remove_register(test_pdev.vpci, off, size))
+
+#define VPCI_REMOVE_INVALID_REG(off, size) \
+ assert(vpci_remove_register(test_pdev.vpci, off, size))
+
+/* Read a 32b register using all possible sizes. */
+void multiread4_check(unsigned int reg, uint32_t val)
+{
+ unsigned int i;
+
+ /* Read using bytes. */
+ for ( i = 0; i < 4; i++ )
+ VPCI_READ_CHECK(reg + i, 1, (val >> (i * 8)) & UINT8_MAX);
+
+ /* Read using 2bytes. */
+ for ( i = 0; i < 2; i++ )
+ VPCI_READ_CHECK(reg + i * 2, 2, (val >> (i * 2 * 8)) & UINT16_MAX);
+
+ VPCI_READ_CHECK(reg, 4, val);
+}
+
+void multiwrite4_check(unsigned int reg)
+{
+ unsigned int i;
+ uint32_t val = 0xa2f51732;
+
+ /* Write using bytes. */
+ for ( i = 0; i < 4; i++ )
+ VPCI_WRITE_CHECK(reg + i, 1, (val >> (i * 8)) & UINT8_MAX);
+ multiread4_check(reg, val);
+
+ /* Change the value each time to be sure writes work fine. */
+ val = 0x2b836fda;
+ /* Write using 2bytes. */
+ for ( i = 0; i < 2; i++ )
+ VPCI_WRITE_CHECK(reg + i * 2, 2, (val >> (i * 2 * 8)) & UINT16_MAX);
+ multiread4_check(reg, val);
+
+ val = 0xc4693beb;
+ VPCI_WRITE_CHECK(reg, 4, val);
+ multiread4_check(reg, val);
+}
+
+int
+main(int argc, char **argv)
+{
+ /* Index storage by offset. */
+ uint32_t r0 = 0xdeadbeef;
+ uint8_t r5 = 0xef;
+ uint8_t r6 = 0xbe;
+ uint8_t r7 = 0xef;
+ uint16_t r12 = 0x8696;
+ uint8_t r16[4] = { };
+ uint16_t r20[2] = { };
+ uint32_t r24 = 0;
+ uint8_t r28, r30;
+ unsigned int i;
+ int rc;
+
+ INIT_LIST_HEAD(&vpci.handlers);
+ spin_lock_init(&vpci.lock);
+
+ VPCI_ADD_REG(vpci_read32, vpci_write32, 0, 4, r0);
+ VPCI_READ_CHECK(0, 4, r0);
+ VPCI_WRITE_CHECK(0, 4, 0xbcbcbcbc);
+
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 5, 1, r5);
+ VPCI_READ_CHECK(5, 1, r5);
+ VPCI_WRITE_CHECK(5, 1, 0xba);
+
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 6, 1, r6);
+ VPCI_READ_CHECK(6, 1, r6);
+ VPCI_WRITE_CHECK(6, 1, 0xba);
+
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 7, 1, r7);
+ VPCI_READ_CHECK(7, 1, r7);
+ VPCI_WRITE_CHECK(7, 1, 0xbd);
+
+ VPCI_ADD_REG(vpci_read16, vpci_write16, 12, 2, r12);
+ VPCI_READ_CHECK(12, 2, r12);
+ VPCI_READ_CHECK(12, 4, 0xffff8696);
+
+ /*
+ * At this point we have the following layout:
+ *
+ * Note that this refers to the position of the variables,
+ * but the value has already changed from the one given at
+ * initialization time because write tests have been performed.
+ *
+ * 32 24 16 8 0
+ * +-----+-----+-----+-----+
+ * | r0 | 0
+ * +-----+-----+-----+-----+
+ * | r7 | r6 | r5 |/////| 32
+ * +-----+-----+-----+-----|
+ * |///////////////////////| 64
+ * +-----------+-----------+
+ * |///////////| r12 | 96
+ * +-----------+-----------+
+ * ...
+ * / = unhandled.
+ */
+
+ /* Try to add an overlapping register handler. */
+ VPCI_ADD_INVALID_REG(vpci_read32, vpci_write32, 4, 4);
+
+ /* Try to add a non-aligned register. */
+ VPCI_ADD_INVALID_REG(vpci_read16, vpci_write16, 15, 2);
+
+ /* Try to add a register with wrong size. */
+ VPCI_ADD_INVALID_REG(vpci_read16, vpci_write16, 8, 3);
+
+ /* Try to add a register with missing handlers. */
+ VPCI_ADD_INVALID_REG(NULL, NULL, 8, 2);
+
+ /* Read/write of unset register. */
+ VPCI_READ_CHECK(8, 4, 0xffffffff);
+ VPCI_READ_CHECK(8, 2, 0xffff);
+ VPCI_READ_CHECK(8, 1, 0xff);
+ VPCI_WRITE(10, 2, 0xbeef);
+ VPCI_READ_CHECK(10, 2, 0xffff);
+
+ /* Read of multiple registers */
+ VPCI_WRITE_CHECK(7, 1, 0xbd);
+ VPCI_READ_CHECK(4, 4, 0xbdbabaff);
+
+ /* Partial read of a register. */
+ VPCI_WRITE_CHECK(0, 4, 0x1a1b1c1d);
+ VPCI_READ_CHECK(2, 1, 0x1b);
+ VPCI_READ_CHECK(6, 2, 0xbdba);
+
+ /* Write of multiple registers. */
+ VPCI_WRITE_CHECK(4, 4, 0xaabbccff);
+
+ /* Partial write of a register. */
+ VPCI_WRITE_CHECK(2, 1, 0xfe);
+ VPCI_WRITE_CHECK(6, 2, 0xfebc);
+
+ /*
+ * Test all possible read/write size combinations.
+ *
+ * Place 4 1B registers at 128bits (16B), 2 2B registers at 160bits
+ * (20B) and finally 1 4B register at 192bits (24B).
+ *
+ * Then perform all possible write and read sizes on each of them.
+ *
+ * ...
+ * 32 24 16 8 0
+ * +------+------+------+------+
+ * |r16[3]|r16[2]|r16[1]|r16[0]| 16
+ * +------+------+------+------+
+ * | r20[1] | r20[0] | 20
+ * +-------------+-------------|
+ * | r24 | 24
+ * +-------------+-------------+
+ *
+ */
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 16, 1, r16[0]);
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 17, 1, r16[1]);
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 18, 1, r16[2]);
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 19, 1, r16[3]);
+
+ VPCI_ADD_REG(vpci_read16, vpci_write16, 20, 2, r20[0]);
+ VPCI_ADD_REG(vpci_read16, vpci_write16, 22, 2, r20[1]);
+
+ VPCI_ADD_REG(vpci_read32, vpci_write32, 24, 4, r24);
+
+ /* Check the initial value is 0. */
+ multiread4_check(16, 0);
+ multiread4_check(20, 0);
+ multiread4_check(24, 0);
+
+ multiwrite4_check(16);
+ multiwrite4_check(20);
+ multiwrite4_check(24);
+
+ /*
+ * Check multiple non-consecutive gaps on the same read/write:
+ *
+ * 32 24 16 8 0
+ * +------+------+------+------+
+ * |//////| r30 |//////| r28 | 28
+ * +------+------+------+------+
+ *
+ */
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 28, 1, r28);
+ VPCI_ADD_REG(vpci_read8, vpci_write8, 30, 1, r30);
+ VPCI_WRITE_CHECK(28, 4, 0xffacffdc);
+
+ /* Finally try to remove a couple of registers. */
+ VPCI_REMOVE_REG(28, 1);
+ VPCI_REMOVE_REG(24, 4);
+ VPCI_REMOVE_REG(12, 2);
+
+ VPCI_REMOVE_INVALID_REG(20, 1);
+ VPCI_REMOVE_INVALID_REG(16, 2);
+ VPCI_REMOVE_INVALID_REG(30, 2);
+
+ return 0;
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
__param_start = .;
*(.data.param)
__param_end = .;
+
+#if defined(CONFIG_HAS_VPCI) && defined(CONFIG_LATE_HWDOM)
+ . = ALIGN(POINTER_ALIGN);
+ __start_vpci_array = .;
+ *(.data.vpci)
+ __end_vpci_array = .;
+#endif
} :text
#if defined(BUILD_ID)
*(.init_array)
*(SORT(.init_array.*))
__ctors_end = .;
+
+#if defined(CONFIG_HAS_VPCI) && !defined(CONFIG_LATE_HWDOM)
+ . = ALIGN(POINTER_ALIGN);
+ __start_vpci_array = .;
+ *(.data.vpci)
+ __end_vpci_array = .;
+#endif
} :text
__init_end_efi = .;
. = ALIGN(STACK_SIZE);
select HAS_PCI
select HAS_PDX
select HAS_UBSAN
+ select HAS_VPCI
select NUMA
config ARCH_DEFCONFIG
if ( is_hvm_domain(d) )
{
if ( is_hardware_domain(d) &&
- emflags != (XEN_X86_EMU_LAPIC|XEN_X86_EMU_IOAPIC) )
+ emflags != (XEN_X86_EMU_VPCI | XEN_X86_EMU_LAPIC |
+ XEN_X86_EMU_IOAPIC) )
return false;
if ( !is_hardware_domain(d) &&
- emflags != XEN_X86_EMU_ALL && emflags != XEN_X86_EMU_LAPIC )
+ emflags != (XEN_X86_EMU_ALL & ~XEN_X86_EMU_VPCI) &&
+ emflags != XEN_X86_EMU_LAPIC )
return false;
}
else if ( emflags != 0 && emflags != XEN_X86_EMU_PIT )
#include <xen/rangeset.h>
#include <xen/monitor.h>
#include <xen/warning.h>
+#include <xen/vpci.h>
#include <asm/shadow.h>
#include <asm/hap.h>
#include <asm/current.h>
d->arch.hvm_domain.io_bitmap = hvm_io_bitmap;
register_g2m_portio_handler(d);
+ register_vpci_portio_handler(d);
hvm_ioreq_init(d);
#include <xen/trace.h>
#include <xen/event.h>
#include <xen/hypercall.h>
+#include <xen/vpci.h>
#include <asm/current.h>
#include <asm/cpufeature.h>
#include <asm/processor.h>
return CF8_ADDR_LO(cf8) | (addr & 3);
}
+/* Do some sanity checks. */
+static bool vpci_access_allowed(unsigned int reg, unsigned int len)
+{
+ /* Check access size. */
+ if ( len != 1 && len != 2 && len != 4 )
+ return false;
+
+ /* Check that access is size aligned. */
+ if ( (reg & (len - 1)) )
+ return false;
+
+ return true;
+}
+
+/* vPCI config space IO ports handlers (0xcf8/0xcfc). */
+static bool vpci_portio_accept(const struct hvm_io_handler *handler,
+ const ioreq_t *p)
+{
+ return (p->addr == 0xcf8 && p->size == 4) || (p->addr & ~3) == 0xcfc;
+}
+
+static int vpci_portio_read(const struct hvm_io_handler *handler,
+ uint64_t addr, uint32_t size, uint64_t *data)
+{
+ const struct domain *d = current->domain;
+ unsigned int reg;
+ pci_sbdf_t sbdf;
+ uint32_t cf8;
+
+ *data = ~(uint64_t)0;
+
+ if ( addr == 0xcf8 )
+ {
+ ASSERT(size == 4);
+ *data = d->arch.hvm_domain.pci_cf8;
+ return X86EMUL_OKAY;
+ }
+
+ ASSERT((addr & ~3) == 0xcfc);
+ cf8 = ACCESS_ONCE(d->arch.hvm_domain.pci_cf8);
+ if ( !CF8_ENABLED(cf8) )
+ return X86EMUL_UNHANDLEABLE;
+
+ reg = hvm_pci_decode_addr(cf8, addr, &sbdf);
+
+ if ( !vpci_access_allowed(reg, size) )
+ return X86EMUL_OKAY;
+
+ *data = vpci_read(sbdf, reg, size);
+
+ return X86EMUL_OKAY;
+}
+
+static int vpci_portio_write(const struct hvm_io_handler *handler,
+ uint64_t addr, uint32_t size, uint64_t data)
+{
+ struct domain *d = current->domain;
+ unsigned int reg;
+ pci_sbdf_t sbdf;
+ uint32_t cf8;
+
+ if ( addr == 0xcf8 )
+ {
+ ASSERT(size == 4);
+ d->arch.hvm_domain.pci_cf8 = data;
+ return X86EMUL_OKAY;
+ }
+
+ ASSERT((addr & ~3) == 0xcfc);
+ cf8 = ACCESS_ONCE(d->arch.hvm_domain.pci_cf8);
+ if ( !CF8_ENABLED(cf8) )
+ return X86EMUL_UNHANDLEABLE;
+
+ reg = hvm_pci_decode_addr(cf8, addr, &sbdf);
+
+ if ( !vpci_access_allowed(reg, size) )
+ return X86EMUL_OKAY;
+
+ vpci_write(sbdf, reg, size, data);
+
+ return X86EMUL_OKAY;
+}
+
+static const struct hvm_io_ops vpci_portio_ops = {
+ .accept = vpci_portio_accept,
+ .read = vpci_portio_read,
+ .write = vpci_portio_write,
+};
+
+void register_vpci_portio_handler(struct domain *d)
+{
+ struct hvm_io_handler *handler;
+
+ if ( !has_vpci(d) )
+ return;
+
+ handler = hvm_next_io_handler(d);
+ if ( !handler )
+ return;
+
+ handler->type = IOREQ_TYPE_PIO;
+ handler->ops = &vpci_portio_ops;
+}
+
/*
* Local variables:
* mode: C
XEN_DOMCTL_CDF_hap : 0));
dom0_cfg.config.emulation_flags |=
- XEN_X86_EMU_LAPIC | XEN_X86_EMU_IOAPIC;
+ XEN_X86_EMU_LAPIC | XEN_X86_EMU_IOAPIC | XEN_X86_EMU_VPCI;
}
/* Create initial domain 0. */
__param_start = .;
*(.data.param)
__param_end = .;
+
+#if defined(CONFIG_HAS_VPCI) && defined(CONFIG_LATE_HWDOM)
+ . = ALIGN(POINTER_ALIGN);
+ __start_vpci_array = .;
+ *(.data.vpci)
+ __end_vpci_array = .;
+#endif
} :text
#if defined(CONFIG_PVH_GUEST) && !defined(EFI)
*(.init_array)
*(SORT(.init_array.*))
__ctors_end = .;
+
+#if defined(CONFIG_HAS_VPCI) && !defined(CONFIG_LATE_HWDOM)
+ . = ALIGN(POINTER_ALIGN);
+ __start_vpci_array = .;
+ *(.data.vpci)
+ __end_vpci_array = .;
+#endif
} :text
. = ALIGN(SECTION_ALIGN);
source "drivers/video/Kconfig"
+config HAS_VPCI
+ bool
+
endmenu
subdir-y += char
subdir-$(CONFIG_HAS_CPUFREQ) += cpufreq
subdir-$(CONFIG_HAS_PCI) += pci
+subdir-$(CONFIG_HAS_VPCI) += vpci
subdir-$(CONFIG_HAS_PASSTHROUGH) += passthrough
subdir-$(CONFIG_ACPI) += acpi
subdir-$(CONFIG_VIDEO) += video
#include <xen/radix-tree.h>
#include <xen/softirq.h>
#include <xen/tasklet.h>
+#include <xen/vpci.h>
#include <xsm/xsm.h>
#include <asm/msi.h>
#include "ats.h"
struct pci_dev *pdev)
{
u8 devfn = pdev->devfn;
+ int err;
do {
- int err = ctxt->handler(devfn, pdev);
-
+ err = ctxt->handler(devfn, pdev);
if ( err )
{
printk(XENLOG_ERR "setup %04x:%02x:%02x.%u for d%d failed (%d)\n",
devfn += pdev->phantom_stride;
} while ( devfn != pdev->devfn &&
PCI_SLOT(devfn) == PCI_SLOT(pdev->devfn) );
+
+ err = vpci_add_handlers(pdev);
+ if ( err )
+ printk(XENLOG_ERR "setup of vPCI for d%d failed: %d\n",
+ ctxt->d->domain_id, err);
}
static int __hwdom_init _setup_hwdom_pci_devices(struct pci_seg *pseg, void *arg)
--- /dev/null
+obj-y += vpci.o
--- /dev/null
+/*
+ * Generic functionality for handling accesses to the PCI configuration space
+ * from guests.
+ *
+ * Copyright (C) 2017 Citrix Systems R&D
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms and conditions of the GNU General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <xen/sched.h>
+#include <xen/vpci.h>
+
+extern vpci_register_init_t *const __start_vpci_array[];
+extern vpci_register_init_t *const __end_vpci_array[];
+#define NUM_VPCI_INIT (__end_vpci_array - __start_vpci_array)
+
+/* Internal struct to store the emulated PCI registers. */
+struct vpci_register {
+ vpci_read_t *read;
+ vpci_write_t *write;
+ unsigned int size;
+ unsigned int offset;
+ void *private;
+ struct list_head node;
+};
+
+int __hwdom_init vpci_add_handlers(struct pci_dev *pdev)
+{
+ unsigned int i;
+ int rc = 0;
+
+ if ( !has_vpci(pdev->domain) )
+ return 0;
+
+ pdev->vpci = xzalloc(struct vpci);
+ if ( !pdev->vpci )
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&pdev->vpci->handlers);
+ spin_lock_init(&pdev->vpci->lock);
+
+ for ( i = 0; i < NUM_VPCI_INIT; i++ )
+ {
+ rc = __start_vpci_array[i](pdev);
+ if ( rc )
+ break;
+ }
+
+ if ( rc )
+ {
+ while ( !list_empty(&pdev->vpci->handlers) )
+ {
+ struct vpci_register *r = list_first_entry(&pdev->vpci->handlers,
+ struct vpci_register,
+ node);
+
+ list_del(&r->node);
+ xfree(r);
+ }
+ xfree(pdev->vpci);
+ pdev->vpci = NULL;
+ }
+
+ return rc;
+}
+
+static int vpci_register_cmp(const struct vpci_register *r1,
+ const struct vpci_register *r2)
+{
+ /* Return 0 if registers overlap. */
+ if ( r1->offset < r2->offset + r2->size &&
+ r2->offset < r1->offset + r1->size )
+ return 0;
+ if ( r1->offset < r2->offset )
+ return -1;
+ if ( r1->offset > r2->offset )
+ return 1;
+
+ ASSERT_UNREACHABLE();
+ return 0;
+}
+
+/* Dummy hooks, writes are ignored, reads return 1's */
+static uint32_t vpci_ignored_read(const struct pci_dev *pdev, unsigned int reg,
+ void *data)
+{
+ return ~(uint32_t)0;
+}
+
+static void vpci_ignored_write(const struct pci_dev *pdev, unsigned int reg,
+ uint32_t val, void *data)
+{
+}
+
+int vpci_add_register(struct vpci *vpci, vpci_read_t *read_handler,
+ vpci_write_t *write_handler, unsigned int offset,
+ unsigned int size, void *data)
+{
+ struct list_head *prev;
+ struct vpci_register *r;
+
+ /* Some sanity checks. */
+ if ( (size != 1 && size != 2 && size != 4) ||
+ offset >= PCI_CFG_SPACE_EXP_SIZE || (offset & (size - 1)) ||
+ (!read_handler && !write_handler) )
+ return -EINVAL;
+
+ r = xmalloc(struct vpci_register);
+ if ( !r )
+ return -ENOMEM;
+
+ r->read = read_handler ?: vpci_ignored_read;
+ r->write = write_handler ?: vpci_ignored_write;
+ r->size = size;
+ r->offset = offset;
+ r->private = data;
+
+ spin_lock(&vpci->lock);
+
+ /* The list of handlers must be kept sorted at all times. */
+ list_for_each ( prev, &vpci->handlers )
+ {
+ const struct vpci_register *this =
+ list_entry(prev, const struct vpci_register, node);
+ int cmp = vpci_register_cmp(r, this);
+
+ if ( cmp < 0 )
+ break;
+ if ( cmp == 0 )
+ {
+ spin_unlock(&vpci->lock);
+ xfree(r);
+ return -EEXIST;
+ }
+ }
+
+ list_add_tail(&r->node, prev);
+ spin_unlock(&vpci->lock);
+
+ return 0;
+}
+
+int vpci_remove_register(struct vpci *vpci, unsigned int offset,
+ unsigned int size)
+{
+ const struct vpci_register r = { .offset = offset, .size = size };
+ struct vpci_register *rm;
+
+ spin_lock(&vpci->lock);
+ list_for_each_entry ( rm, &vpci->handlers, node )
+ {
+ int cmp = vpci_register_cmp(&r, rm);
+
+ /*
+ * NB: do not use a switch so that we can use break to
+ * get out of the list loop earlier if required.
+ */
+ if ( !cmp && rm->offset == offset && rm->size == size )
+ {
+ list_del(&rm->node);
+ spin_unlock(&vpci->lock);
+ xfree(rm);
+ return 0;
+ }
+ if ( cmp <= 0 )
+ break;
+ }
+ spin_unlock(&vpci->lock);
+
+ return -ENOENT;
+}
+
+/* Wrappers for performing reads/writes to the underlying hardware. */
+static uint32_t vpci_read_hw(pci_sbdf_t sbdf, unsigned int reg,
+ unsigned int size)
+{
+ uint32_t data;
+
+ switch ( size )
+ {
+ case 4:
+ data = pci_conf_read32(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg);
+ break;
+
+ case 3:
+ /*
+ * This is possible because a 4byte read can have 1byte trapped and
+ * the rest passed-through.
+ */
+ if ( reg & 1 )
+ {
+ data = pci_conf_read8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func,
+ reg);
+ data |= pci_conf_read16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func,
+ reg + 1) << 8;
+ }
+ else
+ {
+ data = pci_conf_read16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func,
+ reg);
+ data |= pci_conf_read8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func,
+ reg + 2) << 16;
+ }
+ break;
+
+ case 2:
+ data = pci_conf_read16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg);
+ break;
+
+ case 1:
+ data = pci_conf_read8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg);
+ break;
+
+ default:
+ ASSERT_UNREACHABLE();
+ data = ~(uint32_t)0;
+ break;
+ }
+
+ return data;
+}
+
+static void vpci_write_hw(pci_sbdf_t sbdf, unsigned int reg, unsigned int size,
+ uint32_t data)
+{
+ switch ( size )
+ {
+ case 4:
+ pci_conf_write32(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg, data);
+ break;
+
+ case 3:
+ /*
+ * This is possible because a 4byte write can have 1byte trapped and
+ * the rest passed-through.
+ */
+ if ( reg & 1 )
+ {
+ pci_conf_write8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg,
+ data);
+ pci_conf_write16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg + 1,
+ data >> 8);
+ }
+ else
+ {
+ pci_conf_write16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg,
+ data);
+ pci_conf_write8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg + 2,
+ data >> 16);
+ }
+ break;
+
+ case 2:
+ pci_conf_write16(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg, data);
+ break;
+
+ case 1:
+ pci_conf_write8(sbdf.seg, sbdf.bus, sbdf.dev, sbdf.func, reg, data);
+ break;
+
+ default:
+ ASSERT_UNREACHABLE();
+ break;
+ }
+}
+
+/*
+ * Merge new data into a partial result.
+ *
+ * Copy the value found in 'new' from [0, size) left shifted by
+ * 'offset' into 'data'. Note that both 'size' and 'offset' are
+ * in byte units.
+ */
+static uint32_t merge_result(uint32_t data, uint32_t new, unsigned int size,
+ unsigned int offset)
+{
+ uint32_t mask = 0xffffffff >> (32 - 8 * size);
+
+ return (data & ~(mask << (offset * 8))) | ((new & mask) << (offset * 8));
+}
+
+uint32_t vpci_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int size)
+{
+ const struct domain *d = current->domain;
+ const struct pci_dev *pdev;
+ const struct vpci_register *r;
+ unsigned int data_offset = 0;
+ uint32_t data = ~(uint32_t)0;
+
+ /* Find the PCI dev matching the address. */
+ pdev = pci_get_pdev_by_domain(d, sbdf.seg, sbdf.bus, sbdf.extfunc);
+ if ( !pdev )
+ return vpci_read_hw(sbdf, reg, size);
+
+ spin_lock(&pdev->vpci->lock);
+
+ /* Read from the hardware or the emulated register handlers. */
+ list_for_each_entry ( r, &pdev->vpci->handlers, node )
+ {
+ const struct vpci_register emu = {
+ .offset = reg + data_offset,
+ .size = size - data_offset
+ };
+ int cmp = vpci_register_cmp(&emu, r);
+ uint32_t val;
+ unsigned int read_size;
+
+ if ( cmp < 0 )
+ break;
+ if ( cmp > 0 )
+ continue;
+
+ if ( emu.offset < r->offset )
+ {
+ /* Heading gap, read partial content from hardware. */
+ read_size = r->offset - emu.offset;
+ val = vpci_read_hw(sbdf, emu.offset, read_size);
+ data = merge_result(data, val, read_size, data_offset);
+ data_offset += read_size;
+ }
+
+ val = r->read(pdev, r->offset, r->private);
+
+ /* Check if the read is in the middle of a register. */
+ if ( r->offset < emu.offset )
+ val >>= (emu.offset - r->offset) * 8;
+
+ /* Find the intersection size between the two sets. */
+ read_size = min(emu.offset + emu.size, r->offset + r->size) -
+ max(emu.offset, r->offset);
+ /* Merge the emulated data into the native read value. */
+ data = merge_result(data, val, read_size, data_offset);
+ data_offset += read_size;
+ if ( data_offset == size )
+ break;
+ ASSERT(data_offset < size);
+ }
+
+ if ( data_offset < size )
+ {
+ /* Tailing gap, read the remaining. */
+ uint32_t tmp_data = vpci_read_hw(sbdf, reg + data_offset,
+ size - data_offset);
+
+ data = merge_result(data, tmp_data, size - data_offset, data_offset);
+ }
+ spin_unlock(&pdev->vpci->lock);
+
+ return data & (0xffffffff >> (32 - 8 * size));
+}
+
+/*
+ * Perform a maybe partial write to a register.
+ *
+ * Note that this will only work for simple registers, if Xen needs to
+ * trap accesses to rw1c registers (like the status PCI header register)
+ * the logic in vpci_write will have to be expanded in order to correctly
+ * deal with them.
+ */
+static void vpci_write_helper(const struct pci_dev *pdev,
+ const struct vpci_register *r, unsigned int size,
+ unsigned int offset, uint32_t data)
+{
+ ASSERT(size <= r->size);
+
+ if ( size != r->size )
+ {
+ uint32_t val;
+
+ val = r->read(pdev, r->offset, r->private);
+ data = merge_result(val, data, size, offset);
+ }
+
+ r->write(pdev, r->offset, data & (0xffffffff >> (32 - 8 * r->size)),
+ r->private);
+}
+
+void vpci_write(pci_sbdf_t sbdf, unsigned int reg, unsigned int size,
+ uint32_t data)
+{
+ const struct domain *d = current->domain;
+ const struct pci_dev *pdev;
+ const struct vpci_register *r;
+ unsigned int data_offset = 0;
+
+ /*
+ * Find the PCI dev matching the address.
+ * Passthrough everything that's not trapped.
+ */
+ pdev = pci_get_pdev_by_domain(d, sbdf.seg, sbdf.bus, sbdf.extfunc);
+ if ( !pdev )
+ {
+ vpci_write_hw(sbdf, reg, size, data);
+ return;
+ }
+
+ spin_lock(&pdev->vpci->lock);
+
+ /* Write the value to the hardware or emulated registers. */
+ list_for_each_entry ( r, &pdev->vpci->handlers, node )
+ {
+ const struct vpci_register emu = {
+ .offset = reg + data_offset,
+ .size = size - data_offset
+ };
+ int cmp = vpci_register_cmp(&emu, r);
+ unsigned int write_size;
+
+ if ( cmp < 0 )
+ break;
+ if ( cmp > 0 )
+ continue;
+
+ if ( emu.offset < r->offset )
+ {
+ /* Heading gap, write partial content to hardware. */
+ vpci_write_hw(sbdf, emu.offset, r->offset - emu.offset,
+ data >> (data_offset * 8));
+ data_offset += r->offset - emu.offset;
+ }
+
+ /* Find the intersection size between the two sets. */
+ write_size = min(emu.offset + emu.size, r->offset + r->size) -
+ max(emu.offset, r->offset);
+ vpci_write_helper(pdev, r, write_size, reg + data_offset - r->offset,
+ data >> (data_offset * 8));
+ data_offset += write_size;
+ if ( data_offset == size )
+ break;
+ ASSERT(data_offset < size);
+ }
+
+ if ( data_offset < size )
+ /* Tailing gap, write the remaining. */
+ vpci_write_hw(sbdf, reg + data_offset, size - data_offset,
+ data >> (data_offset * 8));
+
+ spin_unlock(&pdev->vpci->lock);
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
#define has_vpit(d) (!!((d)->arch.emulation_flags & XEN_X86_EMU_PIT))
#define has_pirq(d) (!!((d)->arch.emulation_flags & \
XEN_X86_EMU_USE_PIRQ))
+#define has_vpci(d) (!!((d)->arch.emulation_flags & XEN_X86_EMU_VPCI))
#define has_arch_pdevs(d) (!list_empty(&(d)->arch.pdev_list))
*/
void register_g2m_portio_handler(struct domain *d);
+/* HVM port IO handler for vPCI accesses. */
+void register_vpci_portio_handler(struct domain *d);
+
#endif /* __ASM_X86_HVM_IO_H__ */
#define XEN_X86_EMU_PIT (1U<<_XEN_X86_EMU_PIT)
#define _XEN_X86_EMU_USE_PIRQ 9
#define XEN_X86_EMU_USE_PIRQ (1U<<_XEN_X86_EMU_USE_PIRQ)
+#define _XEN_X86_EMU_VPCI 10
+#define XEN_X86_EMU_VPCI (1U<<_XEN_X86_EMU_VPCI)
#define XEN_X86_EMU_ALL (XEN_X86_EMU_LAPIC | XEN_X86_EMU_HPET | \
XEN_X86_EMU_PM | XEN_X86_EMU_RTC | \
XEN_X86_EMU_IOAPIC | XEN_X86_EMU_PIC | \
XEN_X86_EMU_VGA | XEN_X86_EMU_IOMMU | \
- XEN_X86_EMU_PIT | XEN_X86_EMU_USE_PIRQ)
+ XEN_X86_EMU_PIT | XEN_X86_EMU_USE_PIRQ |\
+ XEN_X86_EMU_VPCI)
uint32_t emulation_flags;
};
#define PT_FAULT_THRESHOLD 10
} fault;
u64 vf_rlen[6];
+
+ /* Data for vPCI. */
+ struct vpci *vpci;
};
#define for_each_pdev(domain, pdev) \
#ifndef LINUX_PCI_REGS_H
#define LINUX_PCI_REGS_H
+/*
+ * Conventional PCI and PCI-X Mode 1 devices have 256 bytes of
+ * configuration space. PCI-X Mode 2 and PCIe devices have 4096 bytes of
+ * configuration space.
+ */
+#define PCI_CFG_SPACE_SIZE 256
+#define PCI_CFG_SPACE_EXP_SIZE 4096
+
/*
* Under PCI, each device has 256 bytes of configuration address space,
* of which the first 64 bytes are standardized as follows:
--- /dev/null
+#ifndef _XEN_VPCI_H_
+#define _XEN_VPCI_H_
+
+#include <xen/pci.h>
+#include <xen/types.h>
+#include <xen/list.h>
+
+typedef uint32_t vpci_read_t(const struct pci_dev *pdev, unsigned int reg,
+ void *data);
+
+typedef void vpci_write_t(const struct pci_dev *pdev, unsigned int reg,
+ uint32_t val, void *data);
+
+typedef int vpci_register_init_t(struct pci_dev *dev);
+
+#define REGISTER_VPCI_INIT(x) \
+ static vpci_register_init_t *const x##_entry \
+ __used_section(".data.vpci") = x
+
+/* Add vPCI handlers to device. */
+int __must_check vpci_add_handlers(struct pci_dev *dev);
+
+/* Add/remove a register handler. */
+int __must_check vpci_add_register(struct vpci *vpci,
+ vpci_read_t *read_handler,
+ vpci_write_t *write_handler,
+ unsigned int offset, unsigned int size,
+ void *data);
+int __must_check vpci_remove_register(struct vpci *vpci, unsigned int offset,
+ unsigned int size);
+
+/* Generic read/write handlers for the PCI config space. */
+uint32_t vpci_read(pci_sbdf_t sbdf, unsigned int reg, unsigned int size);
+void vpci_write(pci_sbdf_t sbdf, unsigned int reg, unsigned int size,
+ uint32_t data);
+
+struct vpci {
+ /* List of vPCI handlers for a device. */
+ struct list_head handlers;
+ spinlock_t lock;
+};
+
+#endif
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */