Add a sc_driver_features field that is automatically used by
virtio_negotiate_features() and during reinit.

Make virtio_negotiate_features() return an error code.  Virtio 1.0 has a
special status bit for feature negotiation that means that negotiation
can fail. Make virtio_negotiate_features() return an error code instead
of the features.

Make virtio_reinit_start() automatically call
virtio_negotiate_features().

Add a convenience function virtio_has_feature() to make checking bits
easier.

Add an error check in viomb for virtio_negotiate_features because it has
some feature bits that may cause negotiation to fail. More error
checking in the child drivers is still missing.
---
 sys/dev/fdt/virtio_mmio.c | 38 ++++++++++++++++++++++++++------------
 sys/dev/pci/virtio_pci.c  | 39 +++++++++++++++++++++++++++------------
 sys/dev/pv/if_vio.c       | 38 +++++++++++++-------------------------
 sys/dev/pv/vioblk.c       | 25 +++++++++----------------
 sys/dev/pv/viocon.c       |  6 +++---
 sys/dev/pv/viomb.c        |  9 +++++----
 sys/dev/pv/viornd.c       |  2 +-
 sys/dev/pv/vioscsi.c      |  2 +-
 sys/dev/pv/virtio.c       | 13 +++++++------
 sys/dev/pv/virtiovar.h    | 15 ++++++++++++---
 sys/dev/pv/vmmci.c        | 13 ++++++-------
 11 files changed, 110 insertions(+), 90 deletions(-)

diff --git a/sys/dev/fdt/virtio_mmio.c b/sys/dev/fdt/virtio_mmio.c
index 2d7a131125d..6e09fb0e76f 100644
--- a/sys/dev/fdt/virtio_mmio.c
+++ b/sys/dev/fdt/virtio_mmio.c
@@ -91,8 +91,8 @@ 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_set_status(struct virtio_softc *, int);
-uint64_t       virtio_mmio_negotiate_features(struct virtio_softc *, uint64_t,
-                                             const struct virtio_feature_name 
*);
+int            virtio_mmio_negotiate_features(struct virtio_softc *,
+    const struct virtio_feature_name *);
 int            virtio_mmio_intr(void *);
 
 struct virtio_mmio_softc {
@@ -300,28 +300,42 @@ virtio_mmio_detach(struct device *self, int flags)
  * Prints available / negotiated features if guest_feature_names != NULL and
  * VIRTIO_DEBUG is 1
  */
-uint64_t
-virtio_mmio_negotiate_features(struct virtio_softc *vsc, uint64_t 
guest_features,
-                         const struct virtio_feature_name *guest_feature_names)
+int
+virtio_mmio_negotiate_features(struct virtio_softc *vsc,
+    const struct virtio_feature_name *guest_feature_names)
 {
        struct virtio_mmio_softc *sc = (struct virtio_mmio_softc *)vsc;
        uint64_t host, neg;
 
+       vsc->sc_active_features = 0;
+
        /*
-        * indirect descriptors can be switched off by setting bit 1 in the
-        * driver flags, see config(8)
+        * We enable indirect descriptors by default. They can be switched
+        * off by setting bit 1 in the driver flags, see config(8).
         */
        if (!(vsc->sc_dev.dv_cfdata->cf_flags & VIRTIO_CF_NO_INDIRECT) &&
            !(vsc->sc_child->dv_cfdata->cf_flags & VIRTIO_CF_NO_INDIRECT)) {
-               guest_features |= VIRTIO_F_RING_INDIRECT_DESC;
-       } else {
+               vsc->sc_driver_features |= VIRTIO_F_RING_INDIRECT_DESC;
+       } else if (guest_feature_names != NULL) {
                printf("RingIndirectDesc disabled by UKC\n");
        }
+       /*
+        * The driver must add VIRTIO_F_RING_EVENT_IDX if it supports it.
+        * If it did, check if it is disabled by bit 2 in the driver flags.
+        */
+       if ((vsc->sc_driver_features & VIRTIO_F_RING_EVENT_IDX) &&
+           ((vsc->sc_dev.dv_cfdata->cf_flags & VIRTIO_CF_NO_EVENT_IDX) ||
+           (vsc->sc_child->dv_cfdata->cf_flags & VIRTIO_CF_NO_EVENT_IDX))) {
+               if (guest_feature_names != NULL)
+                       printf(" RingEventIdx disabled by UKC");
+               vsc->sc_driver_features &= ~(VIRTIO_F_RING_EVENT_IDX);
+       }
+
        bus_space_write_4(sc->sc_iot, sc->sc_ioh,
            VIRTIO_MMIO_HOST_FEATURES_SEL, 0);
        host = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
                                VIRTIO_MMIO_HOST_FEATURES);
-       neg = host & guest_features;
+       neg = host & vsc->sc_driver_features;
 #if VIRTIO_DEBUG
        if (guest_feature_names)
                virtio_log_features(host, neg, guest_feature_names);
@@ -330,13 +344,13 @@ virtio_mmio_negotiate_features(struct virtio_softc *vsc, 
uint64_t guest_features
            VIRTIO_MMIO_GUEST_FEATURES_SEL, 0);
        bus_space_write_4(sc->sc_iot, sc->sc_ioh,
                          VIRTIO_MMIO_GUEST_FEATURES, neg);
-       vsc->sc_features = neg;
+       vsc->sc_active_features = neg;
        if (neg & VIRTIO_F_RING_INDIRECT_DESC)
                vsc->sc_indirect = 1;
        else
                vsc->sc_indirect = 0;
 
-       return neg;
+       return 0;
 }
 
 /*
diff --git a/sys/dev/pci/virtio_pci.c b/sys/dev/pci/virtio_pci.c
index 7be93684a68..f370f513e85 100644
--- a/sys/dev/pci/virtio_pci.c
+++ b/sys/dev/pci/virtio_pci.c
@@ -67,8 +67,8 @@ 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_set_status(struct virtio_softc *, int);
-uint64_t       virtio_pci_negotiate_features(struct virtio_softc *, uint64_t,
-                                             const struct virtio_feature_name 
*);
+int            virtio_pci_negotiate_features(struct virtio_softc *,
+    const struct virtio_feature_name *);
 void           virtio_pci_set_msix_queue_vector(struct virtio_pci_softc *, 
uint32_t, uint16_t);
 void           virtio_pci_set_msix_config_vector(struct virtio_pci_softc *, 
uint16_t);
 int            virtio_pci_msix_establish(struct virtio_pci_softc *, struct 
pci_attach_args *, int, int (*)(void *), void *);
@@ -374,39 +374,54 @@ virtio_pci_adjust_config_region(struct virtio_pci_softc 
*sc)
  * Prints available / negotiated features if guest_feature_names != NULL and
  * VIRTIO_DEBUG is 1
  */
-uint64_t
-virtio_pci_negotiate_features(struct virtio_softc *vsc, uint64_t 
guest_features,
+int
+virtio_pci_negotiate_features(struct virtio_softc *vsc,
     const struct virtio_feature_name *guest_feature_names)
 {
        struct virtio_pci_softc *sc = (struct virtio_pci_softc *)vsc;
        uint64_t host, neg;
 
+       vsc->sc_active_features = 0;
+
        /*
-        * indirect descriptors can be switched off by setting bit 1 in the
-        * driver flags, see config(8)
+        * We enable indirect descriptors by default. They can be switched
+        * off by setting bit 1 in the driver flags, see config(8)
         */
        if (!(vsc->sc_dev.dv_cfdata->cf_flags & VIRTIO_CF_NO_INDIRECT) &&
            !(vsc->sc_child->dv_cfdata->cf_flags & VIRTIO_CF_NO_INDIRECT)) {
-               guest_features |= VIRTIO_F_RING_INDIRECT_DESC;
-       } else {
-               printf("RingIndirectDesc disabled by UKC\n");
+               vsc->sc_driver_features |= VIRTIO_F_RING_INDIRECT_DESC;
+       } else if (guest_feature_names != NULL) {
+               printf(" RingIndirectDesc disabled by UKC");
+       }
+
+       /*
+        * The driver must add VIRTIO_F_RING_EVENT_IDX if it supports it.
+        * If it did, check if it is disabled by bit 2 in the driver flags.
+        */
+       if ((vsc->sc_driver_features & VIRTIO_F_RING_EVENT_IDX) &&
+           ((vsc->sc_dev.dv_cfdata->cf_flags & VIRTIO_CF_NO_EVENT_IDX) ||
+           (vsc->sc_child->dv_cfdata->cf_flags & VIRTIO_CF_NO_EVENT_IDX))) {
+               if (guest_feature_names != NULL)
+                       printf(" RingEventIdx disabled by UKC");
+               vsc->sc_driver_features &= ~(VIRTIO_F_RING_EVENT_IDX);
        }
+
        host = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
                                VIRTIO_CONFIG_DEVICE_FEATURES);
-       neg = host & guest_features;
+       neg = host & vsc->sc_driver_features;
 #if VIRTIO_DEBUG
        if (guest_feature_names)
                virtio_log_features(host, neg, guest_feature_names);
 #endif
        bus_space_write_4(sc->sc_iot, sc->sc_ioh,
                          VIRTIO_CONFIG_GUEST_FEATURES, neg);
-       vsc->sc_features = neg;
+       vsc->sc_active_features = neg;
        if (neg & VIRTIO_F_RING_INDIRECT_DESC)
                vsc->sc_indirect = 1;
        else
                vsc->sc_indirect = 0;
 
-       return neg;
+       return 0;
 }
 
 /*
diff --git a/sys/dev/pv/if_vio.c b/sys/dev/pv/if_vio.c
index bfc7cfd1ddc..252ad2fdf9b 100644
--- a/sys/dev/pv/if_vio.c
+++ b/sys/dev/pv/if_vio.c
@@ -515,7 +515,6 @@ 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;
-       uint64_t features;
        int i;
        struct ifnet *ifp = &sc->sc_ac.ac_if;
 
@@ -531,23 +530,13 @@ vio_attach(struct device *parent, struct device *self, 
void *aux)
        vsc->sc_ipl = IPL_NET;
        vsc->sc_vqs = &sc->sc_vq[0];
        vsc->sc_config_change = 0;
-
-       features = VIRTIO_NET_F_MAC | VIRTIO_NET_F_STATUS |
+       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;
-       /*
-        * VIRTIO_F_RING_EVENT_IDX can be switched off by setting bit 2 in the
-        * driver flags, see config(8)
-        */
-       if (!(sc->sc_dev.dv_cfdata->cf_flags & VIRTIO_CF_NO_EVENT_IDX) &&
-           !(vsc->sc_dev.dv_cfdata->cf_flags & VIRTIO_CF_NO_EVENT_IDX))
-               features |= VIRTIO_F_RING_EVENT_IDX;
-       else
-               printf(": RingEventIdx disabled by UKC");
+           VIRTIO_NET_F_MRG_RXBUF | VIRTIO_NET_F_CSUM |
+           VIRTIO_F_RING_EVENT_IDX;
 
-       features = virtio_negotiate_features(vsc, features,
-           virtio_net_feature_names);
-       if (features & VIRTIO_NET_F_MAC) {
+       virtio_negotiate_features(vsc, virtio_net_feature_names);
+       if (virtio_has_feature(vsc, VIRTIO_NET_F_MAC)) {
                vio_get_lladr(&sc->sc_ac, vsc);
        } else {
                ether_fakeaddr(ifp);
@@ -555,7 +544,7 @@ vio_attach(struct device *parent, struct device *self, void 
*aux)
        }
        printf(": address %s\n", ether_sprintf(sc->sc_ac.ac_enaddr));
 
-       if (features & VIRTIO_NET_F_MRG_RXBUF) {
+       if (virtio_has_feature(vsc, VIRTIO_NET_F_MRG_RXBUF)) {
                sc->sc_hdr_size = sizeof(struct virtio_net_hdr);
                ifp->if_hardmtu = 16000; /* arbitrary limit */
        } else {
@@ -575,12 +564,12 @@ vio_attach(struct device *parent, struct device *self, 
void *aux)
        vsc->sc_nvqs = 2;
        sc->sc_vq[VQTX].vq_done = vio_tx_intr;
        virtio_start_vq_intr(vsc, &sc->sc_vq[VQRX]);
-       if (features & VIRTIO_F_RING_EVENT_IDX)
+       if (virtio_has_feature(vsc, VIRTIO_F_RING_EVENT_IDX))
                virtio_postpone_intr_far(&sc->sc_vq[VQTX]);
        else
                virtio_stop_vq_intr(vsc, &sc->sc_vq[VQTX]);
-       if ((features & VIRTIO_NET_F_CTRL_VQ)
-           && (features & VIRTIO_NET_F_CTRL_RX)) {
+       if (virtio_has_feature(vsc, VIRTIO_NET_F_CTRL_VQ)
+           && virtio_has_feature(vsc, VIRTIO_NET_F_CTRL_RX)) {
                if (virtio_alloc_vq(vsc, &sc->sc_vq[VQCTL], 2, NBPG, 1,
                    "control") == 0) {
                        sc->sc_vq[VQCTL].vq_done = vio_ctrleof;
@@ -598,7 +587,7 @@ vio_attach(struct device *parent, struct device *self, void 
*aux)
        ifp->if_start = vio_start;
        ifp->if_ioctl = vio_ioctl;
        ifp->if_capabilities = IFCAP_VLAN_MTU;
-       if (features & VIRTIO_NET_F_CSUM)
+       if (virtio_has_feature(vsc, VIRTIO_NET_F_CSUM))
                ifp->if_capabilities |= IFCAP_CSUM_TCPv4|IFCAP_CSUM_UDPv4;
        IFQ_SET_MAXLEN(&ifp->if_snd, vsc->sc_vqs[1].vq_num - 1);
        ifmedia_init(&sc->sc_media, 0, vio_media_change, vio_media_status);
@@ -629,7 +618,7 @@ vio_link_state(struct ifnet *ifp)
        struct virtio_softc *vsc = sc->sc_virtio;
        int link_state = LINK_STATE_FULL_DUPLEX;
 
-       if (vsc->sc_features & VIRTIO_NET_F_STATUS) {
+       if (virtio_has_feature(vsc, VIRTIO_NET_F_STATUS)) {
                int status = virtio_read_device_config_2(vsc,
                    VIRTIO_NET_CONFIG_STATUS);
                if (!(status & VIRTIO_NET_S_LINK_UP))
@@ -706,7 +695,6 @@ vio_stop(struct ifnet *ifp, int disable)
                vio_rx_drain(sc);
 
        virtio_reinit_start(vsc);
-       virtio_negotiate_features(vsc, vsc->sc_features, NULL);
        virtio_start_vq_intr(vsc, &sc->sc_vq[VQRX]);
        virtio_stop_vq_intr(vsc, &sc->sc_vq[VQTX]);
        if (vsc->sc_nvqs >= 3)
@@ -815,7 +803,7 @@ again:
        }
        if (ifq_is_oactive(&ifp->if_snd)) {
                int r;
-               if (vsc->sc_features & VIRTIO_F_RING_EVENT_IDX)
+               if (virtio_has_feature(vsc, VIRTIO_F_RING_EVENT_IDX))
                        r = virtio_postpone_intr_smart(&sc->sc_vq[VQTX]);
                else
                        r = virtio_start_vq_intr(vsc, &sc->sc_vq[VQTX]);
@@ -1069,7 +1057,7 @@ again:
        if (r) {
                vio_populate_rx_mbufs(sc);
                /* set used event index to the next slot */
-               if (vsc->sc_features & VIRTIO_F_RING_EVENT_IDX) {
+               if (virtio_has_feature(vsc, VIRTIO_F_RING_EVENT_IDX)) {
                        if (virtio_start_vq_intr(vq->vq_owner, vq))
                                goto again;
                }
diff --git a/sys/dev/pv/vioblk.c b/sys/dev/pv/vioblk.c
index 460c2dfa766..9fc6e82f89e 100644
--- a/sys/dev/pv/vioblk.c
+++ b/sys/dev/pv/vioblk.c
@@ -171,7 +171,6 @@ 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 scsibus_attach_args saa;
-       uint64_t features;
        int qsize;
 
        vsc->sc_vqs = &sc->sc_vq[0];
@@ -182,15 +181,12 @@ vioblk_attach(struct device *parent, struct device *self, 
void *aux)
        vsc->sc_child = self;
        vsc->sc_ipl = IPL_BIO;
        sc->sc_virtio = vsc;
+       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;
 
-        features = virtio_negotiate_features(vsc,
-           (VIRTIO_BLK_F_RO       | VIRTIO_F_NOTIFY_ON_EMPTY |
-            VIRTIO_BLK_F_SIZE_MAX | VIRTIO_BLK_F_SEG_MAX |
-            VIRTIO_BLK_F_FLUSH),
-           vioblk_feature_names);
+        virtio_negotiate_features(vsc, vioblk_feature_names);
 
-
-       if (features & VIRTIO_BLK_F_SIZE_MAX) {
+       if (virtio_has_feature(vsc, VIRTIO_BLK_F_SIZE_MAX)) {
                uint32_t size_max = virtio_read_device_config_4(vsc,
                    VIRTIO_BLK_CONFIG_SIZE_MAX);
                if (size_max < PAGE_SIZE) {
@@ -199,7 +195,7 @@ vioblk_attach(struct device *parent, struct device *self, 
void *aux)
                }
        }
 
-       if (features & VIRTIO_BLK_F_SEG_MAX) {
+       if (virtio_has_feature(vsc, VIRTIO_BLK_F_SEG_MAX)) {
                uint32_t seg_max = virtio_read_device_config_4(vsc,
                    VIRTIO_BLK_CONFIG_SEG_MAX);
                if (seg_max < SEG_MAX) {
@@ -220,7 +216,7 @@ vioblk_attach(struct device *parent, struct device *self, 
void *aux)
        qsize = sc->sc_vq[0].vq_num;
        sc->sc_vq[0].vq_done = vioblk_vq_done;
 
-       if (features & VIRTIO_F_NOTIFY_ON_EMPTY) {
+       if (virtio_has_feature(vsc, VIRTIO_F_NOTIFY_ON_EMPTY)) {
                virtio_stop_vq_intr(vsc, &sc->sc_vq[0]);
                sc->sc_notify_on_empty = 1;
        }
@@ -252,7 +248,7 @@ vioblk_attach(struct device *parent, struct device *self, 
void *aux)
        sc->sc_link.luns = 1;
        sc->sc_link.adapter_target = 2;
        DNPRINTF(1, "%s: qsize: %d\n", __func__, qsize);
-       if (features & VIRTIO_BLK_F_RO)
+       if (virtio_has_feature(vsc, VIRTIO_BLK_F_RO))
                sc->sc_link.flags |= SDEV_READONLY;
 
        bzero(&saa, sizeof(saa));
@@ -430,7 +426,7 @@ vioblk_scsi_cmd(struct scsi_xfer *xs)
                break;
 
        case SYNCHRONIZE_CACHE:
-               if ((vsc->sc_features & VIRTIO_BLK_F_FLUSH) == 0) {
+               if (!virtio_has_feature(vsc, VIRTIO_BLK_F_FLUSH)) {
                        vioblk_scsi_done(xs, XS_NOERROR);
                        return;
                }
@@ -553,13 +549,10 @@ vioblk_scsi_cmd(struct scsi_xfer *xs)
                delay(1000);
        } while(--timeout > 0);
        if (timeout <= 0) {
-               uint32_t features;
                printf("%s: SCSI_POLL timed out\n", __func__);
                vioblk_reset(sc);
                virtio_reinit_start(vsc);
-               features = virtio_negotiate_features(vsc, vsc->sc_features,
-                   NULL);
-               KASSERT(features == vsc->sc_features);
+               virtio_reinit_end(vsc);
        }
        splx(s);
        return;
diff --git a/sys/dev/pv/viocon.c b/sys/dev/pv/viocon.c
index ed73152bbdd..a47eae33d39 100644
--- a/sys/dev/pv/viocon.c
+++ b/sys/dev/pv/viocon.c
@@ -193,8 +193,8 @@ viocon_attach(struct device *parent, struct device *self, 
void *aux)
                goto err;
        }
 
-       virtio_negotiate_features(vsc, VIRTIO_CONSOLE_F_SIZE,
-           viocon_feature_names);
+       vsc->sc_driver_features = VIRTIO_CONSOLE_F_SIZE;
+       virtio_negotiate_features(vsc, viocon_feature_names);
 
        printf("\n");
        DPRINTF("%s: softc: %p\n", __func__, sc);
@@ -277,7 +277,7 @@ viocon_port_create(struct viocon_softc *sc, int portidx)
         */
        vp->vp_tx_buf = vp->vp_rx_buf + vp->vp_rx->vq_num * BUFSIZE;
 
-       if (vsc->sc_features & VIRTIO_CONSOLE_F_SIZE) {
+       if (virtio_has_feature(vsc, VIRTIO_CONSOLE_F_SIZE)) {
                vp->vp_cols = virtio_read_device_config_2(vsc,
                    VIRTIO_CONSOLE_COLS);
                vp->vp_rows = virtio_read_device_config_2(vsc,
diff --git a/sys/dev/pv/viomb.c b/sys/dev/pv/viomb.c
index 3be83eed562..71e92abf8a4 100644
--- a/sys/dev/pv/viomb.c
+++ b/sys/dev/pv/viomb.c
@@ -158,8 +158,9 @@ viomb_attach(struct device *parent, struct device *self, 
void *aux)
        vsc->sc_ipl = IPL_BIO;
        vsc->sc_config_change = viomb_config_change;
 
-       virtio_negotiate_features(vsc, VIRTIO_BALLOON_F_MUST_TELL_HOST,
-           viomb_feature_names);
+       vsc->sc_driver_features = VIRTIO_BALLOON_F_MUST_TELL_HOST;
+       if (virtio_negotiate_features(vsc, viomb_feature_names) != 0)
+               goto err;
 
        if ((virtio_alloc_vq(vsc, &sc->sc_vq[VQ_INFLATE], VQ_INFLATE,
             sizeof(u_int32_t) * PGS_PER_REQ, 1, "inflate") != 0))
@@ -365,7 +366,7 @@ viomb_deflate(struct viomb_softc *sc)
        virtio_enqueue_p(vq, slot, b->bl_dmamap, 0,
                         sizeof(u_int32_t) * nvpages, VRING_READ);
 
-       if (!(vsc->sc_features & VIRTIO_BALLOON_F_MUST_TELL_HOST))
+       if (!virtio_has_feature(vsc, VIRTIO_BALLOON_F_MUST_TELL_HOST))
                uvm_pglistfree(&b->bl_pglist);
        virtio_enqueue_commit(vsc, vq, slot, VRING_NOTIFY);
        return;
@@ -463,7 +464,7 @@ viomb_deflate_intr(struct virtqueue *vq)
                        sizeof(u_int32_t) * nvpages,
                        BUS_DMASYNC_POSTWRITE);
 
-       if (vsc->sc_features & VIRTIO_BALLOON_F_MUST_TELL_HOST)
+       if (virtio_has_feature(vsc, VIRTIO_BALLOON_F_MUST_TELL_HOST))
                uvm_pglistfree(&b->bl_pglist);
 
        VIOMBDEBUG(sc, "updating sc->sc_actual from %u to %llu\n",
diff --git a/sys/dev/pv/viornd.c b/sys/dev/pv/viornd.c
index 9d1e8c60938..0886d052310 100644
--- a/sys/dev/pv/viornd.c
+++ b/sys/dev/pv/viornd.c
@@ -96,7 +96,7 @@ viornd_attach(struct device *parent, struct device *self, 
void *aux)
        vsc->sc_ipl = IPL_NET;
        sc->sc_virtio = vsc;
 
-       virtio_negotiate_features(vsc, 0, NULL);
+       virtio_negotiate_features(vsc, NULL);
 
        if (sc->sc_dev.dv_cfdata->cf_flags & VIORND_ONESHOT) {
                sc->sc_interval = 0;
diff --git a/sys/dev/pv/vioscsi.c b/sys/dev/pv/vioscsi.c
index a7ccd8913b3..ad18ec151ec 100644
--- a/sys/dev/pv/vioscsi.c
+++ b/sys/dev/pv/vioscsi.c
@@ -126,7 +126,7 @@ 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, 0, NULL);
+       virtio_negotiate_features(vsc, NULL);
        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,
diff --git a/sys/dev/pv/virtio.c b/sys/dev/pv/virtio.c
index 40ea0b8a85e..904943420e0 100644
--- a/sys/dev/pv/virtio.c
+++ b/sys/dev/pv/virtio.c
@@ -126,15 +126,15 @@ virtio_log_features(uint64_t host, uint64_t neg,
  *     <dequeue finished requests>; // virtio_dequeue() still can be called
  *     <revoke pending requests in the vqs if any>;
  *     virtio_reinit_start(sc);     // dequeue prohibitted
- *     newfeatures = virtio_negotiate_features(sc, requestedfeatures);
  *     <some other initialization>;
  *     virtio_reinit_end(sc);       // device activated; enqueue allowed
- * Once attached, feature negotiation can only be allowed after virtio_reset.
+ * Once attached, features are assumed to not change again.
  */
 void
 virtio_reset(struct virtio_softc *sc)
 {
        virtio_device_reset(sc);
+       sc->sc_active_features = 0;
 }
 
 void
@@ -144,6 +144,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);
        for (i = 0; i < sc->sc_nvqs; i++) {
                int n;
                struct virtqueue *vq = &sc->sc_vqs[i];
@@ -300,7 +301,7 @@ virtio_alloc_vq(struct virtio_softc *sc, struct virtqueue 
*vq, int index,
        if (((vq_size - 1) & vq_size) != 0)
                panic("vq_size not power of two: %d", vq_size);
 
-       hdrlen = (sc->sc_features & VIRTIO_F_RING_EVENT_IDX) ? 3 : 2;
+       hdrlen = virtio_has_feature(sc, VIRTIO_F_RING_EVENT_IDX) ? 3 : 2;
 
        /* allocsize1: descriptor table + avail ring + pad */
        allocsize1 = VIRTQUEUE_ALIGN(sizeof(struct vring_desc) * vq_size
@@ -677,7 +678,7 @@ virtio_enqueue_commit(struct virtio_softc *sc, struct 
virtqueue *vq, int slot,
 
 notify:
        if (notifynow) {
-               if (vq->vq_owner->sc_features & VIRTIO_F_RING_EVENT_IDX) {
+               if (virtio_has_feature(vq->vq_owner, VIRTIO_F_RING_EVENT_IDX)) {
                        uint16_t o = vq->vq_avail->idx;
                        uint16_t n = vq->vq_avail_idx;
                        uint16_t t;
@@ -874,7 +875,7 @@ virtio_postpone_intr_far(struct virtqueue *vq)
 void
 virtio_stop_vq_intr(struct virtio_softc *sc, struct virtqueue *vq)
 {
-       if ((sc->sc_features & VIRTIO_F_RING_EVENT_IDX)) {
+       if (virtio_has_feature(sc, VIRTIO_F_RING_EVENT_IDX)) {
                /*
                 * No way to disable the interrupt completely with
                 * RingEventIdx. Instead advance used_event by half
@@ -898,7 +899,7 @@ virtio_start_vq_intr(struct virtio_softc *sc, struct 
virtqueue *vq)
         * interrupts is done through setting the latest
         * consumed index in the used_event field
         */
-       if (sc->sc_features & VIRTIO_F_RING_EVENT_IDX)
+       if (virtio_has_feature(sc, VIRTIO_F_RING_EVENT_IDX))
                VQ_USED_EVENT(vq) = vq->vq_used_idx;
        else
                vq->vq_avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
diff --git a/sys/dev/pv/virtiovar.h b/sys/dev/pv/virtiovar.h
index 4a52907a4a5..2b43086a4e9 100644
--- a/sys/dev/pv/virtiovar.h
+++ b/sys/dev/pv/virtiovar.h
@@ -151,7 +151,7 @@ struct virtio_ops {
        uint16_t        (*read_queue_size)(struct virtio_softc *, uint16_t);
        void            (*setup_queue)(struct virtio_softc *, struct virtqueue 
*, uint64_t);
        void            (*set_status)(struct virtio_softc *, int);
-       uint64_t        (*neg_features)(struct virtio_softc *, uint64_t, const 
struct virtio_feature_name *);
+       int             (*neg_features)(struct virtio_softc *, const struct 
virtio_feature_name *);
        int             (*poll_intr)(void *);
 };
 
@@ -164,7 +164,8 @@ struct virtio_softc {
 
        int                      sc_ipl;                /* set by child */
 
-       uint64_t                 sc_features;
+       uint64_t                 sc_driver_features;
+       uint64_t                 sc_active_features;
        int                      sc_indirect;
 
        int                      sc_nvqs;       /* set by child */
@@ -189,13 +190,21 @@ struct virtio_softc {
 #define        virtio_write_device_config_8(sc, o, v)  
(sc)->sc_ops->write_dev_cfg_8(sc, o, v)
 #define        virtio_read_queue_size(sc, i)           
(sc)->sc_ops->read_queue_size(sc, i)
 #define        virtio_setup_queue(sc, i, v)            
(sc)->sc_ops->setup_queue(sc, i, v)
-#define        virtio_negotiate_features(sc, f, n)     
(sc)->sc_ops->neg_features(sc, f, n)
+#define        virtio_negotiate_features(sc, n)        
(sc)->sc_ops->neg_features(sc, n)
 #define        virtio_poll_intr(sc)                    
(sc)->sc_ops->poll_intr(sc)
 
 /* only for transport drivers */
 #define        virtio_set_status(sc, i)                
(sc)->sc_ops->set_status(sc, i)
 #define        virtio_device_reset(sc)                 virtio_set_status((sc), 
0)
 
+static inline int
+virtio_has_feature(struct virtio_softc *sc, unsigned int fbit)
+{
+       if (sc->sc_active_features & fbit)
+               return 1;
+       return 0;
+}
+
 int virtio_alloc_vq(struct virtio_softc*, struct virtqueue*, int, int, int,
                    const char*);
 int virtio_free_vq(struct virtio_softc*, struct virtqueue*);
diff --git a/sys/dev/pv/vmmci.c b/sys/dev/pv/vmmci.c
index 80ba1224e3b..2313b11c71e 100644
--- a/sys/dev/pv/vmmci.c
+++ b/sys/dev/pv/vmmci.c
@@ -94,7 +94,6 @@ 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;
-       uint64_t features;
 
        if (vsc->sc_child != NULL)
                panic("already attached to something else");
@@ -105,10 +104,11 @@ vmmci_attach(struct device *parent, struct device *self, 
void *aux)
        vsc->sc_ipl = IPL_NET;
        sc->sc_virtio = vsc;
 
-       features = VMMCI_F_TIMESYNC | VMMCI_F_ACK | VMMCI_F_SYNCRTC;
-       features = virtio_negotiate_features(vsc, features, NULL);
+       vsc->sc_driver_features = VMMCI_F_TIMESYNC | VMMCI_F_ACK |
+           VMMCI_F_SYNCRTC;
+       virtio_negotiate_features(vsc, NULL);
 
-       if (features & VMMCI_F_TIMESYNC) {
+       if (virtio_has_feature(vsc, VMMCI_F_TIMESYNC)) {
                strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
                    sizeof(sc->sc_sensordev.xname));
                sc->sc_sensor.type = SENSOR_TIMEDELTA;
@@ -128,7 +128,7 @@ vmmci_activate(struct device *self, int act)
        struct vmmci_softc      *sc = (struct vmmci_softc *)self;
        struct virtio_softc     *vsc = sc->sc_virtio;
 
-       if ((vsc->sc_features & VMMCI_F_ACK) == 0)
+       if (virtio_has_feature(vsc, VMMCI_F_ACK) == 0)
                return (0);
 
        switch (act) {
@@ -183,8 +183,7 @@ vmmci_config_change(struct virtio_softc *vsc)
                break;
        }
 
-       if ((cmd != VMMCI_NONE) &&
-           (vsc->sc_features & VMMCI_F_ACK))
+       if ((cmd != VMMCI_NONE) && virtio_has_feature(vsc, VMMCI_F_ACK))
                virtio_write_device_config_4(vsc, VMMCI_CONFIG_COMMAND, cmd);
 
        return (1);
-- 
2.19.0

Reply via email to