[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v5 07/11] x86/passthrough: Switch pt_irq_bind_msi() to raw MSI address/data



Some hypervisors let a guest use the "Extended Destination ID" field of
the MSI address (and the IO-APIC RTE) to reach APIC IDs beyond the
architectural 8-bit destination field, extending the range from 8 to 15
bits (as supported by Linux since commit ab0f59c6f135). This way an HVM
guest with APIC IDs above 254 can target an MSI or IO-APIC interrupt.
This series adds support for it, gated on the guest's device model
opting in.

Change pt_irq_bind_msi() (and struct hvm_gmsi_info) to store and operate
on the raw MSI address and data words instead of the pre-decoded
gvec/gflags pair. A new MSI_ADDR_DEST() helper extracts the combined
destination ID from an address, including the extended bits (address
11:5). The extended part is always zero for the messages built here and
is only acted upon once the guest opts in.

pt_irq_bind_msi() now also rejects (-EINVAL) an address that isn't in
MSI format (0xfeexxxxx), so that every path storing into gmsi.addr, in
particular the raw-address device-model op, is guaranteed a well-formed
message. The function vpci_msi_update() already performed this check.

pt_irq_create_bind() keeps working for domctl callers by rebuilding a
raw MSI message from the gflags it is handed. vpci_msi_update() now
calls pt_irq_bind_msi() directly and msi_gflags() goes away.

The "already mapped" fast path now compares the full stored address and
data rather than just gvec/gflags. This is not a behavioural change: the
previous gvec/gflags pair was a loss-free re-encoding of exactly the
destination, delivery-mode, trigger-mode and vector bits that
address/data carry, so any message that would have compared equal before
still does, and messages differing only in bits that were previously
dropped now correctly trigger a re-program.

No functional change for existing (8-bit destination ID) guests.

Signed-off-by: Julian Vetter <julian.vetter@xxxxxxxxxx>
---
Changes in v5:
- Retitled: "Switch pt_irq_bind_msi() to raw MSI address/data"
- struct hvm_gmsi_info stores addr/data, so msi_gflags() is gone and
  vpci_msi_update() calls pt_irq_bind_msi() directly.
- MSI_ADDR_DEST() derives its shift from a new MSI_ADDR_DEST_ID_WIDTH.
  The original MSI_ADDR_DEST_ID_* lines are left untouched.
- Removed the "Intel convention" wording in the comment.
- pt_irq_bind_msi() rejects an address that isn't in MSI format
  (0xfeexxxxx) with -EINVAL, so every caller storing into gmsi.addr is
  guaranteed a well-formed message. Per Jan's comment: "cope with
  existing code passing rubbish there ... e.g. in vpci_msi_update()".
- _hvm_dpci_msi_eoi() dest-mode bug fixed: it tested
  XEN_DOMCTL_VMSI_X86_DM_MASK against a raw address, now uses
  MSI_ADDR_DESTMODE_MASK.
- vmsi_deliver_pirq() reads gmsi.addr/gmsi.data with the standard MSI
  masks (v4 kept XEN_DOMCTL_VMSI_X86_FULL_DEST()).
- pt_irq_create_bind()'s PT_IRQ_TYPE_MSI case keeps working here by
  rebuilding a raw message from gflags (v4 rejected it with -EOPNOTSUPP).
- Commit message explains why the "already mapped" comparison widened to
  full addr/data.
- Use MASK_EXTR/MASK_INSR throughout.

Signed-off-by: Julian Vetter <julian.vetter@xxxxxxxxxx>
---
 xen/arch/x86/hvm/vmsi.c            | 53 +++++++--------------
 xen/arch/x86/include/asm/hvm/irq.h |  4 +-
 xen/arch/x86/include/asm/msi.h     | 19 ++++++++
 xen/drivers/passthrough/x86/hvm.c  | 75 +++++++++++++++++++-----------
 xen/include/xen/iommu.h            |  3 ++
 5 files changed, 91 insertions(+), 63 deletions(-)

diff --git a/xen/arch/x86/hvm/vmsi.c b/xen/arch/x86/hvm/vmsi.c
index 27b1f089e2..6966cabfa7 100644
--- a/xen/arch/x86/hvm/vmsi.c
+++ b/xen/arch/x86/hvm/vmsi.c
@@ -43,6 +43,7 @@
 #include <asm/current.h>
 #include <asm/event.h>
 #include <asm/io_apic.h>
+#include <asm/msi.h>
 
 static void vmsi_inj_irq(
     struct vlapic *target,
@@ -107,12 +108,13 @@ int vmsi_deliver(
 
 void vmsi_deliver_pirq(struct domain *d, const struct hvm_pirq_dpci *pirq_dpci)
 {
-    uint32_t flags = pirq_dpci->gmsi.gflags;
-    int vector = pirq_dpci->gmsi.gvec;
-    uint8_t dest = (uint8_t)flags;
-    bool dest_mode = flags & XEN_DOMCTL_VMSI_X86_DM_MASK;
-    uint8_t delivery_mode = MASK_EXTR(flags, XEN_DOMCTL_VMSI_X86_DELIV_MASK);
-    bool trig_mode = flags & XEN_DOMCTL_VMSI_X86_TRIG_MASK;
+    uint64_t addr = pirq_dpci->gmsi.addr;
+    uint32_t data = pirq_dpci->gmsi.data;
+    unsigned int vector = MASK_EXTR(data, MSI_DATA_VECTOR_MASK);
+    uint32_t dest = MSI_ADDR_DEST(addr);
+    bool dest_mode = addr & MSI_ADDR_DESTMODE_MASK;
+    unsigned int delivery_mode = MASK_EXTR(data, MSI_DATA_DELIVERY_MODE_MASK);
+    bool trig_mode = data & MSI_DATA_TRIGGER_MASK;
 
     HVM_DBG_LOG(DBG_LEVEL_IOAPIC,
                 "msi: dest=%x dest_mode=%x delivery_mode=%x "
@@ -793,27 +795,6 @@ void msix_write_completion(struct vcpu *v)
 }
 
 #ifdef CONFIG_HAS_VPCI
-static unsigned int msi_gflags(uint16_t data, uint64_t addr, bool masked)
-{
-    /*
-     * We need to use the DOMCTL constants here because the output of this
-     * function is used as input to pt_irq_create_bind, which also takes the
-     * input from the DOMCTL itself.
-     */
-    return MASK_INSR(MASK_EXTR(addr, MSI_ADDR_DEST_ID_MASK),
-                     XEN_DOMCTL_VMSI_X86_DEST_ID_MASK) |
-           MASK_INSR(MASK_EXTR(addr, MSI_ADDR_REDIRECTION_MASK),
-                     XEN_DOMCTL_VMSI_X86_RH_MASK) |
-           MASK_INSR(MASK_EXTR(addr, MSI_ADDR_DESTMODE_MASK),
-                     XEN_DOMCTL_VMSI_X86_DM_MASK) |
-           MASK_INSR(MASK_EXTR(data, MSI_DATA_DELIVERY_MODE_MASK),
-                     XEN_DOMCTL_VMSI_X86_DELIV_MASK) |
-           MASK_INSR(MASK_EXTR(data, MSI_DATA_TRIGGER_MASK),
-                     XEN_DOMCTL_VMSI_X86_TRIG_MASK) |
-           /* NB: by default MSI vectors are bound masked. */
-           (masked ? 0 : XEN_DOMCTL_VMSI_X86_UNMASKED);
-}
-
 static void vpci_mask_pirq(struct domain *d, int pirq, bool mask)
 {
     unsigned long flags;
@@ -850,17 +831,19 @@ static int vpci_msi_update(const struct pci_dev *pdev, 
uint32_t data,
     {
         uint8_t vector = MASK_EXTR(data, MSI_DATA_VECTOR_MASK);
         uint8_t vector_mask = 0xff >> (8 - fls(vectors) + 1);
-        struct xen_domctl_bind_pt_irq bind = {
-            .machine_irq = pirq + i,
-            .irq_type = PT_IRQ_TYPE_MSI,
-            .u.msi.gvec = (vector & ~vector_mask) |
-                          ((vector + i) & vector_mask),
-            .u.msi.gflags = msi_gflags(data, address, (mask >> i) & 1),
-        };
-        int rc = pt_irq_create_bind(pdev->domain, &bind);
+        uint8_t gvec = (vector & ~vector_mask) | ((vector + i) & vector_mask);
+        uint32_t msi_data = (data & ~MSI_DATA_VECTOR_MASK) |
+                            MASK_INSR(gvec, MSI_DATA_VECTOR_MASK);
+        int rc = pt_irq_bind_msi(pdev->domain, pirq + i, address, msi_data,
+                                 0 /* gtable */, !((mask >> i) & 1));
 
         if ( rc )
         {
+            struct xen_domctl_bind_pt_irq bind = {
+                .irq_type = PT_IRQ_TYPE_MSI,
+                .machine_irq = pirq + i,
+            };
+
             gdprintk(XENLOG_ERR, "%pp: failed to bind PIRQ %u: %d\n",
                      &pdev->sbdf, pirq + i, rc);
             while ( bind.machine_irq-- > pirq )
diff --git a/xen/arch/x86/include/asm/hvm/irq.h 
b/xen/arch/x86/include/asm/hvm/irq.h
index 77595fb3f4..e79e2e3fed 100644
--- a/xen/arch/x86/include/asm/hvm/irq.h
+++ b/xen/arch/x86/include/asm/hvm/irq.h
@@ -120,8 +120,8 @@ struct dev_intx_gsi_link {
 #define HVM_IRQ_DPCI_TRANSLATE       (1u << _HVM_IRQ_DPCI_TRANSLATE_SHIFT)
 
 struct hvm_gmsi_info {
-    uint32_t gvec;
-    uint32_t gflags;
+    uint64_t addr; /* raw MSI address (0xfeexxxxx) */
+    uint32_t data; /* raw MSI data (vector, delivery mode, trigger mode) */
     int dest_vcpu_id; /* -1 :multi-dest, non-negative: dest_vcpu_id */
     bool posted; /* directly deliver to guest via VT-d PI? */
 };
diff --git a/xen/arch/x86/include/asm/msi.h b/xen/arch/x86/include/asm/msi.h
index 6fb663b2e7..a553922853 100644
--- a/xen/arch/x86/include/asm/msi.h
+++ b/xen/arch/x86/include/asm/msi.h
@@ -54,6 +54,25 @@
 #define         MSI_ADDR_DEST_ID_MASK          0x00ff000
 #define  MSI_ADDR_DEST_ID(dest)                (((dest) << 
MSI_ADDR_DEST_ID_SHIFT) & MSI_ADDR_DEST_ID_MASK)
 
+/* Width of the architectural destination ID field (MSI address bits 19:12). */
+#define MSI_ADDR_DEST_ID_WIDTH         8
+
+/*
+ * "Extended Destination ID": MSI address bits 11:5 carry the top 7 bits of a
+ * 15-bit APIC ID, extending the reachable destination range from 8 to 15 bits.
+ * A guest is told this is available via XEN_HVM_CPUID_EXT_DEST_ID. The Linux
+ * guest side is x86_msi_msg_get_destid() in arch/x86/kernel/apic/apic.c.
+ *
+ * Only interpret these bits this way for a guest that has opted in. Otherwise
+ * treat them as reserved and ignore them.
+ */
+#define MSI_ADDR_EXT_DEST_ID_MASK      0x0000fe0
+
+/* Combine the architectural and extended destination bits of an MSI address. 
*/
+#define MSI_ADDR_DEST(addr)                                              \
+    (MASK_EXTR(addr, MSI_ADDR_DEST_ID_MASK) |                            \
+     (MASK_EXTR(addr, MSI_ADDR_EXT_DEST_ID_MASK) << MSI_ADDR_DEST_ID_WIDTH))
+
 /* MAX fixed pages reserved for mapping MSIX tables. */
 #define FIX_MSIX_MAX_PAGES              512
 
diff --git a/xen/drivers/passthrough/x86/hvm.c 
b/xen/drivers/passthrough/x86/hvm.c
index 5fdb885311..bdab065eb7 100644
--- a/xen/drivers/passthrough/x86/hvm.c
+++ b/xen/drivers/passthrough/x86/hvm.c
@@ -287,19 +287,24 @@ static int pt_irq_dpci_setup(struct domain *d, unsigned 
int pirq,
     return 0;
 }
 
-static int pt_irq_bind_msi(struct domain *d, unsigned int machine_irq,
-                           uint8_t gvec, unsigned int gflags, uint64_t gtable,
-                           bool unmasked)
+int pt_irq_bind_msi(struct domain *d, unsigned int machine_irq,
+                    uint64_t msi_addr, uint32_t msi_data,
+                    uint64_t gtable, bool unmasked)
 {
     struct hvm_irq_dpci *hvm_irq_dpci;
     struct hvm_pirq_dpci *pirq_dpci;
     struct pirq *info;
     int rc;
-    uint8_t dest, delivery_mode;
+    uint8_t gvec;
+    uint32_t dest;
     bool dest_mode;
     int dest_vcpu_id;
     const struct vcpu *vcpu;
 
+    /* A passthrough MSI must carry an MSI-format address (0xfeexxxxx). */
+    if ( (msi_addr & MSI_ADDR_BASE_MASK) != MSI_ADDR_HEADER )
+        return -EINVAL;
+
     rc = pt_irq_dpci_setup(d, machine_irq, &hvm_irq_dpci, &pirq_dpci, &info);
     if ( rc )
         return rc;
@@ -308,8 +313,8 @@ static int pt_irq_bind_msi(struct domain *d, unsigned int 
machine_irq,
     {
         pirq_dpci->flags = HVM_IRQ_DPCI_MAPPED | HVM_IRQ_DPCI_MACH_MSI |
                            HVM_IRQ_DPCI_GUEST_MSI;
-        pirq_dpci->gmsi.gvec = gvec;
-        pirq_dpci->gmsi.gflags = gflags;
+        pirq_dpci->gmsi.addr = msi_addr;
+        pirq_dpci->gmsi.data = msi_data;
         /*
          * 'pt_irq_bind_msi' can be called after 'pt_irq_destroy_bind'.
          * The 'pirq_cleanup_check' which would free the structure is only
@@ -341,8 +346,8 @@ static int pt_irq_bind_msi(struct domain *d, unsigned int 
machine_irq,
         }
         if ( unlikely(rc) )
         {
-            pirq_dpci->gmsi.gflags = 0;
-            pirq_dpci->gmsi.gvec = 0;
+            pirq_dpci->gmsi.addr = 0;
+            pirq_dpci->gmsi.data = 0;
             pirq_dpci->dom = NULL;
             pirq_dpci->flags = 0;
             if ( !info->evtchn )
@@ -362,23 +367,22 @@ static int pt_irq_bind_msi(struct domain *d, unsigned int 
machine_irq,
         }
 
         /* If pirq is already mapped as vmsi, update guest data/addr. */
-        if ( pirq_dpci->gmsi.gvec != gvec ||
-             pirq_dpci->gmsi.gflags != gflags )
+        if ( pirq_dpci->gmsi.addr != msi_addr ||
+             pirq_dpci->gmsi.data != msi_data )
         {
             /* Directly clear pending EOIs before enabling new MSI info. */
             pirq_guest_eoi(info);
 
-            pirq_dpci->gmsi.gvec = gvec;
-            pirq_dpci->gmsi.gflags = gflags;
+            pirq_dpci->gmsi.addr = msi_addr;
+            pirq_dpci->gmsi.data = msi_data;
         }
     }
 
     /* Calculate dest_vcpu_id for MSI-type pirq migration. */
-    dest = MASK_EXTR(pirq_dpci->gmsi.gflags,
-                     XEN_DOMCTL_VMSI_X86_DEST_ID_MASK);
-    dest_mode = pirq_dpci->gmsi.gflags & XEN_DOMCTL_VMSI_X86_DM_MASK;
-    delivery_mode = MASK_EXTR(pirq_dpci->gmsi.gflags,
-                              XEN_DOMCTL_VMSI_X86_DELIV_MASK);
+    gvec = MASK_EXTR(msi_data, MSI_DATA_VECTOR_MASK);
+    dest = MSI_ADDR_DEST(msi_addr);
+    dest_mode = msi_addr & MSI_ADDR_DESTMODE_MASK;
+    delivery_mode = MASK_EXTR(msi_data, MSI_DATA_DELIVERY_MODE_MASK);
 
     dest_vcpu_id = hvm_girq_dest_2_vcpu_id(d, dest, dest_mode);
     pirq_dpci->gmsi.dest_vcpu_id = dest_vcpu_id;
@@ -389,8 +393,7 @@ static int pt_irq_bind_msi(struct domain *d, unsigned int 
machine_irq,
     if ( iommu_intpost )
     {
         if ( delivery_mode == dest_LowestPrio )
-            vcpu = vector_hashing_dest(d, dest, dest_mode,
-                                       pirq_dpci->gmsi.gvec);
+            vcpu = vector_hashing_dest(d, dest, dest_mode, gvec);
         if ( vcpu )
             pirq_dpci->gmsi.posted = true;
     }
@@ -405,7 +408,7 @@ static int pt_irq_bind_msi(struct domain *d, unsigned int 
machine_irq,
             .irq_type = PT_IRQ_TYPE_MSI,
         };
 
-        rc = hvm_pi_update_irte(vcpu, info, pirq_dpci->gmsi.gvec);
+        rc = hvm_pi_update_irte(vcpu, info, gvec);
         if ( rc )
         {
             pt_irq_destroy_bind(d, &pt_irq_bind);
@@ -448,9 +451,30 @@ int pt_irq_create_bind(
     case PT_IRQ_TYPE_MSI:
     {
         unsigned int gflags = pt_irq_bind->u.msi.gflags;
+        uint64_t msi_addr;
+        uint32_t msi_data;
 
-        return pt_irq_bind_msi(d, pirq, pt_irq_bind->u.msi.gvec,
-                               gflags & ~XEN_DOMCTL_VMSI_X86_UNMASKED,
+        /*
+         * Rebuild a raw MSI message from the pre-decoded domctl gflags so the
+         * canonical bind path can decode it uniformly. This legacy path never
+         * carries extended destination ID bits.
+         */
+        msi_addr = MSI_ADDR_HEADER |
+                   MASK_INSR(MASK_EXTR(gflags, 
XEN_DOMCTL_VMSI_X86_DEST_ID_MASK),
+                             MSI_ADDR_DEST_ID_MASK) |
+                   (gflags & XEN_DOMCTL_VMSI_X86_RH_MASK
+                    ? MSI_ADDR_REDIRECTION_LOWPRI
+                    : MSI_ADDR_REDIRECTION_CPU) |
+                   (gflags & XEN_DOMCTL_VMSI_X86_DM_MASK
+                    ? MSI_ADDR_DESTMODE_LOGIC
+                    : MSI_ADDR_DESTMODE_PHYS);
+        msi_data = MASK_INSR(pt_irq_bind->u.msi.gvec, MSI_DATA_VECTOR_MASK) |
+                   MASK_INSR(MASK_EXTR(gflags, XEN_DOMCTL_VMSI_X86_DELIV_MASK),
+                             MSI_DATA_DELIVERY_MODE_MASK) |
+                   (gflags & XEN_DOMCTL_VMSI_X86_TRIG_MASK
+                    ? MSI_DATA_TRIGGER_LEVEL : 0);
+
+        return pt_irq_bind_msi(d, pt_irq_bind->machine_irq, msi_addr, msi_data,
                                pt_irq_bind->u.msi.gtable,
                                gflags & XEN_DOMCTL_VMSI_X86_UNMASKED);
     }
@@ -857,11 +881,10 @@ static int cf_check _hvm_dpci_msi_eoi(
     int vector = (long)arg;
 
     if ( (pirq_dpci->flags & HVM_IRQ_DPCI_MACH_MSI) &&
-         (pirq_dpci->gmsi.gvec == vector) )
+         MASK_EXTR(pirq_dpci->gmsi.data, MSI_DATA_VECTOR_MASK) == vector )
     {
-        unsigned int dest = MASK_EXTR(pirq_dpci->gmsi.gflags,
-                                      XEN_DOMCTL_VMSI_X86_DEST_ID_MASK);
-        bool dest_mode = pirq_dpci->gmsi.gflags & XEN_DOMCTL_VMSI_X86_DM_MASK;
+        unsigned int dest = MSI_ADDR_DEST(pirq_dpci->gmsi.addr);
+        bool dest_mode = pirq_dpci->gmsi.addr & MSI_ADDR_DESTMODE_MASK;
 
         if ( vlapic_match_dest(vcpu_vlapic(current), NULL, 0, dest,
                                dest_mode) )
diff --git a/xen/include/xen/iommu.h b/xen/include/xen/iommu.h
index 37c4a1dc82..d68d9ca6ec 100644
--- a/xen/include/xen/iommu.h
+++ b/xen/include/xen/iommu.h
@@ -222,6 +222,9 @@ int pt_irq_create_bind(struct domain *d,
                        const struct xen_domctl_bind_pt_irq *pt_irq_bind);
 int pt_irq_destroy_bind(struct domain *d,
                         const struct xen_domctl_bind_pt_irq *pt_irq_bind);
+int pt_irq_bind_msi(struct domain *d, unsigned int machine_irq,
+                    uint64_t msi_addr, uint32_t msi_data,
+                    uint64_t gtable, bool unmasked);
 
 struct hvm_irq_dpci *domain_get_irq_dpci(const struct domain *d);
 void free_hvm_irq_dpci(struct hvm_irq_dpci *dpci);
-- 
2.53.0



--
Julian Vetter | Vates Hypervisor & Kernel Developer

XCP-ng & Xen Orchestra - Vates solutions

web: https://vates.tech

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.