* [PATCH v5 1/7] iommu/amd: KVM: SVM: Rename cpu to apicid in IOMMU interface
2026-10-15 6:45 [PATCH v5 0/7] Add support for AMD IOMMU GAPPI Sairaj Kodilkar
@ 2026-10-15 6:45 ` Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 2/7] iommu/amd: KVM: SVM: Rename ga_log_intr to wakeup_intr " Sairaj Kodilkar
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Sairaj Kodilkar @ 2026-10-15 6:45 UTC (permalink / raw)
To: H. Peter Anvin, Joerg Roedel (AMD), Mike Rapoport (Microsoft),
Andrew Morton, Borislav Petkov, Dapeng Mi, Dave Hansen,
Ethan Nelson-Moore, Ingo Molnar, Jakub Kicinski, Jeff Layton,
Jonathan Corbet, Kiryl Shutsemau, Li RongQing, Marco Elver,
Paolo Bonzini, Randy Dunlap, Rick Edgecombe, Robin Murphy,
Sairaj Kodilkar, Sean Christopherson, Shuah Khan,
Suravee Suthikulpanit, Thomas Gleixner, Vasant Hegde, Will Deacon,
iommu, kvm, linux-coco, linux-doc, linux-kernel, x86
The cpu field passed from SVM to the AMD IOMMU driver does not hold a
linux CPU number. It carries the host physical APIC ID used to program
the IRTE Destination field when the target vCPU is running
(IRTE[IsRun] = 1).
Rename the field to apicid everywhere in the IOMMU interface so the
name matches the hardware semantics.
No functional change is intended.
Signed-off-by: Sairaj Kodilkar <sarunkod@amd.com>
Reviewed-by: Vasant Hegde <vasant.hegde@amd.com>
---
arch/x86/include/asm/irq_remapping.h | 2 +-
arch/x86/kvm/svm/avic.c | 12 +++++-----
drivers/iommu/amd/iommu.c | 35 ++++++++++++++--------------
include/linux/amd-iommu.h | 8 +++----
4 files changed, 29 insertions(+), 28 deletions(-)
diff --git a/arch/x86/include/asm/irq_remapping.h b/arch/x86/include/asm/irq_remapping.h
index 37b94f484ef3..263f4df429d8 100644
--- a/arch/x86/include/asm/irq_remapping.h
+++ b/arch/x86/include/asm/irq_remapping.h
@@ -35,7 +35,7 @@ struct amd_iommu_pi_data {
u64 vapic_addr; /* Physical address of the vCPU's vAPIC. */
u32 ga_tag;
u32 vector; /* Guest vector of the interrupt */
- int cpu;
+ int apicid;
bool ga_log_intr;
bool is_guest_mode;
void *ir_data;
diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
index 96ca39c0045f..5ac5ce4039a6 100644
--- a/arch/x86/kvm/svm/avic.c
+++ b/arch/x86/kvm/svm/avic.c
@@ -960,9 +960,9 @@ int avic_pi_update_irte(struct kvm_kernel_irqfd *irqfd, struct kvm *kvm,
*/
entry = svm->avic_physical_id_entry;
if (entry & AVIC_PHYSICAL_ID_ENTRY_IS_RUNNING_MASK) {
- pi_data.cpu = entry & AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK;
+ pi_data.apicid = entry & AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK;
} else {
- pi_data.cpu = -1;
+ pi_data.apicid = -1;
pi_data.ga_log_intr = entry & AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR;
}
@@ -1015,7 +1015,7 @@ enum avic_vcpu_action {
AVIC_START_BLOCKING = BIT(1),
};
-static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int cpu,
+static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int apicid,
enum avic_vcpu_action action)
{
bool ga_log_intr = (action & AVIC_START_BLOCKING);
@@ -1035,9 +1035,9 @@ static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int cpu,
void *data = irqfd->irq_bypass_data;
if (!(action & AVIC_TOGGLE_ON_OFF))
- WARN_ON_ONCE(amd_iommu_update_ga(data, cpu, ga_log_intr));
- else if (cpu >= 0)
- WARN_ON_ONCE(amd_iommu_activate_guest_mode(data, cpu, ga_log_intr));
+ WARN_ON_ONCE(amd_iommu_update_ga(data, apicid, ga_log_intr));
+ else if (apicid >= 0)
+ WARN_ON_ONCE(amd_iommu_activate_guest_mode(data, apicid, ga_log_intr));
else
WARN_ON_ONCE(amd_iommu_deactivate_guest_mode(data));
}
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 56262f6b1f70..aa9848d57f6b 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -3993,14 +3993,14 @@ static const struct irq_domain_ops amd_ir_domain_ops = {
.deactivate = irq_remapping_deactivate,
};
-static void __amd_iommu_update_ga(struct irte_ga *entry, int cpu,
+static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
bool ga_log_intr)
{
- if (cpu >= 0) {
+ if (apicid >= 0) {
entry->lo.fields_vapic.destination =
- APICID_TO_IRTE_DEST_LO(cpu);
+ APICID_TO_IRTE_DEST_LO(apicid);
entry->hi.fields.destination =
- APICID_TO_IRTE_DEST_HI(cpu);
+ APICID_TO_IRTE_DEST_HI(apicid);
entry->lo.fields_vapic.is_run = true;
entry->lo.fields_vapic.ga_log_intr = false;
} else {
@@ -4013,20 +4013,21 @@ static void __amd_iommu_update_ga(struct irte_ga *entry, int cpu,
* Update the pCPU information for an IRTE that is configured to post IRQs to
* a vCPU, without issuing an IOMMU invalidation for the IRTE.
*
- * If the vCPU is associated with a pCPU (@cpu >= 0), configure the Destination
- * with the pCPU's APIC ID, set IsRun, and clear GALogIntr. If the vCPU isn't
- * associated with a pCPU (@cpu < 0), clear IsRun and set/clear GALogIntr based
- * on input from the caller (e.g. KVM only requests GALogIntr when the vCPU is
- * blocking and requires a notification wake event). I.e. treat vCPUs that are
- * associated with a pCPU as running. This API is intended to be used when a
- * vCPU is scheduled in/out (or stops running for any reason), to do a fast
- * update of IsRun, GALogIntr, and (conditionally) Destination.
+ * If the vCPU is associated with a pCPU (@apicid >= 0), configure the
+ * Destination with the pCPU's APIC ID, set IsRun, and clear GALogIntr. If the
+ * vCPU isn't associated with a pCPU (@apicid < 0), clear IsRun and set/clear
+ * GALogIntr based on input from the caller (e.g. KVM only requests GALogIntr
+ * when the vCPU is blocking and requires a notification wake event). I.e.
+ * treat vCPUs that are associated with a pCPU as running. This API is
+ * intended to be used when a vCPU is scheduled in/out (or stops running for
+ * any reason), to do a fast update of IsRun, GALogIntr, and (conditionally)
+ * Destination.
*
* Per the IOMMU spec, the Destination, IsRun, and GATag fields are not cached
* and thus don't require an invalidation to ensure the IOMMU consumes fresh
* information.
*/
-int amd_iommu_update_ga(void *data, int cpu, bool ga_log_intr)
+int amd_iommu_update_ga(void *data, int apicid, bool ga_log_intr)
{
struct amd_ir_data *ir_data = (struct amd_ir_data *)data;
struct irte_ga *entry = (struct irte_ga *) ir_data->entry;
@@ -4040,14 +4041,14 @@ int amd_iommu_update_ga(void *data, int cpu, bool ga_log_intr)
if (!ir_data->iommu)
return -ENODEV;
- __amd_iommu_update_ga(entry, cpu, ga_log_intr);
+ __amd_iommu_update_ga(entry, apicid, ga_log_intr);
return __modify_irte_ga(ir_data->iommu, ir_data->irq_2_irte.devid,
ir_data->irq_2_irte.index, entry);
}
EXPORT_SYMBOL(amd_iommu_update_ga);
-int amd_iommu_activate_guest_mode(void *data, int cpu, bool ga_log_intr)
+int amd_iommu_activate_guest_mode(void *data, int apicid, bool ga_log_intr)
{
struct amd_ir_data *ir_data = (struct amd_ir_data *)data;
struct irte_ga *entry = (struct irte_ga *) ir_data->entry;
@@ -4070,7 +4071,7 @@ int amd_iommu_activate_guest_mode(void *data, int cpu, bool ga_log_intr)
entry->hi.fields.vector = ir_data->ga_vector;
entry->lo.fields_vapic.ga_tag = ir_data->ga_tag;
- __amd_iommu_update_ga(entry, cpu, ga_log_intr);
+ __amd_iommu_update_ga(entry, apicid, ga_log_intr);
return modify_irte_ga(ir_data->iommu, ir_data->irq_2_irte.devid,
ir_data->irq_2_irte.index, entry);
@@ -4141,7 +4142,7 @@ static int amd_ir_set_vcpu_affinity(struct irq_data *data, void *info)
ir_data->ga_vector = pi_data->vector;
ir_data->ga_tag = pi_data->ga_tag;
if (pi_data->is_guest_mode)
- ret = amd_iommu_activate_guest_mode(ir_data, pi_data->cpu,
+ ret = amd_iommu_activate_guest_mode(ir_data, pi_data->apicid,
pi_data->ga_log_intr);
else
ret = amd_iommu_deactivate_guest_mode(ir_data);
diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h
index edcee9f5335a..2c6232aefafa 100644
--- a/include/linux/amd-iommu.h
+++ b/include/linux/amd-iommu.h
@@ -30,8 +30,8 @@ static inline void amd_iommu_detect(void) { }
/* IOMMU AVIC Function */
extern int amd_iommu_register_ga_log_notifier(int (*notifier)(u32));
-extern int amd_iommu_update_ga(void *data, int cpu, bool ga_log_intr);
-extern int amd_iommu_activate_guest_mode(void *data, int cpu, bool ga_log_intr);
+extern int amd_iommu_update_ga(void *data, int apicid, bool ga_log_intr);
+extern int amd_iommu_activate_guest_mode(void *data, int apicid, bool ga_log_intr);
extern int amd_iommu_deactivate_guest_mode(void *data);
#else /* defined(CONFIG_AMD_IOMMU) && defined(CONFIG_IRQ_REMAP) */
@@ -42,12 +42,12 @@ amd_iommu_register_ga_log_notifier(int (*notifier)(u32))
return 0;
}
-static inline int amd_iommu_update_ga(void *data, int cpu, bool ga_log_intr)
+static inline int amd_iommu_update_ga(void *data, int apicid, bool ga_log_intr)
{
return 0;
}
-static inline int amd_iommu_activate_guest_mode(void *data, int cpu, bool ga_log_intr)
+static inline int amd_iommu_activate_guest_mode(void *data, int apicid, bool ga_log_intr)
{
return 0;
}
base-commit: 4056b3c9d3c92ea45955641dbafeb9b960e146ef
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v5 2/7] iommu/amd: KVM: SVM: Rename ga_log_intr to wakeup_intr in IOMMU interface
2026-10-15 6:45 [PATCH v5 0/7] Add support for AMD IOMMU GAPPI Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 1/7] iommu/amd: KVM: SVM: Rename cpu to apicid in IOMMU interface Sairaj Kodilkar
@ 2026-10-15 6:45 ` Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 3/7] iommu/amd: KVM: SVM: Add explicit vCPU running state to " Sairaj Kodilkar
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Sairaj Kodilkar @ 2026-10-15 6:45 UTC (permalink / raw)
To: H. Peter Anvin, Joerg Roedel (AMD), Mike Rapoport (Microsoft),
Andrew Morton, Borislav Petkov, Dapeng Mi, Dave Hansen,
Ethan Nelson-Moore, Ingo Molnar, Jakub Kicinski, Jeff Layton,
Jonathan Corbet, Kiryl Shutsemau, Li RongQing, Marco Elver,
Paolo Bonzini, Randy Dunlap, Rick Edgecombe, Robin Murphy,
Sairaj Kodilkar, Sean Christopherson, Shuah Khan,
Suravee Suthikulpanit, Thomas Gleixner, Vasant Hegde, Will Deacon,
iommu, kvm, linux-coco, linux-doc, linux-kernel, x86
When AVIC is enabled, device interrupts are posted into the guest virtual
APIC backing page regardless of vCPU running state. When the vCPU is not
running, KVM may additionally need the IOMMU to notify the host so the
vCPU can be woken, historically via the GA log mechanism.
The existing ga_log_intr name is tied to one specific hardware mechanism.
Guest APIC Physical Processor Interrupt (GAPPI) provides an alternative
host-notification path when the vCPU is not running, i.e., IRTE[IsRun] = 0.
The KVM-side intent is the same in both cases: request host wakeup
notification while the vCPU is blocked.
Rename ga_log_intr to wakeup_intr in the IOMMU API to describe KVM's
intent rather than a single hardware knob. The IOMMU driver still maps
wakeup_intr to IRTE[GALogIntr] in this patch; GAPPI support is added in
following patches.
Similarly rename the synthetic AVIC physical ID table shadow bit from
AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR to AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR.
No functional change is intended.
Signed-off-by: Sairaj Kodilkar <sarunkod@amd.com>
Reviewed-by: Vasant Hegde <vasant.hegde@amd.com>
---
arch/x86/include/asm/irq_remapping.h | 2 +-
arch/x86/include/asm/svm.h | 9 +++++----
arch/x86/kvm/svm/avic.c | 26 +++++++++++++-------------
drivers/iommu/amd/iommu.c | 16 ++++++++--------
include/linux/amd-iommu.h | 8 ++++----
5 files changed, 31 insertions(+), 30 deletions(-)
diff --git a/arch/x86/include/asm/irq_remapping.h b/arch/x86/include/asm/irq_remapping.h
index 263f4df429d8..789e3e154600 100644
--- a/arch/x86/include/asm/irq_remapping.h
+++ b/arch/x86/include/asm/irq_remapping.h
@@ -36,7 +36,7 @@ struct amd_iommu_pi_data {
u32 ga_tag;
u32 vector; /* Guest vector of the interrupt */
int apicid;
- bool ga_log_intr;
+ bool wakeup_intr;
bool is_guest_mode;
void *ir_data;
};
diff --git a/arch/x86/include/asm/svm.h b/arch/x86/include/asm/svm.h
index aa63431ba92c..3de222d6ae15 100644
--- a/arch/x86/include/asm/svm.h
+++ b/arch/x86/include/asm/svm.h
@@ -260,11 +260,12 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
#define AVIC_LOGICAL_ID_ENTRY_VALID_MASK (1 << 31)
/*
- * GA_LOG_INTR is a synthetic flag that's never propagated to hardware-visible
- * tables. GA_LOG_INTR is set if the vCPU needs device posted IRQs to generate
- * GA log interrupts to wake the vCPU (because it's blocking or about to block).
+ * WAKEUP_INTR is a synthetic flag that's never propagated to hardware-visible
+ * tables. WAKEUP_INTR is set if the vCPU needs device posted IRQs to generate
+ * GA log/GAPPI interrupts to wake the vCPU (because it's blocking or about to
+ * block).
*/
-#define AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR BIT_ULL(61)
+#define AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR BIT_ULL(61)
#define AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK GENMASK_ULL(11, 0)
#define AVIC_PHYSICAL_ID_ENTRY_BACKING_PAGE_MASK GENMASK_ULL(51, 12)
diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
index 5ac5ce4039a6..edbdb5d75062 100644
--- a/arch/x86/kvm/svm/avic.c
+++ b/arch/x86/kvm/svm/avic.c
@@ -963,7 +963,7 @@ int avic_pi_update_irte(struct kvm_kernel_irqfd *irqfd, struct kvm *kvm,
pi_data.apicid = entry & AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK;
} else {
pi_data.apicid = -1;
- pi_data.ga_log_intr = entry & AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR;
+ pi_data.wakeup_intr = entry & AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR;
}
ret = irq_set_vcpu_affinity(host_irq, &pi_data);
@@ -1002,15 +1002,15 @@ enum avic_vcpu_action {
* No unique action is required to deal with a vCPU that stops/starts
* running. A vCPU that starts running by definition stops blocking as
* well, and a vCPU that stops running can't have been blocking, i.e.
- * doesn't need to toggle GALogIntr.
+ * doesn't need to toggle host wakeup notification.
*/
AVIC_START_RUNNING = 0,
AVIC_STOP_RUNNING = 0,
/*
- * When a vCPU starts blocking, KVM needs to set the GALogIntr flag
- * int all associated IRTEs so that KVM can wake the vCPU if an IRQ is
- * sent to the vCPU.
+ * When a vCPU starts blocking, KVM needs to request host wakeup
+ * notification in all associated IRTEs so that KVM can wake the vCPU
+ * if an IRQ is sent to the vCPU.
*/
AVIC_START_BLOCKING = BIT(1),
};
@@ -1018,7 +1018,7 @@ enum avic_vcpu_action {
static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int apicid,
enum avic_vcpu_action action)
{
- bool ga_log_intr = (action & AVIC_START_BLOCKING);
+ bool wakeup_intr = (action & AVIC_START_BLOCKING);
struct vcpu_svm *svm = to_svm(vcpu);
struct kvm_kernel_irqfd *irqfd;
@@ -1035,9 +1035,9 @@ static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int apicid,
void *data = irqfd->irq_bypass_data;
if (!(action & AVIC_TOGGLE_ON_OFF))
- WARN_ON_ONCE(amd_iommu_update_ga(data, apicid, ga_log_intr));
+ WARN_ON_ONCE(amd_iommu_update_ga(data, apicid, wakeup_intr));
else if (apicid >= 0)
- WARN_ON_ONCE(amd_iommu_activate_guest_mode(data, apicid, ga_log_intr));
+ WARN_ON_ONCE(amd_iommu_activate_guest_mode(data, apicid, wakeup_intr));
else
WARN_ON_ONCE(amd_iommu_deactivate_guest_mode(data));
}
@@ -1073,7 +1073,7 @@ static void __avic_vcpu_load(struct kvm_vcpu *vcpu, int cpu,
WARN_ON_ONCE(entry & AVIC_PHYSICAL_ID_ENTRY_IS_RUNNING_MASK);
entry &= ~(AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK |
- AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR);
+ AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR);
entry |= (h_physical_id & AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK);
entry |= AVIC_PHYSICAL_ID_ENTRY_IS_RUNNING_MASK;
@@ -1134,7 +1134,7 @@ static void __avic_vcpu_put(struct kvm_vcpu *vcpu, enum avic_vcpu_action action)
avic_update_iommu_vcpu_affinity(vcpu, -1, action);
- WARN_ON_ONCE(entry & AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR);
+ WARN_ON_ONCE(entry & AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR);
/*
* Keep the previous APIC ID in the entry so that a rogue doorbell from
@@ -1146,11 +1146,11 @@ static void __avic_vcpu_put(struct kvm_vcpu *vcpu, enum avic_vcpu_action action)
WRITE_ONCE(kvm_svm->avic_physical_id_table[vcpu->vcpu_id], entry);
/*
- * Note! Don't set AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR in the table as
+ * Note! Don't set AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR in the table as
* it's a synthetic flag that usurps an unused should-be-zero bit.
*/
if (action & AVIC_START_BLOCKING)
- entry |= AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR;
+ entry |= AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR;
svm->avic_physical_id_entry = entry;
@@ -1182,7 +1182,7 @@ void avic_vcpu_put(struct kvm_vcpu *vcpu)
* The vCPU was preempted while blocking, ensure its IRTEs are
* configured to generate GA Log Interrupts.
*/
- if (!(WARN_ON_ONCE(!(entry & AVIC_PHYSICAL_ID_ENTRY_GA_LOG_INTR))))
+ if (!(WARN_ON_ONCE(!(entry & AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR))))
return;
}
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index aa9848d57f6b..878567708737 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -3994,7 +3994,7 @@ static const struct irq_domain_ops amd_ir_domain_ops = {
};
static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
- bool ga_log_intr)
+ bool wakeup_intr)
{
if (apicid >= 0) {
entry->lo.fields_vapic.destination =
@@ -4005,7 +4005,7 @@ static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
entry->lo.fields_vapic.ga_log_intr = false;
} else {
entry->lo.fields_vapic.is_run = false;
- entry->lo.fields_vapic.ga_log_intr = ga_log_intr;
+ entry->lo.fields_vapic.ga_log_intr = wakeup_intr;
}
}
@@ -4016,7 +4016,7 @@ static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
* If the vCPU is associated with a pCPU (@apicid >= 0), configure the
* Destination with the pCPU's APIC ID, set IsRun, and clear GALogIntr. If the
* vCPU isn't associated with a pCPU (@apicid < 0), clear IsRun and set/clear
- * GALogIntr based on input from the caller (e.g. KVM only requests GALogIntr
+ * GALogIntr based on input from the caller (e.g. KVM only requests wakeup_intr
* when the vCPU is blocking and requires a notification wake event). I.e.
* treat vCPUs that are associated with a pCPU as running. This API is
* intended to be used when a vCPU is scheduled in/out (or stops running for
@@ -4027,7 +4027,7 @@ static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
* and thus don't require an invalidation to ensure the IOMMU consumes fresh
* information.
*/
-int amd_iommu_update_ga(void *data, int apicid, bool ga_log_intr)
+int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr)
{
struct amd_ir_data *ir_data = (struct amd_ir_data *)data;
struct irte_ga *entry = (struct irte_ga *) ir_data->entry;
@@ -4041,14 +4041,14 @@ int amd_iommu_update_ga(void *data, int apicid, bool ga_log_intr)
if (!ir_data->iommu)
return -ENODEV;
- __amd_iommu_update_ga(entry, apicid, ga_log_intr);
+ __amd_iommu_update_ga(entry, apicid, wakeup_intr);
return __modify_irte_ga(ir_data->iommu, ir_data->irq_2_irte.devid,
ir_data->irq_2_irte.index, entry);
}
EXPORT_SYMBOL(amd_iommu_update_ga);
-int amd_iommu_activate_guest_mode(void *data, int apicid, bool ga_log_intr)
+int amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr)
{
struct amd_ir_data *ir_data = (struct amd_ir_data *)data;
struct irte_ga *entry = (struct irte_ga *) ir_data->entry;
@@ -4071,7 +4071,7 @@ int amd_iommu_activate_guest_mode(void *data, int apicid, bool ga_log_intr)
entry->hi.fields.vector = ir_data->ga_vector;
entry->lo.fields_vapic.ga_tag = ir_data->ga_tag;
- __amd_iommu_update_ga(entry, apicid, ga_log_intr);
+ __amd_iommu_update_ga(entry, apicid, wakeup_intr);
return modify_irte_ga(ir_data->iommu, ir_data->irq_2_irte.devid,
ir_data->irq_2_irte.index, entry);
@@ -4143,7 +4143,7 @@ static int amd_ir_set_vcpu_affinity(struct irq_data *data, void *info)
ir_data->ga_tag = pi_data->ga_tag;
if (pi_data->is_guest_mode)
ret = amd_iommu_activate_guest_mode(ir_data, pi_data->apicid,
- pi_data->ga_log_intr);
+ pi_data->wakeup_intr);
else
ret = amd_iommu_deactivate_guest_mode(ir_data);
} else {
diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h
index 2c6232aefafa..e20c909edc56 100644
--- a/include/linux/amd-iommu.h
+++ b/include/linux/amd-iommu.h
@@ -30,8 +30,8 @@ static inline void amd_iommu_detect(void) { }
/* IOMMU AVIC Function */
extern int amd_iommu_register_ga_log_notifier(int (*notifier)(u32));
-extern int amd_iommu_update_ga(void *data, int apicid, bool ga_log_intr);
-extern int amd_iommu_activate_guest_mode(void *data, int apicid, bool ga_log_intr);
+extern int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr);
+extern int amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr);
extern int amd_iommu_deactivate_guest_mode(void *data);
#else /* defined(CONFIG_AMD_IOMMU) && defined(CONFIG_IRQ_REMAP) */
@@ -42,12 +42,12 @@ amd_iommu_register_ga_log_notifier(int (*notifier)(u32))
return 0;
}
-static inline int amd_iommu_update_ga(void *data, int apicid, bool ga_log_intr)
+static inline int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr)
{
return 0;
}
-static inline int amd_iommu_activate_guest_mode(void *data, int apicid, bool ga_log_intr)
+static inline int amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr)
{
return 0;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v5 3/7] iommu/amd: KVM: SVM: Add explicit vCPU running state to IOMMU interface
2026-10-15 6:45 [PATCH v5 0/7] Add support for AMD IOMMU GAPPI Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 1/7] iommu/amd: KVM: SVM: Rename cpu to apicid in IOMMU interface Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 2/7] iommu/amd: KVM: SVM: Rename ga_log_intr to wakeup_intr " Sairaj Kodilkar
@ 2026-10-15 6:45 ` Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 4/7] iommu/amd: Program guest-mode IRTEs for GAPPI wakeup when IRTE[IsRun] = 0 Sairaj Kodilkar
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Sairaj Kodilkar @ 2026-10-15 6:45 UTC (permalink / raw)
To: H. Peter Anvin, Joerg Roedel (AMD), Mike Rapoport (Microsoft),
Andrew Morton, Borislav Petkov, Dapeng Mi, Dave Hansen,
Ethan Nelson-Moore, Ingo Molnar, Jakub Kicinski, Jeff Layton,
Jonathan Corbet, Kiryl Shutsemau, Li RongQing, Marco Elver,
Paolo Bonzini, Randy Dunlap, Rick Edgecombe, Robin Murphy,
Sairaj Kodilkar, Sean Christopherson, Shuah Khan,
Suravee Suthikulpanit, Thomas Gleixner, Vasant Hegde, Will Deacon,
iommu, kvm, linux-coco, linux-doc, linux-kernel, x86
Guest APIC Physical Processor Interrupt (GAPPI) is an AMD IOMMU mechanism
for notifying the host when a device interrupt targets a non-running vCPU
(IRTE[IsRun] = 0). Unlike the GA log path, GAPPI delivers a physical APIC
interrupt directly to the host CPU described by IRTE[Destination], with
the vector carried in IRTE[GATag], while still posting the interrupt into
the guest virtual APIC backing page as usual.
In GAPPI mode, the IOMMU delivers the notification to the host CPU using
the physical APIC ID in IRTE[Destination] and the vector in IRTE[GATag].
KVM must therefore supply a valid apicid even when the vCPU is not running.
The prior interface inferred running state from apicid: apicid >= 0 meant
running and apicid == -1 meant not running. That encoding breaks once
apicid carries the GAPPI destination while the vCPU is not running; the
IOMMU driver can no longer derive vCPU running state from apicid alone
and needs an explicit indication from KVM.
Add an explicit is_running indication to the IOMMU interface so that
SVM can pass vCPU running state to the IOMMU.
SVM calls amd_iommu_activate_guest_mode() only when the vCPU is running,
so keep is_running out of the exported prototype and make it a thin
wrapper that hardcodes is_running = true. The IOMMU internal caller,
amd_ir_set_vcpu_affinity(), calls __amd_iommu_activate_guest_mode()
directly and passes the running state supplied by KVM, i.e. it handles
both a running and a non-running vCPU.
No functional change is intended.
Signed-off-by: Sairaj Kodilkar <sarunkod@amd.com>
Reviewed-by: Vasant Hegde <vasant.hegde@amd.com>
---
arch/x86/include/asm/irq_remapping.h | 1 +
arch/x86/kvm/svm/avic.c | 7 ++++--
drivers/iommu/amd/iommu.c | 34 ++++++++++++++++------------
include/linux/amd-iommu.h | 6 +++--
4 files changed, 30 insertions(+), 18 deletions(-)
diff --git a/arch/x86/include/asm/irq_remapping.h b/arch/x86/include/asm/irq_remapping.h
index 789e3e154600..af0cdfa32c44 100644
--- a/arch/x86/include/asm/irq_remapping.h
+++ b/arch/x86/include/asm/irq_remapping.h
@@ -37,6 +37,7 @@ struct amd_iommu_pi_data {
u32 vector; /* Guest vector of the interrupt */
int apicid;
bool wakeup_intr;
+ bool is_running;
bool is_guest_mode;
void *ir_data;
};
diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
index edbdb5d75062..8b1fa5bded3d 100644
--- a/arch/x86/kvm/svm/avic.c
+++ b/arch/x86/kvm/svm/avic.c
@@ -961,9 +961,11 @@ int avic_pi_update_irte(struct kvm_kernel_irqfd *irqfd, struct kvm *kvm,
entry = svm->avic_physical_id_entry;
if (entry & AVIC_PHYSICAL_ID_ENTRY_IS_RUNNING_MASK) {
pi_data.apicid = entry & AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK;
+ pi_data.is_running = true;
} else {
pi_data.apicid = -1;
pi_data.wakeup_intr = entry & AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR;
+ pi_data.is_running = false;
}
ret = irq_set_vcpu_affinity(host_irq, &pi_data);
@@ -1019,6 +1021,7 @@ static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int apicid,
enum avic_vcpu_action action)
{
bool wakeup_intr = (action & AVIC_START_BLOCKING);
+ bool is_running = apicid >= 0;
struct vcpu_svm *svm = to_svm(vcpu);
struct kvm_kernel_irqfd *irqfd;
@@ -1035,8 +1038,8 @@ static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int apicid,
void *data = irqfd->irq_bypass_data;
if (!(action & AVIC_TOGGLE_ON_OFF))
- WARN_ON_ONCE(amd_iommu_update_ga(data, apicid, wakeup_intr));
- else if (apicid >= 0)
+ WARN_ON_ONCE(amd_iommu_update_ga(data, apicid, wakeup_intr, is_running));
+ else if (is_running)
WARN_ON_ONCE(amd_iommu_activate_guest_mode(data, apicid, wakeup_intr));
else
WARN_ON_ONCE(amd_iommu_deactivate_guest_mode(data));
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 878567708737..c0eec32ee3cb 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -3994,9 +3994,9 @@ static const struct irq_domain_ops amd_ir_domain_ops = {
};
static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
- bool wakeup_intr)
+ bool wakeup_intr, bool is_running)
{
- if (apicid >= 0) {
+ if (is_running) {
entry->lo.fields_vapic.destination =
APICID_TO_IRTE_DEST_LO(apicid);
entry->hi.fields.destination =
@@ -4013,12 +4013,11 @@ static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
* Update the pCPU information for an IRTE that is configured to post IRQs to
* a vCPU, without issuing an IOMMU invalidation for the IRTE.
*
- * If the vCPU is associated with a pCPU (@apicid >= 0), configure the
- * Destination with the pCPU's APIC ID, set IsRun, and clear GALogIntr. If the
- * vCPU isn't associated with a pCPU (@apicid < 0), clear IsRun and set/clear
- * GALogIntr based on input from the caller (e.g. KVM only requests wakeup_intr
- * when the vCPU is blocking and requires a notification wake event). I.e.
- * treat vCPUs that are associated with a pCPU as running. This API is
+ * If the vCPU is scheduled to run on pCPU (@is_running = 1), configure the
+ * Destination with the pCPU's APIC ID, set IsRun, and clear GALogIntr. If the
+ * vCPU is scheduled out (@is_running = 0), clear IsRun and set/clear GALogIntr
+ * based on input from the caller (e.g. KVM only requests wakeup_intr when the
+ * vCPU is blocking and requires a notification wake event). This API is
* intended to be used when a vCPU is scheduled in/out (or stops running for
* any reason), to do a fast update of IsRun, GALogIntr, and (conditionally)
* Destination.
@@ -4027,7 +4026,7 @@ static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
* and thus don't require an invalidation to ensure the IOMMU consumes fresh
* information.
*/
-int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr)
+int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr, bool is_running)
{
struct amd_ir_data *ir_data = (struct amd_ir_data *)data;
struct irte_ga *entry = (struct irte_ga *) ir_data->entry;
@@ -4041,14 +4040,15 @@ int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr)
if (!ir_data->iommu)
return -ENODEV;
- __amd_iommu_update_ga(entry, apicid, wakeup_intr);
+ __amd_iommu_update_ga(entry, apicid, wakeup_intr, is_running);
return __modify_irte_ga(ir_data->iommu, ir_data->irq_2_irte.devid,
ir_data->irq_2_irte.index, entry);
}
EXPORT_SYMBOL(amd_iommu_update_ga);
-int amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr)
+static int __amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr,
+ bool is_running)
{
struct amd_ir_data *ir_data = (struct amd_ir_data *)data;
struct irte_ga *entry = (struct irte_ga *) ir_data->entry;
@@ -4071,11 +4071,16 @@ int amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr)
entry->hi.fields.vector = ir_data->ga_vector;
entry->lo.fields_vapic.ga_tag = ir_data->ga_tag;
- __amd_iommu_update_ga(entry, apicid, wakeup_intr);
+ __amd_iommu_update_ga(entry, apicid, wakeup_intr, is_running);
return modify_irte_ga(ir_data->iommu, ir_data->irq_2_irte.devid,
ir_data->irq_2_irte.index, entry);
}
+
+int amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr)
+{
+ return __amd_iommu_activate_guest_mode(data, apicid, wakeup_intr, true);
+}
EXPORT_SYMBOL(amd_iommu_activate_guest_mode);
int amd_iommu_deactivate_guest_mode(void *data)
@@ -4142,8 +4147,9 @@ static int amd_ir_set_vcpu_affinity(struct irq_data *data, void *info)
ir_data->ga_vector = pi_data->vector;
ir_data->ga_tag = pi_data->ga_tag;
if (pi_data->is_guest_mode)
- ret = amd_iommu_activate_guest_mode(ir_data, pi_data->apicid,
- pi_data->wakeup_intr);
+ ret = __amd_iommu_activate_guest_mode(ir_data, pi_data->apicid,
+ pi_data->wakeup_intr,
+ pi_data->is_running);
else
ret = amd_iommu_deactivate_guest_mode(ir_data);
} else {
diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h
index e20c909edc56..a5f1bd6db0ee 100644
--- a/include/linux/amd-iommu.h
+++ b/include/linux/amd-iommu.h
@@ -30,7 +30,8 @@ static inline void amd_iommu_detect(void) { }
/* IOMMU AVIC Function */
extern int amd_iommu_register_ga_log_notifier(int (*notifier)(u32));
-extern int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr);
+extern int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr,
+ bool is_running);
extern int amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr);
extern int amd_iommu_deactivate_guest_mode(void *data);
@@ -42,7 +43,8 @@ amd_iommu_register_ga_log_notifier(int (*notifier)(u32))
return 0;
}
-static inline int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr)
+static inline int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr,
+ bool is_running)
{
return 0;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v5 4/7] iommu/amd: Program guest-mode IRTEs for GAPPI wakeup when IRTE[IsRun] = 0
2026-10-15 6:45 [PATCH v5 0/7] Add support for AMD IOMMU GAPPI Sairaj Kodilkar
` (2 preceding siblings ...)
2026-10-15 6:45 ` [PATCH v5 3/7] iommu/amd: KVM: SVM: Add explicit vCPU running state to " Sairaj Kodilkar
@ 2026-10-15 6:45 ` Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 5/7] KVM: VMX: Factor out wakeup list handling code to KVM Sairaj Kodilkar
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Sairaj Kodilkar @ 2026-10-15 6:45 UTC (permalink / raw)
To: H. Peter Anvin, Joerg Roedel (AMD), Mike Rapoport (Microsoft),
Andrew Morton, Borislav Petkov, Dapeng Mi, Dave Hansen,
Ethan Nelson-Moore, Ingo Molnar, Jakub Kicinski, Jeff Layton,
Jonathan Corbet, Kiryl Shutsemau, Li RongQing, Marco Elver,
Paolo Bonzini, Randy Dunlap, Rick Edgecombe, Robin Murphy,
Sairaj Kodilkar, Sean Christopherson, Shuah Khan,
Suravee Suthikulpanit, Thomas Gleixner, Vasant Hegde, Will Deacon,
iommu, kvm, linux-coco, linux-doc, linux-kernel, x86
Guest APIC Physical Processor Interrupt (GAPPI) is an alternative to the
GA log for notifying the host when a device interrupt targets a
non-running vCPU (IRTE[IsRun] = 0). Per the AMD IOMMU specification [1]
(section 2.2.5.4), with GAPPI enabled the IOMMU delivers a physical APIC
interrupt to the CPU described by IRTE[Destination], using
IRTE[GATag][7:0] as the vector, while still updating the guest vAPIC
backing page IRR as usual.
The AMD IOMMU also allows suppressing GAPPI interrupts using the
IRTE[GAPPIDis] bit when the GAPPIDisSup feature is available in
extended feature register 2. The AMD IOMMU driver sets this bit when
the wakeup_intr flag is not set by KVM.
Note: amd_iommu_gappi is currently false; a later patch enables it via
the kernel command line.
[1] https://docs.amd.com/v/u/en-US/48882_3.11_IOMMU_PUB
Signed-off-by: Sairaj Kodilkar <sarunkod@amd.com>
Reviewed-by: Vasant Hegde <vasant.hegde@amd.com>
---
drivers/iommu/amd/amd_iommu_types.h | 4 +++-
drivers/iommu/amd/init.c | 3 +++
drivers/iommu/amd/iommu.c | 30 +++++++++++++++++++----------
include/linux/amd-iommu.h | 6 ++++++
4 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h
index 8241ef922519..b89a2b3df0a4 100644
--- a/drivers/iommu/amd/amd_iommu_types.h
+++ b/drivers/iommu/amd/amd_iommu_types.h
@@ -97,6 +97,7 @@
/* Extended Feature 2 Bits */
#define FEATURE_SEVSNPIO_SUP BIT_ULL(1)
#define FEATURE_GCR3TRPMODE BIT_ULL(3)
+#define FEATURE_GAPPIDISSUP BIT_ULL(4)
#define FEATURE_SNPAVICSUP GENMASK_ULL(7, 5)
#define FEATURE_SNPAVICSUP_GAM(x) \
(FIELD_GET(FEATURE_SNPAVICSUP, x) == 0x1)
@@ -982,7 +983,8 @@ union irte_ga_lo {
no_fault : 1,
/* ------ */
ga_log_intr : 1,
- rsvd1 : 3,
+ rsvd1 : 2,
+ gappi_dis : 1,
is_run : 1,
/* ------ */
guest_mode : 1,
diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c
index 8a410d4aa370..ef23e0755c32 100644
--- a/drivers/iommu/amd/init.c
+++ b/drivers/iommu/amd/init.c
@@ -160,6 +160,9 @@ u8 amd_iommu_hpt_vasize;
/* Guest page table level */
int amd_iommu_gpt_level = PAGE_MODE_4_LEVEL;
+bool amd_iommu_gappi;
+EXPORT_SYMBOL(amd_iommu_gappi);
+
int amd_iommu_guest_ir = AMD_IOMMU_GUEST_IR_VAPIC;
static int amd_iommu_xt_mode = IRQ_REMAP_XAPIC_MODE;
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index c0eec32ee3cb..7ceb6bf508f0 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -4004,8 +4004,18 @@ static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
entry->lo.fields_vapic.is_run = true;
entry->lo.fields_vapic.ga_log_intr = false;
} else {
- entry->lo.fields_vapic.is_run = false;
- entry->lo.fields_vapic.ga_log_intr = wakeup_intr;
+ if (amd_iommu_gappi) {
+ entry->lo.fields_vapic.gappi_dis = !wakeup_intr &&
+ check_feature2(FEATURE_GAPPIDISSUP);
+ entry->lo.fields_vapic.is_run = false;
+ entry->lo.fields_vapic.destination =
+ APICID_TO_IRTE_DEST_LO(apicid);
+ entry->hi.fields.destination =
+ APICID_TO_IRTE_DEST_HI(apicid);
+ } else {
+ entry->lo.fields_vapic.is_run = false;
+ entry->lo.fields_vapic.ga_log_intr = wakeup_intr;
+ }
}
}
@@ -4016,15 +4026,15 @@ static void __amd_iommu_update_ga(struct irte_ga *entry, int apicid,
* If the vCPU is scheduled to run on pCPU (@is_running = 1), configure the
* Destination with the pCPU's APIC ID, set IsRun, and clear GALogIntr. If the
* vCPU is scheduled out (@is_running = 0), clear IsRun and set/clear GALogIntr
- * based on input from the caller (e.g. KVM only requests wakeup_intr when the
- * vCPU is blocking and requires a notification wake event). This API is
- * intended to be used when a vCPU is scheduled in/out (or stops running for
- * any reason), to do a fast update of IsRun, GALogIntr, and (conditionally)
- * Destination.
+ * and GAPPIDis based on input from the caller (e.g. KVM only requests
+ * wakeup_intr when the vCPU is blocking and requires a notification wake
+ * event). This API is intended to be used when a vCPU is scheduled in/out (or
+ * stops running for any reason), to do a fast update of IsRun, GALogIntr,
+ * GAPPIDis and (conditionally) Destination.
*
- * Per the IOMMU spec, the Destination, IsRun, and GATag fields are not cached
- * and thus don't require an invalidation to ensure the IOMMU consumes fresh
- * information.
+ * Per the IOMMU spec, the Destination, IsRun, GATag and GAPPIDis fields are
+ * not cached and thus don't require an invalidation to ensure the IOMMU
+ * consumes fresh information.
*/
int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr, bool is_running)
{
diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h
index a5f1bd6db0ee..de228140ce6e 100644
--- a/include/linux/amd-iommu.h
+++ b/include/linux/amd-iommu.h
@@ -35,6 +35,9 @@ extern int amd_iommu_update_ga(void *data, int apicid, bool wakeup_intr,
extern int amd_iommu_activate_guest_mode(void *data, int apicid, bool wakeup_intr);
extern int amd_iommu_deactivate_guest_mode(void *data);
+/* IOMMU AVIC Flag */
+extern bool amd_iommu_gappi;
+
#else /* defined(CONFIG_AMD_IOMMU) && defined(CONFIG_IRQ_REMAP) */
static inline int
@@ -58,6 +61,9 @@ static inline int amd_iommu_deactivate_guest_mode(void *data)
{
return 0;
}
+
+#define amd_iommu_gappi false
+
#endif /* defined(CONFIG_AMD_IOMMU) && defined(CONFIG_IRQ_REMAP) */
int amd_iommu_get_num_iommus(void);
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v5 5/7] KVM: VMX: Factor out wakeup list handling code to KVM
2026-10-15 6:45 [PATCH v5 0/7] Add support for AMD IOMMU GAPPI Sairaj Kodilkar
` (3 preceding siblings ...)
2026-10-15 6:45 ` [PATCH v5 4/7] iommu/amd: Program guest-mode IRTEs for GAPPI wakeup when IRTE[IsRun] = 0 Sairaj Kodilkar
@ 2026-10-15 6:45 ` Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 6/7] KVM: SVM: Add support for AMD IOMMU Guest APIC Physical Processor Interrupt (GAPPI) Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 7/7] iommu/amd: Provide kernel command line option to enable GAPPI Sairaj Kodilkar
6 siblings, 0 replies; 8+ messages in thread
From: Sairaj Kodilkar @ 2026-10-15 6:45 UTC (permalink / raw)
To: H. Peter Anvin, Joerg Roedel (AMD), Mike Rapoport (Microsoft),
Andrew Morton, Borislav Petkov, Dapeng Mi, Dave Hansen,
Ethan Nelson-Moore, Ingo Molnar, Jakub Kicinski, Jeff Layton,
Jonathan Corbet, Kiryl Shutsemau, Li RongQing, Marco Elver,
Paolo Bonzini, Randy Dunlap, Rick Edgecombe, Robin Murphy,
Sairaj Kodilkar, Sean Christopherson, Shuah Khan,
Suravee Suthikulpanit, Thomas Gleixner, Vasant Hegde, Will Deacon,
iommu, kvm, linux-coco, linux-doc, linux-kernel, x86
VMX maintains a per-pCPU list of blocked vCPUs so that they can be woken
when a device interrupt arrives while the vCPU is not running. The vCPU
is queued on the list of the pCPU it last ran on and its notification
vector is switched to POSTED_INTR_WAKEUP_VECTOR, so the handler for that
vector walks the local list and wakes the vCPUs that have a notification
pending.
Guest APIC Physical Processor Interrupt (GAPPI) needs the same
infrastructure on AMD. When a device interrupt targets a non-running
vCPU (IRTE[IsRun] = 0), the IOMMU delivers a physical APIC interrupt to
IRTE[Destination] using IRTE[GATag][7:0] as the vector, which KVM
programs to POSTED_INTR_WAKEUP_VECTOR. SVM therefore requires the same
per-pCPU list of blocked vCPUs that VMX already has. Hence Move the
per-pCPU list, its lock, the wakeup handler and the per-CPU
initialization from VMX to common KVM x86 code.
Intel and AMD determine whether a queued vCPU has a notification pending
in different ways. Hence, introduce a new kvm_x86_ops callback
vcpu_irq_pending(), which the wakeup handler uses to decide whether a
vCPU needs to be woken.
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sairaj Kodilkar <sarunkod@amd.com>
---
arch/x86/include/asm/kvm-x86-ops.h | 1 +
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/vmx/common.h | 3 -
arch/x86/kvm/vmx/main.c | 6 ++
arch/x86/kvm/vmx/posted_intr.c | 82 +--------------------
arch/x86/kvm/vmx/tdx.c | 1 -
arch/x86/kvm/vmx/vmx.c | 14 ++--
arch/x86/kvm/vmx/x86_ops.h | 1 +
arch/x86/kvm/x86.c | 111 +++++++++++++++++++++++++++++
include/linux/kvm_host.h | 6 ++
10 files changed, 135 insertions(+), 91 deletions(-)
diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index e213c9ae3e30..5d337f9336c5 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -30,6 +30,7 @@ KVM_X86_OP(vcpu_reset)
KVM_X86_OP(prepare_switch_to_guest)
KVM_X86_OP(vcpu_load)
KVM_X86_OP(vcpu_put)
+KVM_X86_OP_OPTIONAL(vcpu_irq_pending)
KVM_X86_OP(update_exception_bitmap)
KVM_X86_OP(get_msr)
KVM_X86_OP(set_msr)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 683bb8bf43a9..51ed472c5ec3 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1529,6 +1529,7 @@ struct kvm_x86_ops {
void (*prepare_switch_to_guest)(struct kvm_vcpu *vcpu);
void (*vcpu_load)(struct kvm_vcpu *vcpu, int cpu);
void (*vcpu_put)(struct kvm_vcpu *vcpu);
+ bool (*vcpu_irq_pending)(struct kvm_vcpu *vcpu);
/*
* Mask of DEBUGCTL bits that are owned by the host, i.e. that need to
diff --git a/arch/x86/kvm/vmx/common.h b/arch/x86/kvm/vmx/common.h
index 08005676702c..5762d90a9531 100644
--- a/arch/x86/kvm/vmx/common.h
+++ b/arch/x86/kvm/vmx/common.h
@@ -34,9 +34,6 @@ struct vcpu_vt {
/* Posted interrupt descriptor */
struct pi_desc pi_desc;
- /* Used if this vCPU is waiting for PI notification wakeup. */
- struct list_head pi_wakeup_list;
-
union vmx_exit_reason exit_reason;
unsigned long exit_qualification;
diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c
index 4c52ab8d0786..af5808eacc72 100644
--- a/arch/x86/kvm/vmx/main.c
+++ b/arch/x86/kvm/vmx/main.c
@@ -108,6 +108,11 @@ static void vt_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
vmx_vcpu_load(vcpu, cpu);
}
+static bool vt_vcpu_irq_pending(struct kvm_vcpu *vcpu)
+{
+ return vmx_vcpu_irq_pending(vcpu);
+}
+
static void vt_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
{
/*
@@ -912,6 +917,7 @@ struct kvm_x86_ops vt_x86_ops __initdata = {
.prepare_switch_to_guest = vt_op(prepare_switch_to_guest),
.vcpu_load = vt_op(vcpu_load),
.vcpu_put = vt_op(vcpu_put),
+ .vcpu_irq_pending = vt_op(vcpu_irq_pending),
.HOST_OWNED_DEBUGCTL = VMX_HOST_OWNED_DEBUGCTL_BITS,
diff --git a/arch/x86/kvm/vmx/posted_intr.c b/arch/x86/kvm/vmx/posted_intr.c
index 4a6d9a17da23..1a22730bfafd 100644
--- a/arch/x86/kvm/vmx/posted_intr.c
+++ b/arch/x86/kvm/vmx/posted_intr.c
@@ -14,27 +14,6 @@
#include "vmx.h"
#include "tdx.h"
-/*
- * Maintain a per-CPU list of vCPUs that need to be awakened by wakeup_handler()
- * when a WAKEUP_VECTOR interrupted is posted. vCPUs are added to the list when
- * the vCPU is scheduled out and is blocking (e.g. in HLT) with IRQs enabled.
- * The vCPUs posted interrupt descriptor is updated at the same time to set its
- * notification vector to WAKEUP_VECTOR, so that posted interrupt from devices
- * wake the target vCPUs. vCPUs are removed from the list and the notification
- * vector is reset when the vCPU is scheduled in.
- */
-static DEFINE_PER_CPU(struct list_head, wakeup_vcpus_on_cpu);
-/*
- * Protect the per-CPU list with a per-CPU spinlock to handle task migration.
- * When a blocking vCPU is awakened _and_ migrated to a different pCPU, the
- * ->sched_in() path will need to take the vCPU off the list of the _previous_
- * CPU. IRQs must be disabled when taking this lock, otherwise deadlock will
- * occur if a wakeup IRQ arrives and attempts to acquire the lock.
- */
-static DEFINE_PER_CPU(raw_spinlock_t, wakeup_vcpus_on_cpu_lock);
-
-#define PI_LOCK_SCHED_OUT SINGLE_DEPTH_NESTING
-
static struct pi_desc *vcpu_to_pi_desc(struct kvm_vcpu *vcpu)
{
return &(to_vt(vcpu)->pi_desc);
@@ -57,7 +36,6 @@ static int pi_try_set_control(struct pi_desc *pi_desc, u64 *pold, u64 new)
void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
{
struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
- struct vcpu_vt *vt = to_vt(vcpu);
struct pi_desc old, new;
unsigned long flags;
unsigned int dest;
@@ -90,20 +68,7 @@ void vmx_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
* current pCPU if the task was migrated.
*/
if (pi_desc->nv == POSTED_INTR_WAKEUP_VECTOR) {
- raw_spinlock_t *spinlock = &per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu);
-
- /*
- * In addition to taking the wakeup lock for the regular/IRQ
- * context, tell lockdep it is being taken for the "sched out"
- * context as well. vCPU loads happens in task context, and
- * this is taking the lock of the *previous* CPU, i.e. can race
- * with both the scheduler and the wakeup handler.
- */
- raw_spin_lock(spinlock);
- spin_acquire(&spinlock->dep_map, PI_LOCK_SCHED_OUT, 0, _RET_IP_);
- list_del(&vt->pi_wakeup_list);
- spin_release(&spinlock->dep_map, _RET_IP_);
- raw_spin_unlock(spinlock);
+ kvm_pi_disable_wakeup_handler(vcpu, vcpu->cpu);
}
dest = cpu_physical_id(cpu);
@@ -162,28 +127,9 @@ static bool vmx_can_use_vtd_pi(struct kvm *kvm)
static void pi_enable_wakeup_handler(struct kvm_vcpu *vcpu)
{
struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
- struct vcpu_vt *vt = to_vt(vcpu);
struct pi_desc old, new;
- lockdep_assert_irqs_disabled();
-
- /*
- * Acquire the wakeup lock using the "sched out" context to workaround
- * a lockdep false positive. When this is called, schedule() holds
- * various per-CPU scheduler locks. When the wakeup handler runs, it
- * holds this CPU's wakeup lock while calling try_to_wake_up(), which
- * can eventually take the aforementioned scheduler locks, which causes
- * lockdep to assume there is deadlock.
- *
- * Deadlock can't actually occur because IRQs are disabled for the
- * entirety of the sched_out critical section, i.e. the wakeup handler
- * can't run while the scheduler locks are held.
- */
- raw_spin_lock_nested(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu),
- PI_LOCK_SCHED_OUT);
- list_add_tail(&vt->pi_wakeup_list,
- &per_cpu(wakeup_vcpus_on_cpu, vcpu->cpu));
- raw_spin_unlock(&per_cpu(wakeup_vcpus_on_cpu_lock, vcpu->cpu));
+ kvm_pi_enable_wakeup_handler(vcpu, vcpu->cpu);
WARN(pi_test_sn(pi_desc), "PI descriptor SN field set before blocking");
@@ -247,30 +193,6 @@ void vmx_vcpu_pi_put(struct kvm_vcpu *vcpu)
pi_set_sn(pi_desc);
}
-/*
- * Handler for POSTED_INTERRUPT_WAKEUP_VECTOR.
- */
-void pi_wakeup_handler(void)
-{
- int cpu = smp_processor_id();
- struct list_head *wakeup_list = &per_cpu(wakeup_vcpus_on_cpu, cpu);
- raw_spinlock_t *spinlock = &per_cpu(wakeup_vcpus_on_cpu_lock, cpu);
- struct vcpu_vt *vt;
-
- raw_spin_lock(spinlock);
- list_for_each_entry(vt, wakeup_list, pi_wakeup_list) {
-
- if (pi_test_on(&vt->pi_desc))
- kvm_vcpu_wake_up(vt_to_vcpu(vt));
- }
- raw_spin_unlock(spinlock);
-}
-
-void __init pi_init_cpu(int cpu)
-{
- INIT_LIST_HEAD(&per_cpu(wakeup_vcpus_on_cpu, cpu));
- raw_spin_lock_init(&per_cpu(wakeup_vcpus_on_cpu_lock, cpu));
-}
void pi_apicv_pre_state_restore(struct kvm_vcpu *vcpu)
{
diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c
index 00850a3a77d6..1ba73b1379a7 100644
--- a/arch/x86/kvm/vmx/tdx.c
+++ b/arch/x86/kvm/vmx/tdx.c
@@ -683,7 +683,6 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu)
fpstate_set_confidential(&vcpu->arch.guest_fpu);
vcpu->arch.apic->guest_apic_protected = true;
- INIT_LIST_HEAD(&tdx->vt.pi_wakeup_list);
vcpu->arch.efer = EFER_SCE | EFER_LME | EFER_LMA | EFER_NX;
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 612ab07d4100..3359f2481d77 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -1587,6 +1587,13 @@ void vmx_vcpu_put(struct kvm_vcpu *vcpu)
vmx_prepare_switch_to_host(to_vmx(vcpu));
}
+bool vmx_vcpu_irq_pending(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_vt *vt = to_vt(vcpu);
+
+ return pi_test_on(&vt->pi_desc);
+}
+
static void vmx_switch_loaded_vmcs(struct kvm_vcpu *vcpu,
struct loaded_vmcs *vmcs)
{
@@ -7667,8 +7674,6 @@ int vmx_vcpu_create(struct kvm_vcpu *vcpu)
BUILD_BUG_ON(offsetof(struct vcpu_vmx, vcpu) != 0);
vmx = to_vmx(vcpu);
- INIT_LIST_HEAD(&vmx->vt.pi_wakeup_list);
-
err = -ENOMEM;
vmx->vpid = allocate_vpid();
@@ -8548,8 +8553,6 @@ void vmx_migrate_timers(struct kvm_vcpu *vcpu)
void vmx_hardware_unsetup(void)
{
- kvm_set_posted_intr_wakeup_handler(NULL);
-
if (nested)
nested_vmx_hardware_unsetup();
}
@@ -8830,7 +8833,6 @@ __init int vmx_hardware_setup(void)
}
vmx_nested_ops.enabled = nested;
- kvm_set_posted_intr_wakeup_handler(pi_wakeup_handler);
/*
* On Intel CPUs that lack self-snoop feature, letting the guest control
@@ -8900,8 +8902,6 @@ int __init vmx_init(void)
for_each_possible_cpu(cpu) {
INIT_LIST_HEAD(&per_cpu(loaded_vmcss_on_cpu, cpu));
-
- pi_init_cpu(cpu);
}
vmx_check_vmcs12_offsets();
diff --git a/arch/x86/kvm/vmx/x86_ops.h b/arch/x86/kvm/vmx/x86_ops.h
index 054fd14bb2e1..114dd8bb48b9 100644
--- a/arch/x86/kvm/vmx/x86_ops.h
+++ b/arch/x86/kvm/vmx/x86_ops.h
@@ -26,6 +26,7 @@ void vmx_vcpu_free(struct kvm_vcpu *vcpu);
void vmx_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event);
void vmx_vcpu_load(struct kvm_vcpu *vcpu, int cpu);
void vmx_vcpu_put(struct kvm_vcpu *vcpu);
+bool vmx_vcpu_irq_pending(struct kvm_vcpu *vcpu);
int vmx_handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath);
void vmx_handle_exit_irqoff(struct kvm_vcpu *vcpu);
int vmx_skip_emulated_instruction(struct kvm_vcpu *vcpu);
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index aad065d035fb..0c637a18bcfa 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -310,6 +310,27 @@ const struct kvm_stats_header kvm_vcpu_stats_header = {
static struct kmem_cache *x86_emulator_cache;
+/*
+ * Maintain a per-CPU list of vCPUs that need to be awakened by wakeup_handler()
+ * when a WAKEUP_VECTOR interrupted is posted. vCPUs are added to the list when
+ * the vCPU is scheduled out and is blocking (e.g. in HLT) with IRQs enabled.
+ * The vCPUs posted interrupt descriptor is updated at the same time to set its
+ * notification vector to WAKEUP_VECTOR, so that posted interrupt from devices
+ * wake the target vCPUs. vCPUs are removed from the list and the notification
+ * vector is reset when the vCPU is scheduled in.
+ */
+static DEFINE_PER_CPU(struct list_head, wakeup_vcpus_on_cpu);
+/*
+ * Protect the per-CPU list with a per-CPU spinlock to handle task migration.
+ * When a blocking vCPU is awakened _and_ migrated to a different pCPU, the
+ * ->sched_in() path will need to take the vCPU off the list of the _previous_
+ * CPU. IRQs must be disabled when taking this lock, otherwise deadlock will
+ * occur if a wakeup IRQ arrives and attempts to acquire the lock.
+ */
+static DEFINE_PER_CPU(raw_spinlock_t, wakeup_vcpus_on_cpu_lock);
+
+#define PI_LOCK_SCHED_OUT SINGLE_DEPTH_NESTING
+
static struct kmem_cache *kvm_alloc_emulator_cache(void)
{
unsigned int useroffset = offsetof(struct x86_emulate_ctxt, src);
@@ -2688,6 +2709,79 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
vcpu->arch.last_host_tsc = rdtsc();
}
+void kvm_pi_enable_wakeup_handler(struct kvm_vcpu *vcpu, int cpu)
+{
+ lockdep_assert_irqs_disabled();
+
+ /*
+ * Acquire the wakeup lock using the "sched out" context to workaround
+ * a lockdep false positive. When this is called, schedule() holds
+ * various per-CPU scheduler locks. When the wakeup handler runs, it
+ * holds this CPU's wakeup lock while calling try_to_wake_up(), which
+ * can eventually take the aforementioned scheduler locks, which causes
+ * lockdep to assume there is deadlock.
+ *
+ * Deadlock can't actually occur because IRQs are disabled for the
+ * entirety of the sched_out critical section, i.e. the wakeup handler
+ * can't run while the scheduler locks are held.
+ */
+ raw_spin_lock_nested(&per_cpu(wakeup_vcpus_on_cpu_lock, cpu),
+ PI_LOCK_SCHED_OUT);
+ if (list_empty(&vcpu->pi_wakeup_list))
+ list_add_tail(&vcpu->pi_wakeup_list,
+ &per_cpu(wakeup_vcpus_on_cpu, cpu));
+ raw_spin_unlock(&per_cpu(wakeup_vcpus_on_cpu_lock, cpu));
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_pi_enable_wakeup_handler);
+
+void kvm_pi_disable_wakeup_handler(struct kvm_vcpu *vcpu, int cpu)
+{
+ raw_spinlock_t *spinlock;
+
+ lockdep_assert_irqs_disabled();
+
+ spinlock = &per_cpu(wakeup_vcpus_on_cpu_lock, cpu);
+
+ /*
+ * In addition to taking the wakeup lock for the regular/IRQ
+ * context, tell lockdep it is being taken for the "sched out"
+ * context as well. vCPU loads happens in task context, and
+ * this is taking the lock of the *previous* CPU, i.e. can race
+ * with both the scheduler and the wakeup handler.
+ */
+ raw_spin_lock(spinlock);
+ spin_acquire(&spinlock->dep_map, PI_LOCK_SCHED_OUT, 0, _RET_IP_);
+ if (!list_empty(&vcpu->pi_wakeup_list))
+ list_del_init(&vcpu->pi_wakeup_list);
+ spin_release(&spinlock->dep_map, _RET_IP_);
+ raw_spin_unlock(spinlock);
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_pi_disable_wakeup_handler);
+
+/*
+ * Handler for POSTED_INTERRUPT_WAKEUP_VECTOR.
+ */
+static void kvm_pi_wakeup_handler(void)
+{
+ int cpu = smp_processor_id();
+ struct list_head *wakeup_list = &per_cpu(wakeup_vcpus_on_cpu, cpu);
+ raw_spinlock_t *spinlock = &per_cpu(wakeup_vcpus_on_cpu_lock, cpu);
+ struct kvm_vcpu *vcpu;
+
+ raw_spin_lock(spinlock);
+ list_for_each_entry(vcpu, wakeup_list, pi_wakeup_list) {
+ if (kvm_x86_call(vcpu_irq_pending)(vcpu))
+ kvm_vcpu_wake_up(vcpu);
+ }
+ raw_spin_unlock(spinlock);
+}
+
+static void __init kvm_pi_init_cpu(int cpu)
+{
+ INIT_LIST_HEAD(&per_cpu(wakeup_vcpus_on_cpu, cpu));
+ raw_spin_lock_init(&per_cpu(wakeup_vcpus_on_cpu_lock, cpu));
+}
+
static int kvm_vcpu_ioctl_get_lapic(struct kvm_vcpu *vcpu,
struct kvm_lapic_state *s)
{
@@ -7109,6 +7203,13 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
*/
kvm_timer_init();
+ /*
+ * Do not check if enable_device_posted_irqs is true because VMX
+ * requires wakeup handler for ipiv even if enable_device_posted_irqs
+ * is false
+ */
+ kvm_set_posted_intr_wakeup_handler(kvm_pi_wakeup_handler);
+
if (pi_inject_timer == -1)
pi_inject_timer = housekeeping_enabled(HK_TYPE_TIMER);
#ifdef CONFIG_X86_64
@@ -7175,6 +7276,7 @@ void kvm_x86_vendor_exit(void)
irq_work_sync(&pvclock_irq_work);
cancel_work_sync(&pvclock_gtod_work);
#endif
+ kvm_set_posted_intr_wakeup_handler(NULL);
kvm_x86_call(hardware_unsetup)();
kvm_destroy_user_return_msrs();
kvm_mmu_vendor_module_exit();
@@ -9346,6 +9448,8 @@ int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
struct page *page;
int r;
+ INIT_LIST_HEAD(&vcpu->pi_wakeup_list);
+
vcpu->arch.last_vmentry_cpu = -1;
bitmap_fill(vcpu->arch.regs_avail, NR_VCPU_TOTAL_REGS);
bitmap_fill(vcpu->arch.regs_dirty, NR_VCPU_TOTAL_REGS);
@@ -11010,10 +11114,17 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(kvm_rmp_fault);
static int __init kvm_x86_init(void)
{
+ int cpu;
+
kvm_init_xstate_sizes();
kvm_mmu_x86_module_init();
mitigate_smt_rsb &= boot_cpu_has_bug(X86_BUG_SMT_RSB) && cpu_smt_possible();
+
+ for_each_possible_cpu(cpu) {
+ kvm_pi_init_cpu(cpu);
+ }
+
return 0;
}
module_init(kvm_x86_init);
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index c844def6dd95..efea016e8186 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -398,6 +398,9 @@ struct kvm_vcpu {
*/
struct kvm_memory_slot *last_used_slot;
u64 last_used_slot_gen;
+
+ /* Used if this vCPU is waiting for PI notification wakeup. */
+ struct list_head pi_wakeup_list;
};
/*
@@ -2651,4 +2654,7 @@ long kvm_arch_vcpu_pre_fault_memory(struct kvm_vcpu *vcpu,
struct kvm_pre_fault_memory *range);
#endif
+void kvm_pi_enable_wakeup_handler(struct kvm_vcpu *vcpu, int cpu);
+void kvm_pi_disable_wakeup_handler(struct kvm_vcpu *vcpu, int cpu);
+
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v5 6/7] KVM: SVM: Add support for AMD IOMMU Guest APIC Physical Processor Interrupt (GAPPI)
2026-10-15 6:45 [PATCH v5 0/7] Add support for AMD IOMMU GAPPI Sairaj Kodilkar
` (4 preceding siblings ...)
2026-10-15 6:45 ` [PATCH v5 5/7] KVM: VMX: Factor out wakeup list handling code to KVM Sairaj Kodilkar
@ 2026-10-15 6:45 ` Sairaj Kodilkar
2026-10-15 6:45 ` [PATCH v5 7/7] iommu/amd: Provide kernel command line option to enable GAPPI Sairaj Kodilkar
6 siblings, 0 replies; 8+ messages in thread
From: Sairaj Kodilkar @ 2026-10-15 6:45 UTC (permalink / raw)
To: H. Peter Anvin, Joerg Roedel (AMD), Mike Rapoport (Microsoft),
Andrew Morton, Borislav Petkov, Dapeng Mi, Dave Hansen,
Ethan Nelson-Moore, Ingo Molnar, Jakub Kicinski, Jeff Layton,
Jonathan Corbet, Kiryl Shutsemau, Li RongQing, Marco Elver,
Paolo Bonzini, Randy Dunlap, Rick Edgecombe, Robin Murphy,
Sairaj Kodilkar, Sean Christopherson, Shuah Khan,
Suravee Suthikulpanit, Thomas Gleixner, Vasant Hegde, Will Deacon,
iommu, kvm, linux-coco, linux-doc, linux-kernel, x86
With AVIC guest-mode interrupt remapping, device interrupts are posted into
the guest vAPIC backing page by the IOMMU. When the vCPU is not running
(IRTE[IsRun] = 0), KVM must still be notified to schedule it. The legacy
path uses the GA log.
GAPPI (Guest APIC Physical Processor Interrupt) is an alternative to the
GA log mechanism provided by the AMD IOMMU. With GAPPI enabled, the IOMMU
still updates the vAPIC backing page IRR, but the host wakeup notification
is delivered as a physical APIC interrupt to IRTE[Destination], using
IRTE[GATag][7:0] as the vector (POSTED_INTR_WAKEUP_VECTOR).
SVM follows the Intel posted-interrupt wakeup model. Each pCPU maintains
a list of blocked vCPUs that may be woken by a GAPPI delivery to that CPU.
When a vCPU blocks while waiting for an interrupt, SVM enqueues it on the
wakeup list of the pCPU on which it was previously running and passes that
same pCPU's physical APIC ID to the IOMMU to program IRTE[Destination].
The rationale is that the vCPU is likely to run again on the same pCPU,
which is common when vCPUs are pinned; targeting GAPPI notifications there
reduces unnecessary VMEXITs from GAPPI deliveries on other CPUs. SVM
registers the GAPPI handler via kvm_set_posted_intr_wakeup_handler(). On
delivery, it walks the local vCPU list and wakes vCPUs with a pending IRR.
All GAPPI logic is gated on amd_iommu_gappi. Without it, KVM and the IOMMU
falls back to the legacy GA log mechanism for vCPU wakeup.
Signed-off-by: Sairaj Kodilkar <sarunkod@amd.com>
---
arch/x86/kvm/svm/avic.c | 53 ++++++++++++++++++++++++++++++++++-------
arch/x86/kvm/svm/svm.c | 1 +
arch/x86/kvm/svm/svm.h | 4 ++++
3 files changed, 50 insertions(+), 8 deletions(-)
diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
index 8b1fa5bded3d..054b89c3cb0f 100644
--- a/arch/x86/kvm/svm/avic.c
+++ b/arch/x86/kvm/svm/avic.c
@@ -877,6 +877,8 @@ int avic_init_vcpu(struct vcpu_svm *svm)
INIT_LIST_HEAD(&svm->ir_list);
raw_spin_lock_init(&svm->ir_list_lock);
+ svm->gappi_cpu = 0;
+
if (!enable_apicv || !irqchip_in_kernel(vcpu->kvm))
return 0;
@@ -895,6 +897,17 @@ void avic_vcpu_free(struct kvm_vcpu *vcpu)
if (kvm_svm->avic_physical_id_table && avic_is_addressable_vcpu(vcpu))
WRITE_ONCE(kvm_svm->avic_physical_id_table[vcpu->vcpu_id], 0);
+
+ if (amd_iommu_gappi) {
+ struct vcpu_svm *svm = to_svm(vcpu);
+ unsigned long flags;
+
+ local_irq_save(flags);
+
+ kvm_pi_disable_wakeup_handler(vcpu, svm->gappi_cpu);
+
+ local_irq_restore(flags);
+ }
}
void avic_apicv_post_state_restore(struct kvm_vcpu *vcpu)
@@ -934,8 +947,6 @@ int avic_pi_update_irte(struct kvm_kernel_irqfd *irqfd, struct kvm *kvm,
* if AVIC is enabled/uninhibited in the future.
*/
struct amd_iommu_pi_data pi_data = {
- .ga_tag = AVIC_GATAG(to_kvm_svm(kvm)->avic_vm_id,
- vcpu->vcpu_idx),
.is_guest_mode = kvm_vcpu_apicv_active(vcpu),
.vapic_addr = avic_get_backing_page_address(to_svm(vcpu)),
.vector = vector,
@@ -958,6 +969,12 @@ int avic_pi_update_irte(struct kvm_kernel_irqfd *irqfd, struct kvm *kvm,
* scheduled out, KVM will update the pCPU info when the vCPU
* is awakened and/or scheduled in. See also avic_vcpu_load().
*/
+ if (amd_iommu_gappi)
+ pi_data.ga_tag = POSTED_INTR_WAKEUP_VECTOR;
+ else
+ pi_data.ga_tag = AVIC_GATAG(to_kvm_svm(kvm)->avic_vm_id,
+ vcpu->vcpu_idx);
+
entry = svm->avic_physical_id_entry;
if (entry & AVIC_PHYSICAL_ID_ENTRY_IS_RUNNING_MASK) {
pi_data.apicid = entry & AVIC_PHYSICAL_ID_ENTRY_HOST_PHYSICAL_ID_MASK;
@@ -966,6 +983,9 @@ int avic_pi_update_irte(struct kvm_kernel_irqfd *irqfd, struct kvm *kvm,
pi_data.apicid = -1;
pi_data.wakeup_intr = entry & AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR;
pi_data.is_running = false;
+
+ if (amd_iommu_gappi)
+ pi_data.apicid = cpu_physical_id(svm->gappi_cpu);
}
ret = irq_set_vcpu_affinity(host_irq, &pi_data);
@@ -1018,7 +1038,7 @@ enum avic_vcpu_action {
};
static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int apicid,
- enum avic_vcpu_action action)
+ int cpu, enum avic_vcpu_action action)
{
bool wakeup_intr = (action & AVIC_START_BLOCKING);
bool is_running = apicid >= 0;
@@ -1027,6 +1047,14 @@ static void avic_update_iommu_vcpu_affinity(struct kvm_vcpu *vcpu, int apicid,
lockdep_assert_held(&svm->ir_list_lock);
+ if (amd_iommu_gappi && is_running) {
+ kvm_pi_disable_wakeup_handler(vcpu, svm->gappi_cpu);
+ svm->gappi_cpu = cpu; /* Store cpu number as target for GAPPI */
+ } else if (amd_iommu_gappi) {
+ apicid = cpu_physical_id(svm->gappi_cpu);
+ kvm_pi_enable_wakeup_handler(vcpu, svm->gappi_cpu);
+ }
+
/*
* Here, we go through the per-vcpu ir_list to update all existing
* interrupt remapping table entry targeting this vcpu.
@@ -1093,7 +1121,7 @@ static void __avic_vcpu_load(struct kvm_vcpu *vcpu, int cpu,
WRITE_ONCE(kvm_svm->avic_physical_id_table[vcpu->vcpu_id], entry);
- avic_update_iommu_vcpu_affinity(vcpu, h_physical_id, action);
+ avic_update_iommu_vcpu_affinity(vcpu, h_physical_id, cpu, action);
raw_spin_unlock_irqrestore(&svm->ir_list_lock, flags);
}
@@ -1135,7 +1163,7 @@ static void __avic_vcpu_put(struct kvm_vcpu *vcpu, enum avic_vcpu_action action)
*/
raw_spin_lock_irqsave(&svm->ir_list_lock, flags);
- avic_update_iommu_vcpu_affinity(vcpu, -1, action);
+ avic_update_iommu_vcpu_affinity(vcpu, -1, -1, action);
WARN_ON_ONCE(entry & AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR);
@@ -1183,7 +1211,7 @@ void avic_vcpu_put(struct kvm_vcpu *vcpu)
/*
* The vCPU was preempted while blocking, ensure its IRTEs are
- * configured to generate GA Log Interrupts.
+ * configured to request host wakeup notification.
*/
if (!(WARN_ON_ONCE(!(entry & AVIC_PHYSICAL_ID_ENTRY_WAKEUP_INTR))))
return;
@@ -1308,6 +1336,13 @@ static bool __init avic_want_avic_enabled(void)
return true;
}
+bool avic_vcpu_irq_pending(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+
+ return kvm_lapic_find_highest_irr(&svm->vcpu) >= 0;
+}
+
/*
* Note:
* - The module param avic enable both xAPIC and x2APIC mode.
@@ -1351,6 +1386,8 @@ bool __init avic_hardware_setup(void)
void avic_hardware_unsetup(void)
{
- if (avic)
- amd_iommu_register_ga_log_notifier(NULL);
+ if (!avic)
+ return;
+
+ amd_iommu_register_ga_log_notifier(NULL);
}
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index c054803d4673..cd0d8e8c386e 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5352,6 +5352,7 @@ struct kvm_x86_ops svm_x86_ops __initdata = {
.vcpu_put = svm_vcpu_put,
.vcpu_blocking = avic_vcpu_blocking,
.vcpu_unblocking = avic_vcpu_unblocking,
+ .vcpu_irq_pending = avic_vcpu_irq_pending,
.update_exception_bitmap = svm_update_exception_bitmap,
.get_feature_msr = svm_get_feature_msr,
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 29bde741874a..58d4e85e30fe 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -368,6 +368,9 @@ struct vcpu_svm {
/* Guest GIF value, used when vGIF is not enabled */
bool guest_gif;
+
+ /* GAPPI related fields */
+ int gappi_cpu;
};
struct svm_cpu_data {
@@ -958,6 +961,7 @@ int avic_init_vcpu(struct vcpu_svm *svm);
void avic_vcpu_free(struct kvm_vcpu *vcpu);
void avic_vcpu_load(struct kvm_vcpu *vcpu, int cpu);
void avic_vcpu_put(struct kvm_vcpu *vcpu);
+bool avic_vcpu_irq_pending(struct kvm_vcpu *vcpu);
void avic_apicv_post_state_restore(struct kvm_vcpu *vcpu);
void avic_refresh_apicv_exec_ctrl(struct kvm_vcpu *vcpu);
int avic_pi_update_irte(struct kvm_kernel_irqfd *irqfd, struct kvm *kvm,
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v5 7/7] iommu/amd: Provide kernel command line option to enable GAPPI
2026-10-15 6:45 [PATCH v5 0/7] Add support for AMD IOMMU GAPPI Sairaj Kodilkar
` (5 preceding siblings ...)
2026-10-15 6:45 ` [PATCH v5 6/7] KVM: SVM: Add support for AMD IOMMU Guest APIC Physical Processor Interrupt (GAPPI) Sairaj Kodilkar
@ 2026-10-15 6:45 ` Sairaj Kodilkar
6 siblings, 0 replies; 8+ messages in thread
From: Sairaj Kodilkar @ 2026-10-15 6:45 UTC (permalink / raw)
To: H. Peter Anvin, Joerg Roedel (AMD), Mike Rapoport (Microsoft),
Andrew Morton, Borislav Petkov, Dapeng Mi, Dave Hansen,
Ethan Nelson-Moore, Ingo Molnar, Jakub Kicinski, Jeff Layton,
Jonathan Corbet, Kiryl Shutsemau, Li RongQing, Marco Elver,
Paolo Bonzini, Randy Dunlap, Rick Edgecombe, Robin Murphy,
Sairaj Kodilkar, Sean Christopherson, Shuah Khan,
Suravee Suthikulpanit, Thomas Gleixner, Vasant Hegde, Will Deacon,
iommu, kvm, linux-coco, linux-doc, linux-kernel, x86
Enable GAPPI when the IOMMU GAPPISup extended feature bit is set and
the kernel is booted with the amd_iommu=gappi kernel parameter.
Document the option in Documentation/admin-guide/kernel-parameters.txt
and mechanism in Documentation/arch/x86/iommu.rst.
Co-developed-by: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com>
Signed-off-by: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com>
Signed-off-by: Sairaj Kodilkar <sarunkod@amd.com>
---
.../admin-guide/kernel-parameters.txt | 3 +-
Documentation/arch/x86/iommu.rst | 22 +++++++++++++++
drivers/iommu/amd/amd_iommu_types.h | 2 ++
drivers/iommu/amd/init.c | 28 +++++++++++++++++--
4 files changed, 51 insertions(+), 4 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index e75344f4e0cd..b260ebef118f 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -456,7 +456,8 @@ Kernel parameters
to 4 KiB.
v2_pgsizes_only - Limit page-sizes used for v1 page-tables
to 4KiB/2Mib/1GiB.
-
+ gappi - Use GAPPI instead of GALOG for vCPU
+ wakeup notification.
amd_iommu_dump= [HW,X86-64]
Enable AMD IOMMU driver option to dump the ACPI table
diff --git a/Documentation/arch/x86/iommu.rst b/Documentation/arch/x86/iommu.rst
index 79c33560299b..6bac30f5abbd 100644
--- a/Documentation/arch/x86/iommu.rst
+++ b/Documentation/arch/x86/iommu.rst
@@ -150,3 +150,25 @@ AMD Fault reporting
AMD-Vi: Event logged [IO_PAGE_FAULT domain=0x0007 address=0xffffc02000 flags=0x0000]
AMD-Vi: Event logged [IO_PAGE_FAULT device=07:00.0 domain=0x0007 address=0xffffc02000 flags=0x0000]
+
+
+AMD vCPU wakeup Mechanism
+--------------------------
+
+GALOG
+^^^^^
+
+GALOG is a legacy notification path to wakeup the vCPU waiting on an interrupt.
+When IOMMU receives an interrupt destined to vCPU which is scheduled out, it
+updates the vAPIC backing page IRR and appends vCPU tags to a shared GA log
+buffer and raises a single GA log interrupt. IOMMU driver scans this buffer to
+decide which vCPU to wakeup.
+
+GAPPI
+^^^^^
+
+GAPPI is an alternative to GALOG. With GAPPI enabled, the IOMMU still updates
+the guest vAPIC backing page IRR, but may deliver a physical APIC interrupt
+directly to IRTE[Destination], using IRTE[GATag][7:0] as the vector. This
+distributes host wakeup notifications across CPUs instead of centralizing them
+in a log buffer. This can be enabled by adding option amd_iommu=gappi
diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h
index b89a2b3df0a4..51edd617ddca 100644
--- a/drivers/iommu/amd/amd_iommu_types.h
+++ b/drivers/iommu/amd/amd_iommu_types.h
@@ -78,6 +78,7 @@
#define FEATURE_X2APIC BIT_ULL(2)
#define FEATURE_NX BIT_ULL(3)
#define FEATURE_GT BIT_ULL(4)
+#define FEATURE_GAPPI BIT_ULL(5)
#define FEATURE_IA BIT_ULL(6)
#define FEATURE_GA BIT_ULL(7)
#define FEATURE_HE BIT_ULL(8)
@@ -178,6 +179,7 @@
#define CONTROL_EPH_EN 45
#define CONTROL_XT_EN 50
#define CONTROL_INTCAPXT_EN 51
+#define CONTROL_GAPPI_EN 55
#define CONTROL_GCR3TRPMODE 58
#define CONTROL_IRTCACHEDIS 59
#define CONTROL_SNPAVIC_EN 61
diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c
index ef23e0755c32..c536e0b3c4ff 100644
--- a/drivers/iommu/amd/init.c
+++ b/drivers/iommu/amd/init.c
@@ -446,6 +446,9 @@ static void iommu_disable(struct amd_iommu *iommu)
iommu_feature_disable(iommu, CONTROL_GALOG_EN);
iommu_feature_disable(iommu, CONTROL_GAINT_EN);
+ /* Disable IOMMU GAPPI */
+ iommu_feature_disable(iommu, CONTROL_GAPPI_EN);
+
/* Disable IOMMU PPR logging */
iommu_feature_disable(iommu, CONTROL_PPRLOG_EN);
iommu_feature_disable(iommu, CONTROL_PPRINT_EN);
@@ -2984,6 +2987,13 @@ static void enable_iommus_vapic(void)
struct amd_iommu *iommu;
for_each_iommu(iommu) {
+ /*
+ * Disable GAPPI, do not check amd_iommu_gappi as it may be
+ * false in new kexec kernel even though previous kernel has
+ * enabled it.
+ */
+ iommu_feature_disable(iommu, CONTROL_GAPPI_EN);
+
/*
* Disable GALog if already running. It could have been enabled
* in the previous boot before kdump.
@@ -3025,10 +3035,19 @@ static void enable_iommus_vapic(void)
return;
}
+ if (amd_iommu_gappi &&
+ !(check_feature(FEATURE_GAPPI) &&
+ AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir))) {
+ pr_warn("GAPPI is not supported.\n");
+ amd_iommu_gappi = false;
+ }
+
/* Enabling GAM and SNPAVIC support */
for_each_iommu(iommu) {
- if (iommu_init_ga_log(iommu) ||
- iommu_ga_log_enable(iommu))
+ if (amd_iommu_gappi)
+ iommu_feature_enable(iommu, CONTROL_GAPPI_EN);
+ else if (iommu_init_ga_log(iommu) ||
+ iommu_ga_log_enable(iommu))
return;
iommu_feature_enable(iommu, CONTROL_GAM_EN);
@@ -3037,7 +3056,8 @@ static void enable_iommus_vapic(void)
}
amd_iommu_irq_ops.capability |= (1 << IRQ_POSTING_CAP);
- pr_info("Virtual APIC enabled\n");
+ pr_info("Virtual APIC enabled with %s\n",
+ amd_iommu_gappi ? "GAPPI" : "GALOG");
#endif
}
@@ -3780,6 +3800,8 @@ static int __init parse_amd_iommu_options(char *str)
} else if (strncmp(str, "v2_pgsizes_only", 15) == 0) {
pr_info("Restricting V1 page-sizes to 4KiB/2MiB/1GiB");
amd_iommu_pgsize_bitmap = AMD_IOMMU_PGSIZES_V2;
+ } else if (strncmp(str, "gappi", 5) == 0) {
+ amd_iommu_gappi = true;
} else {
pr_notice("Unknown option - '%s'\n", str);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 8+ messages in thread