254 lines · 6.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * ICS backend for OPAL managed interrupts. * * Copyright 2011 IBM Corp. */ //#define DEBUG #include <linux/types.h>#include <linux/kernel.h>#include <linux/irq.h>#include <linux/smp.h>#include <linux/interrupt.h>#include <linux/init.h>#include <linux/cpu.h>#include <linux/of.h>#include <linux/of_address.h>#include <linux/spinlock.h>#include <linux/msi.h>#include <linux/list.h> #include <asm/smp.h>#include <asm/machdep.h>#include <asm/irq.h>#include <asm/errno.h>#include <asm/xics.h>#include <asm/opal.h>#include <asm/firmware.h> struct ics_native {	struct ics		ics;	struct device_node	*node;	void __iomem    	*base;	u32             	ibase;	u32             	icount;};#define to_ics_native(_ics)     container_of(_ics, struct ics_native, ics) static void __iomem *ics_native_xive(struct ics_native *in, unsigned int vec){	return in->base + 0x800 + ((vec - in->ibase) << 2);} static void ics_native_unmask_irq(struct irq_data *d){	unsigned int vec = (unsigned int)irqd_to_hwirq(d);	struct ics *ics = irq_data_get_irq_chip_data(d);	struct ics_native *in = to_ics_native(ics);	unsigned int server; 	pr_devel("ics-native: unmask virq %d [hw 0x%x]\n", d->irq, vec); 	if (vec < in->ibase || vec >= (in->ibase + in->icount))		return; 	server = xics_get_irq_server(d->irq, irq_data_get_affinity_mask(d), 0);	out_be32(ics_native_xive(in, vec), (server << 8) | DEFAULT_PRIORITY);} static unsigned int ics_native_startup(struct irq_data *d){#ifdef CONFIG_PCI_MSI	/*	 * The generic MSI code returns with the interrupt disabled on the	 * card, using the MSI mask bits. Firmware doesn't appear to unmask	 * at that level, so we do it here by hand.	 */	if (irq_data_get_msi_desc(d))		pci_msi_unmask_irq(d);#endif 	/* unmask it */	ics_native_unmask_irq(d);	return 0;} static void ics_native_do_mask(struct ics_native *in, unsigned int vec){	out_be32(ics_native_xive(in, vec), 0xff);} static void ics_native_mask_irq(struct irq_data *d){	unsigned int vec = (unsigned int)irqd_to_hwirq(d);	struct ics *ics = irq_data_get_irq_chip_data(d);	struct ics_native *in = to_ics_native(ics); 	pr_devel("ics-native: mask virq %d [hw 0x%x]\n", d->irq, vec); 	if (vec < in->ibase || vec >= (in->ibase + in->icount))		return;	ics_native_do_mask(in, vec);} static int ics_native_set_affinity(struct irq_data *d,				   const struct cpumask *cpumask,				   bool force){	unsigned int vec = (unsigned int)irqd_to_hwirq(d);	struct ics *ics = irq_data_get_irq_chip_data(d);	struct ics_native *in = to_ics_native(ics);	int server;	u32 xive; 	if (vec < in->ibase || vec >= (in->ibase + in->icount))		return -EINVAL; 	server = xics_get_irq_server(d->irq, cpumask, 1);	if (server == -1) {		pr_warn("%s: No online cpus in the mask %*pb for irq %d\n",			__func__, cpumask_pr_args(cpumask), d->irq);		return -1;	} 	xive = in_be32(ics_native_xive(in, vec));	xive = (xive & 0xff) | (server << 8);	out_be32(ics_native_xive(in, vec), xive); 	return IRQ_SET_MASK_OK;} static struct irq_chip ics_native_irq_chip = {	.name = "ICS",	.irq_startup		= ics_native_startup,	.irq_mask		= ics_native_mask_irq,	.irq_unmask		= ics_native_unmask_irq,	.irq_eoi		= NULL, /* Patched at init time */	.irq_set_affinity 	= ics_native_set_affinity,	.irq_set_type		= xics_set_irq_type,	.irq_retrigger		= xics_retrigger,}; static int ics_native_check(struct ics *ics, unsigned int hw_irq){	struct ics_native *in = to_ics_native(ics); 	pr_devel("%s: hw_irq=0x%x\n", __func__, hw_irq); 	if (hw_irq < in->ibase || hw_irq >= (in->ibase + in->icount))		return -EINVAL; 	return 0;} static void ics_native_mask_unknown(struct ics *ics, unsigned long vec){	struct ics_native *in = to_ics_native(ics); 	if (vec < in->ibase || vec >= (in->ibase + in->icount))		return; 	ics_native_do_mask(in, vec);} static long ics_native_get_server(struct ics *ics, unsigned long vec){	struct ics_native *in = to_ics_native(ics);	u32 xive; 	if (vec < in->ibase || vec >= (in->ibase + in->icount))		return -EINVAL; 	xive = in_be32(ics_native_xive(in, vec));	return (xive >> 8) & 0xfff;} static int ics_native_host_match(struct ics *ics, struct device_node *node){	struct ics_native *in = to_ics_native(ics); 	return in->node == node;} static struct ics ics_native_template = {	.check		= ics_native_check,	.mask_unknown	= ics_native_mask_unknown,	.get_server	= ics_native_get_server,	.host_match	= ics_native_host_match,	.chip = &ics_native_irq_chip,}; static int __init ics_native_add_one(struct device_node *np){	struct ics_native *ics;	u32 ranges[2];	int rc, count; 	ics = kzalloc_obj(struct ics_native);	if (!ics)		return -ENOMEM;	ics->node = of_node_get(np);	memcpy(&ics->ics, &ics_native_template, sizeof(struct ics)); 	ics->base = of_iomap(np, 0);	if (!ics->base) {		pr_err("Failed to map %pOFP\n", np);		rc = -ENOMEM;		goto fail;	} 	count = of_property_count_u32_elems(np, "interrupt-ranges");	if (count < 2 || count & 1) {		pr_err("Failed to read interrupt-ranges of %pOFP\n", np);		rc = -EINVAL;		goto fail;	}	if (count > 2) {		pr_warn("ICS %pOFP has %d ranges, only one supported\n",			np, count >> 1);	}	rc = of_property_read_u32_array(np, "interrupt-ranges",					ranges, 2);	if (rc) {		pr_err("Failed to read interrupt-ranges of %pOFP\n", np);		goto fail;	}	ics->ibase = ranges[0];	ics->icount = ranges[1]; 	pr_info("ICS native initialized for sources %d..%d\n",		ics->ibase, ics->ibase + ics->icount - 1); 	/* Register ourselves */	xics_register_ics(&ics->ics); 	return 0;fail:	of_node_put(ics->node);	kfree(ics);	return rc;} int __init ics_native_init(void){	struct device_node *ics;	bool found_one = false; 	/* We need to patch our irq chip's EOI to point to the	 * right ICP	 */	ics_native_irq_chip.irq_eoi = icp_ops->eoi; 	/* Find native ICS in the device-tree */	for_each_compatible_node(ics, NULL, "openpower,xics-sources") {		if (ics_native_add_one(ics) == 0)			found_one = true;	} 	if (found_one)		pr_info("ICS native backend registered\n"); 	return found_one ? 0 : -ENODEV;}