Commit 1b00adce authored by Vladimir Oltean's avatar Vladimir Oltean Committed by Marc Zyngier

irqchip/ls-extirq: Fix invalid wait context by avoiding to use regmap

The irqchip->irq_set_type method is called by __irq_set_trigger() under
the desc->lock raw spinlock.

The ls-extirq implementation, ls_extirq_irq_set_type(), uses an MMIO
regmap created by of_syscon_register(), which uses plain spinlocks
(the kind that are sleepable on RT).

Therefore, this is an invalid locking scheme for which we get a kernel
splat stating just that ("[ BUG: Invalid wait context ]"), because the
context in which the plain spinlock may sleep is atomic due to the raw
spinlock. We need to go raw spinlocks all the way.

Make this driver ioremap its INTPCR register on its own, and stop
relying on syscon to provide a regmap.

Fixes: 0dcd9f87 ("irqchip: Add support for Layerscape external interrupt lines")
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
[maz: trimmed down commit log]
Signed-off-by: default avatarMarc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20220728144254.175385-1-vladimir.oltean@nxp.com
parent 732d69c8
...@@ -6,8 +6,7 @@ ...@@ -6,8 +6,7 @@
#include <linux/irqchip.h> #include <linux/irqchip.h>
#include <linux/irqdomain.h> #include <linux/irqdomain.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/mfd/syscon.h> #include <linux/of_address.h>
#include <linux/regmap.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <dt-bindings/interrupt-controller/arm-gic.h> #include <dt-bindings/interrupt-controller/arm-gic.h>
...@@ -16,13 +15,41 @@ ...@@ -16,13 +15,41 @@
#define LS1021A_SCFGREVCR 0x200 #define LS1021A_SCFGREVCR 0x200
struct ls_extirq_data { struct ls_extirq_data {
struct regmap *syscon; void __iomem *intpcr;
u32 intpcr; raw_spinlock_t lock;
bool big_endian;
bool is_ls1021a_or_ls1043a; bool is_ls1021a_or_ls1043a;
u32 nirq; u32 nirq;
struct irq_fwspec map[MAXIRQ]; struct irq_fwspec map[MAXIRQ];
}; };
static void ls_extirq_intpcr_rmw(struct ls_extirq_data *priv, u32 mask,
u32 value)
{
u32 intpcr;
/*
* Serialize concurrent calls to ls_extirq_set_type() from multiple
* IRQ descriptors, making sure the read-modify-write is atomic.
*/
raw_spin_lock(&priv->lock);
if (priv->big_endian)
intpcr = ioread32be(priv->intpcr);
else
intpcr = ioread32(priv->intpcr);
intpcr &= ~mask;
intpcr |= value;
if (priv->big_endian)
iowrite32be(intpcr, priv->intpcr);
else
iowrite32(intpcr, priv->intpcr);
raw_spin_unlock(&priv->lock);
}
static int static int
ls_extirq_set_type(struct irq_data *data, unsigned int type) ls_extirq_set_type(struct irq_data *data, unsigned int type)
{ {
...@@ -51,7 +78,8 @@ ls_extirq_set_type(struct irq_data *data, unsigned int type) ...@@ -51,7 +78,8 @@ ls_extirq_set_type(struct irq_data *data, unsigned int type)
default: default:
return -EINVAL; return -EINVAL;
} }
regmap_update_bits(priv->syscon, priv->intpcr, mask, value);
ls_extirq_intpcr_rmw(priv, mask, value);
return irq_chip_set_type_parent(data, type); return irq_chip_set_type_parent(data, type);
} }
...@@ -143,7 +171,6 @@ ls_extirq_parse_map(struct ls_extirq_data *priv, struct device_node *node) ...@@ -143,7 +171,6 @@ ls_extirq_parse_map(struct ls_extirq_data *priv, struct device_node *node)
static int __init static int __init
ls_extirq_of_init(struct device_node *node, struct device_node *parent) ls_extirq_of_init(struct device_node *node, struct device_node *parent)
{ {
struct irq_domain *domain, *parent_domain; struct irq_domain *domain, *parent_domain;
struct ls_extirq_data *priv; struct ls_extirq_data *priv;
int ret; int ret;
...@@ -151,40 +178,52 @@ ls_extirq_of_init(struct device_node *node, struct device_node *parent) ...@@ -151,40 +178,52 @@ ls_extirq_of_init(struct device_node *node, struct device_node *parent)
parent_domain = irq_find_host(parent); parent_domain = irq_find_host(parent);
if (!parent_domain) { if (!parent_domain) {
pr_err("Cannot find parent domain\n"); pr_err("Cannot find parent domain\n");
return -ENODEV; ret = -ENODEV;
goto err_irq_find_host;
} }
priv = kzalloc(sizeof(*priv), GFP_KERNEL); priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) if (!priv) {
return -ENOMEM; ret = -ENOMEM;
goto err_alloc_priv;
priv->syscon = syscon_node_to_regmap(node->parent);
if (IS_ERR(priv->syscon)) {
ret = PTR_ERR(priv->syscon);
pr_err("Failed to lookup parent regmap\n");
goto out;
} }
ret = of_property_read_u32(node, "reg", &priv->intpcr);
if (ret) { /*
pr_err("Missing INTPCR offset value\n"); * All extirq OF nodes are under a scfg/syscon node with
goto out; * the 'ranges' property
*/
priv->intpcr = of_iomap(node, 0);
if (!priv->intpcr) {
pr_err("Cannot ioremap OF node %pOF\n", node);
ret = -ENOMEM;
goto err_iomap;
} }
ret = ls_extirq_parse_map(priv, node); ret = ls_extirq_parse_map(priv, node);
if (ret) if (ret)
goto out; goto err_parse_map;
priv->big_endian = of_device_is_big_endian(parent);
priv->is_ls1021a_or_ls1043a = of_device_is_compatible(node, "fsl,ls1021a-extirq") || priv->is_ls1021a_or_ls1043a = of_device_is_compatible(node, "fsl,ls1021a-extirq") ||
of_device_is_compatible(node, "fsl,ls1043a-extirq"); of_device_is_compatible(node, "fsl,ls1043a-extirq");
raw_spin_lock_init(&priv->lock);
domain = irq_domain_add_hierarchy(parent_domain, 0, priv->nirq, node, domain = irq_domain_add_hierarchy(parent_domain, 0, priv->nirq, node,
&extirq_domain_ops, priv); &extirq_domain_ops, priv);
if (!domain) if (!domain) {
ret = -ENOMEM; ret = -ENOMEM;
goto err_add_hierarchy;
}
out: return 0;
if (ret)
kfree(priv); err_add_hierarchy:
err_parse_map:
iounmap(priv->intpcr);
err_iomap:
kfree(priv);
err_alloc_priv:
err_irq_find_host:
return ret; return ret;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment