aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan Venteicher <bryanv@FreeBSD.org>2021-01-19 04:55:23 +0000
committerBryan Venteicher <bryanv@FreeBSD.org>2021-01-19 04:55:23 +0000
commit9da9560c4dd3556519cd391a04f0db157dc3c295 (patch)
tree3b0683487a19826301aae9fdf70ede862337e37d
parent1cd1ed3f5dd50ca070908468a3816f570448427b (diff)
downloadsrc-9da9560c4dd3556519cd391a04f0db157dc3c295.tar.gz
src-9da9560c4dd3556519cd391a04f0db157dc3c295.zip
virtio: Add VirtIO PCI modern (V1) support
Use the existing legacy PCI driver as the basis for shared code between the legacy and modern PCI drivers. The existing virtio_pci kernel module will contain both the legacy and modern drivers. Changes to the virtqueue and each device driver (network, block, etc) for V1 support come in later commits. Update the MMIO driver to reflect the VirtIO bus method changes, but the modern compliance can be improved on later. Note that the modern PCI driver requires bus_map_resource() to be implemented, which is not the case on all archs. The hw.virtio.pci.transitional tunable default value is zero so transitional devices will continue to be driven via the legacy driver. Reviewed by: grehan (mentor) Differential Revision: https://reviews.freebsd.org/D27856
-rw-r--r--sys/conf/files3
-rw-r--r--sys/dev/virtio/mmio/virtio_mmio.c23
-rw-r--r--sys/dev/virtio/pci/virtio_pci.c1297
-rw-r--r--sys/dev/virtio/pci/virtio_pci.h173
-rw-r--r--sys/dev/virtio/pci/virtio_pci_if.m71
-rw-r--r--sys/dev/virtio/pci/virtio_pci_legacy.c733
-rw-r--r--sys/dev/virtio/pci/virtio_pci_legacy_var.h78
-rw-r--r--sys/dev/virtio/pci/virtio_pci_modern.c1448
-rw-r--r--sys/dev/virtio/pci/virtio_pci_modern_var.h135
-rw-r--r--sys/dev/virtio/pci/virtio_pci_var.h55
-rw-r--r--sys/dev/virtio/virtio.c76
-rw-r--r--sys/dev/virtio/virtio.h8
-rw-r--r--sys/dev/virtio/virtio_bus_if.m11
-rw-r--r--sys/dev/virtio/virtio_endian.h106
-rw-r--r--sys/dev/virtio/virtqueue.c10
-rw-r--r--sys/dev/virtio/virtqueue.h4
-rw-r--r--sys/modules/virtio/pci/Makefile5
17 files changed, 3292 insertions, 944 deletions
diff --git a/sys/conf/files b/sys/conf/files
index e641b79d9c46..79f3b7ca54e6 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3457,6 +3457,9 @@ dev/virtio/virtqueue.c optional virtio
dev/virtio/virtio_bus_if.m optional virtio
dev/virtio/virtio_if.m optional virtio
dev/virtio/pci/virtio_pci.c optional virtio_pci
+dev/virtio/pci/virtio_pci_if.m optional virtio_pci
+dev/virtio/pci/virtio_pci_legacy.c optional virtio_pci
+dev/virtio/pci/virtio_pci_modern.c optional virtio_pci
dev/virtio/mmio/virtio_mmio.c optional virtio_mmio
dev/virtio/mmio/virtio_mmio_acpi.c optional virtio_mmio acpi
dev/virtio/mmio/virtio_mmio_fdt.c optional virtio_mmio fdt
diff --git a/sys/dev/virtio/mmio/virtio_mmio.c b/sys/dev/virtio/mmio/virtio_mmio.c
index bb5d84754f09..694d2a232fdd 100644
--- a/sys/dev/virtio/mmio/virtio_mmio.c
+++ b/sys/dev/virtio/mmio/virtio_mmio.c
@@ -84,7 +84,7 @@ static void vtmmio_stop(device_t);
static void vtmmio_poll(device_t);
static int vtmmio_reinit(device_t, uint64_t);
static void vtmmio_reinit_complete(device_t);
-static void vtmmio_notify_virtqueue(device_t, uint16_t);
+static void vtmmio_notify_virtqueue(device_t, uint16_t, bus_size_t);
static uint8_t vtmmio_get_status(device_t);
static void vtmmio_set_status(device_t, uint8_t);
static void vtmmio_read_dev_config(device_t, bus_size_t, void *, int);
@@ -352,6 +352,13 @@ vtmmio_read_ivar(device_t dev, device_t child, int index, uintptr_t *result)
*/
*result = 0;
break;
+ case VIRTIO_IVAR_MODERN:
+ /*
+ * There are several modern (aka MMIO v2) spec compliance
+ * issues with this driver, but keep the status quo.
+ */
+ *result = sc->vtmmio_version > 1;
+ break;
default:
return (ENOENT);
}
@@ -388,6 +395,10 @@ vtmmio_negotiate_features(device_t dev, uint64_t child_features)
sc = device_get_softc(dev);
+ if (sc->vtmmio_version > 1) {
+ child_features |= VIRTIO_F_VERSION_1;
+ }
+
vtmmio_write_config_4(sc, VIRTIO_MMIO_HOST_FEATURES_SEL, 1);
host_features = vtmmio_read_config_4(sc, VIRTIO_MMIO_HOST_FEATURES);
host_features <<= 32;
@@ -402,7 +413,7 @@ vtmmio_negotiate_features(device_t dev, uint64_t child_features)
* host all support.
*/
features = host_features & child_features;
- features = virtqueue_filter_features(features);
+ features = virtio_filter_transport_features(features);
sc->vtmmio_features = features;
vtmmio_describe_features(sc, "negotiated", features);
@@ -504,7 +515,8 @@ vtmmio_alloc_virtqueues(device_t dev, int flags, int nvqs,
size = vtmmio_read_config_4(sc, VIRTIO_MMIO_QUEUE_NUM_MAX);
error = virtqueue_alloc(dev, idx, size,
- VIRTIO_MMIO_VRING_ALIGN, ~(vm_paddr_t)0, info, &vq);
+ VIRTIO_MMIO_QUEUE_NOTIFY, VIRTIO_MMIO_VRING_ALIGN,
+ ~(vm_paddr_t)0, info, &vq);
if (error) {
device_printf(dev,
"cannot allocate virtqueue %d: %d\n",
@@ -586,13 +598,14 @@ vtmmio_reinit_complete(device_t dev)
}
static void
-vtmmio_notify_virtqueue(device_t dev, uint16_t queue)
+vtmmio_notify_virtqueue(device_t dev, uint16_t queue, bus_size_t offset)
{
struct vtmmio_softc *sc;
sc = device_get_softc(dev);
+ MPASS(offset == VIRTIO_MMIO_QUEUE_NOTIFY);
- vtmmio_write_config_4(sc, VIRTIO_MMIO_QUEUE_NOTIFY, queue);
+ vtmmio_write_config_4(sc, offset, queue);
}
static uint8_t
diff --git a/sys/dev/virtio/pci/virtio_pci.c b/sys/dev/virtio/pci/virtio_pci.c
index 05a632f526a8..4d6fa929ef19 100644
--- a/sys/dev/virtio/pci/virtio_pci.c
+++ b/sys/dev/virtio/pci/virtio_pci.c
@@ -1,7 +1,7 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
- * Copyright (c) 2011, Bryan Venteicher <bryanv@FreeBSD.org>
+ * Copyright (c) 2017, Bryan Venteicher <bryanv@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -26,8 +26,6 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-/* Driver for the VirtIO PCI interface. */
-
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
@@ -37,7 +35,6 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
-#include <sys/endian.h>
#include <machine/bus.h>
#include <machine/resource.h>
@@ -50,369 +47,236 @@ __FBSDID("$FreeBSD$");
#include <dev/virtio/virtio.h>
#include <dev/virtio/virtqueue.h>
#include <dev/virtio/pci/virtio_pci.h>
+#include <dev/virtio/pci/virtio_pci_var.h>
-#include "virtio_bus_if.h"
+#include "virtio_pci_if.h"
#include "virtio_if.h"
-struct vtpci_interrupt {
- struct resource *vti_irq;
- int vti_rid;
- void *vti_handler;
-};
-
-struct vtpci_virtqueue {
- struct virtqueue *vtv_vq;
- int vtv_no_intr;
-};
-
-struct vtpci_softc {
- device_t vtpci_dev;
- struct resource *vtpci_res;
- struct resource *vtpci_msix_res;
- uint64_t vtpci_features;
- uint32_t vtpci_flags;
-#define VTPCI_FLAG_NO_MSI 0x0001
-#define VTPCI_FLAG_NO_MSIX 0x0002
-#define VTPCI_FLAG_LEGACY 0x1000
-#define VTPCI_FLAG_MSI 0x2000
-#define VTPCI_FLAG_MSIX 0x4000
-#define VTPCI_FLAG_SHARED_MSIX 0x8000
-#define VTPCI_FLAG_ITYPE_MASK 0xF000
-
- /* This "bus" will only ever have one child. */
- device_t vtpci_child_dev;
- struct virtio_feature_desc *vtpci_child_feat_desc;
-
- int vtpci_nvqs;
- struct vtpci_virtqueue *vtpci_vqs;
-
- /*
- * Ideally, each virtqueue that the driver provides a callback for will
- * receive its own MSIX vector. If there are not sufficient vectors
- * available, then attempt to have all the VQs share one vector. For
- * MSIX, the configuration changed notifications must be on their own
- * vector.
- *
- * If MSIX is not available, we will attempt to have the whole device
- * share one MSI vector, and then, finally, one legacy interrupt.
- */
- struct vtpci_interrupt vtpci_device_interrupt;
- struct vtpci_interrupt *vtpci_msix_vq_interrupts;
- int vtpci_nmsix_resources;
-};
-
-static int vtpci_probe(device_t);
-static int vtpci_attach(device_t);
-static int vtpci_detach(device_t);
-static int vtpci_suspend(device_t);
-static int vtpci_resume(device_t);
-static int vtpci_shutdown(device_t);
-static void vtpci_driver_added(device_t, driver_t *);
-static void vtpci_child_detached(device_t, device_t);
-static int vtpci_read_ivar(device_t, device_t, int, uintptr_t *);
-static int vtpci_write_ivar(device_t, device_t, int, uintptr_t);
-
-static uint64_t vtpci_negotiate_features(device_t, uint64_t);
-static int vtpci_with_feature(device_t, uint64_t);
-static int vtpci_alloc_virtqueues(device_t, int, int,
- struct vq_alloc_info *);
-static int vtpci_setup_intr(device_t, enum intr_type);
-static void vtpci_stop(device_t);
-static int vtpci_reinit(device_t, uint64_t);
-static void vtpci_reinit_complete(device_t);
-static void vtpci_notify_virtqueue(device_t, uint16_t);
-static uint8_t vtpci_get_status(device_t);
-static void vtpci_set_status(device_t, uint8_t);
-static void vtpci_read_dev_config(device_t, bus_size_t, void *, int);
-static void vtpci_write_dev_config(device_t, bus_size_t, void *, int);
-
-static void vtpci_describe_features(struct vtpci_softc *, const char *,
+static void vtpci_describe_features(struct vtpci_common *, const char *,
uint64_t);
-static void vtpci_probe_and_attach_child(struct vtpci_softc *);
-
-static int vtpci_alloc_msix(struct vtpci_softc *, int);
-static int vtpci_alloc_msi(struct vtpci_softc *);
-static int vtpci_alloc_intr_msix_pervq(struct vtpci_softc *);
-static int vtpci_alloc_intr_msix_shared(struct vtpci_softc *);
-static int vtpci_alloc_intr_msi(struct vtpci_softc *);
-static int vtpci_alloc_intr_legacy(struct vtpci_softc *);
-static int vtpci_alloc_interrupt(struct vtpci_softc *, int, int,
+static int vtpci_alloc_msix(struct vtpci_common *, int);
+static int vtpci_alloc_msi(struct vtpci_common *);
+static int vtpci_alloc_intr_msix_pervq(struct vtpci_common *);
+static int vtpci_alloc_intr_msix_shared(struct vtpci_common *);
+static int vtpci_alloc_intr_msi(struct vtpci_common *);
+static int vtpci_alloc_intr_intx(struct vtpci_common *);
+static int vtpci_alloc_interrupt(struct vtpci_common *, int, int,
+ struct vtpci_interrupt *);
+static void vtpci_free_interrupt(struct vtpci_common *,
struct vtpci_interrupt *);
-static int vtpci_alloc_intr_resources(struct vtpci_softc *);
-static int vtpci_setup_legacy_interrupt(struct vtpci_softc *,
+static void vtpci_free_interrupts(struct vtpci_common *);
+static void vtpci_free_virtqueues(struct vtpci_common *);
+static void vtpci_cleanup_setup_intr_attempt(struct vtpci_common *);
+static int vtpci_alloc_intr_resources(struct vtpci_common *);
+static int vtpci_setup_intx_interrupt(struct vtpci_common *,
enum intr_type);
-static int vtpci_setup_pervq_msix_interrupts(struct vtpci_softc *,
+static int vtpci_setup_pervq_msix_interrupts(struct vtpci_common *,
enum intr_type);
-static int vtpci_setup_msix_interrupts(struct vtpci_softc *,
+static int vtpci_set_host_msix_vectors(struct vtpci_common *);
+static int vtpci_setup_msix_interrupts(struct vtpci_common *,
enum intr_type);
-static int vtpci_setup_interrupts(struct vtpci_softc *, enum intr_type);
-
-static int vtpci_register_msix_vector(struct vtpci_softc *, int,
- struct vtpci_interrupt *);
-static int vtpci_set_host_msix_vectors(struct vtpci_softc *);
-static int vtpci_reinit_virtqueue(struct vtpci_softc *, int);
-
-static void vtpci_free_interrupt(struct vtpci_softc *,
- struct vtpci_interrupt *);
-static void vtpci_free_interrupts(struct vtpci_softc *);
-static void vtpci_free_virtqueues(struct vtpci_softc *);
-static void vtpci_release_child_resources(struct vtpci_softc *);
-static void vtpci_cleanup_setup_intr_attempt(struct vtpci_softc *);
-static void vtpci_reset(struct vtpci_softc *);
-
-static void vtpci_select_virtqueue(struct vtpci_softc *, int);
-
-static void vtpci_legacy_intr(void *);
+static int vtpci_setup_intrs(struct vtpci_common *, enum intr_type);
+static int vtpci_reinit_virtqueue(struct vtpci_common *, int);
+static void vtpci_intx_intr(void *);
static int vtpci_vq_shared_intr_filter(void *);
static void vtpci_vq_shared_intr(void *);
static int vtpci_vq_intr_filter(void *);
static void vtpci_vq_intr(void *);
static void vtpci_config_intr(void *);
-#define vtpci_setup_msi_interrupt vtpci_setup_legacy_interrupt
-
-#define VIRTIO_PCI_CONFIG(_sc) \
- VIRTIO_PCI_CONFIG_OFF((((_sc)->vtpci_flags & VTPCI_FLAG_MSIX)) != 0)
-
-/*
- * I/O port read/write wrappers.
- */
-#define vtpci_read_config_1(sc, o) bus_read_1((sc)->vtpci_res, (o))
-#define vtpci_write_config_1(sc, o, v) bus_write_1((sc)->vtpci_res, (o), (v))
+#define vtpci_setup_msi_interrupt vtpci_setup_intx_interrupt
/*
- * Virtio-pci specifies that PCI Configuration area is guest endian. However,
- * since PCI devices are inherently little-endian, on BE systems the bus layer
- * transparently converts it to BE. For virtio-legacy, this conversion is
- * undesired, so an extra byte swap is required to fix it.
+ * This module contains two drivers:
+ * - virtio_pci_legacy for pre-V1 support
+ * - virtio_pci_modern for V1 support
*/
-#define vtpci_read_config_2(sc, o) le16toh(bus_read_2((sc)->vtpci_res, (o)))
-#define vtpci_read_config_4(sc, o) le32toh(bus_read_4((sc)->vtpci_res, (o)))
-#define vtpci_write_config_2(sc, o, v) bus_write_2((sc)->vtpci_res, (o), (htole16(v)))
-#define vtpci_write_config_4(sc, o, v) bus_write_4((sc)->vtpci_res, (o), (htole32(v)))
-
-/* PCI Header LE. On BE systems the bus layer takes care of byte swapping */
-#define vtpci_read_header_2(sc, o) (bus_read_2((sc)->vtpci_res, (o)))
-#define vtpci_read_header_4(sc, o) (bus_read_4((sc)->vtpci_res, (o)))
-#define vtpci_write_header_2(sc, o, v) bus_write_2((sc)->vtpci_res, (o), (v))
-#define vtpci_write_header_4(sc, o, v) bus_write_4((sc)->vtpci_res, (o), (v))
-
-/* Tunables. */
-static int vtpci_disable_msix = 0;
-TUNABLE_INT("hw.virtio.pci.disable_msix", &vtpci_disable_msix);
-
-static device_method_t vtpci_methods[] = {
- /* Device interface. */
- DEVMETHOD(device_probe, vtpci_probe),
- DEVMETHOD(device_attach, vtpci_attach),
- DEVMETHOD(device_detach, vtpci_detach),
- DEVMETHOD(device_suspend, vtpci_suspend),
- DEVMETHOD(device_resume, vtpci_resume),
- DEVMETHOD(device_shutdown, vtpci_shutdown),
-
- /* Bus interface. */
- DEVMETHOD(bus_driver_added, vtpci_driver_added),
- DEVMETHOD(bus_child_detached, vtpci_child_detached),
- DEVMETHOD(bus_child_pnpinfo_str, virtio_child_pnpinfo_str),
- DEVMETHOD(bus_read_ivar, vtpci_read_ivar),
- DEVMETHOD(bus_write_ivar, vtpci_write_ivar),
-
- /* VirtIO bus interface. */
- DEVMETHOD(virtio_bus_negotiate_features, vtpci_negotiate_features),
- DEVMETHOD(virtio_bus_with_feature, vtpci_with_feature),
- DEVMETHOD(virtio_bus_alloc_virtqueues, vtpci_alloc_virtqueues),
- DEVMETHOD(virtio_bus_setup_intr, vtpci_setup_intr),
- DEVMETHOD(virtio_bus_stop, vtpci_stop),
- DEVMETHOD(virtio_bus_reinit, vtpci_reinit),
- DEVMETHOD(virtio_bus_reinit_complete, vtpci_reinit_complete),
- DEVMETHOD(virtio_bus_notify_vq, vtpci_notify_virtqueue),
- DEVMETHOD(virtio_bus_read_device_config, vtpci_read_dev_config),
- DEVMETHOD(virtio_bus_write_device_config, vtpci_write_dev_config),
-
- DEVMETHOD_END
-};
-
-static driver_t vtpci_driver = {
- "virtio_pci",
- vtpci_methods,
- sizeof(struct vtpci_softc)
-};
-
-devclass_t vtpci_devclass;
-
-DRIVER_MODULE(virtio_pci, pci, vtpci_driver, vtpci_devclass, 0, 0);
MODULE_VERSION(virtio_pci, 1);
MODULE_DEPEND(virtio_pci, pci, 1, 1, 1);
MODULE_DEPEND(virtio_pci, virtio, 1, 1, 1);
-static int
-vtpci_probe(device_t dev)
-{
- char desc[36];
- const char *name;
+int vtpci_disable_msix = 0;
+TUNABLE_INT("hw.virtio.pci.disable_msix", &vtpci_disable_msix);
- if (pci_get_vendor(dev) != VIRTIO_PCI_VENDORID)
- return (ENXIO);
+static uint8_t
+vtpci_read_isr(struct vtpci_common *cn)
+{
+ return (VIRTIO_PCI_READ_ISR(cn->vtpci_dev));
+}
- if (pci_get_device(dev) < VIRTIO_PCI_DEVICEID_MIN ||
- pci_get_device(dev) > VIRTIO_PCI_DEVICEID_MAX)
- return (ENXIO);
+static uint16_t
+vtpci_get_vq_size(struct vtpci_common *cn, int idx)
+{
+ return (VIRTIO_PCI_GET_VQ_SIZE(cn->vtpci_dev, idx));
+}
- if (pci_get_revid(dev) != VIRTIO_PCI_ABI_VERSION)
- return (ENXIO);
+static bus_size_t
+vtpci_get_vq_notify_off(struct vtpci_common *cn, int idx)
+{
+ return (VIRTIO_PCI_GET_VQ_NOTIFY_OFF(cn->vtpci_dev, idx));
+}
- name = virtio_device_name(pci_get_subdevice(dev));
- if (name == NULL)
- name = "Unknown";
+static void
+vtpci_set_vq(struct vtpci_common *cn, struct virtqueue *vq)
+{
+ VIRTIO_PCI_SET_VQ(cn->vtpci_dev, vq);
+}
- snprintf(desc, sizeof(desc), "VirtIO PCI %s adapter", name);
- device_set_desc_copy(dev, desc);
+static void
+vtpci_disable_vq(struct vtpci_common *cn, int idx)
+{
+ VIRTIO_PCI_DISABLE_VQ(cn->vtpci_dev, idx);
+}
- return (BUS_PROBE_DEFAULT);
+static int
+vtpci_register_cfg_msix(struct vtpci_common *cn, struct vtpci_interrupt *intr)
+{
+ return (VIRTIO_PCI_REGISTER_CFG_MSIX(cn->vtpci_dev, intr));
}
static int
-vtpci_attach(device_t dev)
+vtpci_register_vq_msix(struct vtpci_common *cn, int idx,
+ struct vtpci_interrupt *intr)
{
- struct vtpci_softc *sc;
- device_t child;
- int rid;
+ return (VIRTIO_PCI_REGISTER_VQ_MSIX(cn->vtpci_dev, idx, intr));
+}
- sc = device_get_softc(dev);
- sc->vtpci_dev = dev;
+void
+vtpci_init(struct vtpci_common *cn, device_t dev, bool modern)
+{
- pci_enable_busmaster(dev);
+ cn->vtpci_dev = dev;
- rid = PCIR_BAR(0);
- sc->vtpci_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid,
- RF_ACTIVE);
- if (sc->vtpci_res == NULL) {
- device_printf(dev, "cannot map I/O space\n");
- return (ENXIO);
- }
+ pci_enable_busmaster(dev);
+ if (modern)
+ cn->vtpci_flags |= VTPCI_FLAG_MODERN;
if (pci_find_cap(dev, PCIY_MSI, NULL) != 0)
- sc->vtpci_flags |= VTPCI_FLAG_NO_MSI;
-
- if (pci_find_cap(dev, PCIY_MSIX, NULL) == 0) {
- rid = PCIR_BAR(1);
- sc->vtpci_msix_res = bus_alloc_resource_any(dev,
- SYS_RES_MEMORY, &rid, RF_ACTIVE);
- }
-
- if (sc->vtpci_msix_res == NULL)
- sc->vtpci_flags |= VTPCI_FLAG_NO_MSIX;
+ cn->vtpci_flags |= VTPCI_FLAG_NO_MSI;
+ if (pci_find_cap(dev, PCIY_MSIX, NULL) != 0)
+ cn->vtpci_flags |= VTPCI_FLAG_NO_MSIX;
+}
- vtpci_reset(sc);
+int
+vtpci_add_child(struct vtpci_common *cn)
+{
+ device_t dev, child;
- /* Tell the host we've noticed this device. */
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_ACK);
+ dev = cn->vtpci_dev;
- if ((child = device_add_child(dev, NULL, -1)) == NULL) {
+ child = device_add_child(dev, NULL, -1);
+ if (child == NULL) {
device_printf(dev, "cannot create child device\n");
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_FAILED);
- vtpci_detach(dev);
return (ENOMEM);
}
- sc->vtpci_child_dev = child;
- vtpci_probe_and_attach_child(sc);
+ cn->vtpci_child_dev = child;
return (0);
}
-static int
-vtpci_detach(device_t dev)
+int
+vtpci_delete_child(struct vtpci_common *cn)
{
- struct vtpci_softc *sc;
- device_t child;
+ device_t dev, child;
int error;
- sc = device_get_softc(dev);
+ dev = cn->vtpci_dev;
- if ((child = sc->vtpci_child_dev) != NULL) {
+ child = cn->vtpci_child_dev;
+ if (child != NULL) {
error = device_delete_child(dev, child);
if (error)
return (error);
- sc->vtpci_child_dev = NULL;
- }
-
- vtpci_reset(sc);
-
- if (sc->vtpci_msix_res != NULL) {
- bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(1),
- sc->vtpci_msix_res);
- sc->vtpci_msix_res = NULL;
- }
-
- if (sc->vtpci_res != NULL) {
- bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0),
- sc->vtpci_res);
- sc->vtpci_res = NULL;
+ cn->vtpci_child_dev = NULL;
}
return (0);
}
-static int
-vtpci_suspend(device_t dev)
+void
+vtpci_child_detached(struct vtpci_common *cn)
{
- return (bus_generic_suspend(dev));
-}
-
-static int
-vtpci_resume(device_t dev)
-{
+ vtpci_release_child_resources(cn);
- return (bus_generic_resume(dev));
+ cn->vtpci_child_feat_desc = NULL;
+ cn->vtpci_features = 0;
}
-static int
-vtpci_shutdown(device_t dev)
+int
+vtpci_reinit(struct vtpci_common *cn)
{
+ int idx, error;
- (void) bus_generic_shutdown(dev);
- /* Forcibly stop the host device. */
- vtpci_stop(dev);
+ for (idx = 0; idx < cn->vtpci_nvqs; idx++) {
+ error = vtpci_reinit_virtqueue(cn, idx);
+ if (error)
+ return (error);
+ }
+
+ if (vtpci_is_msix_enabled(cn)) {
+ error = vtpci_set_host_msix_vectors(cn);
+ if (error)
+ return (error);
+ }
return (0);
}
static void
-vtpci_driver_added(device_t dev, driver_t *driver)
+vtpci_describe_features(struct vtpci_common *cn, const char *msg,
+ uint64_t features)
{
- struct vtpci_softc *sc;
+ device_t dev, child;
+
+ dev = cn->vtpci_dev;
+ child = cn->vtpci_child_dev;
- sc = device_get_softc(dev);
+ if (device_is_attached(child) || bootverbose == 0)
+ return;
- vtpci_probe_and_attach_child(sc);
+ virtio_describe(dev, msg, features, cn->vtpci_child_feat_desc);
}
-static void
-vtpci_child_detached(device_t dev, device_t child)
+uint64_t
+vtpci_negotiate_features(struct vtpci_common *cn,
+ uint64_t child_features, uint64_t host_features)
{
- struct vtpci_softc *sc;
+ uint64_t features;
- sc = device_get_softc(dev);
+ vtpci_describe_features(cn, "host", host_features);
- vtpci_reset(sc);
- vtpci_release_child_resources(sc);
+ /*
+ * Limit negotiated features to what the driver, virtqueue, and
+ * host all support.
+ */
+ features = host_features & child_features;
+ features = virtio_filter_transport_features(features);
+ vtpci_describe_features(cn, "negotiated", features);
+
+ cn->vtpci_features = features;
+
+ return (features);
}
-static int
-vtpci_read_ivar(device_t dev, device_t child, int index, uintptr_t *result)
+int
+vtpci_with_feature(struct vtpci_common *cn, uint64_t feature)
{
- struct vtpci_softc *sc;
+ return ((cn->vtpci_features & feature) != 0);
+}
- sc = device_get_softc(dev);
+int
+vtpci_read_ivar(struct vtpci_common *cn, int index, uintptr_t *result)
+{
+ device_t dev;
+ int error;
- if (sc->vtpci_child_dev != child)
- return (ENOENT);
+ dev = cn->vtpci_dev;
+ error = 0;
switch (index) {
- case VIRTIO_IVAR_DEVTYPE:
case VIRTIO_IVAR_SUBDEVICE:
*result = pci_get_subdevice(dev);
break;
@@ -425,100 +289,74 @@ vtpci_read_ivar(device_t dev, device_t child, int index, uintptr_t *result)
case VIRTIO_IVAR_SUBVENDOR:
*result = pci_get_subvendor(dev);
break;
+ case VIRTIO_IVAR_MODERN:
+ *result = vtpci_is_modern(cn);
+ break;
default:
- return (ENOENT);
+ error = ENOENT;
}
- return (0);
+ return (error);
}
-static int
-vtpci_write_ivar(device_t dev, device_t child, int index, uintptr_t value)
+int
+vtpci_write_ivar(struct vtpci_common *cn, int index, uintptr_t value)
{
- struct vtpci_softc *sc;
-
- sc = device_get_softc(dev);
+ int error;
- if (sc->vtpci_child_dev != child)
- return (ENOENT);
+ error = 0;
switch (index) {
case VIRTIO_IVAR_FEATURE_DESC:
- sc->vtpci_child_feat_desc = (void *) value;
+ cn->vtpci_child_feat_desc = (void *) value;
break;
default:
- return (ENOENT);
+ error = ENOENT;
}
- return (0);
+ return (error);
}
-static uint64_t
-vtpci_negotiate_features(device_t dev, uint64_t child_features)
+int
+vtpci_alloc_virtqueues(struct vtpci_common *cn, int flags, int nvqs,
+ struct vq_alloc_info *vq_info)
{
- struct vtpci_softc *sc;
- uint64_t host_features, features;
-
- sc = device_get_softc(dev);
+ device_t dev;
+ int idx, align, error;
- host_features = vtpci_read_header_4(sc, VIRTIO_PCI_HOST_FEATURES);
- vtpci_describe_features(sc, "host", host_features);
+ dev = cn->vtpci_dev;
/*
- * Limit negotiated features to what the driver, virtqueue, and
- * host all support.
+ * This is VIRTIO_PCI_VRING_ALIGN from legacy VirtIO. In modern VirtIO,
+ * the tables do not have to be allocated contiguously, but we do so
+ * anyways.
*/
- features = host_features & child_features;
- features = virtqueue_filter_features(features);
- sc->vtpci_features = features;
-
- vtpci_describe_features(sc, "negotiated", features);
- vtpci_write_header_4(sc, VIRTIO_PCI_GUEST_FEATURES, features);
-
- return (features);
-}
-
-static int
-vtpci_with_feature(device_t dev, uint64_t feature)
-{
- struct vtpci_softc *sc;
+ align = 4096;
- sc = device_get_softc(dev);
-
- return ((sc->vtpci_features & feature) != 0);
-}
-
-static int
-vtpci_alloc_virtqueues(device_t dev, int flags, int nvqs,
- struct vq_alloc_info *vq_info)
-{
- struct vtpci_softc *sc;
- struct virtqueue *vq;
- struct vtpci_virtqueue *vqx;
- struct vq_alloc_info *info;
- int idx, error;
- uint16_t size;
-
- sc = device_get_softc(dev);
-
- if (sc->vtpci_nvqs != 0)
+ if (cn->vtpci_nvqs != 0)
return (EALREADY);
if (nvqs <= 0)
return (EINVAL);
- sc->vtpci_vqs = malloc(nvqs * sizeof(struct vtpci_virtqueue),
+ cn->vtpci_vqs = malloc(nvqs * sizeof(struct vtpci_virtqueue),
M_DEVBUF, M_NOWAIT | M_ZERO);
- if (sc->vtpci_vqs == NULL)
+ if (cn->vtpci_vqs == NULL)
return (ENOMEM);
for (idx = 0; idx < nvqs; idx++) {
- vqx = &sc->vtpci_vqs[idx];
+ struct vtpci_virtqueue *vqx;
+ struct vq_alloc_info *info;
+ struct virtqueue *vq;
+ bus_size_t notify_offset;
+ uint16_t size;
+
+ vqx = &cn->vtpci_vqs[idx];
info = &vq_info[idx];
- vtpci_select_virtqueue(sc, idx);
- size = vtpci_read_header_2(sc, VIRTIO_PCI_QUEUE_NUM);
+ size = vtpci_get_vq_size(cn, idx);
+ notify_offset = vtpci_get_vq_notify_off(cn, idx);
- error = virtqueue_alloc(dev, idx, size, VIRTIO_PCI_VRING_ALIGN,
+ error = virtqueue_alloc(dev, idx, size, notify_offset, align,
~(vm_paddr_t)0, info, &vq);
if (error) {
device_printf(dev,
@@ -526,270 +364,27 @@ vtpci_alloc_virtqueues(device_t dev, int flags, int nvqs,
break;
}
- vtpci_write_header_4(sc, VIRTIO_PCI_QUEUE_PFN,
- virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
+ vtpci_set_vq(cn, vq);
vqx->vtv_vq = *info->vqai_vq = vq;
vqx->vtv_no_intr = info->vqai_intr == NULL;
- sc->vtpci_nvqs++;
+ cn->vtpci_nvqs++;
}
if (error)
- vtpci_free_virtqueues(sc);
+ vtpci_free_virtqueues(cn);
return (error);
}
static int
-vtpci_setup_intr(device_t dev, enum intr_type type)
-{
- struct vtpci_softc *sc;
- int attempt, error;
-
- sc = device_get_softc(dev);
-
- for (attempt = 0; attempt < 5; attempt++) {
- /*
- * Start with the most desirable interrupt configuration and
- * fallback towards less desirable ones.
- */
- switch (attempt) {
- case 0:
- error = vtpci_alloc_intr_msix_pervq(sc);
- break;
- case 1:
- error = vtpci_alloc_intr_msix_shared(sc);
- break;
- case 2:
- error = vtpci_alloc_intr_msi(sc);
- break;
- case 3:
- error = vtpci_alloc_intr_legacy(sc);
- break;
- default:
- device_printf(dev,
- "exhausted all interrupt allocation attempts\n");
- return (ENXIO);
- }
-
- if (error == 0 && vtpci_setup_interrupts(sc, type) == 0)
- break;
-
- vtpci_cleanup_setup_intr_attempt(sc);
- }
-
- if (bootverbose) {
- if (sc->vtpci_flags & VTPCI_FLAG_LEGACY)
- device_printf(dev, "using legacy interrupt\n");
- else if (sc->vtpci_flags & VTPCI_FLAG_MSI)
- device_printf(dev, "using MSI interrupt\n");
- else if (sc->vtpci_flags & VTPCI_FLAG_SHARED_MSIX)
- device_printf(dev, "using shared MSIX interrupts\n");
- else
- device_printf(dev, "using per VQ MSIX interrupts\n");
- }
-
- return (0);
-}
-
-static void
-vtpci_stop(device_t dev)
-{
-
- vtpci_reset(device_get_softc(dev));
-}
-
-static int
-vtpci_reinit(device_t dev, uint64_t features)
-{
- struct vtpci_softc *sc;
- int idx, error;
-
- sc = device_get_softc(dev);
-
- /*
- * Redrive the device initialization. This is a bit of an abuse of
- * the specification, but VirtualBox, QEMU/KVM, and BHyVe seem to
- * play nice.
- *
- * We do not allow the host device to change from what was originally
- * negotiated beyond what the guest driver changed. MSIX state should
- * not change, number of virtqueues and their size remain the same, etc.
- * This will need to be rethought when we want to support migration.
- */
-
- if (vtpci_get_status(dev) != VIRTIO_CONFIG_STATUS_RESET)
- vtpci_stop(dev);
-
- /*
- * Quickly drive the status through ACK and DRIVER. The device
- * does not become usable again until vtpci_reinit_complete().
- */
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_ACK);
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_DRIVER);
-
- vtpci_negotiate_features(dev, features);
-
- for (idx = 0; idx < sc->vtpci_nvqs; idx++) {
- error = vtpci_reinit_virtqueue(sc, idx);
- if (error)
- return (error);
- }
-
- if (sc->vtpci_flags & VTPCI_FLAG_MSIX) {
- error = vtpci_set_host_msix_vectors(sc);
- if (error)
- return (error);
- }
-
- return (0);
-}
-
-static void
-vtpci_reinit_complete(device_t dev)
-{
-
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_DRIVER_OK);
-}
-
-static void
-vtpci_notify_virtqueue(device_t dev, uint16_t queue)
-{
- struct vtpci_softc *sc;
-
- sc = device_get_softc(dev);
-
- vtpci_write_header_2(sc, VIRTIO_PCI_QUEUE_NOTIFY, queue);
-}
-
-static uint8_t
-vtpci_get_status(device_t dev)
-{
- struct vtpci_softc *sc;
-
- sc = device_get_softc(dev);
-
- return (vtpci_read_config_1(sc, VIRTIO_PCI_STATUS));
-}
-
-static void
-vtpci_set_status(device_t dev, uint8_t status)
-{
- struct vtpci_softc *sc;
-
- sc = device_get_softc(dev);
-
- if (status != VIRTIO_CONFIG_STATUS_RESET)
- status |= vtpci_get_status(dev);
-
- vtpci_write_config_1(sc, VIRTIO_PCI_STATUS, status);
-}
-
-static void
-vtpci_read_dev_config(device_t dev, bus_size_t offset,
- void *dst, int length)
-{
- struct vtpci_softc *sc;
- bus_size_t off;
- uint8_t *d;
- int size;
-
- sc = device_get_softc(dev);
- off = VIRTIO_PCI_CONFIG(sc) + offset;
-
- for (d = dst; length > 0; d += size, off += size, length -= size) {
- if (length >= 4) {
- size = 4;
- *(uint32_t *)d = vtpci_read_config_4(sc, off);
- } else if (length >= 2) {
- size = 2;
- *(uint16_t *)d = vtpci_read_config_2(sc, off);
- } else {
- size = 1;
- *d = vtpci_read_config_1(sc, off);
- }
- }
-}
-
-static void
-vtpci_write_dev_config(device_t dev, bus_size_t offset,
- void *src, int length)
-{
- struct vtpci_softc *sc;
- bus_size_t off;
- uint8_t *s;
- int size;
-
- sc = device_get_softc(dev);
- off = VIRTIO_PCI_CONFIG(sc) + offset;
-
- for (s = src; length > 0; s += size, off += size, length -= size) {
- if (length >= 4) {
- size = 4;
- vtpci_write_config_4(sc, off, *(uint32_t *)s);
- } else if (length >= 2) {
- size = 2;
- vtpci_write_config_2(sc, off, *(uint16_t *)s);
- } else {
- size = 1;
- vtpci_write_config_1(sc, off, *s);
- }
- }
-}
-
-static void
-vtpci_describe_features(struct vtpci_softc *sc, const char *msg,
- uint64_t features)
-{
- device_t dev, child;
-
- dev = sc->vtpci_dev;
- child = sc->vtpci_child_dev;
-
- if (device_is_attached(child) || bootverbose == 0)
- return;
-
- virtio_describe(dev, msg, features, sc->vtpci_child_feat_desc);
-}
-
-static void
-vtpci_probe_and_attach_child(struct vtpci_softc *sc)
-{
- device_t dev, child;
-
- dev = sc->vtpci_dev;
- child = sc->vtpci_child_dev;
-
- if (child == NULL)
- return;
-
- if (device_get_state(child) != DS_NOTPRESENT)
- return;
-
- if (device_probe(child) != 0)
- return;
-
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_DRIVER);
- if (device_attach(child) != 0) {
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_FAILED);
- vtpci_reset(sc);
- vtpci_release_child_resources(sc);
- /* Reset status for future attempt. */
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_ACK);
- } else {
- vtpci_set_status(dev, VIRTIO_CONFIG_STATUS_DRIVER_OK);
- VIRTIO_ATTACH_COMPLETED(child);
- }
-}
-
-static int
-vtpci_alloc_msix(struct vtpci_softc *sc, int nvectors)
+vtpci_alloc_msix(struct vtpci_common *cn, int nvectors)
{
device_t dev;
int nmsix, cnt, required;
- dev = sc->vtpci_dev;
+ dev = cn->vtpci_dev;
/* Allocate an additional vector for the config changes. */
required = nvectors + 1;
@@ -800,7 +395,7 @@ vtpci_alloc_msix(struct vtpci_softc *sc, int nvectors)
cnt = required;
if (pci_alloc_msix(dev, &cnt) == 0 && cnt >= required) {
- sc->vtpci_nmsix_resources = required;
+ cn->vtpci_nmsix_resources = required;
return (0);
}
@@ -810,12 +405,12 @@ vtpci_alloc_msix(struct vtpci_softc *sc, int nvectors)
}
static int
-vtpci_alloc_msi(struct vtpci_softc *sc)
+vtpci_alloc_msi(struct vtpci_common *cn)
{
device_t dev;
int nmsi, cnt, required;
- dev = sc->vtpci_dev;
+ dev = cn->vtpci_dev;
required = 1;
nmsi = pci_msi_count(dev);
@@ -832,80 +427,78 @@ vtpci_alloc_msi(struct vtpci_softc *sc)
}
static int
-vtpci_alloc_intr_msix_pervq(struct vtpci_softc *sc)
+vtpci_alloc_intr_msix_pervq(struct vtpci_common *cn)
{
int i, nvectors, error;
- if (vtpci_disable_msix != 0 ||
- sc->vtpci_flags & VTPCI_FLAG_NO_MSIX)
+ if (vtpci_disable_msix != 0 || cn->vtpci_flags & VTPCI_FLAG_NO_MSIX)
return (ENOTSUP);
- for (nvectors = 0, i = 0; i < sc->vtpci_nvqs; i++) {
- if (sc->vtpci_vqs[i].vtv_no_intr == 0)
+ for (nvectors = 0, i = 0; i < cn->vtpci_nvqs; i++) {
+ if (cn->vtpci_vqs[i].vtv_no_intr == 0)
nvectors++;
}
- error = vtpci_alloc_msix(sc, nvectors);
+ error = vtpci_alloc_msix(cn, nvectors);
if (error)
return (error);
- sc->vtpci_flags |= VTPCI_FLAG_MSIX;
+ cn->vtpci_flags |= VTPCI_FLAG_MSIX;
return (0);
}
static int
-vtpci_alloc_intr_msix_shared(struct vtpci_softc *sc)
+vtpci_alloc_intr_msix_shared(struct vtpci_common *cn)
{
int error;
- if (vtpci_disable_msix != 0 ||
- sc->vtpci_flags & VTPCI_FLAG_NO_MSIX)
+ if (vtpci_disable_msix != 0 || cn->vtpci_flags & VTPCI_FLAG_NO_MSIX)
return (ENOTSUP);
- error = vtpci_alloc_msix(sc, 1);
+ error = vtpci_alloc_msix(cn, 1);
if (error)
return (error);
- sc->vtpci_flags |= VTPCI_FLAG_MSIX | VTPCI_FLAG_SHARED_MSIX;
+ cn->vtpci_flags |= VTPCI_FLAG_MSIX | VTPCI_FLAG_SHARED_MSIX;
return (0);
}
static int
-vtpci_alloc_intr_msi(struct vtpci_softc *sc)
+vtpci_alloc_intr_msi(struct vtpci_common *cn)
{
int error;
/* Only BHyVe supports MSI. */
- if (sc->vtpci_flags & VTPCI_FLAG_NO_MSI)
+ if (cn->vtpci_flags & VTPCI_FLAG_NO_MSI)
return (ENOTSUP);
- error = vtpci_alloc_msi(sc);
+ error = vtpci_alloc_msi(cn);
if (error)
return (error);
- sc->vtpci_flags |= VTPCI_FLAG_MSI;
+ cn->vtpci_flags |= VTPCI_FLAG_MSI;
return (0);
}
static int
-vtpci_alloc_intr_legacy(struct vtpci_softc *sc)
+vtpci_alloc_intr_intx(struct vtpci_common *cn)
{
- sc->vtpci_flags |= VTPCI_FLAG_LEGACY;
+ cn->vtpci_flags |= VTPCI_FLAG_INTX;
return (0);
}
static int
-vtpci_alloc_interrupt(struct vtpci_softc *sc, int rid, int flags,
+vtpci_alloc_interrupt(struct vtpci_common *cn, int rid, int flags,
struct vtpci_interrupt *intr)
{
struct resource *irq;
- irq = bus_alloc_resource_any(sc->vtpci_dev, SYS_RES_IRQ, &rid, flags);
+ irq = bus_alloc_resource_any(cn->vtpci_dev, SYS_RES_IRQ, &rid, flags);
if (irq == NULL)
return (ENXIO);
@@ -915,40 +508,136 @@ vtpci_alloc_interrupt(struct vtpci_softc *sc, int rid, int flags,
return (0);
}
+static void
+vtpci_free_interrupt(struct vtpci_common *cn, struct vtpci_interrupt *intr)
+{
+ device_t dev;
+
+ dev = cn->vtpci_dev;
+
+ if (intr->vti_handler != NULL) {
+ bus_teardown_intr(dev, intr->vti_irq, intr->vti_handler);
+ intr->vti_handler = NULL;
+ }
+
+ if (intr->vti_irq != NULL) {
+ bus_release_resource(dev, SYS_RES_IRQ, intr->vti_rid,
+ intr->vti_irq);
+ intr->vti_irq = NULL;
+ intr->vti_rid = -1;
+ }
+}
+
+static void
+vtpci_free_interrupts(struct vtpci_common *cn)
+{
+ struct vtpci_interrupt *intr;
+ int i, nvq_intrs;
+
+ vtpci_free_interrupt(cn, &cn->vtpci_device_interrupt);
+
+ if (cn->vtpci_nmsix_resources != 0) {
+ nvq_intrs = cn->vtpci_nmsix_resources - 1;
+ cn->vtpci_nmsix_resources = 0;
+
+ if ((intr = cn->vtpci_msix_vq_interrupts) != NULL) {
+ for (i = 0; i < nvq_intrs; i++, intr++)
+ vtpci_free_interrupt(cn, intr);
+
+ free(cn->vtpci_msix_vq_interrupts, M_DEVBUF);
+ cn->vtpci_msix_vq_interrupts = NULL;
+ }
+ }
+
+ if (cn->vtpci_flags & (VTPCI_FLAG_MSI | VTPCI_FLAG_MSIX))
+ pci_release_msi(cn->vtpci_dev);
+
+ cn->vtpci_flags &= ~VTPCI_FLAG_ITYPE_MASK;
+}
+
+static void
+vtpci_free_virtqueues(struct vtpci_common *cn)
+{
+ struct vtpci_virtqueue *vqx;
+ int idx;
+
+ for (idx = 0; idx < cn->vtpci_nvqs; idx++) {
+ vtpci_disable_vq(cn, idx);
+
+ vqx = &cn->vtpci_vqs[idx];
+ virtqueue_free(vqx->vtv_vq);
+ vqx->vtv_vq = NULL;
+ }
+
+ free(cn->vtpci_vqs, M_DEVBUF);
+ cn->vtpci_vqs = NULL;
+ cn->vtpci_nvqs = 0;
+}
+
+void
+vtpci_release_child_resources(struct vtpci_common *cn)
+{
+
+ vtpci_free_interrupts(cn);
+ vtpci_free_virtqueues(cn);
+}
+
+static void
+vtpci_cleanup_setup_intr_attempt(struct vtpci_common *cn)
+{
+ int idx;
+
+ if (cn->vtpci_flags & VTPCI_FLAG_MSIX) {
+ vtpci_register_cfg_msix(cn, NULL);
+
+ for (idx = 0; idx < cn->vtpci_nvqs; idx++)
+ vtpci_register_vq_msix(cn, idx, NULL);
+ }
+
+ vtpci_free_interrupts(cn);
+}
+
static int
-vtpci_alloc_intr_resources(struct vtpci_softc *sc)
+vtpci_alloc_intr_resources(struct vtpci_common *cn)
{
struct vtpci_interrupt *intr;
int i, rid, flags, nvq_intrs, error;
- rid = 0;
flags = RF_ACTIVE;
- if (sc->vtpci_flags & VTPCI_FLAG_LEGACY)
+ if (cn->vtpci_flags & VTPCI_FLAG_INTX) {
+ rid = 0;
flags |= RF_SHAREABLE;
- else
+ } else
rid = 1;
/*
- * For legacy and MSI interrupts, this single resource handles all
- * interrupts. For MSIX, this resource is used for the configuration
- * changed interrupt.
+ * When using INTX or MSI interrupts, this resource handles all
+ * interrupts. When using MSIX, this resource handles just the
+ * configuration changed interrupt.
*/
- intr = &sc->vtpci_device_interrupt;
- error = vtpci_alloc_interrupt(sc, rid, flags, intr);
- if (error || sc->vtpci_flags & (VTPCI_FLAG_LEGACY | VTPCI_FLAG_MSI))
+ intr = &cn->vtpci_device_interrupt;
+
+ error = vtpci_alloc_interrupt(cn, rid, flags, intr);
+ if (error || cn->vtpci_flags & (VTPCI_FLAG_INTX | VTPCI_FLAG_MSI))
return (error);
- /* Subtract one for the configuration changed interrupt. */
- nvq_intrs = sc->vtpci_nmsix_resources - 1;
+ /*
+ * Now allocate the interrupts for the virtqueues. This may be one
+ * for all the virtqueues, or one for each virtqueue. Subtract one
+ * below for because of the configuration changed interrupt.
+ */
+ nvq_intrs = cn->vtpci_nmsix_resources - 1;
- intr = sc->vtpci_msix_vq_interrupts = malloc(nvq_intrs *
+ cn->vtpci_msix_vq_interrupts = malloc(nvq_intrs *
sizeof(struct vtpci_interrupt), M_DEVBUF, M_NOWAIT | M_ZERO);
- if (sc->vtpci_msix_vq_interrupts == NULL)
+ if (cn->vtpci_msix_vq_interrupts == NULL)
return (ENOMEM);
+ intr = cn->vtpci_msix_vq_interrupts;
+
for (i = 0, rid++; i < nvq_intrs; i++, rid++, intr++) {
- error = vtpci_alloc_interrupt(sc, rid, flags, intr);
+ error = vtpci_alloc_interrupt(cn, rid, flags, intr);
if (error)
return (error);
}
@@ -957,34 +646,35 @@ vtpci_alloc_intr_resources(struct vtpci_softc *sc)
}
static int
-vtpci_setup_legacy_interrupt(struct vtpci_softc *sc, enum intr_type type)
+vtpci_setup_intx_interrupt(struct vtpci_common *cn, enum intr_type type)
{
struct vtpci_interrupt *intr;
int error;
- intr = &sc->vtpci_device_interrupt;
- error = bus_setup_intr(sc->vtpci_dev, intr->vti_irq, type, NULL,
- vtpci_legacy_intr, sc, &intr->vti_handler);
+ intr = &cn->vtpci_device_interrupt;
+
+ error = bus_setup_intr(cn->vtpci_dev, intr->vti_irq, type, NULL,
+ vtpci_intx_intr, cn, &intr->vti_handler);
return (error);
}
static int
-vtpci_setup_pervq_msix_interrupts(struct vtpci_softc *sc, enum intr_type type)
+vtpci_setup_pervq_msix_interrupts(struct vtpci_common *cn, enum intr_type type)
{
struct vtpci_virtqueue *vqx;
struct vtpci_interrupt *intr;
int i, error;
- intr = sc->vtpci_msix_vq_interrupts;
+ intr = cn->vtpci_msix_vq_interrupts;
- for (i = 0; i < sc->vtpci_nvqs; i++) {
- vqx = &sc->vtpci_vqs[i];
+ for (i = 0; i < cn->vtpci_nvqs; i++) {
+ vqx = &cn->vtpci_vqs[i];
if (vqx->vtv_no_intr)
continue;
- error = bus_setup_intr(sc->vtpci_dev, intr->vti_irq, type,
+ error = bus_setup_intr(cn->vtpci_dev, intr->vti_irq, type,
vtpci_vq_intr_filter, vtpci_vq_intr, vqx->vtv_vq,
&intr->vti_handler);
if (error)
@@ -997,106 +687,24 @@ vtpci_setup_pervq_msix_interrupts(struct vtpci_softc *sc, enum intr_type type)
}
static int
-vtpci_setup_msix_interrupts(struct vtpci_softc *sc, enum intr_type type)
-{
- device_t dev;
- struct vtpci_interrupt *intr;
- int error;
-
- dev = sc->vtpci_dev;
- intr = &sc->vtpci_device_interrupt;
-
- error = bus_setup_intr(dev, intr->vti_irq, type, NULL,
- vtpci_config_intr, sc, &intr->vti_handler);
- if (error)
- return (error);
-
- if (sc->vtpci_flags & VTPCI_FLAG_SHARED_MSIX) {
- intr = sc->vtpci_msix_vq_interrupts;
- error = bus_setup_intr(dev, intr->vti_irq, type,
- vtpci_vq_shared_intr_filter, vtpci_vq_shared_intr, sc,
- &intr->vti_handler);
- } else
- error = vtpci_setup_pervq_msix_interrupts(sc, type);
-
- return (error ? error : vtpci_set_host_msix_vectors(sc));
-}
-
-static int
-vtpci_setup_interrupts(struct vtpci_softc *sc, enum intr_type type)
-{
- int error;
-
- type |= INTR_MPSAFE;
- KASSERT(sc->vtpci_flags & VTPCI_FLAG_ITYPE_MASK,
- ("%s: no interrupt type selected %#x", __func__, sc->vtpci_flags));
-
- error = vtpci_alloc_intr_resources(sc);
- if (error)
- return (error);
-
- if (sc->vtpci_flags & VTPCI_FLAG_LEGACY)
- error = vtpci_setup_legacy_interrupt(sc, type);
- else if (sc->vtpci_flags & VTPCI_FLAG_MSI)
- error = vtpci_setup_msi_interrupt(sc, type);
- else
- error = vtpci_setup_msix_interrupts(sc, type);
-
- return (error);
-}
-
-static int
-vtpci_register_msix_vector(struct vtpci_softc *sc, int offset,
- struct vtpci_interrupt *intr)
-{
- device_t dev;
- uint16_t vector;
-
- dev = sc->vtpci_dev;
-
- if (intr != NULL) {
- /* Map from guest rid to host vector. */
- vector = intr->vti_rid - 1;
- } else
- vector = VIRTIO_MSI_NO_VECTOR;
-
- vtpci_write_header_2(sc, offset, vector);
-
- /* Read vector to determine if the host had sufficient resources. */
- if (vtpci_read_header_2(sc, offset) != vector) {
- device_printf(dev,
- "insufficient host resources for MSIX interrupts\n");
- return (ENODEV);
- }
-
- return (0);
-}
-
-static int
-vtpci_set_host_msix_vectors(struct vtpci_softc *sc)
+vtpci_set_host_msix_vectors(struct vtpci_common *cn)
{
struct vtpci_interrupt *intr, *tintr;
- int idx, offset, error;
-
- intr = &sc->vtpci_device_interrupt;
- offset = VIRTIO_MSI_CONFIG_VECTOR;
+ int idx, error;
- error = vtpci_register_msix_vector(sc, offset, intr);
+ intr = &cn->vtpci_device_interrupt;
+ error = vtpci_register_cfg_msix(cn, intr);
if (error)
return (error);
- intr = sc->vtpci_msix_vq_interrupts;
- offset = VIRTIO_MSI_QUEUE_VECTOR;
-
- for (idx = 0; idx < sc->vtpci_nvqs; idx++) {
- vtpci_select_virtqueue(sc, idx);
-
- if (sc->vtpci_vqs[idx].vtv_no_intr)
+ intr = cn->vtpci_msix_vq_interrupts;
+ for (idx = 0; idx < cn->vtpci_nvqs; idx++) {
+ if (cn->vtpci_vqs[idx].vtv_no_intr)
tintr = NULL;
else
tintr = intr;
- error = vtpci_register_msix_vector(sc, offset, tintr);
+ error = vtpci_register_vq_msix(cn, idx, tintr);
if (error)
break;
@@ -1104,8 +712,8 @@ vtpci_set_host_msix_vectors(struct vtpci_softc *sc)
* For shared MSIX, all the virtqueues share the first
* interrupt.
*/
- if (!sc->vtpci_vqs[idx].vtv_no_intr &&
- (sc->vtpci_flags & VTPCI_FLAG_SHARED_MSIX) == 0)
+ if (!cn->vtpci_vqs[idx].vtv_no_intr &&
+ (cn->vtpci_flags & VTPCI_FLAG_SHARED_MSIX) == 0)
intr++;
}
@@ -1113,164 +721,141 @@ vtpci_set_host_msix_vectors(struct vtpci_softc *sc)
}
static int
-vtpci_reinit_virtqueue(struct vtpci_softc *sc, int idx)
+vtpci_setup_msix_interrupts(struct vtpci_common *cn, enum intr_type type)
{
- struct vtpci_virtqueue *vqx;
- struct virtqueue *vq;
+ struct vtpci_interrupt *intr;
int error;
- uint16_t size;
-
- vqx = &sc->vtpci_vqs[idx];
- vq = vqx->vtv_vq;
-
- KASSERT(vq != NULL, ("%s: vq %d not allocated", __func__, idx));
- vtpci_select_virtqueue(sc, idx);
- size = vtpci_read_header_2(sc, VIRTIO_PCI_QUEUE_NUM);
+ intr = &cn->vtpci_device_interrupt;
- error = virtqueue_reinit(vq, size);
+ error = bus_setup_intr(cn->vtpci_dev, intr->vti_irq, type, NULL,
+ vtpci_config_intr, cn, &intr->vti_handler);
if (error)
return (error);
- vtpci_write_header_4(sc, VIRTIO_PCI_QUEUE_PFN,
- virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
-
- return (0);
-}
+ if (cn->vtpci_flags & VTPCI_FLAG_SHARED_MSIX) {
+ intr = &cn->vtpci_msix_vq_interrupts[0];
-static void
-vtpci_free_interrupt(struct vtpci_softc *sc, struct vtpci_interrupt *intr)
-{
- device_t dev;
-
- dev = sc->vtpci_dev;
-
- if (intr->vti_handler != NULL) {
- bus_teardown_intr(dev, intr->vti_irq, intr->vti_handler);
- intr->vti_handler = NULL;
- }
+ error = bus_setup_intr(cn->vtpci_dev, intr->vti_irq, type,
+ vtpci_vq_shared_intr_filter, vtpci_vq_shared_intr, cn,
+ &intr->vti_handler);
+ } else
+ error = vtpci_setup_pervq_msix_interrupts(cn, type);
- if (intr->vti_irq != NULL) {
- bus_release_resource(dev, SYS_RES_IRQ, intr->vti_rid,
- intr->vti_irq);
- intr->vti_irq = NULL;
- intr->vti_rid = -1;
- }
+ return (error ? error : vtpci_set_host_msix_vectors(cn));
}
-static void
-vtpci_free_interrupts(struct vtpci_softc *sc)
+static int
+vtpci_setup_intrs(struct vtpci_common *cn, enum intr_type type)
{
- struct vtpci_interrupt *intr;
- int i, nvq_intrs;
-
- vtpci_free_interrupt(sc, &sc->vtpci_device_interrupt);
-
- if (sc->vtpci_nmsix_resources != 0) {
- nvq_intrs = sc->vtpci_nmsix_resources - 1;
- sc->vtpci_nmsix_resources = 0;
+ int error;
- intr = sc->vtpci_msix_vq_interrupts;
- if (intr != NULL) {
- for (i = 0; i < nvq_intrs; i++, intr++)
- vtpci_free_interrupt(sc, intr);
+ type |= INTR_MPSAFE;
+ KASSERT(cn->vtpci_flags & VTPCI_FLAG_ITYPE_MASK,
+ ("%s: no interrupt type selected %#x", __func__, cn->vtpci_flags));
- free(sc->vtpci_msix_vq_interrupts, M_DEVBUF);
- sc->vtpci_msix_vq_interrupts = NULL;
- }
- }
+ error = vtpci_alloc_intr_resources(cn);
+ if (error)
+ return (error);
- if (sc->vtpci_flags & (VTPCI_FLAG_MSI | VTPCI_FLAG_MSIX))
- pci_release_msi(sc->vtpci_dev);
+ if (cn->vtpci_flags & VTPCI_FLAG_INTX)
+ error = vtpci_setup_intx_interrupt(cn, type);
+ else if (cn->vtpci_flags & VTPCI_FLAG_MSI)
+ error = vtpci_setup_msi_interrupt(cn, type);
+ else
+ error = vtpci_setup_msix_interrupts(cn, type);
- sc->vtpci_flags &= ~VTPCI_FLAG_ITYPE_MASK;
+ return (error);
}
-static void
-vtpci_free_virtqueues(struct vtpci_softc *sc)
+int
+vtpci_setup_interrupts(struct vtpci_common *cn, enum intr_type type)
{
- struct vtpci_virtqueue *vqx;
- int idx;
-
- for (idx = 0; idx < sc->vtpci_nvqs; idx++) {
- vqx = &sc->vtpci_vqs[idx];
-
- vtpci_select_virtqueue(sc, idx);
- vtpci_write_header_4(sc, VIRTIO_PCI_QUEUE_PFN, 0);
-
- virtqueue_free(vqx->vtv_vq);
- vqx->vtv_vq = NULL;
- }
-
- free(sc->vtpci_vqs, M_DEVBUF);
- sc->vtpci_vqs = NULL;
- sc->vtpci_nvqs = 0;
-}
+ device_t dev;
+ int attempt, error;
-static void
-vtpci_release_child_resources(struct vtpci_softc *sc)
-{
+ dev = cn->vtpci_dev;
- vtpci_free_interrupts(sc);
- vtpci_free_virtqueues(sc);
-}
+ for (attempt = 0; attempt < 5; attempt++) {
+ /*
+ * Start with the most desirable interrupt configuration and
+ * fallback towards less desirable ones.
+ */
+ switch (attempt) {
+ case 0:
+ error = vtpci_alloc_intr_msix_pervq(cn);
+ break;
+ case 1:
+ error = vtpci_alloc_intr_msix_shared(cn);
+ break;
+ case 2:
+ error = vtpci_alloc_intr_msi(cn);
+ break;
+ case 3:
+ error = vtpci_alloc_intr_intx(cn);
+ break;
+ default:
+ device_printf(dev,
+ "exhausted all interrupt allocation attempts\n");
+ return (ENXIO);
+ }
-static void
-vtpci_cleanup_setup_intr_attempt(struct vtpci_softc *sc)
-{
- int idx;
+ if (error == 0 && vtpci_setup_intrs(cn, type) == 0)
+ break;
- if (sc->vtpci_flags & VTPCI_FLAG_MSIX) {
- vtpci_write_header_2(sc, VIRTIO_MSI_CONFIG_VECTOR,
- VIRTIO_MSI_NO_VECTOR);
+ vtpci_cleanup_setup_intr_attempt(cn);
+ }
- for (idx = 0; idx < sc->vtpci_nvqs; idx++) {
- vtpci_select_virtqueue(sc, idx);
- vtpci_write_header_2(sc, VIRTIO_MSI_QUEUE_VECTOR,
- VIRTIO_MSI_NO_VECTOR);
- }
+ if (bootverbose) {
+ if (cn->vtpci_flags & VTPCI_FLAG_INTX)
+ device_printf(dev, "using legacy interrupt\n");
+ else if (cn->vtpci_flags & VTPCI_FLAG_MSI)
+ device_printf(dev, "using MSI interrupt\n");
+ else if (cn->vtpci_flags & VTPCI_FLAG_SHARED_MSIX)
+ device_printf(dev, "using shared MSIX interrupts\n");
+ else
+ device_printf(dev, "using per VQ MSIX interrupts\n");
}
- vtpci_free_interrupts(sc);
+ return (0);
}
-static void
-vtpci_reset(struct vtpci_softc *sc)
+static int
+vtpci_reinit_virtqueue(struct vtpci_common *cn, int idx)
{
+ struct vtpci_virtqueue *vqx;
+ struct virtqueue *vq;
+ int error;
- /*
- * Setting the status to RESET sets the host device to
- * the original, uninitialized state.
- */
- vtpci_set_status(sc->vtpci_dev, VIRTIO_CONFIG_STATUS_RESET);
-}
+ vqx = &cn->vtpci_vqs[idx];
+ vq = vqx->vtv_vq;
-static void
-vtpci_select_virtqueue(struct vtpci_softc *sc, int idx)
-{
+ KASSERT(vq != NULL, ("%s: vq %d not allocated", __func__, idx));
- vtpci_write_header_2(sc, VIRTIO_PCI_QUEUE_SEL, idx);
+ error = virtqueue_reinit(vq, vtpci_get_vq_size(cn, idx));
+ if (error == 0)
+ vtpci_set_vq(cn, vq);
+
+ return (error);
}
static void
-vtpci_legacy_intr(void *xsc)
+vtpci_intx_intr(void *xcn)
{
- struct vtpci_softc *sc;
+ struct vtpci_common *cn;
struct vtpci_virtqueue *vqx;
int i;
uint8_t isr;
- sc = xsc;
- vqx = &sc->vtpci_vqs[0];
-
- /* Reading the ISR also clears it. */
- isr = vtpci_read_config_1(sc, VIRTIO_PCI_ISR);
+ cn = xcn;
+ isr = vtpci_read_isr(cn);
if (isr & VIRTIO_PCI_ISR_CONFIG)
- vtpci_config_intr(sc);
+ vtpci_config_intr(cn);
if (isr & VIRTIO_PCI_ISR_INTR) {
- for (i = 0; i < sc->vtpci_nvqs; i++, vqx++) {
+ vqx = &cn->vtpci_vqs[0];
+ for (i = 0; i < cn->vtpci_nvqs; i++, vqx++) {
if (vqx->vtv_no_intr == 0)
virtqueue_intr(vqx->vtv_vq);
}
@@ -1278,17 +863,17 @@ vtpci_legacy_intr(void *xsc)
}
static int
-vtpci_vq_shared_intr_filter(void *xsc)
+vtpci_vq_shared_intr_filter(void *xcn)
{
- struct vtpci_softc *sc;
+ struct vtpci_common *cn;
struct vtpci_virtqueue *vqx;
int i, rc;
+ cn = xcn;
+ vqx = &cn->vtpci_vqs[0];
rc = 0;
- sc = xsc;
- vqx = &sc->vtpci_vqs[0];
- for (i = 0; i < sc->vtpci_nvqs; i++, vqx++) {
+ for (i = 0; i < cn->vtpci_nvqs; i++, vqx++) {
if (vqx->vtv_no_intr == 0)
rc |= virtqueue_intr_filter(vqx->vtv_vq);
}
@@ -1297,16 +882,16 @@ vtpci_vq_shared_intr_filter(void *xsc)
}
static void
-vtpci_vq_shared_intr(void *xsc)
+vtpci_vq_shared_intr(void *xcn)
{
- struct vtpci_softc *sc;
+ struct vtpci_common *cn;
struct vtpci_virtqueue *vqx;
int i;
- sc = xsc;
- vqx = &sc->vtpci_vqs[0];
+ cn = xcn;
+ vqx = &cn->vtpci_vqs[0];
- for (i = 0; i < sc->vtpci_nvqs; i++, vqx++) {
+ for (i = 0; i < cn->vtpci_nvqs; i++, vqx++) {
if (vqx->vtv_no_intr == 0)
virtqueue_intr(vqx->vtv_vq);
}
@@ -1334,13 +919,13 @@ vtpci_vq_intr(void *xvq)
}
static void
-vtpci_config_intr(void *xsc)
+vtpci_config_intr(void *xcn)
{
- struct vtpci_softc *sc;
+ struct vtpci_common *cn;
device_t child;
- sc = xsc;
- child = sc->vtpci_child_dev;
+ cn = xcn;
+ child = cn->vtpci_child_dev;
if (child != NULL)
VIRTIO_CONFIG_CHANGE(child);
diff --git a/sys/dev/virtio/pci/virtio_pci.h b/sys/dev/virtio/pci/virtio_pci.h
index 8212e36bb733..d65ed853c33e 100644
--- a/sys/dev/virtio/pci/virtio_pci.h
+++ b/sys/dev/virtio/pci/virtio_pci.h
@@ -1,36 +1,29 @@
/*-
- * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
- * Copyright IBM Corp. 2007
- *
- * Authors:
- * Anthony Liguori <aliguori@us.ibm.com>
- *
- * This header is BSD licensed so anyone can use the definitions to implement
- * compatible drivers/servers.
+ * Copyright (c) 2017, Bryan Venteicher <bryanv@FreeBSD.org>
+ * All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
* 2. 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.
- * 3. Neither the name of IBM 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 IBM OR CONTRIBUTORS 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
*
* $FreeBSD$
*/
@@ -38,51 +31,101 @@
#ifndef _VIRTIO_PCI_H
#define _VIRTIO_PCI_H
-/* VirtIO PCI vendor/device ID. */
-#define VIRTIO_PCI_VENDORID 0x1AF4
-#define VIRTIO_PCI_DEVICEID_MIN 0x1000
-#define VIRTIO_PCI_DEVICEID_MAX 0x103F
+struct vtpci_interrupt {
+ struct resource *vti_irq;
+ int vti_rid;
+ void *vti_handler;
+};
-/* VirtIO ABI version, this must match exactly. */
-#define VIRTIO_PCI_ABI_VERSION 0
+struct vtpci_virtqueue {
+ struct virtqueue *vtv_vq;
+ int vtv_no_intr;
+ int vtv_notify_offset;
+};
-/*
- * VirtIO Header, located in BAR 0.
- */
-#define VIRTIO_PCI_HOST_FEATURES 0 /* host's supported features (32bit, RO)*/
-#define VIRTIO_PCI_GUEST_FEATURES 4 /* guest's supported features (32, RW) */
-#define VIRTIO_PCI_QUEUE_PFN 8 /* physical address of VQ (32, RW) */
-#define VIRTIO_PCI_QUEUE_NUM 12 /* number of ring entries (16, RO) */
-#define VIRTIO_PCI_QUEUE_SEL 14 /* current VQ selection (16, RW) */
-#define VIRTIO_PCI_QUEUE_NOTIFY 16 /* notify host regarding VQ (16, RW) */
-#define VIRTIO_PCI_STATUS 18 /* device status register (8, RW) */
-#define VIRTIO_PCI_ISR 19 /* interrupt status register, reading
- * also clears the register (8, RO) */
-/* Only if MSIX is enabled: */
-#define VIRTIO_MSI_CONFIG_VECTOR 20 /* configuration change vector (16, RW) */
-#define VIRTIO_MSI_QUEUE_VECTOR 22 /* vector for selected VQ notifications
- (16, RW) */
-
-/* The bit of the ISR which indicates a device has an interrupt. */
-#define VIRTIO_PCI_ISR_INTR 0x1
-/* The bit of the ISR which indicates a device configuration change. */
-#define VIRTIO_PCI_ISR_CONFIG 0x2
-/* Vector value used to disable MSI for queue. */
-#define VIRTIO_MSI_NO_VECTOR 0xFFFF
-
-/*
- * The remaining space is defined by each driver as the per-driver
- * configuration space.
- */
-#define VIRTIO_PCI_CONFIG_OFF(msix_enabled) ((msix_enabled) ? 24 : 20)
+struct vtpci_common {
+ device_t vtpci_dev;
+ uint64_t vtpci_features;
+ struct vtpci_virtqueue *vtpci_vqs;
+ int vtpci_nvqs;
-/*
- * How many bits to shift physical queue address written to QUEUE_PFN.
- * 12 is historical, and due to x86 page size.
- */
-#define VIRTIO_PCI_QUEUE_ADDR_SHIFT 12
+ uint32_t vtpci_flags;
+#define VTPCI_FLAG_NO_MSI 0x0001
+#define VTPCI_FLAG_NO_MSIX 0x0002
+#define VTPCI_FLAG_MODERN 0x0004
+#define VTPCI_FLAG_INTX 0x1000
+#define VTPCI_FLAG_MSI 0x2000
+#define VTPCI_FLAG_MSIX 0x4000
+#define VTPCI_FLAG_SHARED_MSIX 0x8000
+#define VTPCI_FLAG_ITYPE_MASK 0xF000
+
+ /* The VirtIO PCI "bus" will only ever have one child. */
+ device_t vtpci_child_dev;
+ struct virtio_feature_desc *vtpci_child_feat_desc;
+
+ /*
+ * Ideally, each virtqueue that the driver provides a callback for will
+ * receive its own MSIX vector. If there are not sufficient vectors
+ * available, then attempt to have all the VQs share one vector. For
+ * MSIX, the configuration changed notifications must be on their own
+ * vector.
+ *
+ * If MSIX is not available, attempt to have the whole device share
+ * one MSI vector, and then, finally, one intx interrupt.
+ */
+ struct vtpci_interrupt vtpci_device_interrupt;
+ struct vtpci_interrupt *vtpci_msix_vq_interrupts;
+ int vtpci_nmsix_resources;
+};
+
+extern int vtpci_disable_msix;
+
+static inline device_t
+vtpci_child_device(struct vtpci_common *cn)
+{
+ return (cn->vtpci_child_dev);
+}
+
+static inline bool
+vtpci_is_msix_available(struct vtpci_common *cn)
+{
+ return ((cn->vtpci_flags & VTPCI_FLAG_NO_MSIX) == 0);
+}
+
+static inline bool
+vtpci_is_msix_enabled(struct vtpci_common *cn)
+{
+ return ((cn->vtpci_flags & VTPCI_FLAG_MSIX) != 0);
+}
+
+static inline bool
+vtpci_is_modern(struct vtpci_common *cn)
+{
+ return ((cn->vtpci_flags & VTPCI_FLAG_MODERN) != 0);
+}
+
+static inline int
+vtpci_virtqueue_count(struct vtpci_common *cn)
+{
+ return (cn->vtpci_nvqs);
+}
+
+void vtpci_init(struct vtpci_common *cn, device_t dev, bool modern);
+int vtpci_add_child(struct vtpci_common *cn);
+int vtpci_delete_child(struct vtpci_common *cn);
+void vtpci_child_detached(struct vtpci_common *cn);
+int vtpci_reinit(struct vtpci_common *cn);
+
+uint64_t vtpci_negotiate_features(struct vtpci_common *cn,
+ uint64_t child_features, uint64_t host_features);
+int vtpci_with_feature(struct vtpci_common *cn, uint64_t feature);
+
+int vtpci_read_ivar(struct vtpci_common *cn, int index, uintptr_t *result);
+int vtpci_write_ivar(struct vtpci_common *cn, int index, uintptr_t value);
-/* The alignment to use between consumer and producer parts of vring. */
-#define VIRTIO_PCI_VRING_ALIGN 4096
+int vtpci_alloc_virtqueues(struct vtpci_common *cn, int flags, int nvqs,
+ struct vq_alloc_info *vq_info);
+int vtpci_setup_interrupts(struct vtpci_common *cn, enum intr_type type);
+void vtpci_release_child_resources(struct vtpci_common *cn);
#endif /* _VIRTIO_PCI_H */
diff --git a/sys/dev/virtio/pci/virtio_pci_if.m b/sys/dev/virtio/pci/virtio_pci_if.m
new file mode 100644
index 000000000000..077ed6e1e51e
--- /dev/null
+++ b/sys/dev/virtio/pci/virtio_pci_if.m
@@ -0,0 +1,71 @@
+#-
+# Copyright (c) 2017, Bryan Venteicher <bryanv@FreeBSD.org>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. 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.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE AUTHOR OR CONTRIBUTORS 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.
+#
+# $FreeBSD$
+
+#include <sys/bus.h>
+#include <machine/bus.h>
+
+INTERFACE virtio_pci;
+
+HEADER {
+struct virtqueue;
+struct vtpci_interrupt;
+};
+
+METHOD uint8_t read_isr {
+ device_t dev;
+};
+
+METHOD uint16_t get_vq_size {
+ device_t dev;
+ int idx;
+};
+
+METHOD bus_size_t get_vq_notify_off {
+ device_t dev;
+ int idx;
+};
+
+METHOD void set_vq {
+ device_t dev;
+ struct virtqueue *vq;
+};
+
+METHOD void disable_vq {
+ device_t dev;
+ int idx;
+};
+
+METHOD int register_cfg_msix {
+ device_t dev;
+ struct vtpci_interrupt *intr;
+};
+
+METHOD int register_vq_msix {
+ device_t dev;
+ int idx;
+ struct vtpci_interrupt *intr;
+};
diff --git a/sys/dev/virtio/pci/virtio_pci_legacy.c b/sys/dev/virtio/pci/virtio_pci_legacy.c
new file mode 100644
index 000000000000..22e369097e2e
--- /dev/null
+++ b/sys/dev/virtio/pci/virtio_pci_legacy.c
@@ -0,0 +1,733 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2011, Bryan Venteicher <bryanv@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
+ */
+
+/* Driver for the legacy VirtIO PCI interface. */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/endian.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcireg.h>
+
+#include <dev/virtio/virtio.h>
+#include <dev/virtio/virtqueue.h>
+#include <dev/virtio/pci/virtio_pci.h>
+#include <dev/virtio/pci/virtio_pci_legacy_var.h>
+
+#include "virtio_bus_if.h"
+#include "virtio_pci_if.h"
+#include "virtio_if.h"
+
+struct vtpci_legacy_softc {
+ device_t vtpci_dev;
+ struct vtpci_common vtpci_common;
+ struct resource *vtpci_res;
+ struct resource *vtpci_msix_res;
+};
+
+static int vtpci_legacy_probe(device_t);
+static int vtpci_legacy_attach(device_t);
+static int vtpci_legacy_detach(device_t);
+static int vtpci_legacy_suspend(device_t);
+static int vtpci_legacy_resume(device_t);
+static int vtpci_legacy_shutdown(device_t);
+
+static void vtpci_legacy_driver_added(device_t, driver_t *);
+static void vtpci_legacy_child_detached(device_t, device_t);
+static int vtpci_legacy_read_ivar(device_t, device_t, int, uintptr_t *);
+static int vtpci_legacy_write_ivar(device_t, device_t, int, uintptr_t);
+
+static uint8_t vtpci_legacy_read_isr(device_t);
+static uint16_t vtpci_legacy_get_vq_size(device_t, int);
+static bus_size_t vtpci_legacy_get_vq_notify_off(device_t, int);
+static void vtpci_legacy_set_vq(device_t, struct virtqueue *);
+static void vtpci_legacy_disable_vq(device_t, int);
+static int vtpci_legacy_register_cfg_msix(device_t,
+ struct vtpci_interrupt *);
+static int vtpci_legacy_register_vq_msix(device_t, int idx,
+ struct vtpci_interrupt *);
+
+static uint64_t vtpci_legacy_negotiate_features(device_t, uint64_t);
+static int vtpci_legacy_with_feature(device_t, uint64_t);
+static int vtpci_legacy_alloc_virtqueues(device_t, int, int,
+ struct vq_alloc_info *);
+static int vtpci_legacy_setup_interrupts(device_t, enum intr_type);
+static void vtpci_legacy_stop(device_t);
+static int vtpci_legacy_reinit(device_t, uint64_t);
+static void vtpci_legacy_reinit_complete(device_t);
+static void vtpci_legacy_notify_vq(device_t, uint16_t, bus_size_t);
+static void vtpci_legacy_read_dev_config(device_t, bus_size_t, void *, int);
+static void vtpci_legacy_write_dev_config(device_t, bus_size_t, void *, int);
+
+static int vtpci_legacy_alloc_resources(struct vtpci_legacy_softc *);
+static void vtpci_legacy_free_resources(struct vtpci_legacy_softc *);
+
+static void vtpci_legacy_probe_and_attach_child(struct vtpci_legacy_softc *);
+
+static uint8_t vtpci_legacy_get_status(struct vtpci_legacy_softc *);
+static void vtpci_legacy_set_status(struct vtpci_legacy_softc *, uint8_t);
+static void vtpci_legacy_select_virtqueue(struct vtpci_legacy_softc *, int);
+static void vtpci_legacy_reset(struct vtpci_legacy_softc *);
+
+#define VIRTIO_PCI_LEGACY_CONFIG(_sc) \
+ VIRTIO_PCI_CONFIG_OFF(vtpci_is_msix_enabled(&(_sc)->vtpci_common))
+
+#define vtpci_legacy_read_config_1(sc, o) \
+ bus_read_1((sc)->vtpci_res, (o))
+#define vtpci_legacy_write_config_1(sc, o, v) \
+ bus_write_1((sc)->vtpci_res, (o), (v))
+/*
+ * VirtIO specifies that PCI Configuration area is guest endian. However,
+ * since PCI devices are inherently little-endian, on big-endian systems
+ * the bus layer transparently converts it to BE. For virtio-legacy, this
+ * conversion is undesired, so an extra byte swap is required to fix it.
+ */
+#define vtpci_legacy_read_config_2(sc, o) \
+ le16toh(bus_read_2((sc)->vtpci_res, (o)))
+#define vtpci_legacy_read_config_4(sc, o) \
+ le32toh(bus_read_4((sc)->vtpci_res, (o)))
+#define vtpci_legacy_write_config_2(sc, o, v) \
+ bus_write_2((sc)->vtpci_res, (o), (htole16(v)))
+#define vtpci_legacy_write_config_4(sc, o, v) \
+ bus_write_4((sc)->vtpci_res, (o), (htole32(v)))
+/* PCI Header LE. On BE systems the bus layer takes care of byte swapping. */
+#define vtpci_legacy_read_header_2(sc, o) \
+ bus_read_2((sc)->vtpci_res, (o))
+#define vtpci_legacy_read_header_4(sc, o) \
+ bus_read_4((sc)->vtpci_res, (o))
+#define vtpci_legacy_write_header_2(sc, o, v) \
+ bus_write_2((sc)->vtpci_res, (o), (v))
+#define vtpci_legacy_write_header_4(sc, o, v) \
+ bus_write_4((sc)->vtpci_res, (o), (v))
+
+static device_method_t vtpci_legacy_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, vtpci_legacy_probe),
+ DEVMETHOD(device_attach, vtpci_legacy_attach),
+ DEVMETHOD(device_detach, vtpci_legacy_detach),
+ DEVMETHOD(device_suspend, vtpci_legacy_suspend),
+ DEVMETHOD(device_resume, vtpci_legacy_resume),
+ DEVMETHOD(device_shutdown, vtpci_legacy_shutdown),
+
+ /* Bus interface. */
+ DEVMETHOD(bus_driver_added, vtpci_legacy_driver_added),
+ DEVMETHOD(bus_child_detached, vtpci_legacy_child_detached),
+ DEVMETHOD(bus_child_pnpinfo_str, virtio_child_pnpinfo_str),
+ DEVMETHOD(bus_read_ivar, vtpci_legacy_read_ivar),
+ DEVMETHOD(bus_write_ivar, vtpci_legacy_write_ivar),
+
+ /* VirtIO PCI interface. */
+ DEVMETHOD(virtio_pci_read_isr, vtpci_legacy_read_isr),
+ DEVMETHOD(virtio_pci_get_vq_size, vtpci_legacy_get_vq_size),
+ DEVMETHOD(virtio_pci_get_vq_notify_off, vtpci_legacy_get_vq_notify_off),
+ DEVMETHOD(virtio_pci_set_vq, vtpci_legacy_set_vq),
+ DEVMETHOD(virtio_pci_disable_vq, vtpci_legacy_disable_vq),
+ DEVMETHOD(virtio_pci_register_cfg_msix, vtpci_legacy_register_cfg_msix),
+ DEVMETHOD(virtio_pci_register_vq_msix, vtpci_legacy_register_vq_msix),
+
+ /* VirtIO bus interface. */
+ DEVMETHOD(virtio_bus_negotiate_features, vtpci_legacy_negotiate_features),
+ DEVMETHOD(virtio_bus_with_feature, vtpci_legacy_with_feature),
+ DEVMETHOD(virtio_bus_alloc_virtqueues, vtpci_legacy_alloc_virtqueues),
+ DEVMETHOD(virtio_bus_setup_intr, vtpci_legacy_setup_interrupts),
+ DEVMETHOD(virtio_bus_stop, vtpci_legacy_stop),
+ DEVMETHOD(virtio_bus_reinit, vtpci_legacy_reinit),
+ DEVMETHOD(virtio_bus_reinit_complete, vtpci_legacy_reinit_complete),
+ DEVMETHOD(virtio_bus_notify_vq, vtpci_legacy_notify_vq),
+ DEVMETHOD(virtio_bus_read_device_config, vtpci_legacy_read_dev_config),
+ DEVMETHOD(virtio_bus_write_device_config, vtpci_legacy_write_dev_config),
+
+ DEVMETHOD_END
+};
+
+static driver_t vtpci_legacy_driver = {
+ .name = "virtio_pci",
+ .methods = vtpci_legacy_methods,
+ .size = sizeof(struct vtpci_legacy_softc)
+};
+
+devclass_t vtpci_legacy_devclass;
+
+DRIVER_MODULE(virtio_pci_legacy, pci, vtpci_legacy_driver,
+ vtpci_legacy_devclass, 0, 0);
+
+static int
+vtpci_legacy_probe(device_t dev)
+{
+ char desc[64];
+ const char *name;
+
+ if (pci_get_vendor(dev) != VIRTIO_PCI_VENDORID)
+ return (ENXIO);
+
+ if (pci_get_device(dev) < VIRTIO_PCI_DEVICEID_MIN ||
+ pci_get_device(dev) > VIRTIO_PCI_DEVICEID_LEGACY_MAX)
+ return (ENXIO);
+
+ if (pci_get_revid(dev) != VIRTIO_PCI_ABI_VERSION)
+ return (ENXIO);
+
+ name = virtio_device_name(pci_get_subdevice(dev));
+ if (name == NULL)
+ name = "Unknown";
+
+ snprintf(desc, sizeof(desc), "VirtIO PCI (legacy) %s adapter", name);
+ device_set_desc_copy(dev, desc);
+
+ /* Prefer transitional modern VirtIO PCI. */
+ return (BUS_PROBE_LOW_PRIORITY);
+}
+
+static int
+vtpci_legacy_attach(device_t dev)
+{
+ struct vtpci_legacy_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+ sc->vtpci_dev = dev;
+ vtpci_init(&sc->vtpci_common, dev, false);
+
+ error = vtpci_legacy_alloc_resources(sc);
+ if (error) {
+ device_printf(dev, "cannot map I/O space\n");
+ return (error);
+ }
+
+ vtpci_legacy_reset(sc);
+
+ /* Tell the host we've noticed this device. */
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_ACK);
+
+ error = vtpci_add_child(&sc->vtpci_common);
+ if (error)
+ goto fail;
+
+ vtpci_legacy_probe_and_attach_child(sc);
+
+ return (0);
+
+fail:
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_FAILED);
+ vtpci_legacy_detach(dev);
+
+ return (error);
+}
+
+static int
+vtpci_legacy_detach(device_t dev)
+{
+ struct vtpci_legacy_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ error = vtpci_delete_child(&sc->vtpci_common);
+ if (error)
+ return (error);
+
+ vtpci_legacy_reset(sc);
+ vtpci_legacy_free_resources(sc);
+
+ return (0);
+}
+
+static int
+vtpci_legacy_suspend(device_t dev)
+{
+ return (bus_generic_suspend(dev));
+}
+
+static int
+vtpci_legacy_resume(device_t dev)
+{
+ return (bus_generic_resume(dev));
+}
+
+static int
+vtpci_legacy_shutdown(device_t dev)
+{
+ (void) bus_generic_shutdown(dev);
+ /* Forcibly stop the host device. */
+ vtpci_legacy_stop(dev);
+
+ return (0);
+}
+
+static void
+vtpci_legacy_driver_added(device_t dev, driver_t *driver)
+{
+ vtpci_legacy_probe_and_attach_child(device_get_softc(dev));
+}
+
+static void
+vtpci_legacy_child_detached(device_t dev, device_t child)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_legacy_reset(sc);
+ vtpci_child_detached(&sc->vtpci_common);
+
+ /* After the reset, retell the host we've noticed this device. */
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_ACK);
+}
+
+static int
+vtpci_legacy_read_ivar(device_t dev, device_t child, int index,
+ uintptr_t *result)
+{
+ struct vtpci_legacy_softc *sc;
+ struct vtpci_common *cn;
+
+ sc = device_get_softc(dev);
+ cn = &sc->vtpci_common;
+
+ if (vtpci_child_device(cn) != child)
+ return (ENOENT);
+
+ switch (index) {
+ case VIRTIO_IVAR_DEVTYPE:
+ *result = pci_get_subdevice(dev);
+ break;
+ default:
+ return (vtpci_read_ivar(cn, index, result));
+ }
+
+ return (0);
+}
+
+static int
+vtpci_legacy_write_ivar(device_t dev, device_t child, int index, uintptr_t value)
+{
+ struct vtpci_legacy_softc *sc;
+ struct vtpci_common *cn;
+
+ sc = device_get_softc(dev);
+ cn = &sc->vtpci_common;
+
+ if (vtpci_child_device(cn) != child)
+ return (ENOENT);
+
+ switch (index) {
+ default:
+ return (vtpci_write_ivar(cn, index, value));
+ }
+
+ return (0);
+}
+
+static uint64_t
+vtpci_legacy_negotiate_features(device_t dev, uint64_t child_features)
+{
+ struct vtpci_legacy_softc *sc;
+ uint64_t host_features, features;
+
+ sc = device_get_softc(dev);
+ host_features = vtpci_legacy_read_header_4(sc, VIRTIO_PCI_HOST_FEATURES);
+
+ features = vtpci_negotiate_features(&sc->vtpci_common,
+ child_features, host_features);
+ vtpci_legacy_write_header_4(sc, VIRTIO_PCI_GUEST_FEATURES, features);
+
+ return (features);
+}
+
+static int
+vtpci_legacy_with_feature(device_t dev, uint64_t feature)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ return (vtpci_with_feature(&sc->vtpci_common, feature));
+}
+
+static int
+vtpci_legacy_alloc_virtqueues(device_t dev, int flags, int nvqs,
+ struct vq_alloc_info *vq_info)
+{
+ struct vtpci_legacy_softc *sc;
+ struct vtpci_common *cn;
+
+ sc = device_get_softc(dev);
+ cn = &sc->vtpci_common;
+
+ return (vtpci_alloc_virtqueues(cn, flags, nvqs, vq_info));
+}
+
+static int
+vtpci_legacy_setup_interrupts(device_t dev, enum intr_type type)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ return (vtpci_setup_interrupts(&sc->vtpci_common, type));
+}
+
+static void
+vtpci_legacy_stop(device_t dev)
+{
+ vtpci_legacy_reset(device_get_softc(dev));
+}
+
+static int
+vtpci_legacy_reinit(device_t dev, uint64_t features)
+{
+ struct vtpci_legacy_softc *sc;
+ struct vtpci_common *cn;
+ int error;
+
+ sc = device_get_softc(dev);
+ cn = &sc->vtpci_common;
+
+ /*
+ * Redrive the device initialization. This is a bit of an abuse of
+ * the specification, but VirtualBox, QEMU/KVM, and BHyVe seem to
+ * play nice.
+ *
+ * We do not allow the host device to change from what was originally
+ * negotiated beyond what the guest driver changed. MSIX state should
+ * not change, number of virtqueues and their size remain the same, etc.
+ * This will need to be rethought when we want to support migration.
+ */
+
+ if (vtpci_legacy_get_status(sc) != VIRTIO_CONFIG_STATUS_RESET)
+ vtpci_legacy_stop(dev);
+
+ /*
+ * Quickly drive the status through ACK and DRIVER. The device does
+ * not become usable again until DRIVER_OK in reinit complete.
+ */
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_ACK);
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_DRIVER);
+
+ vtpci_legacy_negotiate_features(dev, features);
+
+ error = vtpci_reinit(cn);
+ if (error)
+ return (error);
+
+ return (0);
+}
+
+static void
+vtpci_legacy_reinit_complete(device_t dev)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_DRIVER_OK);
+}
+
+static void
+vtpci_legacy_notify_vq(device_t dev, uint16_t queue, bus_size_t offset)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+ MPASS(offset == VIRTIO_PCI_QUEUE_NOTIFY);
+
+ vtpci_legacy_write_header_2(sc, offset, queue);
+}
+
+static uint8_t
+vtpci_legacy_get_status(struct vtpci_legacy_softc *sc)
+{
+ return (vtpci_legacy_read_config_1(sc, VIRTIO_PCI_STATUS));
+}
+
+static void
+vtpci_legacy_set_status(struct vtpci_legacy_softc *sc, uint8_t status)
+{
+ if (status != VIRTIO_CONFIG_STATUS_RESET)
+ status |= vtpci_legacy_get_status(sc);
+
+ vtpci_legacy_write_config_1(sc, VIRTIO_PCI_STATUS, status);
+}
+
+static void
+vtpci_legacy_read_dev_config(device_t dev, bus_size_t offset,
+ void *dst, int length)
+{
+ struct vtpci_legacy_softc *sc;
+ bus_size_t off;
+ uint8_t *d;
+ int size;
+
+ sc = device_get_softc(dev);
+ off = VIRTIO_PCI_LEGACY_CONFIG(sc) + offset;
+
+ for (d = dst; length > 0; d += size, off += size, length -= size) {
+ if (length >= 4) {
+ size = 4;
+ *(uint32_t *)d = vtpci_legacy_read_config_4(sc, off);
+ } else if (length >= 2) {
+ size = 2;
+ *(uint16_t *)d = vtpci_legacy_read_config_2(sc, off);
+ } else {
+ size = 1;
+ *d = vtpci_legacy_read_config_1(sc, off);
+ }
+ }
+}
+
+static void
+vtpci_legacy_write_dev_config(device_t dev, bus_size_t offset,
+ void *src, int length)
+{
+ struct vtpci_legacy_softc *sc;
+ bus_size_t off;
+ uint8_t *s;
+ int size;
+
+ sc = device_get_softc(dev);
+ off = VIRTIO_PCI_LEGACY_CONFIG(sc) + offset;
+
+ for (s = src; length > 0; s += size, off += size, length -= size) {
+ if (length >= 4) {
+ size = 4;
+ vtpci_legacy_write_config_4(sc, off, *(uint32_t *)s);
+ } else if (length >= 2) {
+ size = 2;
+ vtpci_legacy_write_config_2(sc, off, *(uint16_t *)s);
+ } else {
+ size = 1;
+ vtpci_legacy_write_config_1(sc, off, *s);
+ }
+ }
+}
+
+static int
+vtpci_legacy_alloc_resources(struct vtpci_legacy_softc *sc)
+{
+ device_t dev;
+ int rid;
+
+ dev = sc->vtpci_dev;
+
+ rid = PCIR_BAR(0);
+ if ((sc->vtpci_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
+ &rid, RF_ACTIVE)) == NULL)
+ return (ENXIO);
+
+ if (vtpci_is_msix_available(&sc->vtpci_common)) {
+ rid = PCIR_BAR(1);
+ if ((sc->vtpci_msix_res = bus_alloc_resource_any(dev,
+ SYS_RES_MEMORY, &rid, RF_ACTIVE)) == NULL)
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static void
+vtpci_legacy_free_resources(struct vtpci_legacy_softc *sc)
+{
+ device_t dev;
+
+ dev = sc->vtpci_dev;
+
+ if (sc->vtpci_msix_res != NULL) {
+ bus_release_resource(dev, SYS_RES_MEMORY, PCIR_BAR(1),
+ sc->vtpci_msix_res);
+ sc->vtpci_msix_res = NULL;
+ }
+
+ if (sc->vtpci_res != NULL) {
+ bus_release_resource(dev, SYS_RES_IOPORT, PCIR_BAR(0),
+ sc->vtpci_res);
+ sc->vtpci_res = NULL;
+ }
+}
+
+static void
+vtpci_legacy_probe_and_attach_child(struct vtpci_legacy_softc *sc)
+{
+ device_t dev, child;
+
+ dev = sc->vtpci_dev;
+ child = vtpci_child_device(&sc->vtpci_common);
+
+ if (child == NULL || device_get_state(child) != DS_NOTPRESENT)
+ return;
+
+ if (device_probe(child) != 0)
+ return;
+
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_DRIVER);
+
+ if (device_attach(child) != 0) {
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_FAILED);
+ /* Reset status for future attempt. */
+ vtpci_legacy_child_detached(dev, child);
+ } else {
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_DRIVER_OK);
+ VIRTIO_ATTACH_COMPLETED(child);
+ }
+}
+
+static int
+vtpci_legacy_register_msix(struct vtpci_legacy_softc *sc, int offset,
+ struct vtpci_interrupt *intr)
+{
+ device_t dev;
+ uint16_t vector;
+
+ dev = sc->vtpci_dev;
+
+ if (intr != NULL) {
+ /* Map from guest rid to host vector. */
+ vector = intr->vti_rid - 1;
+ } else
+ vector = VIRTIO_MSI_NO_VECTOR;
+
+ vtpci_legacy_write_header_2(sc, offset, vector);
+ return (vtpci_legacy_read_header_2(sc, offset) == vector ? 0 : ENODEV);
+}
+
+static int
+vtpci_legacy_register_cfg_msix(device_t dev, struct vtpci_interrupt *intr)
+{
+ struct vtpci_legacy_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ error = vtpci_legacy_register_msix(sc, VIRTIO_MSI_CONFIG_VECTOR, intr);
+ if (error) {
+ device_printf(dev,
+ "unable to register config MSIX interrupt\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+vtpci_legacy_register_vq_msix(device_t dev, int idx,
+ struct vtpci_interrupt *intr)
+{
+ struct vtpci_legacy_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ vtpci_legacy_select_virtqueue(sc, idx);
+ error = vtpci_legacy_register_msix(sc, VIRTIO_MSI_QUEUE_VECTOR, intr);
+ if (error) {
+ device_printf(dev,
+ "unable to register virtqueue MSIX interrupt\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static void
+vtpci_legacy_reset(struct vtpci_legacy_softc *sc)
+{
+ /*
+ * Setting the status to RESET sets the host device to the
+ * original, uninitialized state.
+ */
+ vtpci_legacy_set_status(sc, VIRTIO_CONFIG_STATUS_RESET);
+ (void) vtpci_legacy_get_status(sc);
+}
+
+static void
+vtpci_legacy_select_virtqueue(struct vtpci_legacy_softc *sc, int idx)
+{
+ vtpci_legacy_write_header_2(sc, VIRTIO_PCI_QUEUE_SEL, idx);
+}
+
+static uint8_t
+vtpci_legacy_read_isr(device_t dev)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ return (vtpci_legacy_read_config_1(sc, VIRTIO_PCI_ISR));
+}
+
+static uint16_t
+vtpci_legacy_get_vq_size(device_t dev, int idx)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_legacy_select_virtqueue(sc, idx);
+ return (vtpci_legacy_read_header_2(sc, VIRTIO_PCI_QUEUE_NUM));
+}
+
+static bus_size_t
+vtpci_legacy_get_vq_notify_off(device_t dev, int idx)
+{
+ return (VIRTIO_PCI_QUEUE_NOTIFY);
+}
+
+static void
+vtpci_legacy_set_vq(device_t dev, struct virtqueue *vq)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_legacy_select_virtqueue(sc, virtqueue_index(vq));
+ vtpci_legacy_write_header_4(sc, VIRTIO_PCI_QUEUE_PFN,
+ virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
+}
+
+static void
+vtpci_legacy_disable_vq(device_t dev, int idx)
+{
+ struct vtpci_legacy_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_legacy_select_virtqueue(sc, idx);
+ vtpci_legacy_write_header_4(sc, VIRTIO_PCI_QUEUE_PFN, 0);
+}
diff --git a/sys/dev/virtio/pci/virtio_pci_legacy_var.h b/sys/dev/virtio/pci/virtio_pci_legacy_var.h
new file mode 100644
index 000000000000..615570efb13a
--- /dev/null
+++ b/sys/dev/virtio/pci/virtio_pci_legacy_var.h
@@ -0,0 +1,78 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright IBM Corp. 2007
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of IBM 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 IBM OR CONTRIBUTORS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _VIRTIO_PCI_LEGACY_VAR_H
+#define _VIRTIO_PCI_LEGACY_VAR_H
+
+#include <dev/virtio/pci/virtio_pci_var.h>
+
+/* VirtIO ABI version, this must match exactly. */
+#define VIRTIO_PCI_ABI_VERSION 0
+
+/*
+ * VirtIO Header, located in BAR 0.
+ */
+#define VIRTIO_PCI_HOST_FEATURES 0 /* host's supported features (32bit, RO)*/
+#define VIRTIO_PCI_GUEST_FEATURES 4 /* guest's supported features (32, RW) */
+#define VIRTIO_PCI_QUEUE_PFN 8 /* physical address of VQ (32, RW) */
+#define VIRTIO_PCI_QUEUE_NUM 12 /* number of ring entries (16, RO) */
+#define VIRTIO_PCI_QUEUE_SEL 14 /* current VQ selection (16, RW) */
+#define VIRTIO_PCI_QUEUE_NOTIFY 16 /* notify host regarding VQ (16, RW) */
+#define VIRTIO_PCI_STATUS 18 /* device status register (8, RW) */
+#define VIRTIO_PCI_ISR 19 /* interrupt status register, reading
+ * also clears the register (8, RO) */
+/* Only if MSIX is enabled: */
+#define VIRTIO_MSI_CONFIG_VECTOR 20 /* configuration change vector (16, RW) */
+#define VIRTIO_MSI_QUEUE_VECTOR 22 /* vector for selected VQ notifications
+ (16, RW) */
+
+/*
+ * The remaining space is defined by each driver as the per-driver
+ * configuration space.
+ */
+#define VIRTIO_PCI_CONFIG_OFF(msix_enabled) ((msix_enabled) ? 24 : 20)
+
+/*
+ * How many bits to shift physical queue address written to QUEUE_PFN.
+ * 12 is historical, and due to x86 page size.
+ */
+#define VIRTIO_PCI_QUEUE_ADDR_SHIFT 12
+
+/* The alignment to use between consumer and producer parts of vring. */
+#define VIRTIO_PCI_VRING_ALIGN 4096
+
+#endif /* _VIRTIO_PCI_LEGACY_VAR_H */
diff --git a/sys/dev/virtio/pci/virtio_pci_modern.c b/sys/dev/virtio/pci/virtio_pci_modern.c
new file mode 100644
index 000000000000..09ac0a1232e7
--- /dev/null
+++ b/sys/dev/virtio/pci/virtio_pci_modern.c
@@ -0,0 +1,1448 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2017, Bryan Venteicher <bryanv@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
+ */
+
+/* Driver for the modern VirtIO PCI interface. */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <machine/bus.h>
+#include <machine/cpu.h>
+#include <machine/resource.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcireg.h>
+
+#include <dev/virtio/virtio.h>
+#include <dev/virtio/virtqueue.h>
+#include <dev/virtio/pci/virtio_pci.h>
+#include <dev/virtio/pci/virtio_pci_modern_var.h>
+
+#include "virtio_bus_if.h"
+#include "virtio_pci_if.h"
+#include "virtio_if.h"
+
+struct vtpci_modern_resource_map {
+ struct resource_map vtrm_map;
+ int vtrm_cap_offset;
+ int vtrm_bar;
+ int vtrm_offset;
+ int vtrm_length;
+ int vtrm_type; /* SYS_RES_{MEMORY, IOPORT} */
+};
+
+struct vtpci_modern_bar_resource {
+ struct resource *vtbr_res;
+ int vtbr_type;
+};
+
+struct vtpci_modern_softc {
+ device_t vtpci_dev;
+ struct vtpci_common vtpci_common;
+ uint32_t vtpci_notify_offset_multiplier;
+ uint16_t vtpci_devid;
+ int vtpci_msix_bar;
+ struct resource *vtpci_msix_res;
+
+ struct vtpci_modern_resource_map vtpci_common_res_map;
+ struct vtpci_modern_resource_map vtpci_notify_res_map;
+ struct vtpci_modern_resource_map vtpci_isr_res_map;
+ struct vtpci_modern_resource_map vtpci_device_res_map;
+
+#define VTPCI_MODERN_MAX_BARS 6
+ struct vtpci_modern_bar_resource vtpci_bar_res[VTPCI_MODERN_MAX_BARS];
+};
+
+static int vtpci_modern_probe(device_t);
+static int vtpci_modern_attach(device_t);
+static int vtpci_modern_detach(device_t);
+static int vtpci_modern_suspend(device_t);
+static int vtpci_modern_resume(device_t);
+static int vtpci_modern_shutdown(device_t);
+
+static void vtpci_modern_driver_added(device_t, driver_t *);
+static void vtpci_modern_child_detached(device_t, device_t);
+static int vtpci_modern_read_ivar(device_t, device_t, int, uintptr_t *);
+static int vtpci_modern_write_ivar(device_t, device_t, int, uintptr_t);
+
+static uint8_t vtpci_modern_read_isr(device_t);
+static uint16_t vtpci_modern_get_vq_size(device_t, int);
+static bus_size_t vtpci_modern_get_vq_notify_off(device_t, int);
+static void vtpci_modern_set_vq(device_t, struct virtqueue *);
+static void vtpci_modern_disable_vq(device_t, int);
+static int vtpci_modern_register_msix(struct vtpci_modern_softc *, int,
+ struct vtpci_interrupt *);
+static int vtpci_modern_register_cfg_msix(device_t,
+ struct vtpci_interrupt *);
+static int vtpci_modern_register_vq_msix(device_t, int idx,
+ struct vtpci_interrupt *);
+
+static uint64_t vtpci_modern_negotiate_features(device_t, uint64_t);
+static int vtpci_modern_finalize_features(device_t);
+static int vtpci_modern_with_feature(device_t, uint64_t);
+static int vtpci_modern_alloc_virtqueues(device_t, int, int,
+ struct vq_alloc_info *);
+static int vtpci_modern_setup_interrupts(device_t, enum intr_type);
+static void vtpci_modern_stop(device_t);
+static int vtpci_modern_reinit(device_t, uint64_t);
+static void vtpci_modern_reinit_complete(device_t);
+static void vtpci_modern_notify_vq(device_t, uint16_t, bus_size_t);
+static int vtpci_modern_config_generation(device_t);
+static void vtpci_modern_read_dev_config(device_t, bus_size_t, void *, int);
+static void vtpci_modern_write_dev_config(device_t, bus_size_t, void *, int);
+
+static int vtpci_modern_probe_configs(device_t);
+static int vtpci_modern_find_cap(device_t, uint8_t, int *);
+static int vtpci_modern_map_configs(struct vtpci_modern_softc *);
+static void vtpci_modern_unmap_configs(struct vtpci_modern_softc *);
+static int vtpci_modern_find_cap_resource(struct vtpci_modern_softc *,
+ uint8_t, int, int, struct vtpci_modern_resource_map *);
+static int vtpci_modern_bar_type(struct vtpci_modern_softc *, int);
+static struct resource *vtpci_modern_get_bar_resource(
+ struct vtpci_modern_softc *, int, int);
+static struct resource *vtpci_modern_alloc_bar_resource(
+ struct vtpci_modern_softc *, int, int);
+static void vtpci_modern_free_bar_resources(struct vtpci_modern_softc *);
+static int vtpci_modern_alloc_resource_map(struct vtpci_modern_softc *,
+ struct vtpci_modern_resource_map *);
+static void vtpci_modern_free_resource_map(struct vtpci_modern_softc *,
+ struct vtpci_modern_resource_map *);
+static void vtpci_modern_alloc_msix_resource(struct vtpci_modern_softc *);
+static void vtpci_modern_free_msix_resource(struct vtpci_modern_softc *);
+
+static void vtpci_modern_probe_and_attach_child(struct vtpci_modern_softc *);
+
+static uint64_t vtpci_modern_read_features(struct vtpci_modern_softc *);
+static void vtpci_modern_write_features(struct vtpci_modern_softc *,
+ uint64_t);
+static void vtpci_modern_select_virtqueue(struct vtpci_modern_softc *, int);
+static uint8_t vtpci_modern_get_status(struct vtpci_modern_softc *);
+static void vtpci_modern_set_status(struct vtpci_modern_softc *, uint8_t);
+static void vtpci_modern_reset(struct vtpci_modern_softc *);
+static void vtpci_modern_enable_virtqueues(struct vtpci_modern_softc *);
+
+static uint8_t vtpci_modern_read_common_1(struct vtpci_modern_softc *,
+ bus_size_t);
+static uint16_t vtpci_modern_read_common_2(struct vtpci_modern_softc *,
+ bus_size_t);
+static uint32_t vtpci_modern_read_common_4(struct vtpci_modern_softc *,
+ bus_size_t);
+static void vtpci_modern_write_common_1(struct vtpci_modern_softc *,
+ bus_size_t, uint8_t);
+static void vtpci_modern_write_common_2(struct vtpci_modern_softc *,
+ bus_size_t, uint16_t);
+static void vtpci_modern_write_common_4(struct vtpci_modern_softc *,
+ bus_size_t, uint32_t);
+static void vtpci_modern_write_common_8(struct vtpci_modern_softc *,
+ bus_size_t, uint64_t);
+static void vtpci_modern_write_notify_2(struct vtpci_modern_softc *,
+ bus_size_t, uint16_t);
+static uint8_t vtpci_modern_read_isr_1(struct vtpci_modern_softc *,
+ bus_size_t);
+static uint8_t vtpci_modern_read_device_1(struct vtpci_modern_softc *,
+ bus_size_t);
+static uint16_t vtpci_modern_read_device_2(struct vtpci_modern_softc *,
+ bus_size_t);
+static uint32_t vtpci_modern_read_device_4(struct vtpci_modern_softc *,
+ bus_size_t);
+static uint64_t vtpci_modern_read_device_8(struct vtpci_modern_softc *,
+ bus_size_t);
+static void vtpci_modern_write_device_1(struct vtpci_modern_softc *,
+ bus_size_t, uint8_t);
+static void vtpci_modern_write_device_2(struct vtpci_modern_softc *,
+ bus_size_t, uint16_t);
+static void vtpci_modern_write_device_4(struct vtpci_modern_softc *,
+ bus_size_t, uint32_t);
+static void vtpci_modern_write_device_8(struct vtpci_modern_softc *,
+ bus_size_t, uint64_t);
+
+/* Tunables. */
+static int vtpci_modern_transitional = 0;
+TUNABLE_INT("hw.virtio.pci.transitional", &vtpci_modern_transitional);
+
+static device_method_t vtpci_modern_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, vtpci_modern_probe),
+ DEVMETHOD(device_attach, vtpci_modern_attach),
+ DEVMETHOD(device_detach, vtpci_modern_detach),
+ DEVMETHOD(device_suspend, vtpci_modern_suspend),
+ DEVMETHOD(device_resume, vtpci_modern_resume),
+ DEVMETHOD(device_shutdown, vtpci_modern_shutdown),
+
+ /* Bus interface. */
+ DEVMETHOD(bus_driver_added, vtpci_modern_driver_added),
+ DEVMETHOD(bus_child_detached, vtpci_modern_child_detached),
+ DEVMETHOD(bus_child_pnpinfo_str, virtio_child_pnpinfo_str),
+ DEVMETHOD(bus_read_ivar, vtpci_modern_read_ivar),
+ DEVMETHOD(bus_write_ivar, vtpci_modern_write_ivar),
+
+ /* VirtIO PCI interface. */
+ DEVMETHOD(virtio_pci_read_isr, vtpci_modern_read_isr),
+ DEVMETHOD(virtio_pci_get_vq_size, vtpci_modern_get_vq_size),
+ DEVMETHOD(virtio_pci_get_vq_notify_off, vtpci_modern_get_vq_notify_off),
+ DEVMETHOD(virtio_pci_set_vq, vtpci_modern_set_vq),
+ DEVMETHOD(virtio_pci_disable_vq, vtpci_modern_disable_vq),
+ DEVMETHOD(virtio_pci_register_cfg_msix, vtpci_modern_register_cfg_msix),
+ DEVMETHOD(virtio_pci_register_vq_msix, vtpci_modern_register_vq_msix),
+
+ /* VirtIO bus interface. */
+ DEVMETHOD(virtio_bus_negotiate_features, vtpci_modern_negotiate_features),
+ DEVMETHOD(virtio_bus_finalize_features, vtpci_modern_finalize_features),
+ DEVMETHOD(virtio_bus_with_feature, vtpci_modern_with_feature),
+ DEVMETHOD(virtio_bus_alloc_virtqueues, vtpci_modern_alloc_virtqueues),
+ DEVMETHOD(virtio_bus_setup_intr, vtpci_modern_setup_interrupts),
+ DEVMETHOD(virtio_bus_stop, vtpci_modern_stop),
+ DEVMETHOD(virtio_bus_reinit, vtpci_modern_reinit),
+ DEVMETHOD(virtio_bus_reinit_complete, vtpci_modern_reinit_complete),
+ DEVMETHOD(virtio_bus_notify_vq, vtpci_modern_notify_vq),
+ DEVMETHOD(virtio_bus_config_generation, vtpci_modern_config_generation),
+ DEVMETHOD(virtio_bus_read_device_config, vtpci_modern_read_dev_config),
+ DEVMETHOD(virtio_bus_write_device_config, vtpci_modern_write_dev_config),
+
+ DEVMETHOD_END
+};
+
+static driver_t vtpci_modern_driver = {
+ .name = "virtio_pci",
+ .methods = vtpci_modern_methods,
+ .size = sizeof(struct vtpci_modern_softc)
+};
+
+devclass_t vtpci_modern_devclass;
+
+DRIVER_MODULE(virtio_pci_modern, pci, vtpci_modern_driver,
+ vtpci_modern_devclass, 0, 0);
+
+static int
+vtpci_modern_probe(device_t dev)
+{
+ char desc[64];
+ const char *name;
+ uint16_t devid;
+
+ if (pci_get_vendor(dev) != VIRTIO_PCI_VENDORID)
+ return (ENXIO);
+
+ if (pci_get_device(dev) < VIRTIO_PCI_DEVICEID_MIN ||
+ pci_get_device(dev) > VIRTIO_PCI_DEVICEID_MODERN_MAX)
+ return (ENXIO);
+
+ if (pci_get_device(dev) < VIRTIO_PCI_DEVICEID_MODERN_MIN) {
+ if (!vtpci_modern_transitional)
+ return (ENXIO);
+ devid = pci_get_subdevice(dev);
+ } else
+ devid = pci_get_device(dev) - VIRTIO_PCI_DEVICEID_MODERN_MIN;
+
+ if (vtpci_modern_probe_configs(dev) != 0)
+ return (ENXIO);
+
+ name = virtio_device_name(devid);
+ if (name == NULL)
+ name = "Unknown";
+
+ snprintf(desc, sizeof(desc), "VirtIO PCI (modern) %s adapter", name);
+ device_set_desc_copy(dev, desc);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+vtpci_modern_attach(device_t dev)
+{
+ struct vtpci_modern_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+ sc->vtpci_dev = dev;
+ vtpci_init(&sc->vtpci_common, dev, true);
+
+ if (pci_get_device(dev) < VIRTIO_PCI_DEVICEID_MODERN_MIN)
+ sc->vtpci_devid = pci_get_subdevice(dev);
+ else
+ sc->vtpci_devid = pci_get_device(dev) -
+ VIRTIO_PCI_DEVICEID_MODERN_MIN;
+
+ error = vtpci_modern_map_configs(sc);
+ if (error) {
+ device_printf(dev, "cannot map configs\n");
+ vtpci_modern_unmap_configs(sc);
+ return (error);
+ }
+
+ vtpci_modern_reset(sc);
+
+ /* Tell the host we've noticed this device. */
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_ACK);
+
+ error = vtpci_add_child(&sc->vtpci_common);
+ if (error)
+ goto fail;
+
+ vtpci_modern_probe_and_attach_child(sc);
+
+ return (0);
+
+fail:
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_FAILED);
+ vtpci_modern_detach(dev);
+
+ return (error);
+}
+
+static int
+vtpci_modern_detach(device_t dev)
+{
+ struct vtpci_modern_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ error = vtpci_delete_child(&sc->vtpci_common);
+ if (error)
+ return (error);
+
+ vtpci_modern_reset(sc);
+ vtpci_modern_unmap_configs(sc);
+
+ return (0);
+}
+
+static int
+vtpci_modern_suspend(device_t dev)
+{
+ return (bus_generic_suspend(dev));
+}
+
+static int
+vtpci_modern_resume(device_t dev)
+{
+ return (bus_generic_resume(dev));
+}
+
+static int
+vtpci_modern_shutdown(device_t dev)
+{
+ (void) bus_generic_shutdown(dev);
+ /* Forcibly stop the host device. */
+ vtpci_modern_stop(dev);
+
+ return (0);
+}
+
+static void
+vtpci_modern_driver_added(device_t dev, driver_t *driver)
+{
+ vtpci_modern_probe_and_attach_child(device_get_softc(dev));
+}
+
+static void
+vtpci_modern_child_detached(device_t dev, device_t child)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_modern_reset(sc);
+ vtpci_child_detached(&sc->vtpci_common);
+
+ /* After the reset, retell the host we've noticed this device. */
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_ACK);
+}
+
+static int
+vtpci_modern_read_ivar(device_t dev, device_t child, int index,
+ uintptr_t *result)
+{
+ struct vtpci_modern_softc *sc;
+ struct vtpci_common *cn;
+
+ sc = device_get_softc(dev);
+ cn = &sc->vtpci_common;
+
+ if (vtpci_child_device(cn) != child)
+ return (ENOENT);
+
+ switch (index) {
+ case VIRTIO_IVAR_DEVTYPE:
+ *result = sc->vtpci_devid;
+ break;
+ default:
+ return (vtpci_read_ivar(cn, index, result));
+ }
+
+ return (0);
+}
+
+static int
+vtpci_modern_write_ivar(device_t dev, device_t child, int index,
+ uintptr_t value)
+{
+ struct vtpci_modern_softc *sc;
+ struct vtpci_common *cn;
+
+ sc = device_get_softc(dev);
+ cn = &sc->vtpci_common;
+
+ if (vtpci_child_device(cn) != child)
+ return (ENOENT);
+
+ switch (index) {
+ default:
+ return (vtpci_write_ivar(cn, index, value));
+ }
+
+ return (0);
+}
+
+static uint64_t
+vtpci_modern_negotiate_features(device_t dev, uint64_t child_features)
+{
+ struct vtpci_modern_softc *sc;
+ uint64_t host_features, features;
+
+ sc = device_get_softc(dev);
+ host_features = vtpci_modern_read_features(sc);
+
+ /*
+ * Since the driver was added as a child of the modern PCI bus,
+ * always add the V1 flag.
+ */
+ child_features |= VIRTIO_F_VERSION_1;
+
+ features = vtpci_negotiate_features(&sc->vtpci_common,
+ child_features, host_features);
+ vtpci_modern_write_features(sc, features);
+
+ return (features);
+}
+
+static int
+vtpci_modern_finalize_features(device_t dev)
+{
+ struct vtpci_modern_softc *sc;
+ uint8_t status;
+
+ sc = device_get_softc(dev);
+
+ /*
+ * Must re-read the status after setting it to verify the negotiated
+ * features were accepted by the device.
+ *
+ * BMV: TODO Drivers need to handle possible failure of this method!
+ */
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_S_FEATURES_OK);
+
+ status = vtpci_modern_get_status(sc);
+ if ((status & VIRTIO_CONFIG_S_FEATURES_OK) == 0) {
+ device_printf(dev, "desired features were not accepted\n");
+ return (ENOTSUP);
+ }
+
+ return (0);
+}
+
+static int
+vtpci_modern_with_feature(device_t dev, uint64_t feature)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ return (vtpci_with_feature(&sc->vtpci_common, feature));
+}
+
+static uint64_t
+vtpci_modern_read_features(struct vtpci_modern_softc *sc)
+{
+ uint32_t features0, features1;
+
+ vtpci_modern_write_common_4(sc, VIRTIO_PCI_COMMON_DFSELECT, 0);
+ features0 = vtpci_modern_read_common_4(sc, VIRTIO_PCI_COMMON_DF);
+ vtpci_modern_write_common_4(sc, VIRTIO_PCI_COMMON_DFSELECT, 1);
+ features1 = vtpci_modern_read_common_4(sc, VIRTIO_PCI_COMMON_DF);
+
+ return (((uint64_t) features1 << 32) | features0);
+}
+
+static void
+vtpci_modern_write_features(struct vtpci_modern_softc *sc, uint64_t features)
+{
+ uint32_t features0, features1;
+
+ features0 = features;
+ features1 = features >> 32;
+
+ vtpci_modern_write_common_4(sc, VIRTIO_PCI_COMMON_GFSELECT, 0);
+ vtpci_modern_write_common_4(sc, VIRTIO_PCI_COMMON_GF, features0);
+ vtpci_modern_write_common_4(sc, VIRTIO_PCI_COMMON_GFSELECT, 1);
+ vtpci_modern_write_common_4(sc, VIRTIO_PCI_COMMON_GF, features1);
+}
+
+static int
+vtpci_modern_alloc_virtqueues(device_t dev, int flags, int nvqs,
+ struct vq_alloc_info *vq_info)
+{
+ struct vtpci_modern_softc *sc;
+ struct vtpci_common *cn;
+ uint16_t max_nvqs;
+
+ sc = device_get_softc(dev);
+ cn = &sc->vtpci_common;
+
+ max_nvqs = vtpci_modern_read_common_2(sc, VIRTIO_PCI_COMMON_NUMQ);
+ if (nvqs > max_nvqs) {
+ device_printf(sc->vtpci_dev, "requested virtqueue count %d "
+ "exceeds max %d\n", nvqs, max_nvqs);
+ return (E2BIG);
+ }
+
+ return (vtpci_alloc_virtqueues(cn, flags, nvqs, vq_info));
+}
+
+static int
+vtpci_modern_setup_interrupts(device_t dev, enum intr_type type)
+{
+ struct vtpci_modern_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ error = vtpci_setup_interrupts(&sc->vtpci_common, type);
+ if (error == 0)
+ vtpci_modern_enable_virtqueues(sc);
+
+ return (error);
+}
+
+static void
+vtpci_modern_stop(device_t dev)
+{
+ vtpci_modern_reset(device_get_softc(dev));
+}
+
+static int
+vtpci_modern_reinit(device_t dev, uint64_t features)
+{
+ struct vtpci_modern_softc *sc;
+ struct vtpci_common *cn;
+ int error;
+
+ sc = device_get_softc(dev);
+ cn = &sc->vtpci_common;
+
+ /*
+ * Redrive the device initialization. This is a bit of an abuse of
+ * the specification, but VirtualBox, QEMU/KVM, and BHyVe seem to
+ * play nice.
+ *
+ * We do not allow the host device to change from what was originally
+ * negotiated beyond what the guest driver changed. MSIX state should
+ * not change, number of virtqueues and their size remain the same, etc.
+ * This will need to be rethought when we want to support migration.
+ */
+
+ if (vtpci_modern_get_status(sc) != VIRTIO_CONFIG_STATUS_RESET)
+ vtpci_modern_stop(dev);
+
+ /*
+ * Quickly drive the status through ACK and DRIVER. The device does
+ * not become usable again until DRIVER_OK in reinit complete.
+ */
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_ACK);
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_DRIVER);
+
+ /*
+ * TODO: Check that features are not added as to what was
+ * originally negotiated.
+ */
+ vtpci_modern_negotiate_features(dev, features);
+ error = vtpci_modern_finalize_features(dev);
+ if (error) {
+ device_printf(dev, "cannot finalize features during reinit\n");
+ return (error);
+ }
+
+ error = vtpci_reinit(cn);
+ if (error)
+ return (error);
+
+ return (0);
+}
+
+static void
+vtpci_modern_reinit_complete(device_t dev)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_modern_enable_virtqueues(sc);
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_DRIVER_OK);
+}
+
+static void
+vtpci_modern_notify_vq(device_t dev, uint16_t queue, bus_size_t offset)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_modern_write_notify_2(sc, offset, queue);
+}
+
+static uint8_t
+vtpci_modern_get_status(struct vtpci_modern_softc *sc)
+{
+ return (vtpci_modern_read_common_1(sc, VIRTIO_PCI_COMMON_STATUS));
+}
+
+static void
+vtpci_modern_set_status(struct vtpci_modern_softc *sc, uint8_t status)
+{
+ if (status != VIRTIO_CONFIG_STATUS_RESET)
+ status |= vtpci_modern_get_status(sc);
+
+ vtpci_modern_write_common_1(sc, VIRTIO_PCI_COMMON_STATUS, status);
+}
+
+static int
+vtpci_modern_config_generation(device_t dev)
+{
+ struct vtpci_modern_softc *sc;
+ uint8_t gen;
+
+ sc = device_get_softc(dev);
+ gen = vtpci_modern_read_common_1(sc, VIRTIO_PCI_COMMON_CFGGENERATION);
+
+ return (gen);
+}
+
+static void
+vtpci_modern_read_dev_config(device_t dev, bus_size_t offset, void *dst,
+ int length)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if (sc->vtpci_device_res_map.vtrm_map.r_size == 0) {
+ panic("%s: attempt to read dev config but not present",
+ __func__);
+ }
+
+ switch (length) {
+ case 1:
+ *(uint8_t *) dst = vtpci_modern_read_device_1(sc, offset);
+ break;
+ case 2:
+ *(uint16_t *) dst = virtio_htog16(true,
+ vtpci_modern_read_device_2(sc, offset));
+ break;
+ case 4:
+ *(uint32_t *) dst = virtio_htog32(true,
+ vtpci_modern_read_device_4(sc, offset));
+ break;
+ case 8:
+ *(uint64_t *) dst = virtio_htog64(true,
+ vtpci_modern_read_device_8(sc, offset));
+ break;
+ default:
+ panic("%s: device %s invalid device read length %d offset %d",
+ __func__, device_get_nameunit(dev), length, (int) offset);
+ }
+}
+
+static void
+vtpci_modern_write_dev_config(device_t dev, bus_size_t offset, void *src,
+ int length)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if (sc->vtpci_device_res_map.vtrm_map.r_size == 0) {
+ panic("%s: attempt to write dev config but not present",
+ __func__);
+ }
+
+ switch (length) {
+ case 1:
+ vtpci_modern_write_device_1(sc, offset, *(uint8_t *) src);
+ break;
+ case 2: {
+ uint16_t val = virtio_gtoh16(true, *(uint16_t *) src);
+ vtpci_modern_write_device_2(sc, offset, val);
+ break;
+ }
+ case 4: {
+ uint32_t val = virtio_gtoh32(true, *(uint32_t *) src);
+ vtpci_modern_write_device_4(sc, offset, val);
+ break;
+ }
+ case 8: {
+ uint64_t val = virtio_gtoh64(true, *(uint64_t *) src);
+ vtpci_modern_write_device_8(sc, offset, val);
+ break;
+ }
+ default:
+ panic("%s: device %s invalid device write length %d offset %d",
+ __func__, device_get_nameunit(dev), length, (int) offset);
+ }
+}
+
+static int
+vtpci_modern_probe_configs(device_t dev)
+{
+ int error;
+
+ /*
+ * These config capabilities must be present. The DEVICE_CFG
+ * capability is only present if the device requires it.
+ */
+
+ error = vtpci_modern_find_cap(dev, VIRTIO_PCI_CAP_COMMON_CFG, NULL);
+ if (error) {
+ device_printf(dev, "cannot find COMMON_CFG capability\n");
+ return (error);
+ }
+
+ error = vtpci_modern_find_cap(dev, VIRTIO_PCI_CAP_NOTIFY_CFG, NULL);
+ if (error) {
+ device_printf(dev, "cannot find NOTIFY_CFG capability\n");
+ return (error);
+ }
+
+ error = vtpci_modern_find_cap(dev, VIRTIO_PCI_CAP_ISR_CFG, NULL);
+ if (error) {
+ device_printf(dev, "cannot find ISR_CFG capability\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+vtpci_modern_find_cap(device_t dev, uint8_t cfg_type, int *cap_offset)
+{
+ uint32_t type, bar;
+ int capreg, error;
+
+ for (error = pci_find_cap(dev, PCIY_VENDOR, &capreg);
+ error == 0;
+ error = pci_find_next_cap(dev, PCIY_VENDOR, capreg, &capreg)) {
+
+ type = pci_read_config(dev, capreg +
+ offsetof(struct virtio_pci_cap, cfg_type), 1);
+ bar = pci_read_config(dev, capreg +
+ offsetof(struct virtio_pci_cap, bar), 1);
+
+ /* Must ignore reserved BARs. */
+ if (bar >= VTPCI_MODERN_MAX_BARS)
+ continue;
+
+ if (type == cfg_type) {
+ if (cap_offset != NULL)
+ *cap_offset = capreg;
+ break;
+ }
+ }
+
+ return (error);
+}
+
+static int
+vtpci_modern_map_common_config(struct vtpci_modern_softc *sc)
+{
+ device_t dev;
+ int error;
+
+ dev = sc->vtpci_dev;
+
+ error = vtpci_modern_find_cap_resource(sc, VIRTIO_PCI_CAP_COMMON_CFG,
+ sizeof(struct virtio_pci_common_cfg), 4, &sc->vtpci_common_res_map);
+ if (error) {
+ device_printf(dev, "cannot find cap COMMON_CFG resource\n");
+ return (error);
+ }
+
+ error = vtpci_modern_alloc_resource_map(sc, &sc->vtpci_common_res_map);
+ if (error) {
+ device_printf(dev, "cannot alloc resource for COMMON_CFG\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+vtpci_modern_map_notify_config(struct vtpci_modern_softc *sc)
+{
+ device_t dev;
+ int cap_offset, error;
+
+ dev = sc->vtpci_dev;
+
+ error = vtpci_modern_find_cap_resource(sc, VIRTIO_PCI_CAP_NOTIFY_CFG,
+ -1, 2, &sc->vtpci_notify_res_map);
+ if (error) {
+ device_printf(dev, "cannot find cap NOTIFY_CFG resource\n");
+ return (error);
+ }
+
+ cap_offset = sc->vtpci_notify_res_map.vtrm_cap_offset;
+
+ sc->vtpci_notify_offset_multiplier = pci_read_config(dev, cap_offset +
+ offsetof(struct virtio_pci_notify_cap, notify_off_multiplier), 4);
+
+ error = vtpci_modern_alloc_resource_map(sc, &sc->vtpci_notify_res_map);
+ if (error) {
+ device_printf(dev, "cannot alloc resource for NOTIFY_CFG\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+vtpci_modern_map_isr_config(struct vtpci_modern_softc *sc)
+{
+ device_t dev;
+ int error;
+
+ dev = sc->vtpci_dev;
+
+ error = vtpci_modern_find_cap_resource(sc, VIRTIO_PCI_CAP_ISR_CFG,
+ sizeof(uint8_t), 1, &sc->vtpci_isr_res_map);
+ if (error) {
+ device_printf(dev, "cannot find cap ISR_CFG resource\n");
+ return (error);
+ }
+
+ error = vtpci_modern_alloc_resource_map(sc, &sc->vtpci_isr_res_map);
+ if (error) {
+ device_printf(dev, "cannot alloc resource for ISR_CFG\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+vtpci_modern_map_device_config(struct vtpci_modern_softc *sc)
+{
+ device_t dev;
+ int error;
+
+ dev = sc->vtpci_dev;
+
+ error = vtpci_modern_find_cap_resource(sc, VIRTIO_PCI_CAP_DEVICE_CFG,
+ -1, 4, &sc->vtpci_device_res_map);
+ if (error == ENOENT) {
+ /* Device configuration is optional depending on device. */
+ return (0);
+ } else if (error) {
+ device_printf(dev, "cannot find cap DEVICE_CFG resource\n");
+ return (error);
+ }
+
+ error = vtpci_modern_alloc_resource_map(sc, &sc->vtpci_device_res_map);
+ if (error) {
+ device_printf(dev, "cannot alloc resource for DEVICE_CFG\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+vtpci_modern_map_configs(struct vtpci_modern_softc *sc)
+{
+ int error;
+
+ error = vtpci_modern_map_common_config(sc);
+ if (error)
+ return (error);
+
+ error = vtpci_modern_map_notify_config(sc);
+ if (error)
+ return (error);
+
+ error = vtpci_modern_map_isr_config(sc);
+ if (error)
+ return (error);
+
+ error = vtpci_modern_map_device_config(sc);
+ if (error)
+ return (error);
+
+ vtpci_modern_alloc_msix_resource(sc);
+
+ return (0);
+}
+
+static void
+vtpci_modern_unmap_configs(struct vtpci_modern_softc *sc)
+{
+
+ vtpci_modern_free_resource_map(sc, &sc->vtpci_common_res_map);
+ vtpci_modern_free_resource_map(sc, &sc->vtpci_notify_res_map);
+ vtpci_modern_free_resource_map(sc, &sc->vtpci_isr_res_map);
+ vtpci_modern_free_resource_map(sc, &sc->vtpci_device_res_map);
+
+ vtpci_modern_free_bar_resources(sc);
+ vtpci_modern_free_msix_resource(sc);
+
+ sc->vtpci_notify_offset_multiplier = 0;
+}
+
+static int
+vtpci_modern_find_cap_resource(struct vtpci_modern_softc *sc, uint8_t cfg_type,
+ int min_size, int alignment, struct vtpci_modern_resource_map *res)
+{
+ device_t dev;
+ int cap_offset, offset, length, error;
+ uint8_t bar, cap_length;
+
+ dev = sc->vtpci_dev;
+
+ error = vtpci_modern_find_cap(dev, cfg_type, &cap_offset);
+ if (error)
+ return (error);
+
+ cap_length = pci_read_config(dev,
+ cap_offset + offsetof(struct virtio_pci_cap, cap_len), 1);
+
+ if (cap_length < sizeof(struct virtio_pci_cap)) {
+ device_printf(dev, "cap %u length %d less than expected\n",
+ cfg_type, cap_length);
+ return (ENXIO);
+ }
+
+ bar = pci_read_config(dev,
+ cap_offset + offsetof(struct virtio_pci_cap, bar), 1);
+ offset = pci_read_config(dev,
+ cap_offset + offsetof(struct virtio_pci_cap, offset), 4);
+ length = pci_read_config(dev,
+ cap_offset + offsetof(struct virtio_pci_cap, length), 4);
+
+ if (min_size != -1 && length < min_size) {
+ device_printf(dev, "cap %u struct length %d less than min %d\n",
+ cfg_type, length, min_size);
+ return (ENXIO);
+ }
+
+ if (offset % alignment) {
+ device_printf(dev, "cap %u struct offset %d not aligned to %d\n",
+ cfg_type, offset, alignment);
+ return (ENXIO);
+ }
+
+ /* BMV: TODO Can we determine the size of the BAR here? */
+
+ res->vtrm_cap_offset = cap_offset;
+ res->vtrm_bar = bar;
+ res->vtrm_offset = offset;
+ res->vtrm_length = length;
+ res->vtrm_type = vtpci_modern_bar_type(sc, bar);
+
+ return (0);
+}
+
+static int
+vtpci_modern_bar_type(struct vtpci_modern_softc *sc, int bar)
+{
+ uint32_t val;
+
+ /*
+ * The BAR described by a config capability may be either an IOPORT or
+ * MEM, but we must know the type when calling bus_alloc_resource().
+ */
+ val = pci_read_config(sc->vtpci_dev, PCIR_BAR(bar), 4);
+ if (PCI_BAR_IO(val))
+ return (SYS_RES_IOPORT);
+ else
+ return (SYS_RES_MEMORY);
+}
+
+static struct resource *
+vtpci_modern_get_bar_resource(struct vtpci_modern_softc *sc, int bar, int type)
+{
+ struct resource *res;
+
+ MPASS(bar >= 0 && bar < VTPCI_MODERN_MAX_BARS);
+ res = sc->vtpci_bar_res[bar].vtbr_res;
+ MPASS(res == NULL || sc->vtpci_bar_res[bar].vtbr_type == type);
+
+ return (res);
+}
+
+static struct resource *
+vtpci_modern_alloc_bar_resource(struct vtpci_modern_softc *sc, int bar,
+ int type)
+{
+ struct resource *res;
+ int rid;
+
+ MPASS(bar >= 0 && bar < VTPCI_MODERN_MAX_BARS);
+ MPASS(type == SYS_RES_MEMORY || type == SYS_RES_IOPORT);
+
+ res = sc->vtpci_bar_res[bar].vtbr_res;
+ if (res != NULL) {
+ MPASS(sc->vtpci_bar_res[bar].vtbr_type == type);
+ return (res);
+ }
+
+ rid = PCIR_BAR(bar);
+ res = bus_alloc_resource_any(sc->vtpci_dev, type, &rid,
+ RF_ACTIVE | RF_UNMAPPED);
+ if (res != NULL) {
+ sc->vtpci_bar_res[bar].vtbr_res = res;
+ sc->vtpci_bar_res[bar].vtbr_type = type;
+ }
+
+ return (res);
+}
+
+static void
+vtpci_modern_free_bar_resources(struct vtpci_modern_softc *sc)
+{
+ device_t dev;
+ struct resource *res;
+ int bar, rid, type;
+
+ dev = sc->vtpci_dev;
+
+ for (bar = 0; bar < VTPCI_MODERN_MAX_BARS; bar++) {
+ res = sc->vtpci_bar_res[bar].vtbr_res;
+ type = sc->vtpci_bar_res[bar].vtbr_type;
+
+ if (res != NULL) {
+ rid = PCIR_BAR(bar);
+ bus_release_resource(dev, type, rid, res);
+ sc->vtpci_bar_res[bar].vtbr_res = NULL;
+ sc->vtpci_bar_res[bar].vtbr_type = 0;
+ }
+ }
+}
+
+static int
+vtpci_modern_alloc_resource_map(struct vtpci_modern_softc *sc,
+ struct vtpci_modern_resource_map *map)
+{
+ struct resource_map_request req;
+ struct resource *res;
+ int type;
+
+ type = map->vtrm_type;
+
+ res = vtpci_modern_alloc_bar_resource(sc, map->vtrm_bar, type);
+ if (res == NULL)
+ return (ENXIO);
+
+ resource_init_map_request(&req);
+ req.offset = map->vtrm_offset;
+ req.length = map->vtrm_length;
+
+ return (bus_map_resource(sc->vtpci_dev, type, res, &req,
+ &map->vtrm_map));
+}
+
+static void
+vtpci_modern_free_resource_map(struct vtpci_modern_softc *sc,
+ struct vtpci_modern_resource_map *map)
+{
+ struct resource *res;
+ int type;
+
+ type = map->vtrm_type;
+ res = vtpci_modern_get_bar_resource(sc, map->vtrm_bar, type);
+
+ if (res != NULL && map->vtrm_map.r_size != 0) {
+ bus_unmap_resource(sc->vtpci_dev, type, res, &map->vtrm_map);
+ bzero(map, sizeof(struct vtpci_modern_resource_map));
+ }
+}
+
+static void
+vtpci_modern_alloc_msix_resource(struct vtpci_modern_softc *sc)
+{
+ device_t dev;
+ int bar;
+
+ dev = sc->vtpci_dev;
+
+ if (!vtpci_is_msix_available(&sc->vtpci_common) ||
+ (bar = pci_msix_table_bar(dev)) == -1)
+ return;
+
+ /* TODO: Can this BAR be in the 0-5 range? */
+ sc->vtpci_msix_bar = bar;
+ if ((sc->vtpci_msix_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+ &bar, RF_ACTIVE)) == NULL)
+ device_printf(dev, "Unable to map MSIX table\n");
+}
+
+static void
+vtpci_modern_free_msix_resource(struct vtpci_modern_softc *sc)
+{
+ device_t dev;
+
+ dev = sc->vtpci_dev;
+
+ if (sc->vtpci_msix_res != NULL) {
+ bus_release_resource(dev, SYS_RES_MEMORY, sc->vtpci_msix_bar,
+ sc->vtpci_msix_res);
+ sc->vtpci_msix_bar = 0;
+ sc->vtpci_msix_res = NULL;
+ }
+}
+
+static void
+vtpci_modern_probe_and_attach_child(struct vtpci_modern_softc *sc)
+{
+ device_t dev, child;
+
+ dev = sc->vtpci_dev;
+ child = vtpci_child_device(&sc->vtpci_common);
+
+ if (child == NULL || device_get_state(child) != DS_NOTPRESENT)
+ return;
+
+ if (device_probe(child) != 0)
+ return;
+
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_DRIVER);
+
+ if (device_attach(child) != 0) {
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_FAILED);
+ /* Reset state for later attempt. */
+ vtpci_modern_child_detached(dev, child);
+ } else {
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_DRIVER_OK);
+ VIRTIO_ATTACH_COMPLETED(child);
+ }
+}
+
+static int
+vtpci_modern_register_msix(struct vtpci_modern_softc *sc, int offset,
+ struct vtpci_interrupt *intr)
+{
+ uint16_t vector;
+
+ if (intr != NULL) {
+ /* Map from guest rid to host vector. */
+ vector = intr->vti_rid - 1;
+ } else
+ vector = VIRTIO_MSI_NO_VECTOR;
+
+ vtpci_modern_write_common_2(sc, offset, vector);
+ return (vtpci_modern_read_common_2(sc, offset) == vector ? 0 : ENODEV);
+}
+
+static int
+vtpci_modern_register_cfg_msix(device_t dev, struct vtpci_interrupt *intr)
+{
+ struct vtpci_modern_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ error = vtpci_modern_register_msix(sc, VIRTIO_PCI_COMMON_MSIX, intr);
+ if (error) {
+ device_printf(dev,
+ "unable to register config MSIX interrupt\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+vtpci_modern_register_vq_msix(device_t dev, int idx,
+ struct vtpci_interrupt *intr)
+{
+ struct vtpci_modern_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ vtpci_modern_select_virtqueue(sc, idx);
+ error = vtpci_modern_register_msix(sc, VIRTIO_PCI_COMMON_Q_MSIX, intr);
+ if (error) {
+ device_printf(dev,
+ "unable to register virtqueue MSIX interrupt\n");
+ return (error);
+ }
+
+ return (0);
+}
+
+static void
+vtpci_modern_reset(struct vtpci_modern_softc *sc)
+{
+ /*
+ * Setting the status to RESET sets the host device to the
+ * original, uninitialized state. Must poll the status until
+ * the reset is complete.
+ */
+ vtpci_modern_set_status(sc, VIRTIO_CONFIG_STATUS_RESET);
+
+ while (vtpci_modern_get_status(sc) != VIRTIO_CONFIG_STATUS_RESET)
+ cpu_spinwait();
+}
+
+static void
+vtpci_modern_select_virtqueue(struct vtpci_modern_softc *sc, int idx)
+{
+ vtpci_modern_write_common_2(sc, VIRTIO_PCI_COMMON_Q_SELECT, idx);
+}
+
+static uint8_t
+vtpci_modern_read_isr(device_t dev)
+{
+ return (vtpci_modern_read_isr_1(device_get_softc(dev), 0));
+}
+
+static uint16_t
+vtpci_modern_get_vq_size(device_t dev, int idx)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_modern_select_virtqueue(sc, idx);
+ return (vtpci_modern_read_common_2(sc, VIRTIO_PCI_COMMON_Q_SIZE));
+}
+
+static bus_size_t
+vtpci_modern_get_vq_notify_off(device_t dev, int idx)
+{
+ struct vtpci_modern_softc *sc;
+ uint16_t q_notify_off;
+
+ sc = device_get_softc(dev);
+
+ vtpci_modern_select_virtqueue(sc, idx);
+ q_notify_off = vtpci_modern_read_common_2(sc, VIRTIO_PCI_COMMON_Q_NOFF);
+
+ return (q_notify_off * sc->vtpci_notify_offset_multiplier);
+}
+
+static void
+vtpci_modern_set_vq(device_t dev, struct virtqueue *vq)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_modern_select_virtqueue(sc, virtqueue_index(vq));
+
+ /* BMV: Currently we never adjust the device's proposed VQ size. */
+ vtpci_modern_write_common_2(sc,
+ VIRTIO_PCI_COMMON_Q_SIZE, virtqueue_size(vq));
+
+ vtpci_modern_write_common_8(sc,
+ VIRTIO_PCI_COMMON_Q_DESCLO, virtqueue_desc_paddr(vq));
+ vtpci_modern_write_common_8(sc,
+ VIRTIO_PCI_COMMON_Q_AVAILLO, virtqueue_avail_paddr(vq));
+ vtpci_modern_write_common_8(sc,
+ VIRTIO_PCI_COMMON_Q_USEDLO, virtqueue_used_paddr(vq));
+}
+
+static void
+vtpci_modern_disable_vq(device_t dev, int idx)
+{
+ struct vtpci_modern_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vtpci_modern_select_virtqueue(sc, idx);
+ vtpci_modern_write_common_8(sc, VIRTIO_PCI_COMMON_Q_DESCLO, 0ULL);
+ vtpci_modern_write_common_8(sc, VIRTIO_PCI_COMMON_Q_AVAILLO, 0ULL);
+ vtpci_modern_write_common_8(sc, VIRTIO_PCI_COMMON_Q_USEDLO, 0ULL);
+}
+
+static void
+vtpci_modern_enable_virtqueues(struct vtpci_modern_softc *sc)
+{
+ int idx;
+
+ for (idx = 0; idx < sc->vtpci_common.vtpci_nvqs; idx++) {
+ vtpci_modern_select_virtqueue(sc, idx);
+ vtpci_modern_write_common_2(sc, VIRTIO_PCI_COMMON_Q_ENABLE, 1);
+ }
+}
+
+static uint8_t
+vtpci_modern_read_common_1(struct vtpci_modern_softc *sc, bus_size_t off)
+{
+ return (bus_read_1(&sc->vtpci_common_res_map.vtrm_map, off));
+}
+
+static uint16_t
+vtpci_modern_read_common_2(struct vtpci_modern_softc *sc, bus_size_t off)
+{
+ return (bus_read_2(&sc->vtpci_common_res_map.vtrm_map, off));
+}
+
+static uint32_t
+vtpci_modern_read_common_4(struct vtpci_modern_softc *sc, bus_size_t off)
+{
+ return (bus_read_4(&sc->vtpci_common_res_map.vtrm_map, off));
+}
+
+static void
+vtpci_modern_write_common_1(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint8_t val)
+{
+ bus_write_1(&sc->vtpci_common_res_map.vtrm_map, off, val);
+}
+
+static void
+vtpci_modern_write_common_2(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint16_t val)
+{
+ bus_write_2(&sc->vtpci_common_res_map.vtrm_map, off, val);
+}
+
+static void
+vtpci_modern_write_common_4(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint32_t val)
+{
+ bus_write_4(&sc->vtpci_common_res_map.vtrm_map, off, val);
+}
+
+static void
+vtpci_modern_write_common_8(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint64_t val)
+{
+ uint32_t val0, val1;
+
+ val0 = (uint32_t) val;
+ val1 = val >> 32;
+
+ vtpci_modern_write_common_4(sc, off, val0);
+ vtpci_modern_write_common_4(sc, off + 4, val1);
+}
+
+static void
+vtpci_modern_write_notify_2(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint16_t val)
+{
+ bus_write_2(&sc->vtpci_notify_res_map.vtrm_map, off, val);
+}
+
+static uint8_t
+vtpci_modern_read_isr_1(struct vtpci_modern_softc *sc, bus_size_t off)
+{
+ return (bus_read_1(&sc->vtpci_isr_res_map.vtrm_map, off));
+}
+
+static uint8_t
+vtpci_modern_read_device_1(struct vtpci_modern_softc *sc, bus_size_t off)
+{
+ return (bus_read_1(&sc->vtpci_device_res_map.vtrm_map, off));
+}
+
+static uint16_t
+vtpci_modern_read_device_2(struct vtpci_modern_softc *sc, bus_size_t off)
+{
+ return (bus_read_2(&sc->vtpci_device_res_map.vtrm_map, off));
+}
+
+static uint32_t
+vtpci_modern_read_device_4(struct vtpci_modern_softc *sc, bus_size_t off)
+{
+ return (bus_read_4(&sc->vtpci_device_res_map.vtrm_map, off));
+}
+
+static uint64_t
+vtpci_modern_read_device_8(struct vtpci_modern_softc *sc, bus_size_t off)
+{
+ device_t dev;
+ int gen;
+ uint32_t val0, val1;
+
+ dev = sc->vtpci_dev;
+
+ /*
+ * Treat the 64-bit field as two 32-bit fields. Use the generation
+ * to ensure a consistent read.
+ */
+ do {
+ gen = vtpci_modern_config_generation(dev);
+ val0 = vtpci_modern_read_device_4(sc, off);
+ val1 = vtpci_modern_read_device_4(sc, off + 4);
+ } while (gen != vtpci_modern_config_generation(dev));
+
+ return (((uint64_t) val1 << 32) | val0);
+}
+
+static void
+vtpci_modern_write_device_1(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint8_t val)
+{
+ bus_write_1(&sc->vtpci_device_res_map.vtrm_map, off, val);
+}
+
+static void
+vtpci_modern_write_device_2(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint16_t val)
+{
+ bus_write_2(&sc->vtpci_device_res_map.vtrm_map, off, val);
+}
+
+static void
+vtpci_modern_write_device_4(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint32_t val)
+{
+ bus_write_4(&sc->vtpci_device_res_map.vtrm_map, off, val);
+}
+
+static void
+vtpci_modern_write_device_8(struct vtpci_modern_softc *sc, bus_size_t off,
+ uint64_t val)
+{
+ uint32_t val0, val1;
+
+ val0 = (uint32_t) val;
+ val1 = val >> 32;
+
+ vtpci_modern_write_device_4(sc, off, val0);
+ vtpci_modern_write_device_4(sc, off + 4, val1);
+}
diff --git a/sys/dev/virtio/pci/virtio_pci_modern_var.h b/sys/dev/virtio/pci/virtio_pci_modern_var.h
new file mode 100644
index 000000000000..86d24a3cf54b
--- /dev/null
+++ b/sys/dev/virtio/pci/virtio_pci_modern_var.h
@@ -0,0 +1,135 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright IBM Corp. 2007
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of IBM 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 IBM OR CONTRIBUTORS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _VIRTIO_PCI_MODERN_VAR_H
+#define _VIRTIO_PCI_MODERN_VAR_H
+
+#include <dev/virtio/pci/virtio_pci_var.h>
+
+/* IDs for different capabilities. Must all exist. */
+/* Common configuration */
+#define VIRTIO_PCI_CAP_COMMON_CFG 1
+/* Notifications */
+#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
+/* ISR access */
+#define VIRTIO_PCI_CAP_ISR_CFG 3
+/* Device specific configuration */
+#define VIRTIO_PCI_CAP_DEVICE_CFG 4
+/* PCI configuration access */
+#define VIRTIO_PCI_CAP_PCI_CFG 5
+
+/* This is the PCI capability header: */
+struct virtio_pci_cap {
+ uint8_t cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */
+ uint8_t cap_next; /* Generic PCI field: next ptr. */
+ uint8_t cap_len; /* Generic PCI field: capability length */
+ uint8_t cfg_type; /* Identifies the structure. */
+ uint8_t bar; /* Where to find it. */
+ uint8_t padding[3]; /* Pad to full dword. */
+ uint32_t offset; /* Offset within bar. */
+ uint32_t length; /* Length of the structure, in bytes. */
+};
+
+struct virtio_pci_notify_cap {
+ struct virtio_pci_cap cap;
+ uint32_t notify_off_multiplier; /* Multiplier for queue_notify_off. */
+};
+
+/* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
+struct virtio_pci_common_cfg {
+ /* About the whole device. */
+ uint32_t device_feature_select; /* read-write */
+ uint32_t device_feature; /* read-only */
+ uint32_t guest_feature_select; /* read-write */
+ uint32_t guest_feature; /* read-write */
+ uint16_t msix_config; /* read-write */
+ uint16_t num_queues; /* read-only */
+ uint8_t device_status; /* read-write */
+ uint8_t config_generation; /* read-only */
+
+ /* About a specific virtqueue. */
+ uint16_t queue_select; /* read-write */
+ uint16_t queue_size; /* read-write, power of 2. */
+ uint16_t queue_msix_vector; /* read-write */
+ uint16_t queue_enable; /* read-write */
+ uint16_t queue_notify_off; /* read-only */
+ uint32_t queue_desc_lo; /* read-write */
+ uint32_t queue_desc_hi; /* read-write */
+ uint32_t queue_avail_lo; /* read-write */
+ uint32_t queue_avail_hi; /* read-write */
+ uint32_t queue_used_lo; /* read-write */
+ uint32_t queue_used_hi; /* read-write */
+};
+
+/* Fields in VIRTIO_PCI_CAP_PCI_CFG: */
+struct virtio_pci_cfg_cap {
+ struct virtio_pci_cap cap;
+ uint8_t pci_cfg_data[4]; /* Data for BAR access. */
+};
+
+/* Macro versions of offsets for the Old Timers! */
+#define VIRTIO_PCI_CAP_VNDR 0
+#define VIRTIO_PCI_CAP_NEXT 1
+#define VIRTIO_PCI_CAP_LEN 2
+#define VIRTIO_PCI_CAP_CFG_TYPE 3
+#define VIRTIO_PCI_CAP_BAR 4
+#define VIRTIO_PCI_CAP_OFFSET 8
+#define VIRTIO_PCI_CAP_LENGTH 12
+
+#define VIRTIO_PCI_NOTIFY_CAP_MULT 16
+
+#define VIRTIO_PCI_COMMON_DFSELECT 0
+#define VIRTIO_PCI_COMMON_DF 4
+#define VIRTIO_PCI_COMMON_GFSELECT 8
+#define VIRTIO_PCI_COMMON_GF 12
+#define VIRTIO_PCI_COMMON_MSIX 16
+#define VIRTIO_PCI_COMMON_NUMQ 18
+#define VIRTIO_PCI_COMMON_STATUS 20
+#define VIRTIO_PCI_COMMON_CFGGENERATION 21
+#define VIRTIO_PCI_COMMON_Q_SELECT 22
+#define VIRTIO_PCI_COMMON_Q_SIZE 24
+#define VIRTIO_PCI_COMMON_Q_MSIX 26
+#define VIRTIO_PCI_COMMON_Q_ENABLE 28
+#define VIRTIO_PCI_COMMON_Q_NOFF 30
+#define VIRTIO_PCI_COMMON_Q_DESCLO 32
+#define VIRTIO_PCI_COMMON_Q_DESCHI 36
+#define VIRTIO_PCI_COMMON_Q_AVAILLO 40
+#define VIRTIO_PCI_COMMON_Q_AVAILHI 44
+#define VIRTIO_PCI_COMMON_Q_USEDLO 48
+#define VIRTIO_PCI_COMMON_Q_USEDHI 52
+
+#endif /* _VIRTIO_PCI_MODERN_VAR_H */
diff --git a/sys/dev/virtio/pci/virtio_pci_var.h b/sys/dev/virtio/pci/virtio_pci_var.h
new file mode 100644
index 000000000000..bd0345a4c62a
--- /dev/null
+++ b/sys/dev/virtio/pci/virtio_pci_var.h
@@ -0,0 +1,55 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright IBM Corp. 2007
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of IBM 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 IBM OR CONTRIBUTORS 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _VIRTIO_PCI_VAR_H
+#define _VIRTIO_PCI_VAR_H
+
+/* VirtIO PCI vendor/device ID. */
+#define VIRTIO_PCI_VENDORID 0x1AF4
+#define VIRTIO_PCI_DEVICEID_MIN 0x1000
+#define VIRTIO_PCI_DEVICEID_LEGACY_MAX 0x103F
+#define VIRTIO_PCI_DEVICEID_MODERN_MIN 0x1040
+#define VIRTIO_PCI_DEVICEID_MODERN_MAX 0x107F
+
+/* The bit of the ISR which indicates a device has an interrupt. */
+#define VIRTIO_PCI_ISR_INTR 0x1
+/* The bit of the ISR which indicates a device configuration change. */
+#define VIRTIO_PCI_ISR_CONFIG 0x2
+/* Vector value used to disable MSI for queue. */
+#define VIRTIO_MSI_NO_VECTOR 0xFFFF
+
+#endif /* _VIRTIO_PCI_VAR_H */
diff --git a/sys/dev/virtio/virtio.c b/sys/dev/virtio/virtio.c
index b6537e9305ea..18eace65a12b 100644
--- a/sys/dev/virtio/virtio.c
+++ b/sys/dev/virtio/virtio.c
@@ -60,24 +60,28 @@ static struct virtio_ident {
{ VIRTIO_ID_ENTROPY, "Entropy" },
{ VIRTIO_ID_BALLOON, "Balloon" },
{ VIRTIO_ID_IOMEMORY, "IOMemory" },
- { VIRTIO_ID_RPMSG, "Remote Processor Messaging" },
+ { VIRTIO_ID_RPMSG, "Remote Processor Messaging" },
{ VIRTIO_ID_SCSI, "SCSI" },
{ VIRTIO_ID_9P, "9P Transport" },
{ VIRTIO_ID_RPROC_SERIAL, "Remote Processor Serial" },
{ VIRTIO_ID_CAIF, "CAIF" },
{ VIRTIO_ID_GPU, "GPU" },
- { VIRTIO_ID_INPUT, "Input" },
- { VIRTIO_ID_VSOCK, "VSOCK Transport" },
- { VIRTIO_ID_CRYPTO, "Crypto" },
+ { VIRTIO_ID_INPUT, "Input" },
+ { VIRTIO_ID_VSOCK, "VSOCK Transport" },
+ { VIRTIO_ID_CRYPTO, "Crypto" },
+
{ 0, NULL }
};
/* Device independent features. */
static struct virtio_feature_desc virtio_common_feature_desc[] = {
- { VIRTIO_F_NOTIFY_ON_EMPTY, "NotifyOnEmpty" },
- { VIRTIO_RING_F_INDIRECT_DESC, "RingIndirect" },
- { VIRTIO_RING_F_EVENT_IDX, "EventIdx" },
- { VIRTIO_F_BAD_FEATURE, "BadFeature" },
+ { VIRTIO_F_NOTIFY_ON_EMPTY, "NotifyOnEmpty" }, /* Legacy */
+ { VIRTIO_F_ANY_LAYOUT, "AnyLayout" }, /* Legacy */
+ { VIRTIO_RING_F_INDIRECT_DESC, "RingIndirectDesc" },
+ { VIRTIO_RING_F_EVENT_IDX, "RingEventIdx" },
+ { VIRTIO_F_BAD_FEATURE, "BadFeature" }, /* Legacy */
+ { VIRTIO_F_VERSION_1, "Version1" },
+ { VIRTIO_F_IOMMU_PLATFORM, "IOMMUPlatform" },
{ 0, NULL }
};
@@ -125,12 +129,13 @@ virtio_describe(device_t dev, const char *msg,
const char *name;
int n;
- if ((buf = malloc(512, M_TEMP, M_NOWAIT)) == NULL) {
- device_printf(dev, "%s features: %#jx\n", msg, (uintmax_t) features);
+ if ((buf = malloc(1024, M_TEMP, M_NOWAIT)) == NULL) {
+ device_printf(dev, "%s features: %#jx\n",
+ msg, (uintmax_t) features);
return;
}
- sbuf_new(&sb, buf, 512, SBUF_FIXEDLEN);
+ sbuf_new(&sb, buf, 1024, SBUF_FIXEDLEN);
sbuf_printf(&sb, "%s features: %#jx", msg, (uintmax_t) features);
for (n = 0, val = 1ULL << 63; val != 0; val >>= 1) {
@@ -163,6 +168,48 @@ virtio_describe(device_t dev, const char *msg,
free(buf, M_TEMP);
}
+uint64_t
+virtio_filter_transport_features(uint64_t features)
+{
+ uint64_t transport, mask;
+
+ transport = (1ULL <<
+ (VIRTIO_TRANSPORT_F_END - VIRTIO_TRANSPORT_F_START)) - 1;
+ transport <<= VIRTIO_TRANSPORT_F_START;
+
+ mask = -1ULL & ~transport;
+ mask |= VIRTIO_RING_F_INDIRECT_DESC;
+ mask |= VIRTIO_RING_F_EVENT_IDX;
+ mask |= VIRTIO_F_VERSION_1;
+
+ return (features & mask);
+}
+
+int
+virtio_bus_is_modern(device_t dev)
+{
+ uintptr_t modern;
+
+ virtio_read_ivar(dev, VIRTIO_IVAR_MODERN, &modern);
+ return (modern != 0);
+}
+
+void
+virtio_read_device_config_array(device_t dev, bus_size_t offset, void *dst,
+ int size, int count)
+{
+ int i, gen;
+
+ do {
+ gen = virtio_config_generation(dev);
+
+ for (i = 0; i < count; i++) {
+ virtio_read_device_config(dev, offset + i * size,
+ (uint8_t *) dst + i * size, size);
+ }
+ } while (gen != virtio_config_generation(dev));
+}
+
/*
* VirtIO bus method wrappers.
*/
@@ -191,6 +238,13 @@ virtio_negotiate_features(device_t dev, uint64_t child_features)
}
int
+virtio_finalize_features(device_t dev)
+{
+
+ return (VIRTIO_BUS_FINALIZE_FEATURES(device_get_parent(dev)));
+}
+
+int
virtio_alloc_virtqueues(device_t dev, int flags, int nvqs,
struct vq_alloc_info *info)
{
diff --git a/sys/dev/virtio/virtio.h b/sys/dev/virtio/virtio.h
index 6628b3765bcd..85160eb18d0d 100644
--- a/sys/dev/virtio/virtio.h
+++ b/sys/dev/virtio/virtio.h
@@ -31,6 +31,7 @@
#ifndef _VIRTIO_H_
#define _VIRTIO_H_
+#include <dev/virtio/virtio_endian.h>
#include <dev/virtio/virtio_ids.h>
#include <dev/virtio/virtio_config.h>
@@ -57,6 +58,7 @@ struct vq_alloc_info;
#define VIRTIO_IVAR_DEVICE 4
#define VIRTIO_IVAR_SUBVENDOR 5
#define VIRTIO_IVAR_SUBDEVICE 6
+#define VIRTIO_IVAR_MODERN 7
struct virtio_feature_desc {
uint64_t vfd_val;
@@ -81,6 +83,10 @@ struct virtio_pnp_match {
const char *virtio_device_name(uint16_t devid);
void virtio_describe(device_t dev, const char *msg,
uint64_t features, struct virtio_feature_desc *feature_desc);
+uint64_t virtio_filter_transport_features(uint64_t features);
+int virtio_bus_is_modern(device_t dev);
+void virtio_read_device_config_array(device_t dev, bus_size_t offset,
+ void *dst, int size, int count);
/*
* VirtIO Bus Methods.
@@ -88,6 +94,7 @@ void virtio_describe(device_t dev, const char *msg,
void virtio_read_ivar(device_t dev, int ivar, uintptr_t *val);
void virtio_write_ivar(device_t dev, int ivar, uintptr_t val);
uint64_t virtio_negotiate_features(device_t dev, uint64_t child_features);
+int virtio_finalize_features(device_t dev);
int virtio_alloc_virtqueues(device_t dev, int flags, int nvqs,
struct vq_alloc_info *info);
int virtio_setup_intr(device_t dev, enum intr_type type);
@@ -147,6 +154,7 @@ VIRTIO_READ_IVAR(vendor, VIRTIO_IVAR_VENDOR);
VIRTIO_READ_IVAR(device, VIRTIO_IVAR_DEVICE);
VIRTIO_READ_IVAR(subvendor, VIRTIO_IVAR_SUBVENDOR);
VIRTIO_READ_IVAR(subdevice, VIRTIO_IVAR_SUBDEVICE);
+VIRTIO_READ_IVAR(modern, VIRTIO_IVAR_MODERN);
#undef VIRTIO_READ_IVAR
diff --git a/sys/dev/virtio/virtio_bus_if.m b/sys/dev/virtio/virtio_bus_if.m
index ec280e2d80be..2c3424204842 100644
--- a/sys/dev/virtio/virtio_bus_if.m
+++ b/sys/dev/virtio/virtio_bus_if.m
@@ -36,6 +36,12 @@ struct vq_alloc_info;
CODE {
static int
+ virtio_bus_default_finalize_features(device_t dev)
+ {
+ return (0);
+ }
+
+ static int
virtio_bus_default_config_generation(device_t dev)
{
return (0);
@@ -47,6 +53,10 @@ METHOD uint64_t negotiate_features {
uint64_t child_features;
};
+METHOD int finalize_features {
+ device_t dev;
+} DEFAULT virtio_bus_default_finalize_features;
+
METHOD int with_feature {
device_t dev;
uint64_t feature;
@@ -80,6 +90,7 @@ METHOD void reinit_complete {
METHOD void notify_vq {
device_t dev;
uint16_t queue;
+ bus_size_t offset;
};
METHOD int config_generation {
diff --git a/sys/dev/virtio/virtio_endian.h b/sys/dev/virtio/virtio_endian.h
new file mode 100644
index 000000000000..d0de299c7227
--- /dev/null
+++ b/sys/dev/virtio/virtio_endian.h
@@ -0,0 +1,106 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2017, Bryan Venteicher <bryanv@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice unmodified, this list of conditions, and the following
+ * disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _VIRTIO_ENDIAN_H_
+#define _VIRTIO_ENDIAN_H_
+
+#include <sys/endian.h>
+
+/*
+ * VirtIO V1 (modern) uses little endian, while legacy VirtIO uses the guest's
+ * native endian. These functions convert to and from the Guest's (driver's)
+ * and the Host's (device's) endianness when needed.
+ */
+
+static inline bool
+virtio_swap_endian(bool modern)
+{
+#if _BYTE_ORDER == _LITTLE_ENDIAN
+ return (false);
+#else
+ return (modern);
+#endif
+}
+
+static inline uint16_t
+virtio_htog16(bool modern, uint16_t val)
+{
+ if (virtio_swap_endian(modern))
+ return (le16toh(val));
+ else
+ return (val);
+}
+
+static inline uint16_t
+virtio_gtoh16(bool modern, uint16_t val)
+{
+ if (virtio_swap_endian(modern))
+ return (htole16(val));
+ else
+ return (val);
+}
+
+static inline uint32_t
+virtio_htog32(bool modern, uint32_t val)
+{
+ if (virtio_swap_endian(modern))
+ return (le32toh(val));
+ else
+ return (val);
+}
+
+static inline uint32_t
+virtio_gtoh32(bool modern, uint32_t val)
+{
+ if (virtio_swap_endian(modern))
+ return (htole32(val));
+ else
+ return (val);
+}
+
+static inline uint64_t
+virtio_htog64(bool modern, uint64_t val)
+{
+ if (virtio_swap_endian(modern))
+ return (le64toh(val));
+ else
+ return (val);
+}
+
+static inline uint64_t
+virtio_gtoh64(bool modern, uint64_t val)
+{
+ if (virtio_swap_endian(modern))
+ return (htole64(val));
+ else
+ return (val);
+}
+
+#endif /* _VIRTIO_ENDIAN_H_ */
diff --git a/sys/dev/virtio/virtqueue.c b/sys/dev/virtio/virtqueue.c
index e23d4d25c47f..da6e7bf89fd5 100644
--- a/sys/dev/virtio/virtqueue.c
+++ b/sys/dev/virtio/virtqueue.c
@@ -64,6 +64,7 @@ struct virtqueue {
#define VIRTQUEUE_FLAG_INDIRECT 0x0001
#define VIRTQUEUE_FLAG_EVENT_IDX 0x0002
+ bus_size_t vq_notify_offset;
int vq_alignment;
int vq_ring_size;
void *vq_ring_mem;
@@ -147,8 +148,9 @@ virtqueue_filter_features(uint64_t features)
}
int
-virtqueue_alloc(device_t dev, uint16_t queue, uint16_t size, int align,
- vm_paddr_t highaddr, struct vq_alloc_info *info, struct virtqueue **vqp)
+virtqueue_alloc(device_t dev, uint16_t queue, uint16_t size,
+ bus_size_t notify_offset, int align, vm_paddr_t highaddr,
+ struct vq_alloc_info *info, struct virtqueue **vqp)
{
struct virtqueue *vq;
int error;
@@ -184,6 +186,7 @@ virtqueue_alloc(device_t dev, uint16_t queue, uint16_t size, int align,
vq->vq_dev = dev;
strlcpy(vq->vq_name, info->vqai_name, sizeof(vq->vq_name));
vq->vq_queue_index = queue;
+ vq->vq_notify_offset = notify_offset;
vq->vq_alignment = align;
vq->vq_nentries = size;
vq->vq_free_cnt = size;
@@ -820,7 +823,8 @@ static void
vq_ring_notify_host(struct virtqueue *vq)
{
- VIRTIO_BUS_NOTIFY_VQ(vq->vq_dev, vq->vq_queue_index);
+ VIRTIO_BUS_NOTIFY_VQ(vq->vq_dev, vq->vq_queue_index,
+ vq->vq_notify_offset);
}
static void
diff --git a/sys/dev/virtio/virtqueue.h b/sys/dev/virtio/virtqueue.h
index 7b2db673f7ae..6ac5899cf0c3 100644
--- a/sys/dev/virtio/virtqueue.h
+++ b/sys/dev/virtio/virtqueue.h
@@ -70,8 +70,8 @@ struct vq_alloc_info {
uint64_t virtqueue_filter_features(uint64_t features);
int virtqueue_alloc(device_t dev, uint16_t queue, uint16_t size,
- int align, vm_paddr_t highaddr, struct vq_alloc_info *info,
- struct virtqueue **vqp);
+ bus_size_t notify_offset, int align, vm_paddr_t highaddr,
+ struct vq_alloc_info *info, struct virtqueue **vqp);
void *virtqueue_drain(struct virtqueue *vq, int *last);
void virtqueue_free(struct virtqueue *vq);
int virtqueue_reinit(struct virtqueue *vq, uint16_t size);
diff --git a/sys/modules/virtio/pci/Makefile b/sys/modules/virtio/pci/Makefile
index 3dbf4032a7cf..022d5ccf167c 100644
--- a/sys/modules/virtio/pci/Makefile
+++ b/sys/modules/virtio/pci/Makefile
@@ -26,8 +26,9 @@
.PATH: ${SRCTOP}/sys/dev/virtio/pci
KMOD= virtio_pci
-SRCS= virtio_pci.c
-SRCS+= virtio_bus_if.h virtio_if.h
+SRCS= virtio_pci.c virtio_pci_legacy.c virtio_pci_modern.c
+SRCS+= virtio_pci_if.c virtio_pci_if.h
+SRCS+= virtio_bus_if.h virtio_if.h
SRCS+= bus_if.h device_if.h pci_if.h
.include <bsd.kmod.mk>