Changeset View
Standalone View
sys/dev/virtio/pci/virtio_pci.c
Show All 31 Lines | |||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/systm.h> | #include <sys/systm.h> | ||||
#include <sys/bus.h> | #include <sys/bus.h> | ||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/module.h> | #include <sys/module.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/endian.h> | |||||
#include <machine/bus.h> | #include <machine/bus.h> | ||||
#include <machine/resource.h> | #include <machine/resource.h> | ||||
#include <sys/bus.h> | #include <sys/bus.h> | ||||
#include <sys/rman.h> | #include <sys/rman.h> | ||||
#include <dev/pci/pcivar.h> | #include <dev/pci/pcivar.h> | ||||
#include <dev/pci/pcireg.h> | #include <dev/pci/pcireg.h> | ||||
▲ Show 20 Lines • Show All 131 Lines • ▼ Show 20 Lines | |||||
*/ | */ | ||||
#define vtpci_read_config_1(sc, o) bus_read_1((sc)->vtpci_res, (o)) | #define vtpci_read_config_1(sc, o) bus_read_1((sc)->vtpci_res, (o)) | ||||
#define vtpci_read_config_2(sc, o) bus_read_2((sc)->vtpci_res, (o)) | #define vtpci_read_config_2(sc, o) bus_read_2((sc)->vtpci_res, (o)) | ||||
#define vtpci_read_config_4(sc, o) bus_read_4((sc)->vtpci_res, (o)) | #define vtpci_read_config_4(sc, o) bus_read_4((sc)->vtpci_res, (o)) | ||||
#define vtpci_write_config_1(sc, o, v) bus_write_1((sc)->vtpci_res, (o), (v)) | #define vtpci_write_config_1(sc, o, v) bus_write_1((sc)->vtpci_res, (o), (v)) | ||||
#define vtpci_write_config_2(sc, o, v) bus_write_2((sc)->vtpci_res, (o), (v)) | #define vtpci_write_config_2(sc, o, v) bus_write_2((sc)->vtpci_res, (o), (v)) | ||||
#define vtpci_write_config_4(sc, o, v) bus_write_4((sc)->vtpci_res, (o), (v)) | #define vtpci_write_config_4(sc, o, v) bus_write_4((sc)->vtpci_res, (o), (v)) | ||||
/* | |||||
* Legacy VirtioIO header is always PCI endianness (little), so if we | |||||
bryanv: Typo: VirtioIO
| |||||
Done Inline ActionsThere are some trailing spaces in this block. bdragon: There are some trailing spaces in this block. | |||||
* are in a BE machine we need to swap bytes from LE to BE when reading | |||||
* and from BE to LE when writing. | |||||
* If we are in a LE machine, there will be no swaps. | |||||
*/ | |||||
#define vtpci_read_header_2(sc, o) le16toh(vtpci_read_config_2(sc, o)) | |||||
Done Inline ActionsOn vtpci_write_header_2 you should change le16toh -> *htole16**, and same for the 32 bit version of this macro. The result will be the same but it explains better the idea of forcing the PCI header to be LE no matter what endianess the "host" is (host=="guest VM" in this case) alfredo: On vtpci_write_header_2 you should change **le16toh** -> *htole16**, and same for the 32 bit… | |||||
#define vtpci_read_header_4(sc, o) le32toh(vtpci_read_config_4(sc, o)) | |||||
#define vtpci_write_header_2(sc, o, v) bus_write_2((sc)->vtpci_res, (o), (htole16(v))) | |||||
bryanvUnsubmitted Done Inline ActionsPlease use the vtpci_write_config macros for consistency with the read in this same block bryanv: Please use the vtpci_write_config macros for consistency with the read in this same block | |||||
#define vtpci_write_header_4(sc, o, v) bus_write_4((sc)->vtpci_res, (o), (htole32(v))) | |||||
/* Tunables. */ | /* Tunables. */ | ||||
static int vtpci_disable_msix = 0; | static int vtpci_disable_msix = 0; | ||||
TUNABLE_INT("hw.virtio.pci.disable_msix", &vtpci_disable_msix); | TUNABLE_INT("hw.virtio.pci.disable_msix", &vtpci_disable_msix); | ||||
static device_method_t vtpci_methods[] = { | static device_method_t vtpci_methods[] = { | ||||
/* Device interface. */ | /* Device interface. */ | ||||
DEVMETHOD(device_probe, vtpci_probe), | DEVMETHOD(device_probe, vtpci_probe), | ||||
DEVMETHOD(device_attach, vtpci_attach), | DEVMETHOD(device_attach, vtpci_attach), | ||||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | vtpci_attach(device_t dev) | ||||
rid = PCIR_BAR(0); | rid = PCIR_BAR(0); | ||||
sc->vtpci_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, | sc->vtpci_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, | ||||
RF_ACTIVE); | RF_ACTIVE); | ||||
if (sc->vtpci_res == NULL) { | if (sc->vtpci_res == NULL) { | ||||
device_printf(dev, "cannot map I/O space\n"); | device_printf(dev, "cannot map I/O space\n"); | ||||
return (ENXIO); | return (ENXIO); | ||||
} | } | ||||
/* | |||||
bryanvUnsubmitted Done Inline ActionsThe block indent appears off here. bryanv: The block indent appears off here. | |||||
* For VirtIO legacy drivers, the configurarion area endianness | |||||
bryanvUnsubmitted Done Inline ActionsTypo: configurarion I would reword the first part of this sentence slightly to better match the wordage from the spec, something like
bryanv: Typo: configurarion
I would reword the first part of this sentence slightly to better match… | |||||
* is guest endianness, except the header that is always PCI | |||||
* endianness (little), and will be handled specifically in | |||||
* other parts of this file via functions | |||||
* 'vtpci_[read|write]_header_[2|4]' | |||||
*/ | |||||
#if _BYTE_ORDER == _BIG_ENDIAN | |||||
rman_set_bustag(sc->vtpci_res, &bs_be_tag); | |||||
#endif | |||||
if (pci_find_cap(dev, PCIY_MSI, NULL) != 0) | if (pci_find_cap(dev, PCIY_MSI, NULL) != 0) | ||||
sc->vtpci_flags |= VTPCI_FLAG_NO_MSI; | sc->vtpci_flags |= VTPCI_FLAG_NO_MSI; | ||||
if (pci_find_cap(dev, PCIY_MSIX, NULL) == 0) { | if (pci_find_cap(dev, PCIY_MSIX, NULL) == 0) { | ||||
rid = PCIR_BAR(1); | rid = PCIR_BAR(1); | ||||
sc->vtpci_msix_res = bus_alloc_resource_any(dev, | sc->vtpci_msix_res = bus_alloc_resource_any(dev, | ||||
SYS_RES_MEMORY, &rid, RF_ACTIVE); | SYS_RES_MEMORY, &rid, RF_ACTIVE); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 153 Lines • ▼ Show 20 Lines | |||||
static uint64_t | static uint64_t | ||||
vtpci_negotiate_features(device_t dev, uint64_t child_features) | vtpci_negotiate_features(device_t dev, uint64_t child_features) | ||||
{ | { | ||||
struct vtpci_softc *sc; | struct vtpci_softc *sc; | ||||
uint64_t host_features, features; | uint64_t host_features, features; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
host_features = vtpci_read_config_4(sc, VIRTIO_PCI_HOST_FEATURES); | host_features = vtpci_read_header_4(sc, VIRTIO_PCI_HOST_FEATURES); | ||||
vtpci_describe_features(sc, "host", host_features); | vtpci_describe_features(sc, "host", host_features); | ||||
/* | /* | ||||
* Limit negotiated features to what the driver, virtqueue, and | * Limit negotiated features to what the driver, virtqueue, and | ||||
* host all support. | * host all support. | ||||
*/ | */ | ||||
features = host_features & child_features; | features = host_features & child_features; | ||||
features = virtqueue_filter_features(features); | features = virtqueue_filter_features(features); | ||||
sc->vtpci_features = features; | sc->vtpci_features = features; | ||||
vtpci_describe_features(sc, "negotiated", features); | vtpci_describe_features(sc, "negotiated", features); | ||||
vtpci_write_config_4(sc, VIRTIO_PCI_GUEST_FEATURES, features); | vtpci_write_header_4(sc, VIRTIO_PCI_GUEST_FEATURES, features); | ||||
return (features); | return (features); | ||||
} | } | ||||
static int | static int | ||||
vtpci_with_feature(device_t dev, uint64_t feature) | vtpci_with_feature(device_t dev, uint64_t feature) | ||||
{ | { | ||||
struct vtpci_softc *sc; | struct vtpci_softc *sc; | ||||
Show All 26 Lines | vtpci_alloc_virtqueues(device_t dev, int flags, int nvqs, | ||||
if (sc->vtpci_vqs == NULL) | if (sc->vtpci_vqs == NULL) | ||||
return (ENOMEM); | return (ENOMEM); | ||||
for (idx = 0; idx < nvqs; idx++) { | for (idx = 0; idx < nvqs; idx++) { | ||||
vqx = &sc->vtpci_vqs[idx]; | vqx = &sc->vtpci_vqs[idx]; | ||||
info = &vq_info[idx]; | info = &vq_info[idx]; | ||||
vtpci_select_virtqueue(sc, idx); | vtpci_select_virtqueue(sc, idx); | ||||
size = vtpci_read_config_2(sc, VIRTIO_PCI_QUEUE_NUM); | size = vtpci_read_header_2(sc, VIRTIO_PCI_QUEUE_NUM); | ||||
error = virtqueue_alloc(dev, idx, size, VIRTIO_PCI_VRING_ALIGN, | error = virtqueue_alloc(dev, idx, size, VIRTIO_PCI_VRING_ALIGN, | ||||
~(vm_paddr_t)0, info, &vq); | ~(vm_paddr_t)0, info, &vq); | ||||
if (error) { | if (error) { | ||||
device_printf(dev, | device_printf(dev, | ||||
"cannot allocate virtqueue %d: %d\n", idx, error); | "cannot allocate virtqueue %d: %d\n", idx, error); | ||||
break; | break; | ||||
} | } | ||||
vtpci_write_config_4(sc, VIRTIO_PCI_QUEUE_PFN, | vtpci_write_header_4(sc, VIRTIO_PCI_QUEUE_PFN, | ||||
virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT); | virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT); | ||||
vqx->vtv_vq = *info->vqai_vq = vq; | vqx->vtv_vq = *info->vqai_vq = vq; | ||||
vqx->vtv_no_intr = info->vqai_intr == NULL; | vqx->vtv_no_intr = info->vqai_intr == NULL; | ||||
sc->vtpci_nvqs++; | sc->vtpci_nvqs++; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 117 Lines • ▼ Show 20 Lines | |||||
static void | static void | ||||
vtpci_notify_virtqueue(device_t dev, uint16_t queue) | vtpci_notify_virtqueue(device_t dev, uint16_t queue) | ||||
{ | { | ||||
struct vtpci_softc *sc; | struct vtpci_softc *sc; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
vtpci_write_config_2(sc, VIRTIO_PCI_QUEUE_NOTIFY, queue); | vtpci_write_header_2(sc, VIRTIO_PCI_QUEUE_NOTIFY, queue); | ||||
} | } | ||||
static uint8_t | static uint8_t | ||||
vtpci_get_status(device_t dev) | vtpci_get_status(device_t dev) | ||||
{ | { | ||||
struct vtpci_softc *sc; | struct vtpci_softc *sc; | ||||
sc = device_get_softc(dev); | sc = device_get_softc(dev); | ||||
▲ Show 20 Lines • Show All 383 Lines • ▼ Show 20 Lines | vtpci_register_msix_vector(struct vtpci_softc *sc, int offset, | ||||
dev = sc->vtpci_dev; | dev = sc->vtpci_dev; | ||||
if (intr != NULL) { | if (intr != NULL) { | ||||
/* Map from guest rid to host vector. */ | /* Map from guest rid to host vector. */ | ||||
vector = intr->vti_rid - 1; | vector = intr->vti_rid - 1; | ||||
} else | } else | ||||
vector = VIRTIO_MSI_NO_VECTOR; | vector = VIRTIO_MSI_NO_VECTOR; | ||||
vtpci_write_config_2(sc, offset, vector); | vtpci_write_header_2(sc, offset, vector); | ||||
/* Read vector to determine if the host had sufficient resources. */ | /* Read vector to determine if the host had sufficient resources. */ | ||||
if (vtpci_read_config_2(sc, offset) != vector) { | if (vtpci_read_header_2(sc, offset) != vector) { | ||||
device_printf(dev, | device_printf(dev, | ||||
"insufficient host resources for MSIX interrupts\n"); | "insufficient host resources for MSIX interrupts\n"); | ||||
return (ENODEV); | return (ENODEV); | ||||
} | } | ||||
return (0); | return (0); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | vtpci_reinit_virtqueue(struct vtpci_softc *sc, int idx) | ||||
uint16_t size; | uint16_t size; | ||||
vqx = &sc->vtpci_vqs[idx]; | vqx = &sc->vtpci_vqs[idx]; | ||||
vq = vqx->vtv_vq; | vq = vqx->vtv_vq; | ||||
KASSERT(vq != NULL, ("%s: vq %d not allocated", __func__, idx)); | KASSERT(vq != NULL, ("%s: vq %d not allocated", __func__, idx)); | ||||
vtpci_select_virtqueue(sc, idx); | vtpci_select_virtqueue(sc, idx); | ||||
size = vtpci_read_config_2(sc, VIRTIO_PCI_QUEUE_NUM); | size = vtpci_read_header_2(sc, VIRTIO_PCI_QUEUE_NUM); | ||||
error = virtqueue_reinit(vq, size); | error = virtqueue_reinit(vq, size); | ||||
if (error) | if (error) | ||||
return (error); | return (error); | ||||
vtpci_write_config_4(sc, VIRTIO_PCI_QUEUE_PFN, | vtpci_write_header_4(sc, VIRTIO_PCI_QUEUE_PFN, | ||||
virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT); | virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT); | ||||
return (0); | return (0); | ||||
} | } | ||||
static void | static void | ||||
vtpci_free_interrupt(struct vtpci_softc *sc, struct vtpci_interrupt *intr) | vtpci_free_interrupt(struct vtpci_softc *sc, struct vtpci_interrupt *intr) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
struct vtpci_virtqueue *vqx; | struct vtpci_virtqueue *vqx; | ||||
int idx; | int idx; | ||||
for (idx = 0; idx < sc->vtpci_nvqs; idx++) { | for (idx = 0; idx < sc->vtpci_nvqs; idx++) { | ||||
vqx = &sc->vtpci_vqs[idx]; | vqx = &sc->vtpci_vqs[idx]; | ||||
vtpci_select_virtqueue(sc, idx); | vtpci_select_virtqueue(sc, idx); | ||||
vtpci_write_config_4(sc, VIRTIO_PCI_QUEUE_PFN, 0); | vtpci_write_header_4(sc, VIRTIO_PCI_QUEUE_PFN, 0); | ||||
Done Inline Actionssome spaces leaked into the indentation on this line. bdragon: some spaces leaked into the indentation on this line. | |||||
virtqueue_free(vqx->vtv_vq); | virtqueue_free(vqx->vtv_vq); | ||||
vqx->vtv_vq = NULL; | vqx->vtv_vq = NULL; | ||||
} | } | ||||
free(sc->vtpci_vqs, M_DEVBUF); | free(sc->vtpci_vqs, M_DEVBUF); | ||||
sc->vtpci_vqs = NULL; | sc->vtpci_vqs = NULL; | ||||
sc->vtpci_nvqs = 0; | sc->vtpci_nvqs = 0; | ||||
} | } | ||||
static void | static void | ||||
vtpci_release_child_resources(struct vtpci_softc *sc) | vtpci_release_child_resources(struct vtpci_softc *sc) | ||||
{ | { | ||||
vtpci_free_interrupts(sc); | vtpci_free_interrupts(sc); | ||||
vtpci_free_virtqueues(sc); | vtpci_free_virtqueues(sc); | ||||
} | } | ||||
static void | static void | ||||
vtpci_cleanup_setup_intr_attempt(struct vtpci_softc *sc) | vtpci_cleanup_setup_intr_attempt(struct vtpci_softc *sc) | ||||
{ | { | ||||
int idx; | int idx; | ||||
if (sc->vtpci_flags & VTPCI_FLAG_MSIX) { | if (sc->vtpci_flags & VTPCI_FLAG_MSIX) { | ||||
vtpci_write_config_2(sc, VIRTIO_MSI_CONFIG_VECTOR, | vtpci_write_header_2(sc, VIRTIO_MSI_CONFIG_VECTOR, | ||||
VIRTIO_MSI_NO_VECTOR); | VIRTIO_MSI_NO_VECTOR); | ||||
for (idx = 0; idx < sc->vtpci_nvqs; idx++) { | for (idx = 0; idx < sc->vtpci_nvqs; idx++) { | ||||
vtpci_select_virtqueue(sc, idx); | vtpci_select_virtqueue(sc, idx); | ||||
vtpci_write_config_2(sc, VIRTIO_MSI_QUEUE_VECTOR, | vtpci_write_header_2(sc, VIRTIO_MSI_QUEUE_VECTOR, | ||||
VIRTIO_MSI_NO_VECTOR); | VIRTIO_MSI_NO_VECTOR); | ||||
} | } | ||||
} | } | ||||
vtpci_free_interrupts(sc); | vtpci_free_interrupts(sc); | ||||
} | } | ||||
static void | static void | ||||
vtpci_reset(struct vtpci_softc *sc) | vtpci_reset(struct vtpci_softc *sc) | ||||
{ | { | ||||
/* | /* | ||||
* Setting the status to RESET sets the host device to | * Setting the status to RESET sets the host device to | ||||
* the original, uninitialized state. | * the original, uninitialized state. | ||||
*/ | */ | ||||
vtpci_set_status(sc->vtpci_dev, VIRTIO_CONFIG_STATUS_RESET); | vtpci_set_status(sc->vtpci_dev, VIRTIO_CONFIG_STATUS_RESET); | ||||
} | } | ||||
static void | static void | ||||
vtpci_select_virtqueue(struct vtpci_softc *sc, int idx) | vtpci_select_virtqueue(struct vtpci_softc *sc, int idx) | ||||
{ | { | ||||
vtpci_write_config_2(sc, VIRTIO_PCI_QUEUE_SEL, idx); | vtpci_write_header_2(sc, VIRTIO_PCI_QUEUE_SEL, idx); | ||||
} | } | ||||
static void | static void | ||||
vtpci_legacy_intr(void *xsc) | vtpci_legacy_intr(void *xsc) | ||||
{ | { | ||||
struct vtpci_softc *sc; | struct vtpci_softc *sc; | ||||
struct vtpci_virtqueue *vqx; | struct vtpci_virtqueue *vqx; | ||||
int i; | int i; | ||||
▲ Show 20 Lines • Show All 87 Lines • Show Last 20 Lines |
Typo: VirtioIO