KVM: irqchip: Reduce KVM_SET_GSI_ROUTING allocation overhead
v3 cover KVM: irqchip: Reduce KVM_SET_GSI_ROUTING allocation overhead
KVM_SET_GSI_ROUTING can become expensive for guests with many VFIO devices and MSI-X vectors, Each update rebuilds the routing table, and the repeated allocation/free of routing entries can dominate the cost of kvm_set_irq_routing().
This series reduces that overhead in two steps.
Patch 1 allocates all routing entries in one contiguous allocation, so the entries can be freed together instead of allocating and freeing each entry independently.
Patch 2 adds a per-VM cache for one freed entries[] allocation and reuses it when the next routing update needs the same allocation size. This avoids repeated allocation/free churn for frequent routing updates while preserving the existing SRCU lifetime rules.
I wrote a test that continuously calls KVM_SET_GSI_ROUTING 50000 times and count the elapsed time:
Test machine:
Architecture: x86_64
CPU: Intel Xeon Platinum 8336C @ 2.30GHz
With 512 routes:
Before:
irq_routing_perf_test --routes=512 --iterations=50000
total: 1557023.484 us
average: 31.140 us/update
After:
irq_routing_perf_test --routes=512 --iterations=50000
total: 873104.338 us
average: 17.462 us/updateWith 4096 routes:
Before:
irq_routing_perf_test --routes=4096 --iterations=50000
total: 15192698.026 us
average: 303.854 us/update
After:
irq_routing_perf_test --routes=4096 --iterations=50000
total: 3295068.326 us
average: 65.901 us/updateThe result shows that the time spent per call is reduced by approximately 78.3% when the number of routing increase to 4096. It can significantly improves routing update efficiency.
v2->v3:
1.switch kcalloc to kv flavor of allocation. (Sean's suggestion) 2.introduce patch2 to cache freed entries allocation and reuse it at the
next routing update. (Sean's suggestion)
v1->v2:
1. fix variable 'r' is used uninitialized when a 'if' condition is true
2. simplified free_irq_routing_table() by removing the iteration over the entries and the hlist cleanup.
[v2]: https://lore.kernel.org/kvm/[email protected]/
Yanfei Xu (2):
KVM: irqchip: KVM: Reduce allocation overhead in kvm_set_irq_routing()
KVM: irqchip: Cache IRQ routing entries allocation
arch/loongarch/kvm/irqfd.c | 2 + arch/powerpc/kvm/mpic.c | 2 + arch/x86/kvm/irq.c | 2 + include/linux/kvm_host.h | 7 +++ virt/kvm/irqchip.c | 88 +++++++++++++++++++++++++++----------- 5 files changed, 76 insertions(+), 25 deletions(-)
Signature · 1 line
2.20.1
PATCH v3 1/2 KVM: irqchip: KVM: Reduce allocation overhead in kvm_set_irq_routing()
In guests with many VFIO devices and MSI-X vectors, kvm_set_irq_routing() becomes a high-overhead operation. Each invocation walks the entire IRQ routing table and reallocates/frees every routing entry.
As the routing table grows on each call, entry allocation and freeing dominate the execution time of this function. In scenarios such as VM live migration or live upgrade, this behavior can introduce unnecessary downtime.
Allocate memory for all routing entries in one shot using kvzalloc_objs(), allowing them to be freed together with a single kfree() call.
Example: On a VM with 120 vCPUs and 15 VFIO devices (virtio-net), the total number of calls to kzalloc and kfree is over 20000. With this change, it is reduced to around 30.
--- include/linux/kvm_host.h | 1 + virt/kvm/irqchip.c | 37 ++++++++++++------------------------- 2 files changed, 13 insertions(+), 25 deletions(-)
include/linux/kvm_host.h +1 -0
@@ -693,6 +693,7 @@ struct kvm_kernel_irq_routing_entry {struct kvm_irq_routing_table {int chip[KVM_NR_IRQCHIPS ][KVM_IRQCHIP_NUM_PINS ];u32 nr_rt_entries; +struct kvm_kernel_irq_routing_entry *entries;/* * Array indexed by gsi. Each entry contains list of irq chips * the gsi is connected to.
virt/kvm/irqchip.c +12 -25
@@ -98,21 +98,10 @@ int kvm_set_irq(struct kvm *kvm, int irq_source_id, u32 irq, int level,static void free_irq_routing_table (struct kvm_irq_routing_table *rt) { -int i; -if (!rt)return ; -for (i =0 ; i < rt->nr_rt_entries; ++i) { -struct kvm_kernel_irq_routing_entry *e; -struct hlist_node *n; - -hlist_for_each_entry_safe (e, n, &rt->map[i], link) { -hlist_del (&e->link); -kfree (e); - } - } - +kvfree (rt->entries);kfree (rt); }
@@ -187,31 +176,33 @@ int kvm_set_irq_routing(struct kvm *kvm,if (!new)return -ENOMEM ; + e =kvzalloc_objs (*e, nr,GFP_KERNEL_ACCOUNT ); +if (!e) { + r = -ENOMEM ; + goto out; + } + new->entries = e; + new->nr_rt_entries = nr_rt_entries;for (i =0 ; i <KVM_NR_IRQCHIPS ; i++)for (j =0 ; j <KVM_IRQCHIP_NUM_PINS ; j++) new->chip[i][j] =-1 ;for (i =0 ; i < nr; ++i) { - r = -ENOMEM ; - e =kzalloc_obj (*e,GFP_KERNEL_ACCOUNT ); -if (!e) - goto out; - r = -EINVAL ;switch (ue->type) {case KVM_IRQ_ROUTING_MSI :if (ue->flags & ~KVM_MSI_VALID_DEVID ) - goto free_entry; + goto out;break ;default :if (ue->flags) - goto free_entry; + goto out;break ; } - r =setup_routing_entry (kvm, new, e, ue); + r =setup_routing_entry (kvm, new, e + i, ue);if (r) - goto free_entry; + goto out; ++ue; }
@@ -226,10 +217,6 @@ int kvm_set_irq_routing(struct kvm *kvm, new = old; r =0 ; - goto out; - -free_entry: -kfree (e); out:free_irq_routing_table (new);
Signature · 1 line
2.20.1
PATCH v3 2/2 KVM: irqchip: Cache IRQ routing entries allocation
In reply to Yanfei XuCache one VM-local entries[] allocation from a freed IRQ routing table and reuse it when the next routing update needs the same allocation size.
This reduces repeated allocation/free churn for frequent KVM_SET_GSI_ROUTING updates without changing the existing SRCU lifetime rules. Because cached entries are reused without being zeroed, initialize the remaining MSI fields on x86, LoongArch and PowerPC.
--- arch/loongarch/kvm/irqfd.c | 2 ++ arch/powerpc/kvm/mpic.c | 2 ++ arch/x86/kvm/irq.c | 2 ++ include/linux/kvm_host.h | 6 ++++ virt/kvm/irqchip.c | 61 ++++++++++++++++++++++++++++++++++---- 5 files changed, 68 insertions(+), 5 deletions(-)
arch/loongarch/kvm/irqfd.c +2 -0
@@ -60,6 +60,8 @@ int kvm_set_routing_entry(struct kvm *kvm, e->msi.address_lo = ue->u.msi.address_lo; e->msi.address_hi = ue->u.msi.address_hi; e->msi.data = ue->u.msi.data; + e->msi.flags = ue->flags; + e->msi.devid = ue->u.msi.devid;return 0 ;default :return -EINVAL ;
arch/powerpc/kvm/mpic.c +2 -0
@@ -1841,6 +1841,8 @@ int kvm_set_routing_entry(struct kvm *kvm, e->msi.address_lo = ue->u.msi.address_lo; e->msi.address_hi = ue->u.msi.address_hi; e->msi.data = ue->u.msi.data; + e->msi.flags = ue->flags; + e->msi.devid = ue->u.msi.devid;break ;default : goto out;
arch/x86/kvm/irq.c +2 -0
@@ -332,6 +332,8 @@ int kvm_set_routing_entry(struct kvm *kvm, e->msi.address_lo = ue->u.msi.address_lo; e->msi.address_hi = ue->u.msi.address_hi; e->msi.data = ue->u.msi.data; + e->msi.flags = ue->flags; + e->msi.devid = ue->u.msi.devid;if (kvm_msi_route_invalid (kvm, e))return -EINVAL ;
include/linux/kvm_host.h +6 -0
@@ -694,6 +694,7 @@ struct kvm_irq_routing_table {int chip[KVM_NR_IRQCHIPS ][KVM_IRQCHIP_NUM_PINS ];u32 nr_rt_entries;struct kvm_kernel_irq_routing_entry *entries; +size_t entries_size;/* * Array indexed by gsi. Each entry contains list of irq chips * the gsi is connected to.
@@ -845,6 +846,11 @@ struct kvm {* Update side is protected by irq_lock. */ struct kvm_irq_routing_table __rcu *irq_routing; +/* +* Cache for the entries[] allocation of a freed IRQ routing table. +*/ +struct kvm_kernel_irq_routing_entry *irq_routing_entries_cache; +size_t irq_routing_entries_cache_size;struct hlist_head irq_ack_notifier_list;#endif
virt/kvm/irqchip.c +56 -5
100 lines not on this page · raw message
Signature · 1 line
2.20.1
[email protected]
AI reviewRe: [v3 2/2] KVM: irqchip: Cache IRQ routing entries allocation
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Missing bounds check on user-supplied ue->u.irqchip.irqchip allows an out-of-bounds array write on LoongArch and PowerPC. --
commit 8ac8d5f6cab84711afabacca6347f1ad673dc605
Author: Yanfei Xu <[email protected]>
Subject: KVM: irqchip: Cache IRQ routing entries allocation
This patch caches a VM-local entries array allocation from a freed IRQ routing table to reduce allocation and free churn during frequent routing updates. It also initializes the remaining MSI fields on x86, LoongArch, and PowerPC since cached entries are reused without being zeroed.
arch/loongarch/kvm/irqfd.c int kvm_set_routing_entry(struct kvm *kvm, · line 64
Quoted 10 lines
arch/loongarch/kvm/irqfd.c +2
-0@@ -60,6 +60,8 @@ int kvm_set_routing_entry(struct kvm *kvm, e->msi.address_lo = ue->u.msi.address_lo; e->msi.address_hi = ue->u.msi.address_hi; e->msi.data = ue->u.msi.data; + e->msi.flags = ue->flags; + e->msi.devid = ue->u.msi.devid;
[Severity: Critical]
This isn't a bug introduced by this patch, but does the KVM_IRQ_ROUTING_IRQCHIP case in this function lack bounds checking on the irqchip index?
Looking at kvm_set_routing_entry() on LoongArch (and similarly on PowerPC), the irqchip.pin is validated against KVM_IRQCHIP_NUM_PINS, but there doesn't appear to be a check for e->irqchip.irqchip against KVM_NR_IRQCHIPS.
When kvm_set_routing_entry() returns success, setup_routing_entry() executes this array write:
virt/kvm/irqchip.c:setup_routing_entry() {
...
if (e->type == KVM_IRQ_ROUTING_IRQCHIP)
rt->chip[e->irqchip.irqchip][e->irqchip.pin] = e->gsi;
...
}Could this allow a user-supplied routing entry to provide an arbitrarily large irqchip index, causing an out-of-bounds write of e->gsi into the heap memory following the kvm_irq_routing_table?
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Re: [v3 2/2] KVM: irqchip: Cache IRQ routing entries allocation
Quoted 5 lines · On 2026/5/25 13:17, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Missing bounds check on user-suppliedue->u.irqchip.irqchipallows an out-of-bounds array write on LoongArch and PowerPC. --
Posted a separate fix for this issue.
https://lore.kernel.org/kvm/[email protected]/
Thanks,
Yanfei
Quoted 43 lines
commit
8ac8d5f6cab84711afabacca6347f1ad673dc605
Author: Yanfei Xu <[email protected]>
Subject: KVM: irqchip: Cache IRQ routing entries allocationThis patch caches a VM-local entries array allocation from a freed IRQ routing table to reduce allocation and free churn during frequent routing updates. It also initializes the remaining MSI fields on x86, LoongArch, and PowerPC since cached entries are reused without being zeroed.
arch/loongarch/kvm/irqfd.c int kvm_set_routing_entry(struct kvm *kvm, · line 64
Quoted 10 lines
arch/loongarch/kvm/irqfd.c +2
-0@@ -60,6 +60,8 @@ int kvm_set_routing_entry(struct kvm *kvm, e->msi.address_lo = ue->u.msi.address_lo; e->msi.address_hi = ue->u.msi.address_hi; e->msi.data = ue->u.msi.data; + e->msi.flags = ue->flags; + e->msi.devid = ue->u.msi.devid;[Severity: Critical]
This isn't a bug introduced by this patch, but does theKVM_IRQ_ROUTING_IRQCHIPcase in this function lack bounds checking on the irqchip index?Looking at
kvm_set_routing_entry()on LoongArch (and similarly on PowerPC), the irqchip.pin is validated againstKVM_IRQCHIP_NUM_PINS, but there doesn't appear to be a check fore->irqchip.irqchipagainstKVM_NR_IRQCHIPS.When
kvm_set_routing_entry()returns success,setup_routing_entry()executes this array write:virt/kvm/irqchip.c:setup_routing_entry() { ... if (e->type == KVM_IRQ_ROUTING_IRQCHIP) rt->chip[e->irqchip.irqchip][e->irqchip.pin] = e->gsi; ... }Could this allow a user-supplied routing entry to provide an arbitrarily large irqchip index, causing an out-of-bounds write of
e->gsiinto the heap memory following thekvm_irq_routing_table?[ ... ]