Realize support for MSI config notifiers analogously to MSI-X. The logic
is slightly more complex for legacy MSI as per-vector masking is option
here. Device assignment will be the first user.

Note that this change does not introduce per-vector masking support.
This can to be added at some later point, using the notifications the
MSI layer provides now.

Signed-off-by: Jan Kiszka <jan.kis...@siemens.com>
---
 hw/msi.c |  171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 hw/msi.h |    7 ++-
 hw/pci.c |    2 +-
 hw/pci.h |    3 +
 4 files changed, 166 insertions(+), 17 deletions(-)

diff --git a/hw/msi.c b/hw/msi.c
index 23d79dd..2380ee3 100644
--- a/hw/msi.c
+++ b/hw/msi.c
@@ -241,15 +241,15 @@ void msi_uninit(struct PCIDevice *dev)
 
 void msi_reset(PCIDevice *dev)
 {
-    uint16_t flags;
+    uint16_t flags, old_flags;
     bool msi64bit;
 
     if (!msi_present(dev)) {
         return;
     }
 
-    flags = pci_get_word(dev->config + msi_flags_off(dev));
-    flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
+    old_flags = pci_get_word(dev->config + msi_flags_off(dev));
+    flags = old_flags & ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
     msi64bit = flags & PCI_MSI_FLAGS_64BIT;
 
     pci_set_word(dev->config + msi_flags_off(dev), flags);
@@ -262,6 +262,8 @@ void msi_reset(PCIDevice *dev)
         pci_set_long(dev->config + msi_mask_off(dev, msi64bit), 0);
         pci_set_long(dev->config + msi_pending_off(dev, msi64bit), 0);
     }
+    /* trigger notifier on potential changes */
+    msi_write_config(dev, msi_flags_off(dev), old_flags, 2);
     MSI_DEV_PRINTF(dev, "reset\n");
 }
 
@@ -306,16 +308,20 @@ void msi_notify(PCIDevice *dev, unsigned int vector)
 }
 
 /* Normally called by pci_default_write_config(). */
-void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
+void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t old_val, int len)
 {
     uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
     bool msi64bit = flags & PCI_MSI_FLAGS_64BIT;
     bool msi_per_vector_mask = flags & PCI_MSI_FLAGS_MASKBIT;
+    bool fire_vector_notifier = false;
     unsigned int nr_vectors;
     uint8_t log_num_vecs;
     uint8_t log_max_vecs;
     unsigned int vector;
     uint32_t pending;
+    MSIMessage msg;
+    bool enabled;
+    int ret;
 
     if (!msi_present(dev) ||
         !ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
@@ -342,7 +348,35 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, 
uint32_t val, int len)
     fprintf(stderr, "\n");
 #endif
 
-    if (!(flags & PCI_MSI_FLAGS_ENABLE)) {
+    enabled = flags & PCI_MSI_FLAGS_ENABLE;
+    nr_vectors = msi_nr_vectors(flags);
+
+    if (dev->msi_enable_notifier &&
+        range_covers_byte(addr, len, msi_flags_off(dev))) {
+        old_val >>= (msi_flags_off(dev) - addr) * 8;
+        if ((old_val & PCI_MSI_FLAGS_ENABLE) != enabled) {
+            dev->msi_enable_notifier(dev, enabled);
+            if (enabled && dev->msi_vector_config_notifier) {
+                fire_vector_notifier = true;
+            }
+        }
+    }
+    if (dev->msi_vector_config_notifier) {
+        if (ranges_overlap(addr, len, msi_address_lo_off(dev),
+                   msi64bit ? 10 : 6)) {
+            fire_vector_notifier = true;
+        }
+    }
+    if (fire_vector_notifier) {
+        for (vector = 0; vector < nr_vectors; ++vector) {
+            msi_message_from_vector(dev, flags, vector, &msg);
+            ret = dev->msi_vector_config_notifier(dev, vector, &msg,
+                                                  msi_is_masked(dev, vector));
+            assert(ret >= 0);
+        }
+    }
+
+    if (!enabled) {
         kvm_msi_free(dev);
         return;
     }
@@ -375,13 +409,12 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, 
uint32_t val, int len)
         pci_set_word(dev->config + msi_flags_off(dev), flags);
     }
 
-    if (!msi_per_vector_mask) {
-        /* if per vector masking isn't supported,
-           there is no pending interrupt. */
+    if (!msi_per_vector_mask ||
+        !ranges_overlap(addr, len, msi_mask_off(dev, msi64bit), 4)) {
         return;
     }
 
-    nr_vectors = msi_nr_vectors(flags);
+    old_val >>= (msi_mask_off(dev, msi64bit) - addr) * 8;
 
     /* This will discard pending interrupts, if any. */
     pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit));
@@ -390,13 +423,22 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, 
uint32_t val, int len)
 
     /* deliver pending interrupts which are unmasked */
     for (vector = 0; vector < nr_vectors; ++vector) {
-        if (msi_is_masked(dev, vector) || !(pending & (1U << vector))) {
-            continue;
+        bool is_masked = msi_is_masked(dev, vector);
+        unsigned int vector_mask = 1U << vector;
+
+        if (!fire_vector_notifier && dev->msi_vector_config_notifier &&
+            (bool)(old_val & vector_mask) != is_masked) {
+            msi_message_from_vector(dev, flags, vector, &msg);
+            ret = dev->msi_vector_config_notifier(dev, vector, &msg,
+                                                  is_masked);
+            assert(ret >= 0);
+        }
+        if (!is_masked && pending & vector_mask) {
+            pci_long_test_and_clear_mask(dev->config +
+                                         msi_pending_off(dev, msi64bit),
+                                         vector_mask);
+            msi_notify(dev, vector);
         }
-
-        pci_long_test_and_clear_mask(
-            dev->config + msi_pending_off(dev, msi64bit), 1U << vector);
-        msi_notify(dev, vector);
     }
 }
 
@@ -405,3 +447,102 @@ unsigned int msi_nr_vectors_allocated(const PCIDevice 
*dev)
     uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
     return msi_nr_vectors(flags);
 }
+
+/* Invoke the notifier if vector entry is unmasked. */
+static int
+msi_notify_if_unmasked(PCIDevice *dev, unsigned int vector, int masked)
+{
+    uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev));
+    MSIMessage msg;
+
+    assert(dev->msi_vector_config_notifier);
+
+    if (msi_is_masked(dev, vector)) {
+        return 0;
+    }
+    msi_message_from_vector(dev, flags, vector, &msg);
+    return dev->msi_vector_config_notifier(dev, vector, &msg, masked);
+}
+
+static int
+msi_set_config_notifier_for_vector(PCIDevice *dev, unsigned int vector)
+{
+    /* Notifier has been set. Invoke it on unmasked vectors. */
+    return msi_notify_if_unmasked(dev, vector, 0);
+}
+
+static int
+msi_unset_config_notifier_for_vector(PCIDevice *dev, unsigned int vector)
+{
+    /* Notifier will be unset. Invoke it to mask unmasked entries. */
+    return msi_notify_if_unmasked(dev, vector, 1);
+}
+
+int msi_set_config_notifiers(PCIDevice *dev, MSIEnableNotifier enable_notifier,
+                             MSIVectorConfigNotifier vector_config_notifier)
+{
+    unsigned int nr_vectors;
+    int r, vector;
+
+    assert(!dev->msi_vector_config_notifier);
+
+    dev->msi_enable_notifier = enable_notifier;
+    dev->msi_vector_config_notifier = vector_config_notifier;
+
+    if (enable_notifier && msi_enabled(dev)) {
+        enable_notifier(dev, true);
+    }
+    if (msi_enabled(dev)) {
+        nr_vectors =
+            msi_nr_vectors(pci_get_word(dev->config + msi_flags_off(dev)));
+        for (vector = 0; vector < nr_vectors; ++vector) {
+            r = msi_set_config_notifier_for_vector(dev, vector);
+            if (r < 0) {
+                goto undo;
+            }
+        }
+    }
+    return 0;
+
+undo:
+    while (--vector >= 0) {
+        msi_unset_config_notifier_for_vector(dev, vector);
+    }
+    if (enable_notifier && msi_enabled(dev)) {
+        enable_notifier(dev, false);
+    }
+    dev->msi_enable_notifier = NULL;
+    dev->msi_vector_config_notifier = NULL;
+    return r;
+}
+
+int msi_unset_config_notifiers(PCIDevice *dev)
+{
+    unsigned int nr_vectors;
+    int r, vector;
+
+    assert(dev->msi_vector_config_notifier);
+
+    if (msi_enabled(dev)) {
+        nr_vectors =
+            msi_nr_vectors(pci_get_word(dev->config + msi_flags_off(dev)));
+        for (vector = 0; vector < nr_vectors; ++vector) {
+            r = msi_unset_config_notifier_for_vector(dev, vector);
+            if (r < 0) {
+                goto undo;
+            }
+        }
+    }
+    if (dev->msi_enable_notifier && msi_enabled(dev)) {
+        dev->msi_enable_notifier(dev, false);
+    }
+    dev->msi_enable_notifier = NULL;
+    dev->msi_vector_config_notifier = NULL;
+    return 0;
+
+undo:
+    while (--vector >= 0) {
+        msi_set_config_notifier_for_vector(dev, vector);
+    }
+    return r;
+}
diff --git a/hw/msi.h b/hw/msi.h
index 74f6d52..c28665b 100644
--- a/hw/msi.h
+++ b/hw/msi.h
@@ -50,9 +50,14 @@ int msi_init(struct PCIDevice *dev, uint8_t offset,
 void msi_uninit(struct PCIDevice *dev);
 void msi_reset(PCIDevice *dev);
 void msi_notify(PCIDevice *dev, unsigned int vector);
-void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len);
+void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t old_val,
+                      int len);
 unsigned int msi_nr_vectors_allocated(const PCIDevice *dev);
 
+int msi_set_config_notifiers(PCIDevice *dev, MSIEnableNotifier enable_notifier,
+                             MSIVectorConfigNotifier vector_config_notifier);
+int msi_unset_config_notifiers(PCIDevice *dev);
+
 static inline bool msi_present(const PCIDevice *dev)
 {
     return dev->cap_present & QEMU_PCI_CAP_MSI;
diff --git a/hw/pci.c b/hw/pci.c
index 4f0d7e1..96cd334 100644
--- a/hw/pci.c
+++ b/hw/pci.c
@@ -1155,7 +1155,7 @@ void pci_default_write_config(PCIDevice *d, uint32_t 
addr, uint32_t val, int l)
     if (range_covers_byte(addr, l, PCI_COMMAND))
         pci_update_irq_disabled(d, was_irq_disabled);
 
-    msi_write_config(d, addr, val, l);
+    msi_write_config(d, addr, old_val, l);
     msix_write_config(d, addr, old_val, l);
 }
 
diff --git a/hw/pci.h b/hw/pci.h
index 5cf9a16..266fe34 100644
--- a/hw/pci.h
+++ b/hw/pci.h
@@ -206,6 +206,9 @@ struct PCIDevice {
      * on the rest of the region. */
     target_phys_addr_t msix_page_size;
 
+    MSIEnableNotifier msi_enable_notifier;
+    MSIVectorConfigNotifier msi_vector_config_notifier;
+
     MSIEnableNotifier msix_enable_notifier;
     MSIVectorConfigNotifier msix_vector_config_notifier;
 };
-- 
1.7.3.4


Reply via email to