Index | Thread | Search

From:
Jan Klemkow <jan@openbsd.org>
Subject:
Re: virtio: Refactor attach logic some more
To:
Stefan Fritsch <sf@openbsd.org>
Cc:
tech@openbsd.org
Date:
Thu, 19 Dec 2024 14:11:50 +0100

Download raw body.

Thread
On Mon, Nov 25, 2024 at 09:00:24PM GMT, Stefan Fritsch wrote:
> here is the next reviewable part from the large virtio multiqueue diff.
> 
> virtio 1.x requires that all queue setup, including the queue interrupt
> vector, is done before setting the queue_enable register to 1. This
> conflicts with how we do things right now:
> 
> * We implicitly make queue setup in virtio_alloc_vq(), which is called
>   from the child driver attach functions. This also sets queue_enable=1.
> 
> * Later, we allocate the interrupts and set the queue interrupt vectors
>   in the second half of the virtio transport attach functions.
> 
> This is a violation of a MUST from the standard and causes problems with 
> some hypervisors, in particular those that support only virtio 1.x. As 0.9 
> has no such ordering requirements, hypervisors with 0.9 support tend to be 
> more tolerant.
> 
> To fix this:
> 
> * Move the interrupt allocation to a new virtio_attach_finish() function.
>   This does all queue setup, including the interrupt vectors.
> 
> * Don't call virtio_setup_queue() in virtio_alloc_vq() anymore.
> 
> * We can also move the setting of the DRIVER_OK flag into this function.
>   virtio_attach_finish() must be called before using any virtqueue or
>   writing any virtio config register.
> 
> While there,
> 
> * also streamline the attach error handling in all drivers.
> 
> * skip initially setting sc_config_change to NULL, the softc is
>   initialized to 0.
> 
> Comments? OK?

Diff looks fine to me.  I also tested it on OpenBSD/VMM (amd64) and
Linux/KVM (amd64 and arm64).

ok jan@

> diff --git a/sys/dev/fdt/virtio_mmio.c b/sys/dev/fdt/virtio_mmio.c
> index 5d7a6db6465..5e0fc4d8e01 100644
> --- a/sys/dev/fdt/virtio_mmio.c
> +++ b/sys/dev/fdt/virtio_mmio.c
> @@ -98,6 +98,7 @@ void		virtio_mmio_write_device_config_8(struct virtio_softc *, int, uint64_t);
>  uint16_t	virtio_mmio_read_queue_size(struct virtio_softc *, uint16_t);
>  void		virtio_mmio_setup_queue(struct virtio_softc *, struct virtqueue *, uint64_t);
>  void		virtio_mmio_setup_intrs(struct virtio_softc *);
> +int		virtio_mmio_attach_finish(struct virtio_softc *, struct virtio_attach_args *);
>  int		virtio_mmio_get_status(struct virtio_softc *);
>  void		virtio_mmio_set_status(struct virtio_softc *, int);
>  int		virtio_mmio_negotiate_features(struct virtio_softc *,
> @@ -119,6 +120,11 @@ struct virtio_mmio_softc {
>  	uint32_t		sc_version;
>  };
>  
> +struct virtio_mmio_attach_args {
> +	struct virtio_attach_args	 vma_va;
> +	struct fdt_attach_args          *vma_fa;
> +};
> +
>  const struct cfattach virtio_mmio_ca = {
>  	sizeof(struct virtio_mmio_softc),
>  	virtio_mmio_match,
> @@ -151,6 +157,7 @@ const struct virtio_ops virtio_mmio_ops = {
>  	virtio_mmio_get_status,
>  	virtio_mmio_set_status,
>  	virtio_mmio_negotiate_features,
> +	virtio_mmio_attach_finish,
>  	virtio_mmio_intr,
>  	virtio_mmio_intr_barrier,
>  };
> @@ -250,7 +257,7 @@ virtio_mmio_attach(struct device *parent, struct device *self, void *aux)
>  	struct virtio_mmio_softc *sc = (struct virtio_mmio_softc *)self;
>  	struct virtio_softc *vsc = &sc->sc_sc;
>  	uint32_t id, magic;
> -	struct virtio_attach_args va = { 0 };
> +	struct virtio_mmio_attach_args vma = { { 0 }, faa };
>  
>  	if (faa->fa_nreg < 1) {
>  		printf(": no register data\n");
> @@ -299,36 +306,43 @@ virtio_mmio_attach(struct device *parent, struct device *self, void *aux)
>  	virtio_mmio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_ACK);
>  	virtio_mmio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER);
>  
> -	va.va_devid = id;
> -	va.va_nintr = 1;
> +	vma.vma_va.va_devid = id;
> +	vma.vma_va.va_nintr = 1;
>  	vsc->sc_child = NULL;
> -	config_found(self, &va, NULL);
> +	config_found(self, &vma, NULL);
>  	if (vsc->sc_child == NULL) {
>  		printf("%s: no matching child driver; not configured\n",
>  		    vsc->sc_dev.dv_xname);
> -		goto fail_1;
> +		goto fail;
>  	}
>  	if (vsc->sc_child == VIRTIO_CHILD_ERROR) {
>  		printf("%s: virtio configuration failed\n",
>  		    vsc->sc_dev.dv_xname);
> -		goto fail_1;
> +		goto fail;
>  	}
>  
> -	sc->sc_ih = fdt_intr_establish(faa->fa_node, vsc->sc_ipl,
> +	return;
> +
> +fail:
> +	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_FAILED);
> +}
> +
> +int
> +virtio_mmio_attach_finish(struct virtio_softc *vsc,
> +    struct virtio_attach_args *va)
> +{
> +	struct virtio_mmio_softc *sc = (struct virtio_mmio_softc *)vsc;
> +	struct virtio_mmio_attach_args *vma =
> +	    (struct virtio_mmio_attach_args *)va;
> +
> +	sc->sc_ih = fdt_intr_establish(vma->vma_fa->fa_node, vsc->sc_ipl,
>  	    virtio_mmio_intr, sc, vsc->sc_dev.dv_xname);
>  	if (sc->sc_ih == NULL) {
>  		printf("%s: couldn't establish interrupt\n",
>  		    vsc->sc_dev.dv_xname);
> -		goto fail_2;
> +		return -EIO;
>  	}
> -
> -	return;
> -
> -fail_2:
> -	config_detach(vsc->sc_child, 0);
> -fail_1:
> -	/* no mmio_mapreg_unmap() or mmio_intr_unmap() */
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_FAILED);
> +	return 0;
>  }
>  
>  int
> diff --git a/sys/dev/pci/virtio_pci.c b/sys/dev/pci/virtio_pci.c
> index ad9ce8a1254..9810e834a63 100644
> --- a/sys/dev/pci/virtio_pci.c
> +++ b/sys/dev/pci/virtio_pci.c
> @@ -74,6 +74,7 @@ void		virtio_pci_write_device_config_8(struct virtio_softc *, int, uint64_t);
>  uint16_t	virtio_pci_read_queue_size(struct virtio_softc *, uint16_t);
>  void		virtio_pci_setup_queue(struct virtio_softc *, struct virtqueue *, uint64_t);
>  void		virtio_pci_setup_intrs(struct virtio_softc *);
> +int		virtio_pci_attach_finish(struct virtio_softc *, struct virtio_attach_args *);
>  int		virtio_pci_get_status(struct virtio_softc *);
>  void		virtio_pci_set_status(struct virtio_softc *, int);
>  int		virtio_pci_negotiate_features(struct virtio_softc *, const struct virtio_feature_name *);
> @@ -175,6 +176,7 @@ const struct virtio_ops virtio_pci_ops = {
>  	virtio_pci_get_status,
>  	virtio_pci_set_status,
>  	virtio_pci_negotiate_features,
> +	virtio_pci_attach_finish,
>  	virtio_pci_poll_intr,
>  	virtio_pci_intr_barrier,
>  };
> @@ -592,8 +594,6 @@ virtio_pci_attach(struct device *parent, struct device *self, void *aux)
>  	pcitag_t tag = pa->pa_tag;
>  	int revision, ret = ENODEV;
>  	pcireg_t id;
> -	char const *intrstr;
> -	pci_intr_handle_t ih;
>  	struct virtio_pci_attach_args vpa = { { 0 }, pa };
>  
>  	revision = PCI_REVISION(pa->pa_class);
> @@ -645,13 +645,13 @@ virtio_pci_attach(struct device *parent, struct device *self, void *aux)
>  	}
>  	if (ret != 0) {
>  		printf(": Cannot attach (%d)\n", ret);
> -		goto fail_0;
> +		goto free;
>  	}
>  
>  	sc->sc_devcfg_offset = VIRTIO_CONFIG_DEVICE_CONFIG_NOMSI;
>  	sc->sc_irq_type = IRQ_NO_MSIX;
>  	if (virtio_pci_adjust_config_region(sc) != 0)
> -		goto fail_0;
> +		goto err;
>  
>  	virtio_device_reset(vsc);
>  	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_ACK);
> @@ -664,25 +664,47 @@ virtio_pci_attach(struct device *parent, struct device *self, void *aux)
>  	if (vsc->sc_child == NULL) {
>  		printf("%s: no matching child driver; not configured\n",
>  		    vsc->sc_dev.dv_xname);
> -		goto fail_1;
> +		goto err;
>  	}
>  	if (vsc->sc_child == VIRTIO_CHILD_ERROR) {
>  		printf("%s: virtio configuration failed\n",
>  		    vsc->sc_dev.dv_xname);
> -		goto fail_1;
> +		goto err;
>  	}
>  
> -	if (virtio_pci_setup_msix(sc, &vpa, 0) == 0) {
> +	return;
> +
> +err:
> +	/* no pci_mapreg_unmap() or pci_intr_unmap() */
> +	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_FAILED);
> +free:
> +	free(sc->sc_intr, M_DEVBUF, sc->sc_nintr * sizeof(*sc->sc_intr));
> +}
> +
> +int
> +virtio_pci_attach_finish(struct virtio_softc *vsc,
> +    struct virtio_attach_args *va)
> +{
> +	struct virtio_pci_softc *sc = (struct virtio_pci_softc *)vsc;
> +	struct virtio_pci_attach_args *vpa =
> +	    (struct virtio_pci_attach_args *)va;
> +	pci_intr_handle_t ih;
> +	pci_chipset_tag_t pc = vpa->vpa_pa->pa_pc;
> +	char const *intrstr;
> +
> +	if (virtio_pci_setup_msix(sc, vpa, 0) == 0) {
>  		sc->sc_irq_type = IRQ_MSIX_PER_VQ;
>  		intrstr = "msix per-VQ";
> -	} else if (virtio_pci_setup_msix(sc, &vpa, 1) == 0) {
> +	} else if (virtio_pci_setup_msix(sc, vpa, 1) == 0) {
>  		sc->sc_irq_type = IRQ_MSIX_SHARED;
>  		intrstr = "msix shared";
>  	} else {
>  		int (*ih_func)(void *) = virtio_pci_legacy_intr;
> -		if (pci_intr_map_msi(pa, &ih) != 0 && pci_intr_map(pa, &ih) != 0) {
> -			printf("%s: couldn't map interrupt\n", vsc->sc_dev.dv_xname);
> -			goto fail_2;
> +		if (pci_intr_map_msi(vpa->vpa_pa, &ih) != 0 &&
> +		    pci_intr_map(vpa->vpa_pa, &ih) != 0) {
> +			printf("%s: couldn't map interrupt\n",
> +			    vsc->sc_dev.dv_xname);
> +			return -EIO;
>  		}
>  		intrstr = pci_intr_string(pc, ih);
>  		/*
> @@ -696,25 +718,17 @@ virtio_pci_attach(struct device *parent, struct device *self, void *aux)
>  		    vsc->sc_ipl | IPL_MPSAFE, ih_func, sc,
>  		    vsc->sc_child->dv_xname);
>  		if (sc->sc_intr[0].ih == NULL) {
> -			printf("%s: couldn't establish interrupt", vsc->sc_dev.dv_xname);
> +			printf("%s: couldn't establish interrupt",
> +			    vsc->sc_dev.dv_xname);
>  			if (intrstr != NULL)
>  				printf(" at %s", intrstr);
>  			printf("\n");
> -			goto fail_2;
> +			return -EIO;
>  		}
>  	}
> -	virtio_pci_setup_intrs(vsc);
> -	printf("%s: %s\n", vsc->sc_dev.dv_xname, intrstr);
>  
> -	return;
> -
> -fail_2:
> -	config_detach(vsc->sc_child, 0);
> -fail_1:
> -	/* no pci_mapreg_unmap() or pci_intr_unmap() */
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_FAILED);
> -fail_0:
> -	free(sc->sc_intr, M_DEVBUF, sc->sc_nintr * sizeof(*sc->sc_intr));
> +	printf("%s: %s\n", vsc->sc_dev.dv_xname, intrstr);
> +	return 0;
>  }
>  
>  int
> diff --git a/sys/dev/pv/if_vio.c b/sys/dev/pv/if_vio.c
> index 3d4d728e02d..9ca8f27eac6 100644
> --- a/sys/dev/pv/if_vio.c
> +++ b/sys/dev/pv/if_vio.c
> @@ -597,6 +597,7 @@ vio_attach(struct device *parent, struct device *self, void *aux)
>  {
>  	struct vio_softc *sc = (struct vio_softc *)self;
>  	struct virtio_softc *vsc = (struct virtio_softc *)parent;
> +	struct virtio_attach_args *va = aux;
>  	int i, tx_max_segments;
>  	struct ifnet *ifp = &sc->sc_ac.ac_if;
>  
> @@ -610,7 +611,6 @@ vio_attach(struct device *parent, struct device *self, void *aux)
>  
>  	vsc->sc_child = self;
>  	vsc->sc_ipl = IPL_NET | IPL_MPSAFE;
> -	vsc->sc_config_change = NULL;
>  	vsc->sc_driver_features = VIRTIO_NET_F_MAC | VIRTIO_NET_F_STATUS |
>  	    VIRTIO_NET_F_CTRL_VQ | VIRTIO_NET_F_CTRL_RX |
>  	    VIRTIO_NET_F_MRG_RXBUF | VIRTIO_NET_F_CSUM |
> @@ -623,7 +623,8 @@ vio_attach(struct device *parent, struct device *self, void *aux)
>  	vsc->sc_driver_features |= VIRTIO_NET_F_GUEST_TSO4;
>  	vsc->sc_driver_features |= VIRTIO_NET_F_GUEST_TSO6;
>  
> -	virtio_negotiate_features(vsc, virtio_net_feature_names);
> +	if (virtio_negotiate_features(vsc, virtio_net_feature_names) != 0)
> +		goto err;
>  
>  	sc->sc_nqueues = 1;
>  	vsc->sc_nvqs = 2 * sc->sc_nqueues;
> @@ -756,7 +757,9 @@ vio_attach(struct device *parent, struct device *self, void *aux)
>  	timeout_set(&sc->sc_txtick, vio_txtick, sc->sc_q[0].viq_txvq);
>  	timeout_set(&sc->sc_rxtick, vio_rxtick, sc->sc_q[0].viq_rxvq);
>  
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> +	if (virtio_attach_finish(vsc, va) != 0)
> +		goto err;
> +
>  	if_attach(ifp);
>  	ether_ifattach(ifp);
>  	vio_link_state(ifp);
> diff --git a/sys/dev/pv/vioblk.c b/sys/dev/pv/vioblk.c
> index 153a2762170..0bf8fdd1ac3 100644
> --- a/sys/dev/pv/vioblk.c
> +++ b/sys/dev/pv/vioblk.c
> @@ -170,12 +170,12 @@ vioblk_attach(struct device *parent, struct device *self, void *aux)
>  {
>  	struct vioblk_softc *sc = (struct vioblk_softc *)self;
>  	struct virtio_softc *vsc = (struct virtio_softc *)parent;
> +	struct virtio_attach_args *va = aux;
>  	struct scsibus_attach_args saa;
>  	int qsize;
>  
>  	vsc->sc_vqs = &sc->sc_vq[0];
>  	vsc->sc_nvqs = 1;
> -	vsc->sc_config_change = NULL;
>  	if (vsc->sc_child)
>  		panic("already attached to something else");
>  	vsc->sc_child = self;
> @@ -184,7 +184,8 @@ vioblk_attach(struct device *parent, struct device *self, void *aux)
>  	vsc->sc_driver_features = VIRTIO_BLK_F_RO | VIRTIO_F_NOTIFY_ON_EMPTY |
>  	     VIRTIO_BLK_F_SIZE_MAX | VIRTIO_BLK_F_SEG_MAX | VIRTIO_BLK_F_FLUSH;
>  
> -        virtio_negotiate_features(vsc, vioblk_feature_names);
> +        if (virtio_negotiate_features(vsc, vioblk_feature_names) != 0)
> +		goto err;
>  
>  	if (virtio_has_feature(vsc, VIRTIO_BLK_F_SIZE_MAX)) {
>  		uint32_t size_max = virtio_read_device_config_4(vsc,
> @@ -252,10 +253,11 @@ vioblk_attach(struct device *parent, struct device *self, void *aux)
>  	saa.saa_quirks = 0;
>  	saa.saa_wwpn = saa.saa_wwnn = 0;
>  
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> +	if (virtio_attach_finish(vsc, va) != 0)
> +		goto err;
>  	config_found(self, &saa, scsiprint);
> -
>  	return;
> +
>  err:
>  	vsc->sc_child = VIRTIO_CHILD_ERROR;
>  	return;
> diff --git a/sys/dev/pv/viocon.c b/sys/dev/pv/viocon.c
> index 681a842cd93..6f097a3309b 100644
> --- a/sys/dev/pv/viocon.c
> +++ b/sys/dev/pv/viocon.c
> @@ -179,7 +179,6 @@ viocon_attach(struct device *parent, struct device *self, void *aux)
>  		panic("already attached to something else");
>  	vsc->sc_child = self;
>  	vsc->sc_ipl = IPL_TTY;
> -	vsc->sc_config_change = NULL;
>  	sc->sc_virtio = vsc;
>  	sc->sc_max_ports = maxports;
>  
> @@ -193,7 +192,8 @@ viocon_attach(struct device *parent, struct device *self, void *aux)
>  	}
>  
>  	vsc->sc_driver_features = VIRTIO_CONSOLE_F_SIZE;
> -	virtio_negotiate_features(vsc, viocon_feature_names);
> +	if (virtio_negotiate_features(vsc, viocon_feature_names) != 0)
> +		goto err;
>  
>  	printf("\n");
>  	DPRINTF("%s: softc: %p\n", __func__, sc);
> @@ -201,10 +201,11 @@ viocon_attach(struct device *parent, struct device *self, void *aux)
>  		printf("\n%s: viocon_port_create failed\n", __func__);
>  		goto err;
>  	}
> +	if (virtio_attach_finish(vsc, va) != 0)
> +		goto err;
>  	viocon_rx_fill(sc->sc_ports[0]);
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> -
>  	return;
> +
>  err:
>  	vsc->sc_child = VIRTIO_CHILD_ERROR;
>  	free(vsc->sc_vqs, M_DEVBUF, 2 * (maxports + 1) * sizeof(struct virtqueue));
> diff --git a/sys/dev/pv/viogpu.c b/sys/dev/pv/viogpu.c
> index cce31d8780e..1bf45b7caa7 100644
> --- a/sys/dev/pv/viogpu.c
> +++ b/sys/dev/pv/viogpu.c
> @@ -150,6 +150,7 @@ viogpu_attach(struct device *parent, struct device *self, void *aux)
>  {
>  	struct viogpu_softc *sc = (struct viogpu_softc *)self;
>  	struct virtio_softc *vsc = (struct virtio_softc *)parent;
> +	struct virtio_attach_args *va = aux;
>  	struct wsemuldisplaydev_attach_args waa;
>  	struct rasops_info *ri = &sc->sc_ri;
>  	uint32_t defattr;
> @@ -161,10 +162,11 @@ viogpu_attach(struct device *parent, struct device *self, void *aux)
>  	}
>  	vsc->sc_child = self;
>  
> -	virtio_negotiate_features(vsc, viogpu_feature_names);
> +	if (virtio_negotiate_features(vsc, viogpu_feature_names) != 0)
> +		goto err;
>  	if (!vsc->sc_version_1) {
>  		printf(": requires virtio version 1\n");
> -		return;
> +		goto err;
>  	}
>  
>  	vsc->sc_ipl = IPL_TTY;
> @@ -175,13 +177,13 @@ viogpu_attach(struct device *parent, struct device *self, void *aux)
>  	vsc->sc_vqs = sc->sc_vqs;
>  	if (virtio_alloc_vq(vsc, &sc->sc_vqs[VQCTRL], VQCTRL, 1, "control")) {
>  		printf(": alloc_vq failed\n");
> -		return;
> +		goto err;
>  	}
>  	sc->sc_vqs[VQCTRL].vq_done = viogpu_vq_done;
>  
>  	if (virtio_alloc_vq(vsc, &sc->sc_vqs[VQCURS], VQCURS, 1, "cursor")) {
>  		printf(": alloc_vq failed\n");
> -		return;
> +		goto err;
>  	}
>  	vsc->sc_nvqs = nitems(sc->sc_vqs);
>  
> @@ -191,7 +193,7 @@ viogpu_attach(struct device *parent, struct device *self, void *aux)
>  	    sc->sc_dma_size, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW,
>  	    &sc->sc_dma_map) != 0) {
>  		printf(": create failed");
> -		goto err;
> +		goto errdma;
>  	}
>  	if (bus_dmamem_alloc(vsc->sc_dmat, sc->sc_dma_size, 16, 0,
>  	    &sc->sc_dma_seg, 1, &nsegs, BUS_DMA_NOWAIT | BUS_DMA_ZERO) != 0) {
> @@ -209,7 +211,8 @@ viogpu_attach(struct device *parent, struct device *self, void *aux)
>  		goto unmap;
>  	}
>  
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> +	if (virtio_attach_finish(vsc, va) != 0)
> +		goto unmap;
>  
>  	if (viogpu_get_display_info(sc) != 0)
>  		goto unmap;
> @@ -302,8 +305,10 @@ free:
>  	bus_dmamem_free(vsc->sc_dmat, &sc->sc_dma_seg, 1);
>  destroy:
>  	bus_dmamap_destroy(vsc->sc_dmat, sc->sc_dma_map);
> -err:
> +errdma:
>  	printf(": DMA setup failed\n");
> +err:
> +	vsc->sc_child = VIRTIO_CHILD_ERROR;
>  	return;
>  }
>  
> diff --git a/sys/dev/pv/viomb.c b/sys/dev/pv/viomb.c
> index e8d16c58102..52eddc30a76 100644
> --- a/sys/dev/pv/viomb.c
> +++ b/sys/dev/pv/viomb.c
> @@ -135,6 +135,7 @@ viomb_attach(struct device *parent, struct device *self, void *aux)
>  {
>  	struct viomb_softc *sc = (struct viomb_softc *)self;
>  	struct virtio_softc *vsc = (struct virtio_softc *)parent;
> +	struct virtio_attach_args *va = aux;
>  	int i;
>  
>  	if (vsc->sc_child != NULL) {
> @@ -219,8 +220,10 @@ viomb_attach(struct device *parent, struct device *self, void *aux)
>  	sensordev_install(&sc->sc_sensdev);
>  
>  	printf("\n");
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> +	if (virtio_attach_finish(vsc, va) != 0)
> +		goto err_dmamap;
>  	return;
> +
>  err_dmamap:
>  	bus_dmamap_destroy(vsc->sc_dmat, sc->sc_req.bl_dmamap);
>  err:
> diff --git a/sys/dev/pv/viornd.c b/sys/dev/pv/viornd.c
> index 8139b4a6a27..f349cc63257 100644
> --- a/sys/dev/pv/viornd.c
> +++ b/sys/dev/pv/viornd.c
> @@ -83,18 +83,19 @@ viornd_attach(struct device *parent, struct device *self, void *aux)
>  {
>  	struct viornd_softc *sc = (struct viornd_softc *)self;
>  	struct virtio_softc *vsc = (struct virtio_softc *)parent;
> +	struct virtio_attach_args *va = aux;
>  	unsigned int shift;
>  
>  	vsc->sc_vqs = &sc->sc_vq;
>  	vsc->sc_nvqs = 1;
> -	vsc->sc_config_change = NULL;
>  	if (vsc->sc_child != NULL)
>  		panic("already attached to something else");
>  	vsc->sc_child = self;
>  	vsc->sc_ipl = IPL_NET;
>  	sc->sc_virtio = vsc;
>  
> -	virtio_negotiate_features(vsc, NULL);
> +	if (virtio_negotiate_features(vsc, NULL) != 0)
> +		goto err;
>  
>  	if (sc->sc_dev.dv_cfdata->cf_flags & VIORND_ONESHOT) {
>  		sc->sc_interval = 0;
> @@ -136,7 +137,8 @@ viornd_attach(struct device *parent, struct device *self, void *aux)
>  	timeout_add(&sc->sc_tick, 1);
>  
>  	printf("\n");
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> +	if (virtio_attach_finish(vsc, va) != 0)
> +		goto err2;
>  	return;
>  err2:
>  	bus_dmamap_destroy(vsc->sc_dmat, sc->sc_dmamap);
> diff --git a/sys/dev/pv/vioscsi.c b/sys/dev/pv/vioscsi.c
> index 41dbc113d0d..0e8f9b41a78 100644
> --- a/sys/dev/pv/vioscsi.c
> +++ b/sys/dev/pv/vioscsi.c
> @@ -105,6 +105,7 @@ vioscsi_attach(struct device *parent, struct device *self, void *aux)
>  {
>  	struct virtio_softc *vsc = (struct virtio_softc *)parent;
>  	struct vioscsi_softc *sc = (struct vioscsi_softc *)self;
> +	struct virtio_attach_args *va = aux;
>  	struct scsibus_attach_args saa;
>  	int i, rv;
>  
> @@ -120,7 +121,8 @@ vioscsi_attach(struct device *parent, struct device *self, void *aux)
>  	vsc->sc_vqs = sc->sc_vqs;
>  	vsc->sc_nvqs = nitems(sc->sc_vqs);
>  
> -	virtio_negotiate_features(vsc, NULL);
> +	if (virtio_negotiate_features(vsc, NULL) != 0)
> +		goto err;
>  	uint32_t cmd_per_lun = virtio_read_device_config_4(vsc,
>  	    VIRTIO_SCSI_CONFIG_CMD_PER_LUN);
>  	uint32_t seg_max = virtio_read_device_config_4(vsc,
> @@ -166,7 +168,8 @@ vioscsi_attach(struct device *parent, struct device *self, void *aux)
>  	saa.saa_quirks = saa.saa_flags = 0;
>  	saa.saa_wwpn = saa.saa_wwnn = 0;
>  
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> +	if (virtio_attach_finish(vsc, va) != 0)
> +		goto err;
>  	config_found(self, &saa, scsiprint);
>  	return;
>  
> diff --git a/sys/dev/pv/virtio.c b/sys/dev/pv/virtio.c
> index 53e75db9a23..bc3dca19a0b 100644
> --- a/sys/dev/pv/virtio.c
> +++ b/sys/dev/pv/virtio.c
> @@ -154,6 +154,25 @@ virtio_reset(struct virtio_softc *sc)
>  	sc->sc_active_features = 0;
>  }
>  
> +int
> +virtio_attach_finish(struct virtio_softc *sc, struct virtio_attach_args *va)
> +{
> +	int i, ret;
> +
> +	ret = sc->sc_ops->attach_finish(sc, va);
> +	if (ret != 0)
> +		return ret;
> +
> +	sc->sc_ops->setup_intrs(sc);
> +	for (i = 0; i < sc->sc_nvqs; i++) {
> +		struct virtqueue *vq = &sc->sc_vqs[i];
> +
> +		virtio_setup_queue(sc, vq, vq->vq_dmamap->dm_segs[0].ds_addr);
> +	}
> +	virtio_set_status(sc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> +	return 0;
> +}
> +
>  void
>  virtio_reinit_start(struct virtio_softc *sc)
>  {
> @@ -162,6 +181,7 @@ virtio_reinit_start(struct virtio_softc *sc)
>  	virtio_set_status(sc, VIRTIO_CONFIG_DEVICE_STATUS_ACK);
>  	virtio_set_status(sc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER);
>  	virtio_negotiate_features(sc, NULL);
> +	sc->sc_ops->setup_intrs(sc);
>  	for (i = 0; i < sc->sc_nvqs; i++) {
>  		int n;
>  		struct virtqueue *vq = &sc->sc_vqs[i];
> @@ -175,7 +195,6 @@ virtio_reinit_start(struct virtio_softc *sc)
>  		virtio_init_vq(sc, vq);
>  		virtio_setup_queue(sc, vq, vq->vq_dmamap->dm_segs[0].ds_addr);
>  	}
> -	sc->sc_ops->setup_intrs(sc);
>  }
>  
>  void
> @@ -421,7 +440,6 @@ virtio_alloc_vq(struct virtio_softc *sc, struct virtqueue *vq, int index,
>  	}
>  
>  	virtio_init_vq(sc, vq);
> -	virtio_setup_queue(sc, vq, vq->vq_dmamap->dm_segs[0].ds_addr);
>  
>  #if VIRTIO_DEBUG
>  	printf("\nallocated %u byte for virtqueue %d for %s, size %d\n",
> diff --git a/sys/dev/pv/virtiovar.h b/sys/dev/pv/virtiovar.h
> index eb15dace89a..c8c09d3d75b 100644
> --- a/sys/dev/pv/virtiovar.h
> +++ b/sys/dev/pv/virtiovar.h
> @@ -161,6 +161,7 @@ struct virtio_ops {
>  	int		(*get_status)(struct virtio_softc *);
>  	void		(*set_status)(struct virtio_softc *, int);
>  	int		(*neg_features)(struct virtio_softc *, const struct virtio_feature_name *);
> +	int		(*attach_finish)(struct virtio_softc *, struct virtio_attach_args *);
>  	int		(*poll_intr)(void *);
>  	void		(*intr_barrier)(struct virtio_softc *);
>  };
> @@ -220,6 +221,7 @@ virtio_has_feature(struct virtio_softc *sc, uint64_t fbit)
>  int virtio_alloc_vq(struct virtio_softc*, struct virtqueue*, int, int,
>  		    const char*);
>  int virtio_free_vq(struct virtio_softc*, struct virtqueue*);
> +int virtio_attach_finish(struct virtio_softc *, struct virtio_attach_args *);
>  void virtio_reset(struct virtio_softc *);
>  void virtio_reinit_start(struct virtio_softc *);
>  void virtio_reinit_end(struct virtio_softc *);
> diff --git a/sys/dev/pv/vmmci.c b/sys/dev/pv/vmmci.c
> index 580228bdb4c..e91e9f7e53e 100644
> --- a/sys/dev/pv/vmmci.c
> +++ b/sys/dev/pv/vmmci.c
> @@ -89,6 +89,7 @@ vmmci_attach(struct device *parent, struct device *self, void *aux)
>  {
>  	struct vmmci_softc *sc = (struct vmmci_softc *)self;
>  	struct virtio_softc *vsc = (struct virtio_softc *)parent;
> +	struct virtio_attach_args *va = aux;
>  
>  	if (vsc->sc_child != NULL)
>  		panic("already attached to something else");
> @@ -101,7 +102,8 @@ vmmci_attach(struct device *parent, struct device *self, void *aux)
>  
>  	vsc->sc_driver_features = VMMCI_F_TIMESYNC | VMMCI_F_ACK |
>  	    VMMCI_F_SYNCRTC;
> -	virtio_negotiate_features(vsc, NULL);
> +	if (virtio_negotiate_features(vsc, NULL) != 0)
> +		goto err;
>  
>  	if (virtio_has_feature(vsc, VMMCI_F_TIMESYNC)) {
>  		strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
> @@ -115,7 +117,12 @@ vmmci_attach(struct device *parent, struct device *self, void *aux)
>  	}
>  
>  	printf("\n");
> -	virtio_set_status(vsc, VIRTIO_CONFIG_DEVICE_STATUS_DRIVER_OK);
> +	if (virtio_attach_finish(vsc, va) != 0)
> +		goto err;
> +	return;
> +
> +err:
> +	vsc->sc_child = VIRTIO_CHILD_ERROR;
>  }
>  
>  int
>