Olaf Hering e9d267
From: Sunil Muthuswamy <sunilmut@microsoft.com>
Olaf Hering e9d267
Date: Wed, 5 Jan 2022 11:32:36 -0800
Takashi Iwai 12240b
Patch-mainline: v5.17-rc1
Takashi Iwai 12240b
Git-commit: d9932b46915664c88709d59927fa67e797adec56
Olaf Hering e9d267
Subject: PCI: hv: Add arm64 Hyper-V vPCI support
Olaf Hering e9d267
References: jsc#SLE-17855,bsc#1186071
Olaf Hering e9d267
Olaf Hering e9d267
Add arm64 Hyper-V vPCI support by implementing the arch specific
Olaf Hering e9d267
interfaces. Introduce an IRQ domain and chip specific to Hyper-v vPCI that
Olaf Hering e9d267
is based on SPIs. The IRQ domain parents itself to the arch GIC IRQ domain
Olaf Hering e9d267
for basic vector management.
Olaf Hering e9d267
Olaf Hering e9d267
Signed-off-by: Sunil Muthuswamy <sunilmut@microsoft.com>
Olaf Hering e9d267
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Olaf Hering e9d267
Reviewed-by: Marc Zyngier <maz@kernel.org>
Olaf Hering e9d267
Reviewed-by: Michael Kelley <mikelley@microsoft.com>
Olaf Hering e9d267
Acked-by: Olaf Hering <ohering@suse.de>
Olaf Hering e9d267
---
Olaf Hering e9d267
 arch/arm64/include/asm/hyperv-tlfs.h |   9 +
Olaf Hering e9d267
 drivers/pci/Kconfig                  |   2 +-
Olaf Hering e9d267
 drivers/pci/controller/Kconfig       |   2 +-
Olaf Hering e9d267
 drivers/pci/controller/pci-hyperv.c  | 235 +++++++++++++++++++++-
Olaf Hering e9d267
 4 files changed, 245 insertions(+), 3 deletions(-)
Olaf Hering e9d267
Olaf Hering e9d267
diff --git a/arch/arm64/include/asm/hyperv-tlfs.h b/arch/arm64/include/asm/hyperv-tlfs.h
Olaf Hering e9d267
--- a/arch/arm64/include/asm/hyperv-tlfs.h
Olaf Hering e9d267
+++ b/arch/arm64/include/asm/hyperv-tlfs.h
Olaf Hering e9d267
@@ -64,6 +64,15 @@
Olaf Hering e9d267
 #define HV_REGISTER_STIMER0_CONFIG	0x000B0000
Olaf Hering e9d267
 #define HV_REGISTER_STIMER0_COUNT	0x000B0001
Olaf Hering e9d267
 
Olaf Hering e9d267
+union hv_msi_entry {
Olaf Hering e9d267
+	u64 as_uint64[2];
Olaf Hering e9d267
+	struct {
Olaf Hering e9d267
+		u64 address;
Olaf Hering e9d267
+		u32 data;
Olaf Hering e9d267
+		u32 reserved;
Olaf Hering e9d267
+	} __packed;
Olaf Hering e9d267
+};
Olaf Hering e9d267
+
Olaf Hering e9d267
 #include <asm-generic/hyperv-tlfs.h>
Olaf Hering e9d267
 
Olaf Hering e9d267
 #endif
Olaf Hering e9d267
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
Olaf Hering e9d267
--- a/drivers/pci/Kconfig
Olaf Hering e9d267
+++ b/drivers/pci/Kconfig
Olaf Hering e9d267
@@ -184,7 +184,7 @@ config PCI_LABEL
Olaf Hering e9d267
 
Olaf Hering e9d267
 config PCI_HYPERV
Olaf Hering e9d267
 	tristate "Hyper-V PCI Frontend"
Olaf Hering e9d267
-	depends on X86_64 && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && SYSFS
Olaf Hering e9d267
+	depends on ((X86 && X86_64) || ARM64) && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && SYSFS
Olaf Hering e9d267
 	select PCI_HYPERV_INTERFACE
Olaf Hering e9d267
 	help
Olaf Hering e9d267
 	  The PCI device frontend driver allows the kernel to import arbitrary
Olaf Hering e9d267
diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
Olaf Hering e9d267
--- a/drivers/pci/controller/Kconfig
Olaf Hering e9d267
+++ b/drivers/pci/controller/Kconfig
Olaf Hering e9d267
@@ -281,7 +281,7 @@ config PCIE_BRCMSTB
Olaf Hering e9d267
 
Olaf Hering e9d267
 config PCI_HYPERV_INTERFACE
Olaf Hering e9d267
 	tristate "Hyper-V PCI Interface"
Olaf Hering e9d267
-	depends on X86 && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && X86_64
Olaf Hering e9d267
+	depends on ((X86 && X86_64) || ARM64) && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN
Olaf Hering e9d267
 	help
Olaf Hering e9d267
 	  The Hyper-V PCI Interface is a helper driver allows other drivers to
Olaf Hering e9d267
 	  have a common interface with the Hyper-V PCI frontend driver.
Olaf Hering e9d267
diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c
Olaf Hering e9d267
--- a/drivers/pci/controller/pci-hyperv.c
Olaf Hering e9d267
+++ b/drivers/pci/controller/pci-hyperv.c
Olaf Hering e9d267
@@ -47,6 +47,8 @@
Olaf Hering e9d267
 #include <linux/msi.h>
Olaf Hering e9d267
 #include <linux/hyperv.h>
Olaf Hering e9d267
 #include <linux/refcount.h>
Olaf Hering e9d267
+#include <linux/irqdomain.h>
Olaf Hering e9d267
+#include <linux/acpi.h>
Olaf Hering e9d267
 #include <asm/mshyperv.h>
Olaf Hering e9d267
 
Olaf Hering e9d267
 /*
Olaf Hering e9d267
@@ -614,7 +616,230 @@ static int hv_msi_prepare(struct irq_domain *domain, struct device *dev,
Olaf Hering e9d267
 {
Olaf Hering e9d267
 	return pci_msi_prepare(domain, dev, nvec, info);
Olaf Hering e9d267
 }
Olaf Hering e9d267
-#endif /* CONFIG_X86 */
Olaf Hering e9d267
+#elif defined(CONFIG_ARM64)
Olaf Hering e9d267
+/*
Olaf Hering e9d267
+ * SPI vectors to use for vPCI; arch SPIs range is [32, 1019], but leaving a bit
Olaf Hering e9d267
+ * of room at the start to allow for SPIs to be specified through ACPI and
Olaf Hering e9d267
+ * starting with a power of two to satisfy power of 2 multi-MSI requirement.
Olaf Hering e9d267
+ */
Olaf Hering e9d267
+#define HV_PCI_MSI_SPI_START	64
Olaf Hering e9d267
+#define HV_PCI_MSI_SPI_NR	(1020 - HV_PCI_MSI_SPI_START)
Olaf Hering e9d267
+#define DELIVERY_MODE		0
Olaf Hering e9d267
+#define FLOW_HANDLER		NULL
Olaf Hering e9d267
+#define FLOW_NAME		NULL
Olaf Hering e9d267
+#define hv_msi_prepare		NULL
Olaf Hering e9d267
+
Olaf Hering e9d267
+struct hv_pci_chip_data {
Olaf Hering e9d267
+	DECLARE_BITMAP(spi_map, HV_PCI_MSI_SPI_NR);
Olaf Hering e9d267
+	struct mutex	map_lock;
Olaf Hering e9d267
+};
Olaf Hering e9d267
+
Olaf Hering e9d267
+/* Hyper-V vPCI MSI GIC IRQ domain */
Olaf Hering e9d267
+static struct irq_domain *hv_msi_gic_irq_domain;
Olaf Hering e9d267
+
Olaf Hering e9d267
+/* Hyper-V PCI MSI IRQ chip */
Olaf Hering e9d267
+static struct irq_chip hv_arm64_msi_irq_chip = {
Olaf Hering e9d267
+	.name = "MSI",
Olaf Hering e9d267
+	.irq_set_affinity = irq_chip_set_affinity_parent,
Olaf Hering e9d267
+	.irq_eoi = irq_chip_eoi_parent,
Olaf Hering e9d267
+	.irq_mask = irq_chip_mask_parent,
Olaf Hering e9d267
+	.irq_unmask = irq_chip_unmask_parent
Olaf Hering e9d267
+};
Olaf Hering e9d267
+
Olaf Hering e9d267
+static unsigned int hv_msi_get_int_vector(struct irq_data *irqd)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	return irqd->parent_data->hwirq;
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+static void hv_set_msi_entry_from_desc(union hv_msi_entry *msi_entry,
Olaf Hering e9d267
+				       struct msi_desc *msi_desc)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	msi_entry->address = ((u64)msi_desc->msg.address_hi << 32) |
Olaf Hering e9d267
+			      msi_desc->msg.address_lo;
Olaf Hering e9d267
+	msi_entry->data = msi_desc->msg.data;
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+/*
Olaf Hering e9d267
+ * @nr_bm_irqs:		Indicates the number of IRQs that were allocated from
Olaf Hering e9d267
+ *			the bitmap.
Olaf Hering e9d267
+ * @nr_dom_irqs:	Indicates the number of IRQs that were allocated from
Olaf Hering e9d267
+ *			the parent domain.
Olaf Hering e9d267
+ */
Olaf Hering e9d267
+static void hv_pci_vec_irq_free(struct irq_domain *domain,
Olaf Hering e9d267
+				unsigned int virq,
Olaf Hering e9d267
+				unsigned int nr_bm_irqs,
Olaf Hering e9d267
+				unsigned int nr_dom_irqs)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	struct hv_pci_chip_data *chip_data = domain->host_data;
Olaf Hering e9d267
+	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
Olaf Hering e9d267
+	int first = d->hwirq - HV_PCI_MSI_SPI_START;
Olaf Hering e9d267
+	int i;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	mutex_lock(&chip_data->map_lock);
Olaf Hering e9d267
+	bitmap_release_region(chip_data->spi_map,
Olaf Hering e9d267
+			      first,
Olaf Hering e9d267
+			      get_count_order(nr_bm_irqs));
Olaf Hering e9d267
+	mutex_unlock(&chip_data->map_lock);
Olaf Hering e9d267
+	for (i = 0; i < nr_dom_irqs; i++) {
Olaf Hering e9d267
+		if (i)
Olaf Hering e9d267
+			d = irq_domain_get_irq_data(domain, virq + i);
Olaf Hering e9d267
+		irq_domain_reset_irq_data(d);
Olaf Hering e9d267
+	}
Olaf Hering e9d267
+
Olaf Hering e9d267
+	irq_domain_free_irqs_parent(domain, virq, nr_dom_irqs);
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+static void hv_pci_vec_irq_domain_free(struct irq_domain *domain,
Olaf Hering e9d267
+				       unsigned int virq,
Olaf Hering e9d267
+				       unsigned int nr_irqs)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	hv_pci_vec_irq_free(domain, virq, nr_irqs, nr_irqs);
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+static int hv_pci_vec_alloc_device_irq(struct irq_domain *domain,
Olaf Hering e9d267
+				       unsigned int nr_irqs,
Olaf Hering e9d267
+				       irq_hw_number_t *hwirq)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	struct hv_pci_chip_data *chip_data = domain->host_data;
Olaf Hering e9d267
+	unsigned int index;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	/* Find and allocate region from the SPI bitmap */
Olaf Hering e9d267
+	mutex_lock(&chip_data->map_lock);
Olaf Hering e9d267
+	index = bitmap_find_free_region(chip_data->spi_map,
Olaf Hering e9d267
+					HV_PCI_MSI_SPI_NR,
Olaf Hering e9d267
+					get_count_order(nr_irqs));
Olaf Hering e9d267
+	mutex_unlock(&chip_data->map_lock);
Olaf Hering e9d267
+	if (index < 0)
Olaf Hering e9d267
+		return -ENOSPC;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	*hwirq = index + HV_PCI_MSI_SPI_START;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	return 0;
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+static int hv_pci_vec_irq_gic_domain_alloc(struct irq_domain *domain,
Olaf Hering e9d267
+					   unsigned int virq,
Olaf Hering e9d267
+					   irq_hw_number_t hwirq)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	struct irq_fwspec fwspec;
Olaf Hering e9d267
+	struct irq_data *d;
Olaf Hering e9d267
+	int ret;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	fwspec.fwnode = domain->parent->fwnode;
Olaf Hering e9d267
+	fwspec.param_count = 2;
Olaf Hering e9d267
+	fwspec.param[0] = hwirq;
Olaf Hering e9d267
+	fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
Olaf Hering e9d267
+	if (ret)
Olaf Hering e9d267
+		return ret;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	/*
Olaf Hering e9d267
+	 * Since the interrupt specifier is not coming from ACPI or DT, the
Olaf Hering e9d267
+	 * trigger type will need to be set explicitly. Otherwise, it will be
Olaf Hering e9d267
+	 * set to whatever is in the GIC configuration.
Olaf Hering e9d267
+	 */
Olaf Hering e9d267
+	d = irq_domain_get_irq_data(domain->parent, virq);
Olaf Hering e9d267
+
Olaf Hering e9d267
+	return d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING);
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+static int hv_pci_vec_irq_domain_alloc(struct irq_domain *domain,
Olaf Hering e9d267
+				       unsigned int virq, unsigned int nr_irqs,
Olaf Hering e9d267
+				       void *args)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	irq_hw_number_t hwirq;
Olaf Hering e9d267
+	unsigned int i;
Olaf Hering e9d267
+	int ret;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	ret = hv_pci_vec_alloc_device_irq(domain, nr_irqs, &hwirq);
Olaf Hering e9d267
+	if (ret)
Olaf Hering e9d267
+		return ret;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	for (i = 0; i < nr_irqs; i++) {
Olaf Hering e9d267
+		ret = hv_pci_vec_irq_gic_domain_alloc(domain, virq + i,
Olaf Hering e9d267
+						      hwirq + i);
Olaf Hering e9d267
+		if (ret) {
Olaf Hering e9d267
+			hv_pci_vec_irq_free(domain, virq, nr_irqs, i);
Olaf Hering e9d267
+			return ret;
Olaf Hering e9d267
+		}
Olaf Hering e9d267
+
Olaf Hering e9d267
+		irq_domain_set_hwirq_and_chip(domain, virq + i,
Olaf Hering e9d267
+					      hwirq + i,
Olaf Hering e9d267
+					      &hv_arm64_msi_irq_chip,
Olaf Hering e9d267
+					      domain->host_data);
Olaf Hering e9d267
+		pr_debug("pID:%d vID:%u\n", (int)(hwirq + i), virq + i);
Olaf Hering e9d267
+	}
Olaf Hering e9d267
+
Olaf Hering e9d267
+	return 0;
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+/*
Olaf Hering e9d267
+ * Pick the first cpu as the irq affinity that can be temporarily used for
Olaf Hering e9d267
+ * composing MSI from the hypervisor. GIC will eventually set the right
Olaf Hering e9d267
+ * affinity for the irq and the 'unmask' will retarget the interrupt to that
Olaf Hering e9d267
+ * cpu.
Olaf Hering e9d267
+ */
Olaf Hering e9d267
+static int hv_pci_vec_irq_domain_activate(struct irq_domain *domain,
Olaf Hering e9d267
+					  struct irq_data *irqd, bool reserve)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	int cpu = cpumask_first(cpu_present_mask);
Olaf Hering e9d267
+
Olaf Hering e9d267
+	irq_data_update_effective_affinity(irqd, cpumask_of(cpu));
Olaf Hering e9d267
+
Olaf Hering e9d267
+	return 0;
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+static const struct irq_domain_ops hv_pci_domain_ops = {
Olaf Hering e9d267
+	.alloc	= hv_pci_vec_irq_domain_alloc,
Olaf Hering e9d267
+	.free	= hv_pci_vec_irq_domain_free,
Olaf Hering e9d267
+	.activate = hv_pci_vec_irq_domain_activate,
Olaf Hering e9d267
+};
Olaf Hering e9d267
+
Olaf Hering e9d267
+static int hv_pci_irqchip_init(void)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	static struct hv_pci_chip_data *chip_data;
Olaf Hering e9d267
+	struct fwnode_handle *fn = NULL;
Olaf Hering e9d267
+	int ret = -ENOMEM;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	chip_data = kzalloc(sizeof(*chip_data), GFP_KERNEL);
Olaf Hering e9d267
+	if (!chip_data)
Olaf Hering e9d267
+		return ret;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	mutex_init(&chip_data->map_lock);
Olaf Hering e9d267
+	fn = irq_domain_alloc_named_fwnode("hv_vpci_arm64");
Olaf Hering e9d267
+	if (!fn)
Olaf Hering e9d267
+		goto free_chip;
Olaf Hering e9d267
+
Olaf Hering e9d267
+	/*
Olaf Hering e9d267
+	 * IRQ domain once enabled, should not be removed since there is no
Olaf Hering e9d267
+	 * way to ensure that all the corresponding devices are also gone and
Olaf Hering e9d267
+	 * no interrupts will be generated.
Olaf Hering e9d267
+	 */
Olaf Hering e9d267
+	hv_msi_gic_irq_domain = acpi_irq_create_hierarchy(0, HV_PCI_MSI_SPI_NR,
Olaf Hering e9d267
+							  fn, &hv_pci_domain_ops,
Olaf Hering e9d267
+							  chip_data);
Olaf Hering e9d267
+
Olaf Hering e9d267
+	if (!hv_msi_gic_irq_domain) {
Olaf Hering e9d267
+		pr_err("Failed to create Hyper-V arm64 vPCI MSI IRQ domain\n");
Olaf Hering e9d267
+		goto free_chip;
Olaf Hering e9d267
+	}
Olaf Hering e9d267
+
Olaf Hering e9d267
+	return 0;
Olaf Hering e9d267
+
Olaf Hering e9d267
+free_chip:
Olaf Hering e9d267
+	kfree(chip_data);
Olaf Hering e9d267
+	if (fn)
Olaf Hering e9d267
+		irq_domain_free_fwnode(fn);
Olaf Hering e9d267
+
Olaf Hering e9d267
+	return ret;
Olaf Hering e9d267
+}
Olaf Hering e9d267
+
Olaf Hering e9d267
+static struct irq_domain *hv_pci_get_root_domain(void)
Olaf Hering e9d267
+{
Olaf Hering e9d267
+	return hv_msi_gic_irq_domain;
Olaf Hering e9d267
+}
Olaf Hering e9d267
+#endif /* CONFIG_ARM64 */
Olaf Hering e9d267
 
Olaf Hering e9d267
 /**
Olaf Hering e9d267
  * hv_pci_generic_compl() - Invoked for a completion packet
Olaf Hering e9d267
@@ -1227,6 +1452,8 @@ static void hv_msi_free(struct irq_domain *domain, struct msi_domain_info *info,
Olaf Hering e9d267
 static void hv_irq_mask(struct irq_data *data)
Olaf Hering e9d267
 {
Olaf Hering e9d267
 	pci_msi_mask_irq(data);
Olaf Hering e9d267
+	if (data->parent_data->chip->irq_mask)
Olaf Hering e9d267
+		irq_chip_mask_parent(data);
Olaf Hering e9d267
 }
Olaf Hering e9d267
 
Olaf Hering e9d267
 /**
Olaf Hering e9d267
@@ -1343,6 +1570,8 @@ exit_unlock:
Olaf Hering e9d267
 		dev_err(&hbus->hdev->device,
Olaf Hering e9d267
 			"%s() failed: %#llx", __func__, res);
Olaf Hering e9d267
 
Olaf Hering e9d267
+	if (data->parent_data->chip->irq_unmask)
Olaf Hering e9d267
+		irq_chip_unmask_parent(data);
Olaf Hering e9d267
 	pci_msi_unmask_irq(data);
Olaf Hering e9d267
 }
Olaf Hering e9d267
 
Olaf Hering e9d267
@@ -1618,7 +1847,11 @@ static struct irq_chip hv_msi_irq_chip = {
Olaf Hering e9d267
 	.name			= "Hyper-V PCIe MSI",
Olaf Hering e9d267
 	.irq_compose_msi_msg	= hv_compose_msi_msg,
Olaf Hering e9d267
 	.irq_set_affinity	= irq_chip_set_affinity_parent,
Olaf Hering e9d267
+#ifdef CONFIG_X86
Olaf Hering e9d267
 	.irq_ack		= irq_chip_ack_parent,
Olaf Hering e9d267
+#elif defined(CONFIG_ARM64)
Olaf Hering e9d267
+	.irq_eoi		= irq_chip_eoi_parent,
Olaf Hering e9d267
+#endif
Olaf Hering e9d267
 	.irq_mask		= hv_irq_mask,
Olaf Hering e9d267
 	.irq_unmask		= hv_irq_unmask,
Olaf Hering e9d267
 };