[PATCH] irqchip: stm32: add a second level init to request hwspinlock
From: Fabien Dessenne
Date: Thu Mar 07 2019 - 11:24:11 EST
Requesting hwspinlock, at the first time it is used, is not correct:
indeed, at that moment we are under raw_spin_lock_irqsave() context and
hwspin_lock_request_specific() may sleep ("BUG: sleeping function called
from invalid context").
Requesting hwspinlock during the init (stm32*_exti_of_init()) is also
not possible (the hwspinlock framework is not ready at that stage of the
kernel init).
As a consequence, add a second level init (probed with arch_initcall)
where we can safely request hwspinlock.
Signed-off-by: Fabien Dessenne <fabien.dessenne@xxxxxx>
---
drivers/irqchip/irq-stm32-exti.c | 160 +++++++++++++++++++++++++--------------
1 file changed, 102 insertions(+), 58 deletions(-)
diff --git a/drivers/irqchip/irq-stm32-exti.c b/drivers/irqchip/irq-stm32-exti.c
index 6edfd4b..2a5cc00 100644
--- a/drivers/irqchip/irq-stm32-exti.c
+++ b/drivers/irqchip/irq-stm32-exti.c
@@ -14,8 +14,10 @@
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
+#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
+#include <linux/of_platform.h>
#include <linux/syscore_ops.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
@@ -37,12 +39,6 @@ struct stm32_exti_bank {
#define UNDEF_REG ~0
-enum stm32_exti_hwspinlock {
- HWSPINLOCK_UNKNOWN,
- HWSPINLOCK_NONE,
- HWSPINLOCK_READY,
-};
-
struct stm32_desc_irq {
u32 exti;
u32 irq_parent;
@@ -69,8 +65,6 @@ struct stm32_exti_host_data {
void __iomem *base;
struct stm32_exti_chip_data *chips_data;
const struct stm32_exti_drv_data *drv_data;
- struct device_node *node;
- enum stm32_exti_hwspinlock hwlock_state;
struct hwspinlock *hwlock;
};
@@ -285,49 +279,27 @@ static int stm32_exti_set_type(struct irq_data *d,
static int stm32_exti_hwspin_lock(struct stm32_exti_chip_data *chip_data)
{
- struct stm32_exti_host_data *host_data = chip_data->host_data;
- struct hwspinlock *hwlock;
- int id, ret = 0, timeout = 0;
-
- /* first time, check for hwspinlock availability */
- if (unlikely(host_data->hwlock_state == HWSPINLOCK_UNKNOWN)) {
- id = of_hwspin_lock_get_id(host_data->node, 0);
- if (id >= 0) {
- hwlock = hwspin_lock_request_specific(id);
- if (hwlock) {
- /* found valid hwspinlock */
- host_data->hwlock_state = HWSPINLOCK_READY;
- host_data->hwlock = hwlock;
- pr_debug("%s hwspinlock = %d\n", __func__, id);
- } else {
- host_data->hwlock_state = HWSPINLOCK_NONE;
- }
- } else if (id != -EPROBE_DEFER) {
- host_data->hwlock_state = HWSPINLOCK_NONE;
- } else {
- /* hwspinlock driver shall be ready at that stage */
- ret = -EPROBE_DEFER;
- }
- }
+ int ret, timeout = 0;
- if (likely(host_data->hwlock_state == HWSPINLOCK_READY)) {
- /*
- * Use the x_raw API since we are under spin_lock protection.
- * Do not use the x_timeout API because we are under irq_disable
- * mode (see __setup_irq())
- */
- do {
- ret = hwspin_trylock_raw(host_data->hwlock);
- if (!ret)
- return 0;
-
- udelay(HWSPNLCK_RETRY_DELAY);
- timeout += HWSPNLCK_RETRY_DELAY;
- } while (timeout < HWSPNLCK_TIMEOUT);
-
- if (ret == -EBUSY)
- ret = -ETIMEDOUT;
- }
+ if (!chip_data->host_data->hwlock)
+ return 0;
+
+ /*
+ * Use the x_raw API since we are under spin_lock protection.
+ * Do not use the x_timeout API because we are under irq_disable
+ * mode (see __setup_irq())
+ */
+ do {
+ ret = hwspin_trylock_raw(chip_data->host_data->hwlock);
+ if (!ret)
+ return 0;
+
+ udelay(HWSPNLCK_RETRY_DELAY);
+ timeout += HWSPNLCK_RETRY_DELAY;
+ } while (timeout < HWSPNLCK_TIMEOUT);
+
+ if (ret == -EBUSY)
+ ret = -ETIMEDOUT;
if (ret)
pr_err("%s can't get hwspinlock (%d)\n", __func__, ret);
@@ -337,7 +309,7 @@ static int stm32_exti_hwspin_lock(struct stm32_exti_chip_data *chip_data)
static void stm32_exti_hwspin_unlock(struct stm32_exti_chip_data *chip_data)
{
- if (likely(chip_data->host_data->hwlock_state == HWSPINLOCK_READY))
+ if (chip_data->host_data->hwlock)
hwspin_unlock_raw(chip_data->host_data->hwlock);
}
@@ -683,8 +655,6 @@ stm32_exti_host_data *stm32_exti_host_init(const struct stm32_exti_drv_data *dd,
return NULL;
host_data->drv_data = dd;
- host_data->node = node;
- host_data->hwlock_state = HWSPINLOCK_UNKNOWN;
host_data->chips_data = kcalloc(dd->bank_nr,
sizeof(struct stm32_exti_chip_data),
GFP_KERNEL);
@@ -711,7 +681,8 @@ stm32_exti_host_data *stm32_exti_host_init(const struct stm32_exti_drv_data *dd,
static struct
stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data,
- u32 bank_idx)
+ u32 bank_idx,
+ struct device_node *node)
{
const struct stm32_exti_bank *stm32_bank;
struct stm32_exti_chip_data *chip_data;
@@ -741,7 +712,7 @@ stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data,
if (stm32_bank->fpr_ofst != UNDEF_REG)
writel_relaxed(~0UL, base + stm32_bank->fpr_ofst);
- pr_info("%pOF: bank%d\n", h_data->node, bank_idx);
+ pr_info("%pOF: bank%d\n", node, bank_idx);
return chip_data;
}
@@ -781,7 +752,7 @@ static int __init stm32_exti_init(const struct stm32_exti_drv_data *drv_data,
struct stm32_exti_chip_data *chip_data;
stm32_bank = drv_data->exti_banks[i];
- chip_data = stm32_exti_chip_init(host_data, i);
+ chip_data = stm32_exti_chip_init(host_data, i, node);
gc = irq_get_domain_generic_chip(domain, i * IRQS_PER_BANK);
@@ -844,7 +815,7 @@ __init stm32_exti_hierarchy_init(const struct stm32_exti_drv_data *drv_data,
return -ENOMEM;
for (i = 0; i < drv_data->bank_nr; i++)
- stm32_exti_chip_init(host_data, i);
+ stm32_exti_chip_init(host_data, i, node);
domain = irq_domain_add_hierarchy(parent_domain, 0,
drv_data->bank_nr * IRQS_PER_BANK,
@@ -868,6 +839,71 @@ __init stm32_exti_hierarchy_init(const struct stm32_exti_drv_data *drv_data,
return ret;
}
+/* Note : stm32_exti_probe() is called after stm32*_exti_of_init() */
+static int stm32_exti_probe(struct platform_device *pdev)
+{
+ int id, ret = 0;
+
+ id = of_hwspin_lock_get_id(pdev->dev.of_node, 0);
+
+ if (id == -EPROBE_DEFER)
+ /* hwspinlock framework not ready */
+ return -EPROBE_DEFER;
+
+ if (id == -ENOENT)
+ /* no hwspinlock defined (not an error, it is optional) */
+ return 0;
+
+ if (id >= 0) {
+ stm32_host_data->hwlock = hwspin_lock_request_specific(id);
+ if (!stm32_host_data->hwlock) {
+ dev_err(&pdev->dev, "Failed to request hwspinlock\n");
+ ret = -EINVAL;
+ }
+ } else {
+ dev_err(&pdev->dev, "Failed to get hwspinlock\n");
+ ret = id;
+ }
+
+ return ret;
+}
+
+static int stm32_exti_remove(struct platform_device *pdev)
+{
+ if (stm32_host_data->hwlock)
+ return hwspin_lock_free(stm32_host_data->hwlock);
+
+ return 0;
+}
+
+static const struct of_device_id stm32_exti_ids[] = {
+ { .compatible = "st,stm32mp1-exti", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, stm32_exti_ids);
+
+static struct platform_driver stm32_exti_driver = {
+ .probe = stm32_exti_probe,
+ .remove = stm32_exti_remove,
+ .driver = {
+ .name = "stm32_exti",
+ .of_match_table = stm32_exti_ids,
+ },
+};
+
+static int __init stm32_exti_arch_init(void)
+{
+ return platform_driver_register(&stm32_exti_driver);
+}
+
+static void __exit stm32_exti_arch_exit(void)
+{
+ return platform_driver_unregister(&stm32_exti_driver);
+}
+
+arch_initcall(stm32_exti_arch_init);
+module_exit(stm32_exti_arch_exit);
+
static int __init stm32f4_exti_of_init(struct device_node *np,
struct device_node *parent)
{
@@ -887,7 +923,15 @@ IRQCHIP_DECLARE(stm32h7_exti, "st,stm32h7-exti", stm32h7_exti_of_init);
static int __init stm32mp1_exti_of_init(struct device_node *np,
struct device_node *parent)
{
- return stm32_exti_hierarchy_init(&stm32mp1_drv_data, np, parent);
+ int ret;
+
+ ret = stm32_exti_hierarchy_init(&stm32mp1_drv_data, np, parent);
+
+ /* Clear the OF_POPULATED flag so that stm32_exti_probe can be called */
+ if (!ret)
+ of_node_clear_flag(np, OF_POPULATED);
+
+ return ret;
}
IRQCHIP_DECLARE(stm32mp1_exti, "st,stm32mp1-exti", stm32mp1_exti_of_init);
--
2.7.4