1
0
mirror of https://git.FreeBSD.org/src.git synced 2025-01-13 14:40:22 +00:00

Various VirtIO improvements

PCI:
        - Properly handle interrupt fallback from MSIX to MSI to legacy.
          The host may not have sufficient resources to support MSIX,
          so we must be able to fallback to legacy interrupts.
        - Add interface to get the (sub) vendor and device IDs.
        - Rename flags to VTPCI_FLAG_* like other VirtIO drivers.
      Block:
        - No longer allocate vtblk_requests from separate UMA zone.
          malloc(9) from M_DEVBUF is sufficient. Assert segment counts
          at allocation.
        - More verbose error and debug messages.
      Network:
        - Remove stray write once variable.
      Virtqueue:
        - Shuffle code around in preparation of converting the mb()s to
          the appropriate atomic(9) operations.
        - Only walk the descriptor chain when freeing if INVARIANTS is
          defined since the result is only KASSERT()ed.

Submitted by:	Bryan Venteicher (bryanv@daemoninthecloset.org)
This commit is contained in:
Peter Grehan 2012-07-11 02:57:19 +00:00
parent 571a1e92aa
commit 310dacd09b
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=238360
10 changed files with 536 additions and 349 deletions

View File

@ -412,7 +412,6 @@ vtballoon_send_page_frames(struct vtballoon_softc *sc, struct virtqueue *vq,
* interrupt handler will wake us up.
*/
VTBALLOON_LOCK(sc);
while ((c = virtqueue_dequeue(vq, NULL)) == NULL)
msleep_spin(sc, VTBALLOON_MTX(sc), "vtbspf", 0);
VTBALLOON_UNLOCK(sc);

View File

@ -42,7 +42,6 @@ __FBSDID("$FreeBSD$");
#include <sys/taskqueue.h>
#include <geom/geom_disk.h>
#include <vm/uma.h>
#include <machine/bus.h>
#include <machine/resource.h>
@ -119,7 +118,7 @@ static int vtblk_shutdown(device_t);
static int vtblk_open(struct disk *);
static int vtblk_close(struct disk *);
static int vtblk_ioctl(struct disk *, u_long, void *, int,
struct thread *);
struct thread *);
static int vtblk_dump(void *, void *, vm_offset_t, off_t, size_t);
static void vtblk_strategy(struct bio *);
@ -193,7 +192,7 @@ TUNABLE_INT("hw.vtblk.no_ident", &vtblk_no_ident);
mtx_assert(VTBLK_MTX((_sc)), MA_NOTOWNED)
#define VTBLK_DISK_NAME "vtbd"
#define VTBLK_QUIESCE_TIMEOUT (30 * hz)
#define VTBLK_QUIESCE_TIMEOUT (30 * hz)
/*
* Each block request uses at least two segments - one for the header
@ -201,8 +200,6 @@ TUNABLE_INT("hw.vtblk.no_ident", &vtblk_no_ident);
*/
#define VTBLK_MIN_SEGMENTS 2
static uma_zone_t vtblk_req_zone;
static device_method_t vtblk_methods[] = {
/* Device methods. */
DEVMETHOD(device_probe, vtblk_probe),
@ -236,19 +233,8 @@ vtblk_modevent(module_t mod, int type, void *unused)
switch (type) {
case MOD_LOAD:
vtblk_req_zone = uma_zcreate("vtblk_request",
sizeof(struct vtblk_request),
NULL, NULL, NULL, NULL, 0, 0);
break;
case MOD_QUIESCE:
case MOD_UNLOAD:
if (uma_zone_get_cur(vtblk_req_zone) > 0)
error = EBUSY;
else if (type == MOD_UNLOAD) {
uma_zdestroy(vtblk_req_zone);
vtblk_req_zone = NULL;
}
break;
case MOD_SHUTDOWN:
break;
default:
@ -316,7 +302,7 @@ vtblk_attach(device_t dev)
}
sc->vtblk_max_nsegs = vtblk_maximum_segments(sc, &blkcfg);
if (sc->vtblk_max_nsegs <= VTBLK_MIN_SEGMENTS) {
if (sc->vtblk_max_nsegs <= VTBLK_MIN_SEGMENTS) {
error = EINVAL;
device_printf(dev, "fewer than minimum number of segments "
"allowed: %d\n", sc->vtblk_max_nsegs);
@ -493,7 +479,6 @@ vtblk_dump(void *arg, void *virtual, vm_offset_t physical, off_t offset,
int error;
dp = arg;
error = 0;
if ((sc = dp->d_drv1) == NULL)
return (ENXIO);
@ -539,7 +524,7 @@ vtblk_strategy(struct bio *bp)
return;
}
#ifdef INVARIANTS
#ifdef INVARIANTS
/*
* Prevent read/write buffers spanning too many segments from
* getting into the queue. This should only trip if d_maxsize
@ -547,13 +532,13 @@ vtblk_strategy(struct bio *bp)
*/
if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE) {
int nsegs, max_nsegs;
nsegs = sglist_count(bp->bio_data, bp->bio_bcount);
max_nsegs = sc->vtblk_max_nsegs - VTBLK_MIN_SEGMENTS;
KASSERT(nsegs <= max_nsegs,
("bio spanned too many segments: %d, max: %d",
nsegs, max_nsegs));
("bio %p spanned too many segments: %d, max: %d",
bp, nsegs, max_nsegs));
}
#endif
@ -800,27 +785,22 @@ vtblk_execute_request(struct vtblk_softc *sc, struct vtblk_request *req)
VTBLK_LOCK_ASSERT(sc);
sglist_reset(sg);
error = sglist_append(sg, &req->vbr_hdr,
sizeof(struct virtio_blk_outhdr));
KASSERT(error == 0, ("error adding header to sglist"));
KASSERT(sg->sg_nseg == 1,
("header spanned multiple segments: %d", sg->sg_nseg));
sglist_append(sg, &req->vbr_hdr, sizeof(struct virtio_blk_outhdr));
if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_WRITE) {
error = sglist_append(sg, bp->bio_data, bp->bio_bcount);
KASSERT(error == 0, ("error adding buffer to sglist"));
if (error || sg->sg_nseg == sg->sg_maxseg)
panic("%s: data buffer too big bio:%p error:%d",
__FUNCTION__, bp, error);
/* BIO_READ means the host writes into our buffer. */
if (bp->bio_cmd == BIO_READ)
writable += sg->sg_nseg - 1;
writable = sg->sg_nseg - 1;
}
error = sglist_append(sg, &req->vbr_ack, sizeof(uint8_t));
KASSERT(error == 0, ("error adding ack to sglist"));
writable++;
KASSERT(sg->sg_nseg >= VTBLK_MIN_SEGMENTS,
("fewer than min segments: %d", sg->sg_nseg));
sglist_append(sg, &req->vbr_ack, sizeof(uint8_t));
readable = sg->sg_nseg - writable;
@ -995,12 +975,10 @@ vtblk_flush_dump(struct vtblk_softc *sc)
static int
vtblk_poll_request(struct vtblk_softc *sc, struct vtblk_request *req)
{
device_t dev;
struct virtqueue *vq;
struct vtblk_request *r;
int error;
dev = sc->vtblk_dev;
vq = sc->vtblk_vq;
if (!virtqueue_empty(vq))
@ -1013,12 +991,12 @@ vtblk_poll_request(struct vtblk_softc *sc, struct vtblk_request *req)
virtqueue_notify(vq);
r = virtqueue_poll(vq, NULL);
KASSERT(r == req, ("unexpected request response"));
KASSERT(r == req, ("unexpected request response: %p/%p", r, req));
error = vtblk_request_error(req);
if (error && bootverbose) {
device_printf(dev, "vtblk_poll_request: IO error: %d\n",
error);
device_printf(sc->vtblk_dev,
"%s: IO error: %d\n", __FUNCTION__, error);
}
return (error);
@ -1090,6 +1068,20 @@ vtblk_drain(struct vtblk_softc *sc)
vtblk_free_requests(sc);
}
#ifdef INVARIANTS
static void
vtblk_request_invariants(struct vtblk_request *req)
{
int hdr_nsegs, ack_nsegs;
hdr_nsegs = sglist_count(&req->vbr_hdr, sizeof(req->vbr_hdr));
ack_nsegs = sglist_count(&req->vbr_ack, sizeof(req->vbr_ack));
KASSERT(hdr_nsegs == 1, ("request header crossed page boundary"));
KASSERT(ack_nsegs == 1, ("request ack crossed page boundary"));
}
#endif
static int
vtblk_alloc_requests(struct vtblk_softc *sc)
{
@ -1107,10 +1099,14 @@ vtblk_alloc_requests(struct vtblk_softc *sc)
nreqs /= VTBLK_MIN_SEGMENTS;
for (i = 0; i < nreqs; i++) {
req = uma_zalloc(vtblk_req_zone, M_NOWAIT);
req = malloc(sizeof(struct vtblk_request), M_DEVBUF, M_NOWAIT);
if (req == NULL)
return (ENOMEM);
#ifdef INVARIANTS
vtblk_request_invariants(req);
#endif
sc->vtblk_request_count++;
vtblk_enqueue_request(sc, req);
}
@ -1128,10 +1124,11 @@ vtblk_free_requests(struct vtblk_softc *sc)
while ((req = vtblk_dequeue_request(sc)) != NULL) {
sc->vtblk_request_count--;
uma_zfree(vtblk_req_zone, req);
free(req, M_DEVBUF);
}
KASSERT(sc->vtblk_request_count == 0, ("leaked requests"));
KASSERT(sc->vtblk_request_count == 0,
("leaked requests: %d", sc->vtblk_request_count));
}
static struct vtblk_request *

View File

@ -748,11 +748,9 @@ vtnet_is_link_up(struct vtnet_softc *sc)
static void
vtnet_update_link_status(struct vtnet_softc *sc)
{
device_t dev;
struct ifnet *ifp;
int link;
dev = sc->vtnet_dev;
ifp = sc->vtnet_ifp;
link = vtnet_is_link_up(sc);

View File

@ -57,12 +57,15 @@ struct vtpci_softc {
struct resource *vtpci_msix_res;
uint64_t vtpci_features;
uint32_t vtpci_flags;
#define VIRTIO_PCI_FLAG_NO_MSI 0x0001
#define VIRTIO_PCI_FLAG_MSI 0x0002
#define VIRTIO_PCI_FLAG_NO_MSIX 0x0010
#define VIRTIO_PCI_FLAG_MSIX 0x0020
#define VIRTIO_PCI_FLAG_SHARED_MSIX 0x0040
#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;
@ -80,7 +83,8 @@ struct vtpci_softc {
int vtpci_nvqs;
struct vtpci_virtqueue {
struct virtqueue *vq;
/* Device did not provide a callback for this virtqueue. */
int no_intr;
/* Index into vtpci_intr_res[] below. Unused, then -1. */
int ires_idx;
} vtpci_vqx[VIRTIO_MAX_VIRTQUEUES];
@ -130,24 +134,39 @@ static void vtpci_describe_features(struct vtpci_softc *, const char *,
uint64_t);
static void vtpci_probe_and_attach_child(struct vtpci_softc *);
static int vtpci_alloc_interrupts(struct vtpci_softc *, int, int,
struct vq_alloc_info *);
static int vtpci_alloc_intr_resources(struct vtpci_softc *, int,
struct vq_alloc_info *);
static int vtpci_alloc_msi(struct vtpci_softc *);
static int vtpci_alloc_msix(struct vtpci_softc *, int);
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_intr_resources(struct vtpci_softc *);
static int vtpci_setup_legacy_interrupt(struct vtpci_softc *,
enum intr_type);
static int vtpci_setup_msix_interrupts(struct vtpci_softc *,
enum intr_type);
static int vtpci_setup_interrupts(struct vtpci_softc *, enum intr_type);
static int vtpci_register_msix_vector(struct vtpci_softc *, int, int);
static int vtpci_set_host_msix_vectors(struct vtpci_softc *);
static int vtpci_reinit_virtqueue(struct vtpci_softc *, int);
static void vtpci_free_interrupts(struct vtpci_softc *);
static void vtpci_free_virtqueues(struct vtpci_softc *);
static void vtpci_cleanup_setup_intr_attempt(struct vtpci_softc *);
static void vtpci_release_child_resources(struct vtpci_softc *);
static void vtpci_reset(struct vtpci_softc *);
static void vtpci_select_virtqueue(struct vtpci_softc *, int);
static int vtpci_legacy_intr(void *);
static int vtpci_vq_shared_intr(void *);
static int vtpci_vq_intr(void *);
static int vtpci_config_intr(void *);
#define vtpci_setup_msi_interrupt vtpci_setup_legacy_interrupt
/*
* I/O port read/write wrappers.
*/
@ -252,7 +271,7 @@ vtpci_attach(device_t dev)
}
if (pci_find_cap(dev, PCIY_MSI, NULL) != 0)
sc->vtpci_flags |= VIRTIO_PCI_FLAG_NO_MSI;
sc->vtpci_flags |= VTPCI_FLAG_NO_MSI;
if (pci_find_cap(dev, PCIY_MSIX, NULL) == 0) {
rid = PCIR_BAR(1);
@ -261,7 +280,7 @@ vtpci_attach(device_t dev)
}
if (sc->vtpci_msix_res == NULL)
sc->vtpci_flags |= VIRTIO_PCI_FLAG_NO_MSIX;
sc->vtpci_flags |= VTPCI_FLAG_NO_MSIX;
vtpci_reset(sc);
@ -372,6 +391,16 @@ vtpci_read_ivar(device_t dev, device_t child, int index, uintptr_t *result)
switch (index) {
case VIRTIO_IVAR_DEVTYPE:
case VIRTIO_IVAR_SUBDEVICE:
*result = pci_get_subdevice(dev);
break;
case VIRTIO_IVAR_VENDOR:
*result = pci_get_vendor(dev);
break;
case VIRTIO_IVAR_DEVICE:
*result = pci_get_device(dev);
break;
case VIRTIO_IVAR_SUBVENDOR:
*result = pci_get_subdevice(dev);
break;
default:
@ -442,102 +471,97 @@ 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 queue, error;
uint16_t vq_size;
int idx, error;
uint16_t size;
sc = device_get_softc(dev);
error = 0;
if (sc->vtpci_nvqs != 0 || nvqs <= 0 ||
nvqs > VIRTIO_MAX_VIRTQUEUES)
if (sc->vtpci_nvqs != 0)
return (EALREADY);
if (nvqs <= 0 || nvqs > VIRTIO_MAX_VIRTQUEUES)
return (EINVAL);
error = vtpci_alloc_interrupts(sc, flags, nvqs, vq_info);
if (error) {
device_printf(dev, "cannot allocate interrupts\n");
return (error);
}
if (flags & VIRTIO_ALLOC_VQS_DISABLE_MSIX)
sc->vtpci_flags |= VTPCI_FLAG_NO_MSIX;
if (sc->vtpci_flags & VIRTIO_PCI_FLAG_MSIX) {
error = vtpci_register_msix_vector(sc,
VIRTIO_MSI_CONFIG_VECTOR, 0);
if (error)
return (error);
}
for (idx = 0; idx < nvqs; idx++) {
vqx = &sc->vtpci_vqx[idx];
info = &vq_info[idx];
for (queue = 0; queue < nvqs; queue++) {
vqx = &sc->vtpci_vqx[queue];
info = &vq_info[queue];
vtpci_select_virtqueue(sc, idx);
size = vtpci_read_config_2(sc, VIRTIO_PCI_QUEUE_NUM);
vtpci_write_config_2(sc, VIRTIO_PCI_QUEUE_SEL, queue);
vq_size = vtpci_read_config_2(sc, VIRTIO_PCI_QUEUE_NUM);
error = virtqueue_alloc(dev, queue, vq_size,
VIRTIO_PCI_VRING_ALIGN, 0xFFFFFFFFUL, info, &vqx->vq);
if (error)
return (error);
if (sc->vtpci_flags & VIRTIO_PCI_FLAG_MSIX) {
error = vtpci_register_msix_vector(sc,
VIRTIO_MSI_QUEUE_VECTOR, vqx->ires_idx);
if (error)
return (error);
error = virtqueue_alloc(dev, idx, size, VIRTIO_PCI_VRING_ALIGN,
0xFFFFFFFFUL, info, &vq);
if (error) {
device_printf(dev,
"cannot allocate virtqueue %d: %d\n", idx, error);
break;
}
vtpci_write_config_4(sc, VIRTIO_PCI_QUEUE_PFN,
virtqueue_paddr(vqx->vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
vqx->vq = *info->vqai_vq = vq;
vqx->no_intr = info->vqai_intr == NULL;
*info->vqai_vq = vqx->vq;
sc->vtpci_nvqs++;
}
return (0);
return (error);
}
static int
vtpci_setup_intr(device_t dev, enum intr_type type)
{
struct vtpci_softc *sc;
struct vtpci_intr_resource *ires;
struct vtpci_virtqueue *vqx;
int i, flags, error;
int attempt, error;
sc = device_get_softc(dev);
flags = type | INTR_MPSAFE;
ires = &sc->vtpci_intr_res[0];
if ((sc->vtpci_flags & VIRTIO_PCI_FLAG_MSIX) == 0) {
error = bus_setup_intr(dev, ires->irq, flags,
vtpci_legacy_intr, NULL, sc, &ires->intrhand);
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);
}
return (error);
if (error == 0 && vtpci_setup_interrupts(sc, type) == 0)
break;
vtpci_cleanup_setup_intr_attempt(sc);
}
error = bus_setup_intr(dev, ires->irq, flags, vtpci_config_intr,
NULL, sc, &ires->intrhand);
if (error)
return (error);
if (sc->vtpci_flags & VIRTIO_PCI_FLAG_SHARED_MSIX) {
ires = &sc->vtpci_intr_res[1];
error = bus_setup_intr(dev, ires->irq, flags,
vtpci_vq_shared_intr, NULL, sc, &ires->intrhand);
return (error);
}
/* Setup an interrupt handler for each virtqueue. */
for (i = 0; i < sc->vtpci_nvqs; i++) {
vqx = &sc->vtpci_vqx[i];
if (vqx->ires_idx < 1)
continue;
ires = &sc->vtpci_intr_res[vqx->ires_idx];
error = bus_setup_intr(dev, ires->irq, flags,
vtpci_vq_intr, NULL, vqx->vq, &ires->intrhand);
if (error)
return (error);
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);
@ -554,20 +578,19 @@ static int
vtpci_reinit(device_t dev, uint64_t features)
{
struct vtpci_softc *sc;
struct vtpci_virtqueue *vqx;
struct virtqueue *vq;
int queue, error;
uint16_t vq_size;
int idx, error;
sc = device_get_softc(dev);
/*
* Redrive the device initialization. This is a bit of an abuse
* of the specification, but both VirtualBox and QEMU/KVM 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).
* 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)
@ -582,34 +605,16 @@ vtpci_reinit(device_t dev, uint64_t features)
vtpci_negotiate_features(dev, features);
if (sc->vtpci_flags & VIRTIO_PCI_FLAG_MSIX) {
error = vtpci_register_msix_vector(sc,
VIRTIO_MSI_CONFIG_VECTOR, 0);
for (idx = 0; idx < sc->vtpci_nvqs; idx++) {
error = vtpci_reinit_virtqueue(sc, idx);
if (error)
return (error);
}
for (queue = 0; queue < sc->vtpci_nvqs; queue++) {
vqx = &sc->vtpci_vqx[queue];
vq = vqx->vq;
KASSERT(vq != NULL, ("vq %d not allocated", queue));
vtpci_write_config_2(sc, VIRTIO_PCI_QUEUE_SEL, queue);
vq_size = vtpci_read_config_2(sc, VIRTIO_PCI_QUEUE_NUM);
error = virtqueue_reinit(vq, vq_size);
if (sc->vtpci_flags & VTPCI_FLAG_MSIX) {
error = vtpci_set_host_msix_vectors(sc);
if (error)
return (error);
if (sc->vtpci_flags & VIRTIO_PCI_FLAG_MSIX) {
error = vtpci_register_msix_vector(sc,
VIRTIO_MSI_QUEUE_VECTOR, vqx->ires_idx);
if (error)
return (error);
}
vtpci_write_config_4(sc, VIRTIO_PCI_QUEUE_PFN,
virtqueue_paddr(vqx->vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
}
return (0);
@ -744,7 +749,6 @@ vtpci_probe_and_attach_child(struct vtpci_softc *sc)
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
@ -752,42 +756,126 @@ vtpci_probe_and_attach_child(struct vtpci_softc *sc)
}
static int
vtpci_alloc_interrupts(struct vtpci_softc *sc, int flags, int nvqs,
struct vq_alloc_info *vq_info)
vtpci_alloc_msix(struct vtpci_softc *sc, int nvectors)
{
int i, nvectors, error;
device_t dev;
int nmsix, cnt, required;
/*
* Only allocate a vector for virtqueues that are actually
* expecting an interrupt.
*/
for (nvectors = 0, i = 0; i < nvqs; i++)
if (vq_info[i].vqai_intr != NULL)
nvectors++;
dev = sc->vtpci_dev;
if (vtpci_disable_msix != 0 ||
sc->vtpci_flags & VIRTIO_PCI_FLAG_NO_MSIX ||
flags & VIRTIO_ALLOC_VQS_DISABLE_MSIX ||
vtpci_alloc_msix(sc, nvectors) != 0) {
/*
* Use MSI interrupts if available. Otherwise, we fallback
* to legacy interrupts.
*/
if ((sc->vtpci_flags & VIRTIO_PCI_FLAG_NO_MSI) == 0 &&
vtpci_alloc_msi(sc) == 0)
sc->vtpci_flags |= VIRTIO_PCI_FLAG_MSI;
/* Allocate an additional vector for the config changes. */
required = nvectors + 1;
sc->vtpci_nintr_res = 1;
nmsix = pci_msix_count(dev);
if (nmsix < required)
return (1);
cnt = required;
if (pci_alloc_msix(dev, &cnt) == 0 && cnt >= required) {
sc->vtpci_nintr_res = required;
return (0);
}
error = vtpci_alloc_intr_resources(sc, nvqs, vq_info);
pci_release_msi(dev);
return (error);
return (1);
}
static int
vtpci_alloc_intr_resources(struct vtpci_softc *sc, int nvqs,
struct vq_alloc_info *vq_info)
vtpci_alloc_msi(struct vtpci_softc *sc)
{
device_t dev;
int nmsi, cnt, required;
dev = sc->vtpci_dev;
required = 1;
nmsi = pci_msi_count(dev);
if (nmsi < required)
return (1);
cnt = required;
if (pci_alloc_msi(dev, &cnt) == 0 && cnt >= required) {
sc->vtpci_nintr_res = required;
return (0);
}
pci_release_msi(dev);
return (1);
}
static int
vtpci_alloc_intr_msix_pervq(struct vtpci_softc *sc)
{
int i, nvectors, error;
if (vtpci_disable_msix != 0 ||
sc->vtpci_flags & VTPCI_FLAG_NO_MSIX)
return (ENOTSUP);
for (nvectors = 0, i = 0; i < sc->vtpci_nvqs; i++) {
if (sc->vtpci_vqx[i].no_intr == 0)
nvectors++;
}
error = vtpci_alloc_msix(sc, nvectors);
if (error)
return (error);
sc->vtpci_flags |= VTPCI_FLAG_MSIX;
return (0);
}
static int
vtpci_alloc_intr_msix_shared(struct vtpci_softc *sc)
{
int error;
if (vtpci_disable_msix != 0 ||
sc->vtpci_flags & VTPCI_FLAG_NO_MSIX)
return (ENOTSUP);
error = vtpci_alloc_msix(sc, 1);
if (error)
return (error);
sc->vtpci_flags |= VTPCI_FLAG_MSIX | VTPCI_FLAG_SHARED_MSIX;
return (0);
}
static int
vtpci_alloc_intr_msi(struct vtpci_softc *sc)
{
int error;
/* Only BHyVe supports MSI. */
if (sc->vtpci_flags & VTPCI_FLAG_NO_MSI)
return (ENOTSUP);
error = vtpci_alloc_msi(sc);
if (error)
return (error);
sc->vtpci_flags |= VTPCI_FLAG_MSI;
return (0);
}
static int
vtpci_alloc_intr_legacy(struct vtpci_softc *sc)
{
sc->vtpci_flags |= VTPCI_FLAG_LEGACY;
sc->vtpci_nintr_res = 1;
return (0);
}
static int
vtpci_alloc_intr_resources(struct vtpci_softc *sc)
{
device_t dev;
struct resource *irq;
@ -795,14 +883,14 @@ vtpci_alloc_intr_resources(struct vtpci_softc *sc, int nvqs,
int i, rid, flags, res_idx;
dev = sc->vtpci_dev;
flags = RF_ACTIVE;
if ((sc->vtpci_flags &
(VIRTIO_PCI_FLAG_MSI | VIRTIO_PCI_FLAG_MSIX)) == 0) {
if (sc->vtpci_flags & VTPCI_FLAG_LEGACY) {
rid = 0;
flags |= RF_SHAREABLE;
} else
flags = RF_ACTIVE | RF_SHAREABLE;
} else {
rid = 1;
flags = RF_ACTIVE;
}
for (i = 0; i < sc->vtpci_nintr_res; i++) {
irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, flags);
@ -814,16 +902,16 @@ vtpci_alloc_intr_resources(struct vtpci_softc *sc, int nvqs,
}
/*
* Map the virtqueue into the correct index in vq_intr_res[]. Note the
* first index is reserved for configuration changes notifications.
* Map the virtqueue into the correct index in vq_intr_res[]. The
* first index is reserved for configuration changed notifications.
*/
for (i = 0, res_idx = 1; i < nvqs; i++) {
for (i = 0, res_idx = 1; i < sc->vtpci_nvqs; i++) {
vqx = &sc->vtpci_vqx[i];
if (sc->vtpci_flags & VIRTIO_PCI_FLAG_MSIX) {
if (vq_info[i].vqai_intr == NULL)
if (sc->vtpci_flags & VTPCI_FLAG_MSIX) {
if (vqx->no_intr != 0)
vqx->ires_idx = -1;
else if (sc->vtpci_flags & VIRTIO_PCI_FLAG_SHARED_MSIX)
else if (sc->vtpci_flags & VTPCI_FLAG_SHARED_MSIX)
vqx->ires_idx = res_idx;
else
vqx->ires_idx = res_idx++;
@ -835,110 +923,180 @@ vtpci_alloc_intr_resources(struct vtpci_softc *sc, int nvqs,
}
static int
vtpci_alloc_msi(struct vtpci_softc *sc)
vtpci_setup_legacy_interrupt(struct vtpci_softc *sc, enum intr_type type)
{
device_t dev;
int nmsi, cnt;
struct vtpci_intr_resource *ires;
int error;
dev = sc->vtpci_dev;
nmsi = pci_msi_count(dev);
if (nmsi < 1)
return (1);
ires = &sc->vtpci_intr_res[0];
error = bus_setup_intr(dev, ires->irq, type, vtpci_legacy_intr, NULL,
sc, &ires->intrhand);
cnt = 1;
if (pci_alloc_msi(dev, &cnt) == 0 && cnt == 1)
return (0);
return (1);
return (error);
}
static int
vtpci_alloc_msix(struct vtpci_softc *sc, int nvectors)
vtpci_setup_msix_interrupts(struct vtpci_softc *sc, enum intr_type type)
{
device_t dev;
int nmsix, cnt, required;
struct vtpci_intr_resource *ires;
struct vtpci_virtqueue *vqx;
int i, error;
dev = sc->vtpci_dev;
nmsix = pci_msix_count(dev);
if (nmsix < 1)
return (1);
/*
* The first resource is used for configuration changed interrupts.
*/
ires = &sc->vtpci_intr_res[0];
error = bus_setup_intr(dev, ires->irq, type, vtpci_config_intr,
NULL, sc, &ires->intrhand);
if (error)
return (error);
/* An additional vector is needed for the config changes. */
required = nvectors + 1;
if (nmsix >= required) {
cnt = required;
if (pci_alloc_msix(dev, &cnt) == 0 && cnt >= required)
goto out;
if (sc->vtpci_flags & VTPCI_FLAG_SHARED_MSIX) {
ires = &sc->vtpci_intr_res[1];
pci_release_msi(dev);
}
error = bus_setup_intr(dev, ires->irq, type,
vtpci_vq_shared_intr, NULL, sc, &ires->intrhand);
if (error)
return (error);
} else {
/*
* Each remaining resource is assigned to a specific virtqueue.
*/
for (i = 0; i < sc->vtpci_nvqs; i++) {
vqx = &sc->vtpci_vqx[i];
if (vqx->ires_idx < 1)
continue;
/* Attempt shared MSIX configuration. */
required = 2;
if (nmsix >= required) {
cnt = required;
if (pci_alloc_msix(dev, &cnt) == 0 && cnt >= required) {
sc->vtpci_flags |= VIRTIO_PCI_FLAG_SHARED_MSIX;
goto out;
ires = &sc->vtpci_intr_res[vqx->ires_idx];
error = bus_setup_intr(dev, ires->irq, type,
vtpci_vq_intr, NULL, vqx->vq, &ires->intrhand);
if (error)
return (error);
}
pci_release_msi(dev);
}
return (1);
out:
sc->vtpci_nintr_res = required;
sc->vtpci_flags |= VIRTIO_PCI_FLAG_MSIX;
if (bootverbose) {
if (sc->vtpci_flags & VIRTIO_PCI_FLAG_SHARED_MSIX)
device_printf(dev, "using shared virtqueue MSIX\n");
else
device_printf(dev, "using per virtqueue MSIX\n");
}
error = vtpci_set_host_msix_vectors(sc);
if (error)
return (error);
return (0);
}
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,
("no interrupt type selected: %#x", 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, int res_idx)
{
device_t dev;
uint16_t vector;
uint16_t vector, rdvector;
dev = sc->vtpci_dev;
if (offset != VIRTIO_MSI_CONFIG_VECTOR &&
offset != VIRTIO_MSI_QUEUE_VECTOR)
return (EINVAL);
if (res_idx != -1) {
/* Map from rid to host vector. */
/* Map from guest rid to host vector. */
vector = sc->vtpci_intr_res[res_idx].rid - 1;
} else
vector = VIRTIO_MSI_NO_VECTOR;
/* The first resource is special; make sure it is used correctly. */
/*
* Assert the first resource is always used for the configuration
* changed interrupts.
*/
if (res_idx == 0) {
KASSERT(vector == 0, ("unexpected config vector"));
KASSERT(offset == VIRTIO_MSI_CONFIG_VECTOR,
("unexpected config offset"));
}
KASSERT(vector == 0 && offset == VIRTIO_MSI_CONFIG_VECTOR,
("bad first res use vector:%d offset:%d", vector, offset));
} else
KASSERT(offset == VIRTIO_MSI_QUEUE_VECTOR, ("bad offset"));
vtpci_write_config_2(sc, offset, vector);
if (vtpci_read_config_2(sc, offset) != vector) {
device_printf(dev, "insufficient host resources for "
"MSIX interrupts\n");
/* Read vector to determine if the host had sufficient resources. */
rdvector = vtpci_read_config_2(sc, offset);
if (rdvector != 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)
{
struct vtpci_virtqueue *vqx;
int idx, error;
error = vtpci_register_msix_vector(sc, VIRTIO_MSI_CONFIG_VECTOR, 0);
if (error)
return (error);
for (idx = 0; idx < sc->vtpci_nvqs; idx++) {
vqx = &sc->vtpci_vqx[idx];
vtpci_select_virtqueue(sc, idx);
error = vtpci_register_msix_vector(sc, VIRTIO_MSI_QUEUE_VECTOR,
vqx->ires_idx);
if (error)
return (error);
}
return (0);
}
static int
vtpci_reinit_virtqueue(struct vtpci_softc *sc, int idx)
{
struct vtpci_virtqueue *vqx;
struct virtqueue *vq;
int error;
uint16_t size;
vqx = &sc->vtpci_vqx[idx];
vq = vqx->vq;
KASSERT(vq != NULL, ("vq %d not allocated", idx));
vtpci_select_virtqueue(sc, idx);
size = vtpci_read_config_2(sc, VIRTIO_PCI_QUEUE_NUM);
error = virtqueue_reinit(vq, size);
if (error)
return (error);
vtpci_write_config_4(sc, VIRTIO_PCI_QUEUE_PFN,
virtqueue_paddr(vq) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT);
return (0);
}
static void
vtpci_free_interrupts(struct vtpci_softc *sc)
{
@ -947,15 +1105,8 @@ vtpci_free_interrupts(struct vtpci_softc *sc)
int i;
dev = sc->vtpci_dev;
sc->vtpci_nintr_res = 0;
if (sc->vtpci_flags & (VIRTIO_PCI_FLAG_MSI | VIRTIO_PCI_FLAG_MSIX)) {
pci_release_msi(dev);
sc->vtpci_flags &= ~(VIRTIO_PCI_FLAG_MSI |
VIRTIO_PCI_FLAG_MSIX | VIRTIO_PCI_FLAG_SHARED_MSIX);
}
for (i = 0; i < 1 + VIRTIO_MAX_VIRTQUEUES; i++) {
for (i = 0; i < sc->vtpci_nintr_res; i++) {
ires = &sc->vtpci_intr_res[i];
if (ires->intrhand != NULL) {
@ -971,6 +1122,12 @@ vtpci_free_interrupts(struct vtpci_softc *sc)
ires->rid = -1;
}
if (sc->vtpci_flags & (VTPCI_FLAG_MSI | VTPCI_FLAG_MSIX))
pci_release_msi(dev);
sc->vtpci_nintr_res = 0;
sc->vtpci_flags &= ~VTPCI_FLAG_ITYPE_MASK;
}
static void
@ -979,16 +1136,33 @@ vtpci_free_virtqueues(struct vtpci_softc *sc)
struct vtpci_virtqueue *vqx;
int i;
sc->vtpci_nvqs = 0;
for (i = 0; i < VIRTIO_MAX_VIRTQUEUES; i++) {
for (i = 0; i < sc->vtpci_nvqs; i++) {
vqx = &sc->vtpci_vqx[i];
if (vqx->vq != NULL) {
virtqueue_free(vqx->vq);
vqx->vq = NULL;
virtqueue_free(vqx->vq);
vqx->vq = NULL;
}
sc->vtpci_nvqs = 0;
}
static void
vtpci_cleanup_setup_intr_attempt(struct vtpci_softc *sc)
{
int idx;
if (sc->vtpci_flags & VTPCI_FLAG_MSIX) {
vtpci_write_config_2(sc, VIRTIO_MSI_CONFIG_VECTOR,
VIRTIO_MSI_NO_VECTOR);
for (idx = 0; idx < sc->vtpci_nvqs; idx++) {
vtpci_select_virtqueue(sc, idx);
vtpci_write_config_2(sc, VIRTIO_MSI_QUEUE_VECTOR,
VIRTIO_MSI_NO_VECTOR);
}
}
vtpci_free_interrupts(sc);
}
static void
@ -1010,6 +1184,13 @@ vtpci_reset(struct vtpci_softc *sc)
vtpci_set_status(sc->vtpci_dev, VIRTIO_CONFIG_STATUS_RESET);
}
static void
vtpci_select_virtqueue(struct vtpci_softc *sc, int idx)
{
vtpci_write_config_2(sc, VIRTIO_PCI_QUEUE_SEL, idx);
}
static int
vtpci_legacy_intr(void *xsc)
{

View File

@ -73,7 +73,7 @@
* configuration space.
*/
#define VIRTIO_PCI_CONFIG(sc) \
(((sc)->vtpci_flags & VIRTIO_PCI_FLAG_MSIX) ? 24 : 20)
(((sc)->vtpci_flags & VTPCI_FLAG_MSIX) ? 24 : 20)
/*
* How many bits to shift physical queue address written to QUEUE_PFN.

View File

@ -86,28 +86,6 @@ virtio_device_name(uint16_t devid)
return (NULL);
}
int
virtio_get_device_type(device_t dev)
{
uintptr_t devtype;
devtype = -1;
BUS_READ_IVAR(device_get_parent(dev), dev,
VIRTIO_IVAR_DEVTYPE, &devtype);
return ((int) devtype);
}
void
virtio_set_feature_desc(device_t dev,
struct virtio_feature_desc *feature_desc)
{
BUS_WRITE_IVAR(device_get_parent(dev), dev,
VIRTIO_IVAR_FEATURE_DESC, (uintptr_t) feature_desc);
}
void
virtio_describe(device_t dev, const char *msg,
uint64_t features, struct virtio_feature_desc *feature_desc)
@ -184,6 +162,21 @@ virtio_feature_name(uint64_t val, struct virtio_feature_desc *feature_desc)
* VirtIO bus method wrappers.
*/
void
virtio_read_ivar(device_t dev, int ivar, uintptr_t *val)
{
*val = -1;
BUS_READ_IVAR(device_get_parent(dev), dev, ivar, val);
}
void
virtio_write_ivar(device_t dev, int ivar, uintptr_t val)
{
BUS_WRITE_IVAR(device_get_parent(dev), dev, ivar, val);
}
uint64_t
virtio_negotiate_features(device_t dev, uint64_t child_features)
{

View File

@ -91,6 +91,10 @@ struct vq_alloc_info;
*/
#define VIRTIO_IVAR_DEVTYPE 1
#define VIRTIO_IVAR_FEATURE_DESC 2
#define VIRTIO_IVAR_VENDOR 3
#define VIRTIO_IVAR_DEVICE 4
#define VIRTIO_IVAR_SUBVENDOR 5
#define VIRTIO_IVAR_SUBDEVICE 6
struct virtio_feature_desc {
uint64_t vfd_val;
@ -98,15 +102,14 @@ struct virtio_feature_desc {
};
const char *virtio_device_name(uint16_t devid);
int virtio_get_device_type(device_t dev);
void virtio_set_feature_desc(device_t dev,
struct virtio_feature_desc *feature_desc);
void virtio_describe(device_t dev, const char *msg,
uint64_t features, struct virtio_feature_desc *feature_desc);
/*
* VirtIO Bus Methods.
*/
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_alloc_virtqueues(device_t dev, int flags, int nvqs,
struct vq_alloc_info *info);
@ -148,4 +151,28 @@ VIRTIO_RDWR_DEVICE_CONFIG(1, uint8_t);
VIRTIO_RDWR_DEVICE_CONFIG(2, uint16_t);
VIRTIO_RDWR_DEVICE_CONFIG(4, uint32_t);
#define VIRTIO_READ_IVAR(name, ivar) \
static inline int \
__CONCAT(virtio_get_,name)(device_t dev) \
{ \
uintptr_t val; \
virtio_read_ivar(dev, ivar, &val); \
return ((int) val); \
}
VIRTIO_READ_IVAR(device_type, VIRTIO_IVAR_DEVTYPE);
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);
#define VIRTIO_WRITE_IVAR(name, ivar) \
static inline void \
__CONCAT(virtio_set_,name)(device_t dev, void *val) \
{ \
virtio_write_ivar(dev, ivar, (uintptr_t) val); \
}
VIRTIO_WRITE_IVAR(feature_desc, VIRTIO_IVAR_FEATURE_DESC);
#endif /* _VIRTIO_H_ */

View File

@ -120,8 +120,8 @@ struct vring {
* We publish the used event index at the end of the available ring, and vice
* versa. They are at the end for backwards compatibility.
*/
#define vring_used_event(vr) ((vr)->avail->ring[(vr)->num])
#define vring_avail_event(vr) (*(uint16_t *)&(vr)->used->ring[(vr)->num])
#define vring_used_event(vr) ((vr)->avail->ring[(vr)->num])
#define vring_avail_event(vr) (*(uint16_t *)&(vr)->used->ring[(vr)->num])
static inline int
vring_size(unsigned int num, unsigned long align)
@ -129,10 +129,11 @@ vring_size(unsigned int num, unsigned long align)
int size;
size = num * sizeof(struct vring_desc);
size += sizeof(struct vring_avail) + (num * sizeof(uint16_t));
size += sizeof(struct vring_avail) + (num * sizeof(uint16_t)) +
sizeof(uint16_t);
size = (size + align - 1) & ~(align - 1);
size += sizeof(struct vring_used) +
(num * sizeof(struct vring_used_elem));
(num * sizeof(struct vring_used_elem)) + sizeof(uint16_t);
return (size);
}

View File

@ -127,6 +127,7 @@ static uint16_t vq_ring_enqueue_segments(struct virtqueue *,
static int vq_ring_use_indirect(struct virtqueue *, int);
static void vq_ring_enqueue_indirect(struct virtqueue *, void *,
struct sglist *, int, int);
static int vq_ring_enable_interrupt(struct virtqueue *, uint16_t);
static int vq_ring_must_notify_host(struct virtqueue *);
static void vq_ring_notify_host(struct virtqueue *);
static void vq_ring_free_chain(struct virtqueue *, uint16_t);
@ -311,7 +312,7 @@ virtqueue_reinit(struct virtqueue *vq, uint16_t size)
/* Warn if the virtqueue was not properly cleaned up. */
if (vq->vq_free_cnt != vq->vq_nentries) {
device_printf(vq->vq_dev,
"%s: warning, '%s' virtqueue not empty, "
"%s: warning '%s' virtqueue not empty, "
"leaking %d entries\n", __func__, vq->vq_name,
vq->vq_nentries - vq->vq_free_cnt);
}
@ -390,6 +391,7 @@ virtqueue_full(struct virtqueue *vq)
void
virtqueue_notify(struct virtqueue *vq)
{
/* Ensure updated avail->idx is visible to host. */
mb();
@ -428,58 +430,22 @@ int
virtqueue_enable_intr(struct virtqueue *vq)
{
/*
* Enable interrupts, making sure we get the latest
* index of what's already been consumed.
*/
vq->vq_ring.avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
if (vq->vq_flags & VIRTQUEUE_FLAG_EVENT_IDX)
vring_used_event(&vq->vq_ring) = vq->vq_used_cons_idx;
else
vq->vq_ring.avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
mb();
/*
* Additional items may have been consumed in the time between
* since we last checked and enabled interrupts above. Let our
* caller know so it processes the new entries.
*/
if (vq->vq_used_cons_idx != vq->vq_ring.used->idx)
return (1);
return (0);
return (vq_ring_enable_interrupt(vq, 0));
}
int
virtqueue_postpone_intr(struct virtqueue *vq)
{
uint16_t ndesc;
uint16_t ndesc, avail_idx;
/*
* Postpone until at least half of the available descriptors
* have been consumed.
*
* XXX Adaptive factor? (Linux uses 3/4)
* Request the next interrupt be postponed until at least half
* of the available descriptors have been consumed.
*/
ndesc = (uint16_t)(vq->vq_ring.avail->idx - vq->vq_used_cons_idx) / 2;
avail_idx = vq->vq_ring.avail->idx;
ndesc = (uint16_t)(avail_idx - vq->vq_used_cons_idx) / 2;
if (vq->vq_flags & VIRTQUEUE_FLAG_EVENT_IDX)
vring_used_event(&vq->vq_ring) = vq->vq_used_cons_idx + ndesc;
else
vq->vq_ring.avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
mb();
/*
* Enough items may have already been consumed to meet our
* threshold since we last checked. Let our caller know so
* it processes the new entries.
*/
if (virtqueue_nused(vq) > ndesc)
return (1);
return (0);
return (vq_ring_enable_interrupt(vq, ndesc));
}
void
@ -751,6 +717,32 @@ vq_ring_enqueue_indirect(struct virtqueue *vq, void *cookie,
vq_ring_update_avail(vq, head_idx);
}
static int
vq_ring_enable_interrupt(struct virtqueue *vq, uint16_t ndesc)
{
/*
* Enable interrupts, making sure we get the latest index of
* what's already been consumed.
*/
if (vq->vq_flags & VIRTQUEUE_FLAG_EVENT_IDX)
vring_used_event(&vq->vq_ring) = vq->vq_used_cons_idx + ndesc;
else
vq->vq_ring.avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
mb();
/*
* Enough items may have already been consumed to meet our threshold
* since we last checked. Let our caller know so it processes the new
* entries.
*/
if (virtqueue_nused(vq) > ndesc)
return (1);
return (0);
}
static int
vq_ring_must_notify_host(struct virtqueue *vq)
{
@ -788,8 +780,8 @@ vq_ring_free_chain(struct virtqueue *vq, uint16_t desc_idx)
VQ_RING_ASSERT_CHAIN_TERM(vq);
vq->vq_free_cnt += dxp->ndescs;
dxp->ndescs--;
#ifdef INVARIANTS
if ((dp->flags & VRING_DESC_F_INDIRECT) == 0) {
while (dp->flags & VRING_DESC_F_NEXT) {
VQ_RING_ASSERT_VALID_IDX(vq, dp->next);
@ -797,7 +789,10 @@ vq_ring_free_chain(struct virtqueue *vq, uint16_t desc_idx)
dxp->ndescs--;
}
}
VQASSERT(vq, dxp->ndescs == 0, "failed to free entire desc chain");
VQASSERT(vq, dxp->ndescs == 1,
"failed to free entire desc chain, remaining: %d", dxp->ndescs);
#endif
dxp->ndescs = 0;
/*
* We must append the existing free chain, if any, to the end of

View File

@ -35,11 +35,7 @@ struct sglist;
/* Support for indirect buffer descriptors. */
#define VIRTIO_RING_F_INDIRECT_DESC (1 << 28)
/* The guest publishes the used index for which it expects an interrupt
* at the end of the avail ring. Host should ignore the avail->flags field.
* The host publishes the avail index for which it expects a kick
* at the end of the used ring. Guest should ignore the used->flags field.
*/
/* Support to suppress interrupt until specific index is reached. */
#define VIRTIO_RING_F_EVENT_IDX (1 << 29)
/* Device callback for a virtqueue interrupt. */