Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

irqbypass: Use xarray to track producers and consumers

Track IRQ bypass producers and consumers using an xarray to avoid the O(2n)
insertion time associated with walking a list to check for duplicate
entries, and to search for an partner.

At low (tens or few hundreds) total producer/consumer counts, using a list
is faster due to the need to allocate backing storage for xarray. But as
count creeps into the thousands, xarray wins easily, and can provide
several orders of magnitude better latency at high counts. E.g. hundreds
of nanoseconds vs. hundreds of milliseconds.

Cc: Oliver Upton <oliver.upton@linux.dev>
Cc: David Matlack <dmatlack@google.com>
Cc: Like Xu <like.xu.linux@gmail.com>
Cc: Binbin Wu <binbin.wu@linux.intel.com>
Reported-by: Yong He <alexyonghe@tencent.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=217379
Link: https://lore.kernel.org/all/20230801115646.33990-1-likexu@tencent.com
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Acked-by: Michael S. Tsirkin <mst@redhat.com>
Reviewed-by: Alex Williamson <alex.williamson@redhat.com>
Link: https://lore.kernel.org/r/20250516230734.2564775-8-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>

+38 -40
-4
include/linux/irqbypass.h
··· 33 33 34 34 /** 35 35 * struct irq_bypass_producer - IRQ bypass producer definition 36 - * @node: IRQ bypass manager private list management 37 36 * @eventfd: eventfd context used to match producers and consumers 38 37 * @consumer: The connected consumer (NULL if no connection) 39 38 * @irq: Linux IRQ number for the producer device ··· 46 47 * for a physical device assigned to a VM. 47 48 */ 48 49 struct irq_bypass_producer { 49 - struct list_head node; 50 50 struct eventfd_ctx *eventfd; 51 51 struct irq_bypass_consumer *consumer; 52 52 int irq; ··· 59 61 60 62 /** 61 63 * struct irq_bypass_consumer - IRQ bypass consumer definition 62 - * @node: IRQ bypass manager private list management 63 64 * @eventfd: eventfd context used to match producers and consumers 64 65 * @producer: The connected producer (NULL if no connection) 65 66 * @add_producer: Connect the IRQ consumer to an IRQ producer ··· 72 75 * portions of the interrupt handling to the VM. 73 76 */ 74 77 struct irq_bypass_consumer { 75 - struct list_head node; 76 78 struct eventfd_ctx *eventfd; 77 79 struct irq_bypass_producer *producer; 78 80
+38 -36
virt/lib/irqbypass.c
··· 22 22 MODULE_LICENSE("GPL v2"); 23 23 MODULE_DESCRIPTION("IRQ bypass manager utility module"); 24 24 25 - static LIST_HEAD(producers); 26 - static LIST_HEAD(consumers); 25 + static DEFINE_XARRAY(producers); 26 + static DEFINE_XARRAY(consumers); 27 27 static DEFINE_MUTEX(lock); 28 28 29 29 /* @lock must be held when calling connect */ ··· 86 86 * @producer: pointer to producer structure 87 87 * @eventfd: pointer to the eventfd context associated with the producer 88 88 * 89 - * Add the provided IRQ producer to the list of producers and connect 90 - * with any matching eventfd found on the IRQ consumers list. 89 + * Add the provided IRQ producer to the set of producers and connect with the 90 + * consumer with a matching eventfd, if one exists. 91 91 */ 92 92 int irq_bypass_register_producer(struct irq_bypass_producer *producer, 93 93 struct eventfd_ctx *eventfd) 94 94 { 95 - struct irq_bypass_producer *tmp; 95 + unsigned long index = (unsigned long)eventfd; 96 96 struct irq_bypass_consumer *consumer; 97 97 int ret; 98 98 ··· 101 101 102 102 guard(mutex)(&lock); 103 103 104 - list_for_each_entry(tmp, &producers, node) { 105 - if (tmp->eventfd == eventfd) 106 - return -EBUSY; 107 - } 104 + ret = xa_insert(&producers, index, producer, GFP_KERNEL); 105 + if (ret) 106 + return ret; 108 107 109 - list_for_each_entry(consumer, &consumers, node) { 110 - if (consumer->eventfd == eventfd) { 111 - ret = __connect(producer, consumer); 112 - if (ret) 113 - return ret; 114 - break; 108 + consumer = xa_load(&consumers, index); 109 + if (consumer) { 110 + ret = __connect(producer, consumer); 111 + if (ret) { 112 + WARN_ON_ONCE(xa_erase(&producers, index) != producer); 113 + return ret; 115 114 } 116 115 } 117 116 118 117 producer->eventfd = eventfd; 119 - list_add(&producer->node, &producers); 120 118 return 0; 121 119 } 122 120 EXPORT_SYMBOL_GPL(irq_bypass_register_producer); ··· 123 125 * irq_bypass_unregister_producer - unregister IRQ bypass producer 124 126 * @producer: pointer to producer structure 125 127 * 126 - * Remove a previously registered IRQ producer from the list of producers 127 - * and disconnect it from any connected IRQ consumer. 128 + * Remove a previously registered IRQ producer (note, it's safe to call this 129 + * even if registration was unsuccessful). Disconnect from the associated 130 + * consumer, if one exists. 128 131 */ 129 132 void irq_bypass_unregister_producer(struct irq_bypass_producer *producer) 130 133 { 134 + unsigned long index = (unsigned long)producer->eventfd; 135 + 131 136 if (!producer->eventfd) 132 137 return; 133 138 ··· 139 138 if (producer->consumer) 140 139 __disconnect(producer, producer->consumer); 141 140 141 + WARN_ON_ONCE(xa_erase(&producers, index) != producer); 142 142 producer->eventfd = NULL; 143 - list_del(&producer->node); 144 143 } 145 144 EXPORT_SYMBOL_GPL(irq_bypass_unregister_producer); 146 145 ··· 149 148 * @consumer: pointer to consumer structure 150 149 * @eventfd: pointer to the eventfd context associated with the consumer 151 150 * 152 - * Add the provided IRQ consumer to the list of consumers and connect 153 - * with any matching eventfd found on the IRQ producer list. 151 + * Add the provided IRQ consumer to the set of consumers and connect with the 152 + * producer with a matching eventfd, if one exists. 154 153 */ 155 154 int irq_bypass_register_consumer(struct irq_bypass_consumer *consumer, 156 155 struct eventfd_ctx *eventfd) 157 156 { 158 - struct irq_bypass_consumer *tmp; 157 + unsigned long index = (unsigned long)eventfd; 159 158 struct irq_bypass_producer *producer; 160 159 int ret; 161 160 ··· 167 166 168 167 guard(mutex)(&lock); 169 168 170 - list_for_each_entry(tmp, &consumers, node) { 171 - if (tmp->eventfd == eventfd) 172 - return -EBUSY; 173 - } 169 + ret = xa_insert(&consumers, index, consumer, GFP_KERNEL); 170 + if (ret) 171 + return ret; 174 172 175 - list_for_each_entry(producer, &producers, node) { 176 - if (producer->eventfd == eventfd) { 177 - ret = __connect(producer, consumer); 178 - if (ret) 179 - return ret; 180 - break; 173 + producer = xa_load(&producers, index); 174 + if (producer) { 175 + ret = __connect(producer, consumer); 176 + if (ret) { 177 + WARN_ON_ONCE(xa_erase(&consumers, index) != consumer); 178 + return ret; 181 179 } 182 180 } 183 181 184 182 consumer->eventfd = eventfd; 185 - list_add(&consumer->node, &consumers); 186 183 return 0; 187 184 } 188 185 EXPORT_SYMBOL_GPL(irq_bypass_register_consumer); ··· 189 190 * irq_bypass_unregister_consumer - unregister IRQ bypass consumer 190 191 * @consumer: pointer to consumer structure 191 192 * 192 - * Remove a previously registered IRQ consumer from the list of consumers 193 - * and disconnect it from any connected IRQ producer. 193 + * Remove a previously registered IRQ consumer (note, it's safe to call this 194 + * even if registration was unsuccessful). Disconnect from the associated 195 + * producer, if one exists. 194 196 */ 195 197 void irq_bypass_unregister_consumer(struct irq_bypass_consumer *consumer) 196 198 { 199 + unsigned long index = (unsigned long)consumer->eventfd; 200 + 197 201 if (!consumer->eventfd) 198 202 return; 199 203 ··· 205 203 if (consumer->producer) 206 204 __disconnect(consumer->producer, consumer); 207 205 206 + WARN_ON_ONCE(xa_erase(&consumers, index) != consumer); 208 207 consumer->eventfd = NULL; 209 - list_del(&consumer->node); 210 208 } 211 209 EXPORT_SYMBOL_GPL(irq_bypass_unregister_consumer);