Skip to contentSkip to conversation

KVM&genirq: Enable adaptive IRQ sharing for passed-through devices

PATCH Superseded Jan Kiszka 4 participants 17 messages Dec 3, 2010 – Dec 6, 2010

Subsystems (5)
Documentation x86 KVM KVM x86 +1 more subsystems

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Jan Kiszka

author
#0 lore raw

cover KVM&genirq: Enable adaptive IRQ sharing for passed-through devices

Besides 3 cleanup patches, this series consists of two major changes. The first introduces an interrupt sharing notifier to the genirq subsystem. It fires when an interrupt line is about to be use by more than one driver or the last but one user called free_irq.

The second major change makes use of this interface in KVM's PCI pass- through subsystem. KVM has to keep the interrupt source disabled while calling into the guest to handle the event. This can be done at device or line level. The former is required to share the interrupt line, the latter is an order of magnitude faster (see patch 3 for details).

Beside pass-through support of KVM, further users of the IRQ notifier could become VFIO (not yet mainline) and uio_pci_generic which have to resolve the same conflict.

Jan Kiszka (5):
genirq: Pass descriptor to __free_irq
genirq: Introduce interrupt sharing notifier
KVM: Split up MSI-X assigned device IRQ handler
KVM: Clean up unneeded void pointer casts
KVM: Allow host IRQ sharing for passed-through PCI 2.3 devices

 Documentation/kvm/api.txt |   25 +++
 arch/x86/kvm/x86.c        |    1 +
 include/linux/interrupt.h |   21 +++
 include/linux/irqdesc.h   |    9 +
 include/linux/kvm.h       |    6 +
 include/linux/kvm_host.h  |    4 +
 kernel/irq/irqdesc.c      |    6 +
 kernel/irq/manage.c       |  174 ++++++++++++++++++--
 virt/kvm/assigned-dev.c   |  420 +++++++++++++++++++++++++++++++++++++++------
 9 files changed, 606 insertions(+), 60 deletions(-)
author
#1 lore raw

PATCH 1/5 genirq: Pass descriptor to __free_irq

From: Jan Kiszka <[email protected]>

One caller of __free_free already has to resolve and check the irq descriptor. So this small cleanup consistently pushes irq_to_desc and the NULL check to the call site to avoid redundant work.

---
 kernel/irq/manage.c |   16 +++++++++-------
 1 files changed, 9 insertions(+), 7 deletions(-)
kernel/irq/manage.c +9 -7
index 5f92acc..6341765 100644
@@ -879,17 +879,14 @@ EXPORT_SYMBOL_GPL(setup_irq);
  * Internal function to unregister an irqaction - used to free
  * regular and special interrupts that are part of the architecture.
  */
-static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
+static struct irqaction *__free_irq(struct irq_desc *desc, void *dev_id)
 {
-	struct irq_desc *desc = irq_to_desc(irq);
 	struct irqaction *action, **action_ptr;
+	unsigned int irq = desc->irq_data.irq;
 	unsigned long flags;
 
 	WARN(in_interrupt(), "Trying to free IRQ %d from IRQ context!\n", irq);
 
-	if (!desc)
-		return NULL;
-
 	raw_spin_lock_irqsave(&desc->lock, flags);
 
 	/*
@@ -977,7 +974,12 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
  */
 void remove_irq(unsigned int irq, struct irqaction *act)
 {
-	__free_irq(irq, act->dev_id);
+	struct irq_desc *desc = irq_to_desc(irq);
+
+	if (!desc)
+		return;
+
+	__free_irq(desc, act->dev_id);
 }
 EXPORT_SYMBOL_GPL(remove_irq);
 
@@ -1003,7 +1005,7 @@ void free_irq(unsigned int irq, void *dev_id)
 		return;
 
 	chip_bus_lock(desc);
-	kfree(__free_irq(irq, dev_id));
+	kfree(__free_irq(desc, dev_id));
 	chip_bus_sync_unlock(desc);
 }
 EXPORT_SYMBOL(free_irq);
Signature · 1 line
1.7.1
author
#2 lore raw

PATCH 2/5 genirq: Introduce interrupt sharing notifier

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

This enables drivers to switch their interrupt handling between exclusive and shared mode.

While there is generally no advantage in exclusive interrupt handling patterns, at least one use case can benefit from adaptive handling:
Generic stub drivers that hand out PCI devices for unprivileged use in virtualization or user space device driver scenarios. They need to mask the interrupt until the unprivileged driver had a chance to process the event. As generic IRQ masking at device level (via PCI config space) is at least 10 times slower than disabling at interrupt controller level on x86, we only want to apply the costly method when there is a real need.

The sharing notifier provides both the required information about the number of interrupt handlers as well as a properly synchronized execution context to run interrupt de-/registration and mode switch procedures. The notifier is called with IRQN_SETUP_USED or IRQN_SETUP_USED, respectively, during registration to allow setup according to the current interrupt use. When the number of users is one on entry of request_threaded_irq or on exit of free_irq, IRQN_SHARED or IRQN_EXCLUSIVE, respectively, are signaled. Deregistration is signaled as IRQN_SHUTDOWN to the removed notifier.

---
 include/linux/interrupt.h |   21 ++++++
 include/linux/irqdesc.h   |    9 +++
 kernel/irq/irqdesc.c      |    6 ++
 kernel/irq/manage.c       |  158 +++++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 190 insertions(+), 4 deletions(-)
include/linux/interrupt.h +21 -0
index 79d0c4f..9ebb98f 100644
@@ -96,6 +96,23 @@ enum {
 	IRQC_IS_NESTED,
 };
 
+/*
+ * These values are passed to the sharing notifier as second argument.
+ *
+ * IRQN_SHARED - interrupt line is shared by two or more devices
+ * IRQN_EXCLUSIVE - interrupt line is used by one device only
+ * IRQN_SETUP_USED - initial notification for interrupt line already in use
+ * IRQN_SETUP_UNUSED - initial notification for yet unused interrupt line
+ * IRQN_SHUTDOWN - shutdown notification on notifier unregistration
+ */
+enum {
+	IRQN_SHARED = 0,
+	IRQN_EXCLUSIVE,
+	IRQN_SETUP_USED,
+	IRQN_SETUP_UNUSED,
+	IRQN_SHUTDOWN,
+};
+
 typedef irqreturn_t (*irq_handler_t)(int, void *);
 
 /**
@@ -176,6 +193,10 @@ static inline void exit_irq_thread(void) { }
 
 extern void free_irq(unsigned int, void *);
 
+int register_irq_sharing_notifier(unsigned int irq, struct notifier_block *nb);
+int unregister_irq_sharing_notifier(unsigned int irq,
+				    struct notifier_block *nb);
+
 struct device;
 
 extern int __must_check
include/linux/irqdesc.h +9 -0
index 979c68c..eefffef 100644
@@ -29,6 +29,10 @@ struct timer_rand_state;
  * @wait_for_threads:	wait queue for sync_irq to wait for threaded handlers
  * @dir:		/proc/irq/ procfs entry
  * @name:		flow handler name for /proc/interrupts output
+ * @users:		number of registered users (>1: irq is shared)
+ * @sh_lock:		mutex to synchronize sharing notifications
+ * @sh_lock_holder:	holder of sh_lock, allows recursion
+ * @sh_notifier:	fired when the sharing state changes
  */
 struct irq_desc {
 
@@ -80,6 +84,11 @@ struct irq_desc {
 	struct proc_dir_entry	*dir;
 #endif
 	const char		*name;
+
+	unsigned int		users;
+	struct mutex		sh_lock;
+	struct task_struct	*sh_lock_holder;
+	struct raw_notifier_head sh_notifier;
 } ____cacheline_internodealigned_in_smp;
 
 #ifndef CONFIG_SPARSE_IRQ
kernel/irq/irqdesc.c +6 -0
index 9988d03..8c5399d 100644
@@ -83,6 +83,9 @@ static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node)
 	desc->irq_count = 0;
 	desc->irqs_unhandled = 0;
 	desc->name = NULL;
+	desc->users = 0;
+	desc->sh_lock_holder = NULL;
+	RAW_INIT_NOTIFIER_HEAD(&desc->sh_notifier);
 	memset(desc->kstat_irqs, 0, nr_cpu_ids * sizeof(*(desc->kstat_irqs)));
 	desc_smp_init(desc, node);
 }
@@ -144,6 +147,8 @@ static struct irq_desc *alloc_desc(int irq, int node)
 	raw_spin_lock_init(&desc->lock);
 	lockdep_set_class(&desc->lock, &irq_desc_lock_class);
 
+	mutex_init(&desc->sh_lock);
+
 	desc_set_defaults(irq, desc, node);
 
 	return desc;
@@ -254,6 +259,7 @@ int __init early_irq_init(void)
 		alloc_masks(desc + i, GFP_KERNEL, node);
 		desc_smp_init(desc + i, node);
 		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
+		mutex_init(&desc[i].sh_lock);
 	}
 	return arch_early_irq_init();
 }
kernel/irq/manage.c +154 -4

206 lines not on this page · raw message

Signature · 1 line
1.7.1
author
#3 lore raw

PATCH 3/5 KVM: Split up MSI-X assigned device IRQ handler

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

The threaded IRQ handler for MSI-X has almost nothing in common with the INTx/MSI handler. Move its code into a dedicated handler.

---
 virt/kvm/assigned-dev.c |   32 +++++++++++++++++++-------------
 1 files changed, 19 insertions(+), 13 deletions(-)
virt/kvm/assigned-dev.c +19 -13
index ae72ae6..3f8a745 100644
@@ -58,8 +58,6 @@ static int find_index_from_host_irq(struct kvm_assigned_dev_kernel
 static irqreturn_t kvm_assigned_dev_thread(int irq, void *dev_id)
 {
 	struct kvm_assigned_dev_kernel *assigned_dev = dev_id;
-	u32 vector;
-	int index;
 
 	if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_INTX) {
 		spin_lock(&assigned_dev->intx_lock);
@@ -68,20 +66,28 @@ static irqreturn_t kvm_assigned_dev_thread(int irq, void *dev_id)
 		spin_unlock(&assigned_dev->intx_lock);
 	}
 
-	if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSIX) {
-		index = find_index_from_host_irq(assigned_dev, irq);
-		if (index >= 0) {
-			vector = assigned_dev->
-					guest_msix_entries[index].vector;
-			kvm_set_irq(assigned_dev->kvm,
-				    assigned_dev->irq_source_id, vector, 1);
-		}
-	} else
+	kvm_set_irq(assigned_dev->kvm, assigned_dev->irq_source_id,
+		    assigned_dev->guest_irq, 1);
+
+	return IRQ_HANDLED;
+}
+
+#ifdef __KVM_HAVE_MSIX
+static irqreturn_t kvm_assigned_dev_thread_msix(int irq, void *dev_id)
+{
+	struct kvm_assigned_dev_kernel *assigned_dev = dev_id;
+	int index = find_index_from_host_irq(assigned_dev, irq);
+	u32 vector;
+
+	if (index >= 0) {
+		vector = assigned_dev->guest_msix_entries[index].vector;
 		kvm_set_irq(assigned_dev->kvm, assigned_dev->irq_source_id,
-			    assigned_dev->guest_irq, 1);
+			    vector, 1);
+	}
 
 	return IRQ_HANDLED;
 }
+#endif
 
 /* Ack the irq line for an assigned device */
 static void kvm_assigned_dev_ack_irq(struct kvm_irq_ack_notifier *kian)
@@ -277,7 +283,7 @@ static int assigned_device_enable_host_msix(struct kvm *kvm,
 
 	for (i = 0; i < dev->entries_nr; i++) {
 		r = request_threaded_irq(dev->host_msix_entries[i].vector,
-					 NULL, kvm_assigned_dev_thread,
+					 NULL, kvm_assigned_dev_thread_msix,
 					 0, dev->irq_name, (void *)dev);
 		if (r)
 			goto err;
Signature · 1 line
1.7.1
author
#4 lore raw

PATCH 4/5 KVM: Clean up unneeded void pointer casts

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

---
 virt/kvm/assigned-dev.c |   12 ++++++------
 1 files changed, 6 insertions(+), 6 deletions(-)
virt/kvm/assigned-dev.c +6 -6
index 3f8a745..c6114d3 100644
@@ -149,7 +149,7 @@ static void deassign_host_irq(struct kvm *kvm,
 
 		for (i = 0; i < assigned_dev->entries_nr; i++)
 			free_irq(assigned_dev->host_msix_entries[i].vector,
-				 (void *)assigned_dev);
+				 assigned_dev);
 
 		assigned_dev->entries_nr = 0;
 		kfree(assigned_dev->host_msix_entries);
@@ -159,7 +159,7 @@ static void deassign_host_irq(struct kvm *kvm,
 		/* Deal with MSI and INTx */
 		disable_irq(assigned_dev->host_irq);
 
-		free_irq(assigned_dev->host_irq, (void *)assigned_dev);
+		free_irq(assigned_dev->host_irq, assigned_dev);
 
 		if (assigned_dev->irq_requested_type & KVM_DEV_IRQ_HOST_MSI)
 			pci_disable_msi(assigned_dev->dev);
@@ -238,7 +238,7 @@ static int assigned_device_enable_host_intx(struct kvm *kvm,
 	 * are going to be long delays in accepting, acking, etc.
 	 */
 	if (request_threaded_irq(dev->host_irq, NULL, kvm_assigned_dev_thread,
-				 IRQF_ONESHOT, dev->irq_name, (void *)dev))
+				 IRQF_ONESHOT, dev->irq_name, dev))
 		return -EIO;
 	return 0;
 }
@@ -257,7 +257,7 @@ static int assigned_device_enable_host_msi(struct kvm *kvm,
 
 	dev->host_irq = dev->dev->irq;
 	if (request_threaded_irq(dev->host_irq, NULL, kvm_assigned_dev_thread,
-				 0, dev->irq_name, (void *)dev)) {
+				 0, dev->irq_name, dev)) {
 		pci_disable_msi(dev->dev);
 		return -EIO;
 	}
@@ -284,7 +284,7 @@ static int assigned_device_enable_host_msix(struct kvm *kvm,
 	for (i = 0; i < dev->entries_nr; i++) {
 		r = request_threaded_irq(dev->host_msix_entries[i].vector,
 					 NULL, kvm_assigned_dev_thread_msix,
-					 0, dev->irq_name, (void *)dev);
+					 0, dev->irq_name, dev);
 		if (r)
 			goto err;
 	}
@@ -292,7 +292,7 @@ static int assigned_device_enable_host_msix(struct kvm *kvm,
 	return 0;
 err:
 	for (i -= 1; i >= 0; i--)
-		free_irq(dev->host_msix_entries[i].vector, (void *)dev);
+		free_irq(dev->host_msix_entries[i].vector, dev);
 	pci_disable_msix(dev->dev);
 	return r;
 }
Signature · 1 line
1.7.1
author
#5 lore raw

PATCH 5/5 KVM: Allow host IRQ sharing for passed-through PCI 2.3 devices

In reply to Jan Kiszka

From: Jan Kiszka <[email protected]>

PCI 2.3 allows to generically disable IRQ sources at device level. This enables us to share IRQs of such devices on the host side when passing them to a guest.

However, IRQ disabling via the PCI config space is more costly than masking the line via disable_irq. Therefore we register an IRQ sharing notifier and switch between line and device level disabling on demand.

This feature is optional, user space has to request it explicitly as it also has to inform us about its view of PCI_COMMAND_INTX_DISABLE. That way, we can avoid unmasking the interrupt and signaling it if the guest masked it via the PCI config space.

---
 Documentation/kvm/api.txt |   25 +++
 arch/x86/kvm/x86.c        |    1 +
 include/linux/kvm.h       |    6 +
 include/linux/kvm_host.h  |    4 +
 virt/kvm/assigned-dev.c   |  382 +++++++++++++++++++++++++++++++++++++++++----
 5 files changed, 385 insertions(+), 33 deletions(-)
Documentation/kvm/api.txt +25 -0
index e1a9297..5e164db 100644
@@ -1112,6 +1112,14 @@ following flags are specified:
 
 /* Depends on KVM_CAP_IOMMU */
 #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1 << 0)
+/* The following two depend on KVM_CAP_PCI_2_3 */
+#define KVM_DEV_ASSIGN_PCI_2_3		(1 << 1)
+#define KVM_DEV_ASSIGN_MASK_INTX	(1 << 2)
+
+If KVM_DEV_ASSIGN_PCI_2_3 is set, the kernel will manage legacy INTx interrupts
+via the PCI-2.3-compliant device-level mask, but only if IRQ sharing with other
+assigned or host devices requires it. KVM_DEV_ASSIGN_MASK_INTX specifies the
+guest's view on the INTx mask, see KVM_ASSIGN_SET_INTX_MASK for details.
 
 4.48 KVM_DEASSIGN_PCI_DEVICE
 
@@ -1263,6 +1271,23 @@ struct kvm_assigned_msix_entry {
 	__u16 padding[3];
 };
 
+4.54 KVM_ASSIGN_SET_INTX_MASK
+
+Capability: KVM_CAP_PCI_2_3
+Architectures: x86
+Type: vm ioctl
+Parameters: struct kvm_assigned_pci_dev (in)
+Returns: 0 on success, -1 on error
+
+Informs the kernel about the guest's view on the INTx mask. As long as the
+guest masks the legacy INTx, the kernel will refrain from unmasking it at
+hardware level and will not assert the guest's IRQ line. User space is still
+responsible for applying this state to the assigned device's real config space.
+
+See KVM_ASSIGN_DEV_IRQ for the data structure. The target device is specified
+by assigned_dev_id. In the flags field, only KVM_DEV_ASSIGN_MASK_INTX is
+evaluated.
+
 5. The kvm_run structure
 
 Application code obtains a pointer to the kvm_run structure by
arch/x86/kvm/x86.c +1 -0
index 410d2d1..c77c129 100644
@@ -1969,6 +1969,7 @@ int kvm_dev_ioctl_check_extension(long ext)
 	case KVM_CAP_X86_ROBUST_SINGLESTEP:
 	case KVM_CAP_XSAVE:
 	case KVM_CAP_ASYNC_PF:
+	case KVM_CAP_PCI_2_3:
 		r = 1;
 		break;
 	case KVM_CAP_COALESCED_MMIO:
include/linux/kvm.h +6 -0
index ea2dc1a..3cadb42 100644
@@ -541,6 +541,7 @@ struct kvm_ppc_pvinfo {
 #define KVM_CAP_PPC_GET_PVINFO 57
 #define KVM_CAP_PPC_IRQ_LEVEL 58
 #define KVM_CAP_ASYNC_PF 59
+#define KVM_CAP_PCI_2_3 60
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
@@ -677,6 +678,9 @@ struct kvm_clock_data {
 #define KVM_SET_PIT2              _IOW(KVMIO,  0xa0, struct kvm_pit_state2)
 /* Available with KVM_CAP_PPC_GET_PVINFO */
 #define KVM_PPC_GET_PVINFO	  _IOW(KVMIO,  0xa1, struct kvm_ppc_pvinfo)
+/* Available with KVM_CAP_PCI_2_3 */
+#define KVM_ASSIGN_SET_INTX_MASK  _IOW(KVMIO,  0xa2, \
+				       struct kvm_assigned_pci_dev)
 
 /*
  * ioctls for vcpu fds
@@ -742,6 +746,8 @@ struct kvm_clock_data {
 #define KVM_SET_XCRS		  _IOW(KVMIO,  0xa7, struct kvm_xcrs)
 
 #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1 << 0)
+#define KVM_DEV_ASSIGN_PCI_2_3		(1 << 1)
+#define KVM_DEV_ASSIGN_MASK_INTX	(1 << 2)
 
 struct kvm_assigned_pci_dev {
 	__u32 assigned_dev_id;
include/linux/kvm_host.h +4 -0
index da0794f..6849568 100644
@@ -18,6 +18,7 @@
 #include <linux/msi.h>
 #include <linux/slab.h>
 #include <linux/rcupdate.h>
+#include <linux/notifier.h>
 #include <asm/signal.h>
 
 #include <linux/kvm.h>
@@ -485,6 +486,7 @@ struct kvm_assigned_dev_kernel {
 	unsigned int entries_nr;
 	int host_irq;
 	bool host_irq_disabled;
+	bool pci_2_3_masking;
 	struct msix_entry *host_msix_entries;
 	int guest_irq;
 	struct msix_entry *guest_msix_entries;
@@ -494,7 +496,9 @@ struct kvm_assigned_dev_kernel {
 	struct pci_dev *dev;
 	struct kvm *kvm;
 	spinlock_t intx_lock;
+	spinlock_t intx_mask_lock;
 	char irq_name[32];
+	struct notifier_block sh_notifier;
 };
 
 struct kvm_irq_mask_notifier {
virt/kvm/assigned-dev.c +349 -33

499 lines not on this page · raw message

Signature · 1 line
1.7.1
#6 lore raw

Re: [PATCH 5/5] KVM: Allow host IRQ sharing for passed-through PCI 2.3 devices

Quoted 41 lines · Jan Kiszka

From: Jan Kiszka<[email protected]>

PCI 2.3 allows to generically disable IRQ sources at device level. This enables us to share IRQs of such devices on the host side when passing them to a guest.

However, IRQ disabling via the PCI config space is more costly than masking the line via disable_irq. Therefore we register an IRQ sharing notifier and switch between line and device level disabling on demand.

This feature is optional, user space has to request it explicitly as it also has to inform us about its view of PCI_COMMAND_INTX_DISABLE. That way, we can avoid unmasking the interrupt and signaling it if the guest masked it via the PCI config space.

Documentation/kvm/api.txt +10 -0
index e1a9297..5e164db 100644
@@ -1112,6 +1112,14 @@ following flags are specified:
 
  /* Depends on KVM_CAP_IOMMU */
  #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1<<  0)
+/* The following two depend on KVM_CAP_PCI_2_3 */
+#define KVM_DEV_ASSIGN_PCI_2_3		(1<<  1)
+#define KVM_DEV_ASSIGN_MASK_INTX	(1<<  2)
+
+If KVM_DEV_ASSIGN_PCI_2_3 is set, the kernel will manage legacy INTx interrupts
+via the PCI-2.3-compliant device-level mask, but only if IRQ sharing with other
+assigned or host devices requires it. KVM_DEV_ASSIGN_MASK_INTX specifies the
+guest's view on the INTx mask, see KVM_ASSIGN_SET_INTX_MASK for details.
 
  4.48 KVM_DEASSIGN_PCI_DEVICE
 
@@ -1263,6 +1271,23 @@ struct kvm_assigned_msix_entry {
  	__u16 padding[3];
  };
 
+4.54 KVM_ASSIGN_SET_INTX_MASK
+

Documentation/kvm/api.txt struct kvm_assigned_msix_entry { · line 1285

Quoted 10 lines
Documentation/kvm/api.txt +10 -0
+Capability: KVM_CAP_PCI_2_3
+Architectures: x86
+Type: vm ioctl
+Parameters: struct kvm_assigned_pci_dev (in)
+Returns: 0 on success, -1 on error
+
+Informs the kernel about the guest's view on the INTx mask. As long as the
+guest masks the legacy INTx, the kernel will refrain from unmasking it at
+hardware level and will not assert the guest's IRQ line. User space is still
+responsible for applying this state to the assigned device's real config space.

What's the protocol for doing this? I suppose userspace has to disable interrupts, ioctl(SET_INTX_MASK, masked), ..., ioctl(SET_INTX_MASK, unmasked), enable interrupts?

Isn't there a race window between the two operations?

Maybe we should give the kernel full ownership of that bit.

Quoted 5 lines
Documentation/kvm/api.txt +5 -0
+
+See KVM_ASSIGN_DEV_IRQ for the data structure. The target device is specified
+by assigned_dev_id. In the flags field, only KVM_DEV_ASSIGN_MASK_INTX is
+evaluated.
+
Signature · 1 line
error compiling committee.c: too many arguments to function
#7 lore raw

Re: [PATCH 5/5] KVM: Allow host IRQ sharing for passed-through PCI 2.3 devices

Quoted 40 lines · Avi Kivity
Quoted 39 lines · Jan Kiszka

From: Jan Kiszka<[email protected]>

PCI 2.3 allows to generically disable IRQ sources at device level. This enables us to share IRQs of such devices on the host side when passing them to a guest.

However, IRQ disabling via the PCI config space is more costly than masking the line via disable_irq. Therefore we register an IRQ sharing notifier and switch between line and device level disabling on demand.

This feature is optional, user space has to request it explicitly as it also has to inform us about its view of PCI_COMMAND_INTX_DISABLE. That way, we can avoid unmasking the interrupt and signaling it if the guest masked it via the PCI config space.

Documentation/kvm/api.txt +8 -0
index e1a9297..5e164db 100644
@@ -1112,6 +1112,14 @@ following flags are specified:
 
  /* Depends on KVM_CAP_IOMMU */
  #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1<<  0)
+/* The following two depend on KVM_CAP_PCI_2_3 */
+#define KVM_DEV_ASSIGN_PCI_2_3		(1<<  1)
+#define KVM_DEV_ASSIGN_MASK_INTX	(1<<  2)
+
+If KVM_DEV_ASSIGN_PCI_2_3 is set, the kernel will manage legacy INTx interrupts
+via the PCI-2.3-compliant device-level mask, but only if IRQ sharing with other
+assigned or host devices requires it. KVM_DEV_ASSIGN_MASK_INTX specifies the
+guest's view on the INTx mask, see KVM_ASSIGN_SET_INTX_MASK for details.
 
  4.48 KVM_DEASSIGN_PCI_DEVICE
 
@@ -1263,6 +1271,23 @@ struct kvm_assigned_msix_entry {
  	__u16 padding[3];
  };
 
Quoted 16 lines

Documentation/kvm/api.txt struct kvm_assigned_msix_entry { · line 1285

Quoted 12 lines
Documentation/kvm/api.txt +12 -0
+4.54 KVM_ASSIGN_SET_INTX_MASK
+
+Capability: KVM_CAP_PCI_2_3
+Architectures: x86
+Type: vm ioctl
+Parameters: struct kvm_assigned_pci_dev (in)
+Returns: 0 on success, -1 on error
+
+Informs the kernel about the guest's view on the INTx mask. As long as the
+guest masks the legacy INTx, the kernel will refrain from unmasking it at
+hardware level and will not assert the guest's IRQ line. User space is still
+responsible for applying this state to the assigned device's real config space.

What's the protocol for doing this? I suppose userspace has to disable interrupts, ioctl(SET_INTX_MASK, masked), ..., ioctl(SET_INTX_MASK, unmasked), enable interrupts?

Userspace just has to synchronize against itself - what it already does:
qemu_mutex, and masking/unmasking is synchronous /wrt the the executing VCPU. Otherwise, masking/unmasking is naturally racy, also in Real Life. The guest resolves the remaining races.

Quoted 4 lines

Isn't there a race window between the two operations?

Maybe we should give the kernel full ownership of that bit.

I think this is what VFIO does and is surely cleaner than this approach. But it's not possible with the existing interface (sysfs + KVM ioctls) - or can you restrict the sysfs access to the config space in such details?

Jan

Signature · 2 lines
Siemens AG, Corporate Technology, CT T DE IT 1
Corporate Competence Center Embedded Linux
#8 lore raw

Re: [PATCH 5/5] KVM: Allow host IRQ sharing for passed-through PCI 2.3 devices

Quoted 9 lines · Jan Kiszka
Quoted 4 lines

What's the protocol for doing this? I suppose userspace has to disable interrupts, ioctl(SET_INTX_MASK, masked), ..., ioctl(SET_INTX_MASK, unmasked), enable interrupts?

Userspace just has to synchronize against itself - what it already does:
qemu_mutex, and masking/unmasking is synchronous /wrt the the executing VCPU. Otherwise, masking/unmasking is naturally racy, also in Real Life. The guest resolves the remaining races.

I meant when qemu sets INTX_MASK and the kernel clears it immediately afterwards because the two are not synchronized. I guess that won't happen in practice because playing with INTX_MASK is very rare.

Quoted 8 lines
Quoted 4 lines

Isn't there a race window between the two operations?

Maybe we should give the kernel full ownership of that bit.

I think this is what VFIO does and is surely cleaner than this approach. But it's not possible with the existing interface (sysfs + KVM ioctls) - or can you restrict the sysfs access to the config space in such details?

I'm sure you can, not sure it's worth it. Can the situation be exploited? what if userspace lies?

Signature · 1 line
error compiling committee.c: too many arguments to function
#9 lore raw

Re: [PATCH 5/5] KVM: Allow host IRQ sharing for passed-through PCI 2.3 devices

Quoted 6 lines · Avi Kivity
Quoted 5 lines · Jan Kiszka
Quoted 4 lines

What's the protocol for doing this? I suppose userspace has to disable interrupts, ioctl(SET_INTX_MASK, masked), ..., ioctl(SET_INTX_MASK, unmasked), enable interrupts?

Quoted 8 lines
Quoted 4 lines

Userspace just has to synchronize against itself - what it already does:
qemu_mutex, and masking/unmasking is synchronous /wrt the the executing VCPU. Otherwise, masking/unmasking is naturally racy, also in Real Life. The guest resolves the remaining races.

I meant when qemu sets INTX_MASK and the kernel clears it immediately afterwards because the two are not synchronized. I guess that won't happen in practice because playing with INTX_MASK is very rare.

Ah, there is indeed a race, and the qemu-kvm patches I did not post yet (to wait for the kernel interface to settle) actually suffer from it:
userspace needs to set the kernel mask before writing the config space (it's the other way around ATM). This avoids that the kernel overwrites what userspace just wrote out. We always suffer from the race the other way around, see below.

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

Isn't there a race window between the two operations?

Quoted 8 lines
Quoted 5 lines
Quoted 1 line

Maybe we should give the kernel full ownership of that bit.

I think this is what VFIO does and is surely cleaner than this approach. But it's not possible with the existing interface (sysfs + KVM ioctls) - or can you restrict the sysfs access to the config space in such details?

I'm sure you can, not sure it's worth it. Can the situation be exploited? what if userspace lies?

That's also the above scenario inverted: Userspace can mask or unmask at any time. If it unmasks a yet unhandled, thus raise interrupt, it will trigger another one. The kernel will catch it and mask it again. That can repeat forever with the frequency userspace is able to run its unmasking code. Not nice, but nothing to leverage for a DoS.

Jan

Signature · 2 lines
Siemens AG, Corporate Technology, CT T DE IT 1
Corporate Competence Center Embedded Linux
#10 lore raw

Re: [PATCH 5/5] KVM: Allow host IRQ sharing for passed-through PCI 2.3 devices

Quoted 16 lines · Jan Kiszka
Quoted 6 lines · Avi Kivity
Quoted 5 lines · Jan Kiszka
Quoted 4 lines

What's the protocol for doing this? I suppose userspace has to disable interrupts, ioctl(SET_INTX_MASK, masked), ..., ioctl(SET_INTX_MASK, unmasked), enable interrupts?

Quoted 8 lines
Quoted 4 lines

Userspace just has to synchronize against itself - what it already does:
qemu_mutex, and masking/unmasking is synchronous /wrt the the executing VCPU. Otherwise, masking/unmasking is naturally racy, also in Real Life. The guest resolves the remaining races.

I meant when qemu sets INTX_MASK and the kernel clears it immediately afterwards because the two are not synchronized. I guess that won't happen in practice because playing with INTX_MASK is very rare.

Quoted 6 lines

Ah, there is indeed a race, and the qemu-kvm patches I did not post yet (to wait for the kernel interface to settle) actually suffer from it:
userspace needs to set the kernel mask before writing the config space (it's the other way around ATM). This avoids that the kernel overwrites what userspace just wrote out. We always suffer from the race the other way around, see below.

Please document the protocol. Is this always the right order? Shouldn't it be reversed when unmasking? I admit I'm confused about this.

Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

I think this is what VFIO does and is surely cleaner than this approach. But it's not possible with the existing interface (sysfs + KVM ioctls) - or can you restrict the sysfs access to the config space in such details?

Quoted 8 lines
Quoted 2 lines

I'm sure you can, not sure it's worth it. Can the situation be exploited? what if userspace lies?

That's also the above scenario inverted: Userspace can mask or unmask at any time. If it unmasks a yet unhandled, thus raise interrupt, it will trigger another one. The kernel will catch it and mask it again. That can repeat forever with the frequency userspace is able to run its unmasking code. Not nice, but nothing to leverage for a DoS.

Ok (I think).

Signature · 1 line
error compiling committee.c: too many arguments to function
#11 lore raw

Re: [PATCH 5/5] KVM: Allow host IRQ sharing for passed-through PCI 2.3 devices

Quoted 17 lines · Avi Kivity
Quoted 16 lines · Jan Kiszka
Quoted 14 lines · Avi Kivity
Quoted 9 lines · Jan Kiszka
Quoted 4 lines

What's the protocol for doing this? I suppose userspace has to disable interrupts, ioctl(SET_INTX_MASK, masked), ..., ioctl(SET_INTX_MASK, unmasked), enable interrupts?

Userspace just has to synchronize against itself - what it already does:
qemu_mutex, and masking/unmasking is synchronous /wrt the the executing VCPU. Otherwise, masking/unmasking is naturally racy, also in Real Life. The guest resolves the remaining races.

I meant when qemu sets INTX_MASK and the kernel clears it immediately afterwards because the two are not synchronized. I guess that won't happen in practice because playing with INTX_MASK is very rare.

Quoted 8 lines
Quoted 6 lines

Ah, there is indeed a race, and the qemu-kvm patches I did not post yet (to wait for the kernel interface to settle) actually suffer from it:
userspace needs to set the kernel mask before writing the config space (it's the other way around ATM). This avoids that the kernel overwrites what userspace just wrote out. We always suffer from the race the other way around, see below.

Please document the protocol.

Yes, will do.

Quoted 2 lines

Is this always the right order?
Shouldn't it be reversed when unmasking? I admit I'm confused about this.

The kernel only notes down userspace's view on the mask, it doesn't modify the hardware (unless it is in line-disabling mode, but then there is no conflict anyway). Therefore, if userspace first unmasks and an interrupts arrives before this was reported to the kernel, the device interrupt will remain masked and no event is delivered to userspace.

Jan

Signature · 2 lines
Siemens AG, Corporate Technology, CT T DE IT 1
Corporate Competence Center Embedded Linux
#12 lore raw
In reply to Jan Kiszka
Quoted 11 lines · Jan Kiszka

Besides 3 cleanup patches, this series consists of two major changes. The first introduces an interrupt sharing notifier to the genirq subsystem. It fires when an interrupt line is about to be use by more than one driver or the last but one user called free_irq.

The second major change makes use of this interface in KVM's PCI pass- through subsystem. KVM has to keep the interrupt source disabled while calling into the guest to handle the event. This can be done at device or line level. The former is required to share the interrupt line, the latter is an order of magnitude faster (see patch 3 for details).

Quoted 3 lines

Beside pass-through support of KVM, further users of the IRQ notifier could become VFIO (not yet mainline) and uio_pci_generic which have to resolve the same conflict.

Hmm. You basically want to have the following functionality:

If interrupt is shared, then you want to keep the current behaviour:

disable at line level (IRQF_ONESHOT)
run handler thread (PCI level masking) reenable at line level in irq_finalize_oneshot() reenable at PCI level when guest is done

If interrupt is not shared:

disable at line level (IRQF_ONESHOT)
run handler thread (no PCI level masking)
no reenable at line level
reenable at line level when guest is done

I think the whole notifier approach including the extra irq handlers plus the requirement to call disable_irq_nosync() from the non shared handler is overkill. We can be more clever.

The genirq code knows whether you have one or more handler registered. So we can add IRQF_ONESHOT_UNMASK_SHARED and add a status field to irq_data (which I was going to do anyway for other reasons). In that status field you get a bit which says IRQ_MASK_DEVICE.

So with IRQF_ONESHOT_UNMASK_SHARED == 0 we keep the current behaviour.

If IRQF_ONESHOT_UNMASK_SHARED== 1 and IRQ_MASK_DEVICE == 1 we keep the current behaviour.

If IRQF_ONESHOT_UNMASK_SHARED== 1 and IRQ_MASK_DEVICE == 0 then then irq_finalize_oneshot() simply marks the interrupt disabled (equivalent to disable_irq_nosync()) and returns.

Now in your primary irq handler you simply check the IRQ_MASK_DEVICE status flag and decide whether you need to mask at PCI level or not.

Your threaded handler gets the same information via IRQ_MASK_DEVICE so it can issue the appropriate user space notification depending on that flag.

This works fully transparent across adding and removing handlers. On request_irq/free_irq we update the IRQ_MASK_DEVICE flag with the following logic:

    nr_actions	IRQF_ONESHOT_UNMASK_SHARED	IRQ_MASK_DEVICE
    1		0				1
    1		1				0
    >1		don't care			1

If interrupts are in flight accross request/free then this change takes effect when the next interrupt comes in.

No notifiers, no disable_irq_nosync() magic, less and simpler code.

Thoughts ?

tglx

author
#13 lore raw
Quoted 17 lines · Thomas Gleixner
Quoted 11 lines · Jan Kiszka

Besides 3 cleanup patches, this series consists of two major changes. The first introduces an interrupt sharing notifier to the genirq subsystem. It fires when an interrupt line is about to be use by more than one driver or the last but one user called free_irq.

The second major change makes use of this interface in KVM's PCI pass- through subsystem. KVM has to keep the interrupt source disabled while calling into the guest to handle the event. This can be done at device or line level. The former is required to share the interrupt line, the latter is an order of magnitude faster (see patch 3 for details).

Quoted 3 lines

Beside pass-through support of KVM, further users of the IRQ notifier could become VFIO (not yet mainline) and uio_pci_generic which have to resolve the same conflict.

Quoted 8 lines

Hmm. You basically want to have the following functionality:

If interrupt is shared, then you want to keep the current behaviour:

disable at line level (IRQF_ONESHOT)
run handler thread (PCI level masking) reenable at line level in irq_finalize_oneshot() reenable at PCI level when guest is done

If the interrupt is shared, we must mask at PCI level inside the primary handler as we also have to support non-threaded users of the same line. So we always have a transition line-level -> device-level masking in a primary handler.

Quoted 7 lines

If interrupt is not shared:

disable at line level (IRQF_ONESHOT)
run handler thread (no PCI level masking)
no reenable at line level
reenable at line level when guest is done

We only use threaded handlers so far for hairy lock-dependency reasons and as certain injection complexity is too much guest-controllable. We hope to move most injection cases (ie. any IRQ directed to a single VCPU / user space task) directly into a primary handler in the future to reduce the latency. So both threaded and non-threaded cases should be addressable by any approach.

Quoted 4 lines

I think the whole notifier approach including the extra irq handlers plus the requirement to call disable_irq_nosync() from the non shared handler is overkill. We can be more clever.

Always welcome.

Quoted 6 lines

The genirq code knows whether you have one or more handler registered. So we can add IRQF_ONESHOT_UNMASK_SHARED and add a status field to irq_data (which I was going to do anyway for other reasons). In that status field you get a bit which says IRQ_MASK_DEVICE.

Quoted 8 lines

So with IRQF_ONESHOT_UNMASK_SHARED == 0 we keep the current behaviour.

If IRQF_ONESHOT_UNMASK_SHARED== 1 and IRQ_MASK_DEVICE == 1 we keep the current behaviour.

If IRQF_ONESHOT_UNMASK_SHARED== 1 and IRQ_MASK_DEVICE == 0 then then irq_finalize_oneshot() simply marks the interrupt disabled (equivalent to disable_irq_nosync()) and returns.

That sounds good.

Quoted 12 lines

Now in your primary irq handler you simply check the IRQ_MASK_DEVICE status flag and decide whether you need to mask at PCI level or not.

Your threaded handler gets the same information via IRQ_MASK_DEVICE so it can issue the appropriate user space notification depending on that flag.

This works fully transparent across adding and removing handlers. On request_irq/free_irq we update the IRQ_MASK_DEVICE flag with the following logic:

Quoted 7 lines
    nr_actions	IRQF_ONESHOT_UNMASK_SHARED	IRQ_MASK_DEVICE
    1		0				1
    1		1				0
    >1		don't care			1

If interrupts are in flight accross request/free then this change takes effect when the next interrupt comes in.

For registration, that might be too late. We need to synchronize on in-flight interrupts for that line and then ensure that it gets enabled independently of the registered user. That user may have applied outdated information, thus would block the line for too long if user space decides to do something else.

I think this will be the trickier part of the otherwise nice & simple concept.

Quoted 4 lines

No notifiers, no disable_irq_nosync() magic, less and simpler code.

Thoughts ?

Pulling the effect of disable_irq_nosync into genirq also for threaded handling would remove the need for re-registering handlers. That's good. But we need to think about the hand-over scenarios again. Maybe solvable, but non-trivial.

Jan

#14 lore raw

Jan,

Quoted 9 lines · Jan Kiszka
Quoted 7 lines · Thomas Gleixner

On Sat, 4 Dec 2010, Jan Kiszka wrote:
If interrupt is shared, then you want to keep the current behaviour:

disable at line level (IRQF_ONESHOT)
run handler thread (PCI level masking) reenable at line level in irq_finalize_oneshot() reenable at PCI level when guest is done

Quoted 4 lines

If the interrupt is shared, we must mask at PCI level inside the primary handler as we also have to support non-threaded users of the same line. So we always have a transition line-level -> device-level masking in a primary handler.

Sorry that left out the hard irq part. Of course it needs to do the PCI level masking in the primary one.

Quoted 2 lines

reduce the latency. So both threaded and non-threaded cases should be addressable by any approach.

The oneshot magic should work on non threaded cases as well. Needs some modifications, but nothing complex.

Quoted 8 lines
Quoted 2 lines

If interrupts are in flight accross request/free then this change takes effect when the next interrupt comes in.

For registration, that might be too late. We need to synchronize on in-flight interrupts for that line and then ensure that it gets enabled independently of the registered user. That user may have applied outdated information, thus would block the line for too long if user space decides to do something else.

No, that's really just a corner case when going from one to two handlers and I don't think it matters much. If you setup a new driver it's not really important whether that first thing comes in a few ms later.

Also there is a pretty simple solution for this: The core code knows, that there is an ONESHOT interrupt in flight, so it simply can call the primary handler of that device with the appropriate flag set (maybe an additional one to indicate the transition) and let that deal with it. Needs some thought vs. locking and races, but that shouldn't be hard.

Quoted 4 lines

Pulling the effect of disable_irq_nosync into genirq also for threaded handling would remove the need for re-registering handlers. That's good. But we need to think about the hand-over scenarios again. Maybe solvable, but non-trivial.

See above.

Thanks,

tglx

author
#15 lore raw
Quoted 36 lines · Thomas Gleixner

Jan,

Quoted 9 lines · Jan Kiszka
Quoted 7 lines · Thomas Gleixner

On Sat, 4 Dec 2010, Jan Kiszka wrote:
If interrupt is shared, then you want to keep the current behaviour:

disable at line level (IRQF_ONESHOT)
run handler thread (PCI level masking) reenable at line level in irq_finalize_oneshot() reenable at PCI level when guest is done

Quoted 4 lines

If the interrupt is shared, we must mask at PCI level inside the primary handler as we also have to support non-threaded users of the same line. So we always have a transition line-level -> device-level masking in a primary handler.

Sorry that left out the hard irq part. Of course it needs to do the PCI level masking in the primary one.

Quoted 2 lines

reduce the latency. So both threaded and non-threaded cases should be addressable by any approach.

The oneshot magic should work on non threaded cases as well. Needs some modifications, but nothing complex.

Quoted 8 lines
Quoted 2 lines

If interrupts are in flight accross request/free then this change takes effect when the next interrupt comes in.

For registration, that might be too late. We need to synchronize on in-flight interrupts for that line and then ensure that it gets enabled independently of the registered user. That user may have applied outdated information, thus would block the line for too long if user space decides to do something else.

Quoted 4 lines

No, that's really just a corner case when going from one to two handlers and I don't think it matters much. If you setup a new driver it's not really important whether that first thing comes in a few ms later.

The worst case remains infinite (user space never signals end of interrupt).

Quoted 3 lines

Also there is a pretty simple solution for this: The core code knows, that there is an ONESHOT interrupt in flight, so it simply can call

It doesn't synchronize the tail part against the masking in the handler(s), that's driver business.

Quoted 4 lines

the primary handler of that device with the appropriate flag set (maybe an additional one to indicate the transition) and let that deal with it. Needs some thought vs. locking and races, but that shouldn't be hard.

Yes, I thought about this kind of transition (re-invoking the existing handler) already. We do need notification of the switch (at least for exclusive->shared) as only the driver can migrate the masking for in-flight interrupts.

Jan

#16 lore raw
Quoted 6 lines · Jan Kiszka
Quoted 2 lines · Thomas Gleixner

Also there is a pretty simple solution for this: The core code knows, that there is an ONESHOT interrupt in flight, so it simply can call

It doesn't synchronize the tail part against the masking in the handler(s), that's driver business.

Right, but the core knows from the irq state, that the line is masked and due to the ONESHOT or whatever feature it knows that it needs to call the handler.

The other way round shared -> exclusive does not matter at all.

Quoted 9 lines
Quoted 4 lines

the primary handler of that device with the appropriate flag set (maybe an additional one to indicate the transition) and let that deal with it. Needs some thought vs. locking and races, but that shouldn't be hard.

Yes, I thought about this kind of transition (re-invoking the existing handler) already. We do need notification of the switch (at least for exclusive->shared) as only the driver can migrate the masking for in-flight interrupts.

Right. It needs to set the device level mask in that case. As the interrupt handler already has the code to do that it's the most obvious function to call.

Thanks,

tglx