[RFC 2/3] ARM alpine: introduce msi-x unit support

Tsahee Zidenberg tsahee at annapurnalabs.com
Tue Mar 24 04:39:47 PDT 2015


This patch adds support for the alpine-msix unit.
The unit captures memory writes and triggers interrupts in the GIC.

The address written encodes the level-sensitive SGI that will be raised.
The data written is ignored by the unit. There is no need to ack an interrupt
raised bu msi-x unit (other then acking the gic interrupt).

Signed-off-by: Barak Wasserstrom <barak at annapurnaLabs.com>
Signed-off-by: Tsahee Zidenberg <tsahee at annapurnalabs.com>
---
 .../devicetree/bindings/arm/al/al-msix.txt         |  35 +++
 arch/arm/mach-alpine/Kconfig                       |   2 +
 drivers/irqchip/Makefile                           |   1 +
 drivers/irqchip/irq-alpine-msi.c                   | 291 +++++++++++++++++++++
 4 files changed, 329 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/arm/al/al-msix.txt
 create mode 100644 drivers/irqchip/irq-alpine-msi.c

diff --git a/Documentation/devicetree/bindings/arm/al/al-msix.txt b/Documentation/devicetree/bindings/arm/al/al-msix.txt
new file mode 100644
index 0000000..502c4a1
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/al/al-msix.txt
@@ -0,0 +1,35 @@
+Annapurna Labs MSIX handler
+
+The alpine msi-x handler is an internal unit in the Alpine P.O.C able to capture
+memory writes and generate interrupts to the GIC accordingly.
+
+Required properties:
+
+- compatible : The value here should contain "al,alpine-msix".
+
+- interrupt-controller : Identifies the node as an interrupt controller
+
+- msi-controller : Identifies the node as an MSI controller.
+
+- reg : Offset and length of the MSIX address space
+
+- interrupts : first and last interrupts to be mannaged by msi-x unit.
+	These two interrupts are defined as GIC interrupts [1].
+	Interrupt type of both must be GIC_SPI.
+	Interrupt flags of both must be IRQ_TYPE_EDGE_RISING.
+
+The interrupt-parent of this unit must be an arm GIC.
+
+Example:
+
+msix: msix at 0xfbe10000 {
+	compatible = "al,alpine-msix";
+	reg = <0x0 0xfbe10000 0x0 0x10000>;
+	interrupts = <GIC_SPI 96 IRQ_TYPE_EDGE_RISING>,
+		     <GIC_SPI 159 IRQ_TYPE_EDGE_RISING>;
+	interrupt-controller;
+	msi-controller;
+	interrupt-parent = <&gic>;
+};
+
+[1] arm/gic.txt
diff --git a/arch/arm/mach-alpine/Kconfig b/arch/arm/mach-alpine/Kconfig
index 2c44b93..f8eb27a 100644
--- a/arch/arm/mach-alpine/Kconfig
+++ b/arch/arm/mach-alpine/Kconfig
@@ -8,5 +8,7 @@ config ARCH_ALPINE
 	select MFD_SYSCON
 	select PCI
 	select PCI_HOST_GENERIC
+	select PCI_MSI
+	select PCI_MSI_IRQ_DOMAIN
 	help
 	  This enables support for the Annapurna Labs Alpine V1 boards.
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 42965d2..ab213d7 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -1,5 +1,6 @@
 obj-$(CONFIG_IRQCHIP)			+= irqchip.o
 
+obj-$(CONFIG_ARCH_ALPINE)		+= irq-alpine-msi.o
 obj-$(CONFIG_ARCH_BCM2835)		+= irq-bcm2835.o
 obj-$(CONFIG_ARCH_EXYNOS)		+= exynos-combiner.o
 obj-$(CONFIG_ARCH_HIP04)		+= irq-hip04.o
diff --git a/drivers/irqchip/irq-alpine-msi.c b/drivers/irqchip/irq-alpine-msi.c
new file mode 100644
index 0000000..bbdeb25
--- /dev/null
+++ b/drivers/irqchip/irq-alpine-msi.c
@@ -0,0 +1,291 @@
+/*
+ * Annapurna Labs MSIX support services
+ *
+ * Copyright (C) 2015 Annapurna Labs Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * based on: irq-gic-v2m.c
+ */
+#define pr_fmt(fmt) "Alpine MSI-X: " fmt
+
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/msi.h>
+#include <asm/irq.h>
+#include <linux/irqchip/arm-gic.h>
+
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_pci.h>
+
+#include <asm-generic/msi.h>
+
+#include "irqchip.h"
+
+/*
+ * Generic PCI/MSI-X level: generic, minimal required changes
+ * hwirq is allocated by generic env, ignored
+ */
+static void mask_pci_msi_andparent(struct irq_data *d)
+{
+	pci_msi_mask_irq(d);
+	irq_chip_mask_parent(d);
+}
+
+static void unmask_pci_msi_andparent(struct irq_data *d)
+{
+	pci_msi_unmask_irq(d);
+	irq_chip_unmask_parent(d);
+}
+
+static struct irq_chip parent_msi_irq_chip = {
+	.name			= "PCI/MSI-X",
+	.irq_mask		= mask_pci_msi_andparent,
+	.irq_unmask		= unmask_pci_msi_andparent,
+	.irq_eoi		= irq_chip_eoi_parent,
+};
+
+static struct msi_domain_info parent_msi_domain_info = {
+	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS
+		   | MSI_FLAG_PCI_MSIX),
+	.chip	= &parent_msi_irq_chip,
+};
+
+/*
+ * Alpine MSI-X level:
+ * child of gic domain/irqchip, parent of generic msi domain/irqchip
+ * hwirq is # of sgi raised in gic
+ */
+struct alpine_msix_data {
+	struct msi_controller controller;
+	struct irq_domain *domain;
+
+	u32 addr_high;
+	u32 addr_low;
+
+	unsigned int irq_offset;
+	unsigned int num_irqs;
+	unsigned long *irq_bitmap;
+};
+
+static void alpine_msix_compose_msi_msg(struct irq_data *data,
+					struct msi_msg *msg)
+{
+	struct alpine_msix_data *mdata = irq_data_get_irq_chip_data(data);
+
+	msg->address_hi = mdata->addr_high;
+	msg->address_lo = mdata->addr_low + (data->hwirq << 3);
+	msg->data = 0;
+}
+
+static int alpine_msix_set_affinity(struct irq_data *irq_data,
+				    const struct cpumask *mask, bool force)
+{
+	int ret;
+
+	ret = irq_chip_set_affinity_parent(irq_data, mask, force);
+	if (ret == IRQ_SET_MASK_OK)
+		ret = IRQ_SET_MASK_OK_DONE;
+
+	return ret;
+}
+
+static struct irq_chip alpine_msix_irq_chip = {
+	.name			= "Alpine/MSI-X",
+	.irq_mask		= irq_chip_mask_parent,
+	.irq_unmask		= irq_chip_unmask_parent,
+	.irq_eoi		= irq_chip_eoi_parent,
+	.irq_set_affinity	= alpine_msix_set_affinity,
+	.irq_compose_msi_msg	= alpine_msix_compose_msi_msg,
+};
+
+static int alpine_irq_allocate(struct alpine_msix_data *mdata)
+{
+	int pos;
+
+again:
+	pos = find_first_zero_bit(mdata->irq_bitmap, mdata->num_irqs);
+	if (pos >= mdata->num_irqs)
+		return -ENOSPC;
+
+	if (test_and_set_bit(pos, mdata->irq_bitmap))
+		goto again;
+
+	return mdata->irq_offset + pos;
+}
+
+static void alpine_irq_free(struct alpine_msix_data *mdata, unsigned int sgi)
+{
+	clear_bit(sgi - mdata->irq_offset, mdata->irq_bitmap);
+}
+
+static int alpine_msix_gic_alloc(struct irq_domain *gic_domain,
+				 unsigned int virq, int sgi)
+{
+	struct of_phandle_args args;
+	struct irq_data *d;
+	int err;
+
+	args.np = gic_domain->of_node;
+	args.args_count = 3;
+	args.args[0] = 0;
+	args.args[1] = sgi;
+	args.args[2] = IRQ_TYPE_EDGE_RISING;
+	err = gic_domain->ops->alloc(gic_domain, virq, 1, &args);
+	if (err)
+		return err;
+	d = irq_domain_get_irq_data(gic_domain, virq);
+	d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING);
+	return 0;
+}
+
+static void alpine_msix_domain_free(struct irq_domain *domain,
+				    unsigned int first_virq,
+				    unsigned int nr_irqs)
+{
+	struct irq_data *d;
+	struct alpine_msix_data *mdata;
+	int virq;
+
+	for (virq = first_virq; virq < first_virq + nr_irqs; virq++) {
+		d = irq_domain_get_irq_data(domain, virq);
+		mdata = irq_data_get_irq_chip_data(d);
+		alpine_irq_free(mdata, d->hwirq);
+		irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+	}
+}
+
+static int alpine_msix_domain_alloc(struct irq_domain *domain,
+				    unsigned int first_virq,
+				    unsigned int nr_irqs, void *args)
+{
+	struct alpine_msix_data *mdata = domain->host_data;
+	int hwirq, err, virq;
+
+	err = 0;
+	for (virq = first_virq; virq < first_virq + nr_irqs; virq++) {
+		hwirq = alpine_irq_allocate(mdata);
+		if (hwirq < 0) {
+			err = hwirq;
+			break;
+		}
+		err = alpine_msix_gic_alloc(domain->parent, virq, hwirq);
+		if (err) {
+			alpine_irq_free(mdata, hwirq);
+			break;
+		}
+		irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+					      &alpine_msix_irq_chip, mdata);
+	}
+	if (err)
+		alpine_msix_domain_free(domain, first_virq, virq - first_virq);
+	return err;
+}
+
+static const struct irq_domain_ops alpine_msix_domain_ops = {
+	.alloc			= alpine_msix_domain_alloc,
+	.free			= alpine_msix_domain_free,
+};
+
+static int alpine_msix_init(struct device_node *node,
+			    struct device_node *parent)
+{
+	struct alpine_msix_data *mdata;
+	struct irq_domain *gic_domain;
+	struct device_node *gic_node;
+	int status = 0;
+	struct resource res;
+	struct of_phandle_args first, last;
+
+	status = of_address_to_resource(node, 0, &res);
+	if (status)
+		return status;
+
+	mdata = kzalloc(sizeof(struct alpine_msix_data), GFP_KERNEL);
+
+	if (!mdata)
+		return -ENOMEM;
+
+	mdata->addr_high = ((u64)res.start) >> 32;
+	mdata->addr_low = (res.start & 0xffffffff);
+
+	if (of_irq_parse_one(node, 0, &first) ||
+	    of_irq_parse_one(node, 1, &last)) {
+		pr_err("Failed to parse irq\n");
+		status = -EINVAL;
+		goto error;
+	}
+	if (first.args_count != 3 || first.args[0] != 0 ||
+	    first.args[2] != IRQ_TYPE_EDGE_RISING ||
+	    last.args_count != 3 || last.args[0] != 0 ||
+	    last.args[2] != IRQ_TYPE_EDGE_RISING ||
+	    first.args[1] >= last.args[1]) {
+		pr_err("Invalid interrupts\n");
+		status = -EINVAL;
+		goto error;
+	}
+	mdata->num_irqs = last.args[1] - first.args[1];
+	mdata->irq_offset = first.args[1];
+	pr_info("Allocating %u interrupts, starting with %u\n", mdata->num_irqs,
+		 mdata->irq_offset);
+
+	mdata->irq_bitmap = kcalloc(BITS_TO_LONGS(mdata->num_irqs),
+				    sizeof(long), GFP_KERNEL);
+	if (!mdata->irq_bitmap) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	gic_node = of_irq_find_parent(node);
+
+	if (!gic_node) {
+		pr_err("IRQ parent not found\n");
+		status = -EINVAL;
+		goto error;
+	}
+	gic_domain = irq_find_host(gic_node);
+
+	mdata->domain = irq_domain_add_tree(NULL, &alpine_msix_domain_ops,
+					    mdata);
+	if (!mdata->domain) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	mdata->domain->parent = gic_domain;
+
+	mdata->controller.of_node = node;
+	mdata->controller.domain = pci_msi_create_irq_domain(node,
+							     &parent_msi_domain_info,
+							     mdata->domain);
+	if (!mdata->controller.domain) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	status = of_pci_msi_chip_add(&mdata->controller);
+	if (status < 0)
+		goto error;
+
+	return 0;
+
+error:
+	if (mdata->controller.domain)
+		irq_domain_remove(mdata->controller.domain);
+	if (mdata->domain)
+		irq_domain_remove(mdata->domain);
+	kfree(mdata->irq_bitmap);
+	kfree(mdata);
+	return status;
+}
+IRQCHIP_DECLARE(alpine_msix, "al,alpine-msix", alpine_msix_init);
-- 
1.9.1




More information about the linux-arm-kernel mailing list