Commit 57d72196 authored by Lorenzo Pieralisi's avatar Lorenzo Pieralisi Committed by Marc Zyngier
Browse files

irqchip/gic-v5: Add GICv5 ITS support



The GICv5 architecture implements Interrupt Translation Service
(ITS) components in order to translate events coming from peripherals
into interrupt events delivered to the connected IRSes.

Events (ie MSI memory writes to ITS translate frame), are translated
by the ITS using tables kept in memory.

ITS translation tables for peripherals is kept in memory storage
(device table [DT] and Interrupt Translation Table [ITT]) that
is allocated by the driver on boot.

Both tables can be 1- or 2-level; the structure is chosen by the
driver after probing the ITS HW parameters and checking the
allowed table splits and supported {device/event}_IDbits.

DT table entries are allocated on demand (ie when a device is
probed); the DT table is sized using the number of supported
deviceID bits in that that's a system design decision (ie the
number of deviceID bits implemented should reflect the number
of devices expected in a system) therefore it makes sense to
allocate a DT table that can cater for the maximum number of
devices.

DT and ITT tables are allocated using the kmalloc interface;
the allocation size may be smaller than a page or larger,
and must provide contiguous memory pages.

LPIs INTIDs backing the device events are allocated one-by-one
and only upon Linux IRQ allocation; this to avoid preallocating
a large number of LPIs to cover the HW device MSI vector
size whereas few MSI entries are actually enabled by a device.

ITS cacheability/shareability attributes are programmed
according to the provided firmware ITS description.

The GICv5 partially reuses the GICv3 ITS MSI parent infrastructure
and adds functions required to retrieve the ITS translate frame
addresses out of msi-map and msi-parent properties to implement
the GICv5 ITS MSI parent callbacks.

Co-developed-by: default avatarSascha Bischoff <sascha.bischoff@arm.com>
Signed-off-by: default avatarSascha Bischoff <sascha.bischoff@arm.com>
Co-developed-by: default avatarTimothy Hayes <timothy.hayes@arm.com>
Signed-off-by: default avatarTimothy Hayes <timothy.hayes@arm.com>
Signed-off-by: default avatarLorenzo Pieralisi <lpieralisi@kernel.org>
Reviewed-by: default avatarMarc Zyngier <maz@kernel.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20250703-gicv5-host-v7-28-12e71f1b3528@kernel.org


Signed-off-by: default avatarMarc Zyngier <maz@kernel.org>
parent 8b65db1e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1970,6 +1970,7 @@ M: Marc Zyngier <maz@kernel.org>
L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S:	Maintained
F:	Documentation/devicetree/bindings/interrupt-controller/arm,gic-v5*.yaml
F:	drivers/irqchip/irq-gic-its-msi-parent.[ch]
F:	drivers/irqchip/irq-gic-v5*.[ch]
F:	include/linux/irqchip/arm-gic-v5.h
+3 −0
Original line number Diff line number Diff line
@@ -62,6 +62,9 @@ config ARM_GIC_V5
	bool
	select IRQ_DOMAIN_HIERARCHY
	select GENERIC_IRQ_EFFECTIVE_AFF_MASK
	select GENERIC_MSI_IRQ
	select IRQ_MSI_LIB
	select ARM_GIC_ITS_PARENT

config ARM_NVIC
	bool
+1 −1
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ obj-$(CONFIG_ARM_GIC_ITS_PARENT) += irq-gic-its-msi-parent.o
obj-$(CONFIG_ARM_GIC_V3_ITS)		+= irq-gic-v3-its.o irq-gic-v4.o
obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC)	+= irq-gic-v3-its-fsl-mc-msi.o
obj-$(CONFIG_PARTITION_PERCPU)		+= irq-partition-percpu.o
obj-$(CONFIG_ARM_GIC_V5)		+= irq-gic-v5.o irq-gic-v5-irs.o
obj-$(CONFIG_ARM_GIC_V5)		+= irq-gic-v5.o irq-gic-v5-irs.o irq-gic-v5-its.o
obj-$(CONFIG_HISILICON_IRQ_MBIGEN)	+= irq-mbigen.o
obj-$(CONFIG_ARM_NVIC)			+= irq-nvic.o
obj-$(CONFIG_ARM_VIC)			+= irq-vic.o
+166 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
// Copyright (C) 2022 Intel

#include <linux/acpi_iort.h>
#include <linux/of_address.h>
#include <linux/pci.h>

#include "irq-gic-its-msi-parent.h"
@@ -18,6 +19,23 @@
				 MSI_FLAG_PCI_MSIX      |	\
				 MSI_FLAG_MULTI_PCI_MSI)

static int its_translate_frame_address(struct device_node *msi_node, phys_addr_t *pa)
{
	struct resource res;
	int ret;

	ret = of_property_match_string(msi_node, "reg-names", "ns-translate");
	if (ret < 0)
		return ret;

	ret = of_address_to_resource(msi_node, ret, &res);
	if (ret)
		return ret;

	*pa = res.start;
	return 0;
}

#ifdef CONFIG_PCI_MSI
static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data)
{
@@ -82,8 +100,46 @@ static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
	msi_info = msi_get_domain_info(domain->parent);
	return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
}

static int its_v5_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
				  int nvec, msi_alloc_info_t *info)
{
	struct device_node *msi_node = NULL;
	struct msi_domain_info *msi_info;
	struct pci_dev *pdev;
	phys_addr_t pa;
	u32 rid;
	int ret;

	if (!dev_is_pci(dev))
		return -EINVAL;

	pdev = to_pci_dev(dev);

	rid = pci_msi_map_rid_ctlr_node(pdev, &msi_node);
	if (!msi_node)
		return -ENODEV;

	ret = its_translate_frame_address(msi_node, &pa);
	if (ret)
		return -ENODEV;

	of_node_put(msi_node);

	/* ITS specific DeviceID */
	info->scratchpad[0].ul = rid;
	/* ITS translate frame physical address */
	info->scratchpad[1].ul = pa;

	/* Always allocate power of two vectors */
	nvec = roundup_pow_of_two(nvec);

	msi_info = msi_get_domain_info(domain->parent);
	return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
}
#else /* CONFIG_PCI_MSI */
#define its_pci_msi_prepare	NULL
#define its_v5_pci_msi_prepare	NULL
#endif /* !CONFIG_PCI_MSI */

static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev,
@@ -118,6 +174,53 @@ static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev,
	return ret;
}

static int of_v5_pmsi_get_msi_info(struct irq_domain *domain, struct device *dev,
				   u32 *dev_id, phys_addr_t *pa)
{
	int ret, index = 0;
	/*
	 * Retrieve the DeviceID and the ITS translate frame node pointer
	 * out of the msi-parent property.
	 */
	do {
		struct of_phandle_args args;

		ret = of_parse_phandle_with_args(dev->of_node,
						 "msi-parent", "#msi-cells",
						 index, &args);
		if (ret)
			break;
		/*
		 * The IRQ domain fwnode is the msi controller parent
		 * in GICv5 (where the msi controller nodes are the
		 * ITS translate frames).
		 */
		if (args.np->parent == irq_domain_get_of_node(domain)) {
			if (WARN_ON(args.args_count != 1))
				return -EINVAL;
			*dev_id = args.args[0];

			ret = its_translate_frame_address(args.np, pa);
			if (ret)
				return -ENODEV;
			break;
		}
		index++;
	} while (!ret);

	if (ret) {
		struct device_node *np = NULL;

		ret = of_map_id(dev->of_node, dev->id, "msi-map", "msi-map-mask", &np, dev_id);
		if (np) {
			ret = its_translate_frame_address(np, pa);
			of_node_put(np);
		}
	}

	return ret;
}

int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id)
{
	return -1;
@@ -148,6 +251,33 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev,
					  dev, nvec, info);
}

static int its_v5_pmsi_prepare(struct irq_domain *domain, struct device *dev,
			       int nvec, msi_alloc_info_t *info)
{
	struct msi_domain_info *msi_info;
	phys_addr_t pa;
	u32 dev_id;
	int ret;

	if (!dev->of_node)
		return -ENODEV;

	ret = of_v5_pmsi_get_msi_info(domain->parent, dev, &dev_id, &pa);
	if (ret)
		return ret;

	/* ITS specific DeviceID */
	info->scratchpad[0].ul = dev_id;
	/* ITS translate frame physical address */
	info->scratchpad[1].ul = pa;

	/* Allocate always as a power of 2 */
	nvec = roundup_pow_of_two(nvec);

	msi_info = msi_get_domain_info(domain->parent);
	return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
}

static void its_msi_teardown(struct irq_domain *domain, msi_alloc_info_t *info)
{
	struct msi_domain_info *msi_info;
@@ -199,6 +329,32 @@ static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
	return true;
}

static bool its_v5_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
				     struct irq_domain *real_parent, struct msi_domain_info *info)
{
	if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
		return false;

	switch (info->bus_token) {
	case DOMAIN_BUS_PCI_DEVICE_MSI:
	case DOMAIN_BUS_PCI_DEVICE_MSIX:
		info->ops->msi_prepare = its_v5_pci_msi_prepare;
		info->ops->msi_teardown = its_msi_teardown;
		break;
	case DOMAIN_BUS_DEVICE_MSI:
	case DOMAIN_BUS_WIRED_TO_MSI:
		info->ops->msi_prepare = its_v5_pmsi_prepare;
		info->ops->msi_teardown = its_msi_teardown;
		break;
	default:
		/* Confused. How did the lib return true? */
		WARN_ON_ONCE(1);
		return false;
	}

	return true;
}

const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
	.supported_flags	= ITS_MSI_FLAGS_SUPPORTED,
	.required_flags		= ITS_MSI_FLAGS_REQUIRED,
@@ -208,3 +364,13 @@ const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
	.prefix			= "ITS-",
	.init_dev_msi_info	= its_init_dev_msi_info,
};

const struct msi_parent_ops gic_v5_its_msi_parent_ops = {
	.supported_flags	= ITS_MSI_FLAGS_SUPPORTED,
	.required_flags		= ITS_MSI_FLAGS_REQUIRED,
	.chip_flags		= MSI_CHIP_FLAG_SET_EOI,
	.bus_select_token	= DOMAIN_BUS_NEXUS,
	.bus_select_mask	= MATCH_PCI_MSI | MATCH_PLATFORM_MSI,
	.prefix			= "ITS-v5-",
	.init_dev_msi_info	= its_v5_init_dev_msi_info,
};
+1 −0
Original line number Diff line number Diff line
@@ -7,5 +7,6 @@
#define _IRQ_GIC_ITS_MSI_PARENT_H

extern const struct msi_parent_ops gic_v3_its_msi_parent_ops;
extern const struct msi_parent_ops gic_v5_its_msi_parent_ops;

#endif /* _IRQ_GIC_ITS_MSI_PARENT_H */
Loading