From 66bf8252cf0d79ba693183d6b858885a19825d10 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 12 Feb 2018 14:55:11 +0100 Subject: irqchip/renesas-intc-irqpin: Use wakeup_path i.s.o. explicit clock handling Since commit 705bc96c2c15313c ("irqchip: renesas-intc-irqpin: Add minimal runtime PM support"), when an IRQ is used for wakeup, the INTC block's module clock (if exists) is manually kept running during system suspend, to make sure the device stays active. However, this explicit clock handling is merely a workaround for a failure to properly communicate wakeup information to the device core. Instead, set the device's power.wakeup_path field, to indicate this device is part of the wakeup path. Depending on the PM Domain's active_wakeup configuration, the genpd core code will keep the device enabled (and the clock running) during system suspend when needed. This allows for the removal of all explicit clock handling code from the driver. Reviewed-by: Ulf Hansson Signed-off-by: Geert Uytterhoeven Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-renesas-intc-irqpin.c | 40 +++++++++++++------------------ 1 file changed, 16 insertions(+), 24 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-renesas-intc-irqpin.c b/drivers/irqchip/irq-renesas-intc-irqpin.c index cee59fe1321c..c6e6c9e9137a 100644 --- a/drivers/irqchip/irq-renesas-intc-irqpin.c +++ b/drivers/irqchip/irq-renesas-intc-irqpin.c @@ -17,7 +17,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include #include #include #include @@ -78,16 +77,14 @@ struct intc_irqpin_priv { struct platform_device *pdev; struct irq_chip irq_chip; struct irq_domain *irq_domain; - struct clk *clk; + atomic_t wakeup_path; unsigned shared_irqs:1; - unsigned needs_clk:1; u8 shared_irq_mask; }; struct intc_irqpin_config { unsigned int irlm_bit; unsigned needs_irlm:1; - unsigned needs_clk:1; }; static unsigned long intc_irqpin_read32(void __iomem *iomem) @@ -287,14 +284,10 @@ static int intc_irqpin_irq_set_wake(struct irq_data *d, unsigned int on) int hw_irq = irqd_to_hwirq(d); irq_set_irq_wake(p->irq[hw_irq].requested_irq, on); - - if (!p->clk) - return 0; - if (on) - clk_enable(p->clk); + atomic_inc(&p->wakeup_path); else - clk_disable(p->clk); + atomic_dec(&p->wakeup_path); return 0; } @@ -369,12 +362,10 @@ static const struct irq_domain_ops intc_irqpin_irq_domain_ops = { static const struct intc_irqpin_config intc_irqpin_irlm_r8a777x = { .irlm_bit = 23, /* ICR0.IRLM0 */ .needs_irlm = 1, - .needs_clk = 0, }; static const struct intc_irqpin_config intc_irqpin_rmobile = { .needs_irlm = 0, - .needs_clk = 1, }; static const struct of_device_id intc_irqpin_dt_ids[] = { @@ -426,18 +417,6 @@ static int intc_irqpin_probe(struct platform_device *pdev) platform_set_drvdata(pdev, p); config = of_device_get_match_data(dev); - if (config) - p->needs_clk = config->needs_clk; - - p->clk = devm_clk_get(dev, NULL); - if (IS_ERR(p->clk)) { - if (p->needs_clk) { - dev_err(dev, "unable to get clock\n"); - ret = PTR_ERR(p->clk); - goto err0; - } - p->clk = NULL; - } pm_runtime_enable(dev); pm_runtime_get_sync(dev); @@ -606,12 +585,25 @@ static int intc_irqpin_remove(struct platform_device *pdev) return 0; } +static int __maybe_unused intc_irqpin_suspend(struct device *dev) +{ + struct intc_irqpin_priv *p = dev_get_drvdata(dev); + + if (atomic_read(&p->wakeup_path)) + device_set_wakeup_path(dev); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(intc_irqpin_pm_ops, intc_irqpin_suspend, NULL); + static struct platform_driver intc_irqpin_device_driver = { .probe = intc_irqpin_probe, .remove = intc_irqpin_remove, .driver = { .name = "renesas_intc_irqpin", .of_match_table = intc_irqpin_dt_ids, + .pm = &intc_irqpin_pm_ops, } }; -- cgit From 734e036a9e1052da0b4b22bc320f6b30964e346d Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 12 Feb 2018 14:55:12 +0100 Subject: irqchip/renesas-irqc: Use wakeup_path i.s.o. explicit clock handling Since commit 6f46aedb9c85873b ("irqchip: renesas-irqc: Add wake-up support"), when an IRQ is used for wakeup, the INTC block's module clock is manually kept running during system suspend, to make sure the device stays active. However, this explicit clock handling is merely a workaround for a failure to properly communicate wakeup information to the device core. Instead, set the device's power.wakeup_path field, to indicate this device is part of the wakeup path. Depending on the PM Domain's active_wakeup configuration, the genpd core code will keep the device enabled (and the clock running) during system suspend when needed. This allows for the removal of all explicit clock handling code from the driver. Reviewed-by: Ulf Hansson Signed-off-by: Geert Uytterhoeven Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-renesas-irqc.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-renesas-irqc.c b/drivers/irqchip/irq-renesas-irqc.c index 52304b139aa4..a4f11124024d 100644 --- a/drivers/irqchip/irq-renesas-irqc.c +++ b/drivers/irqchip/irq-renesas-irqc.c @@ -17,7 +17,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include #include #include #include @@ -64,7 +63,7 @@ struct irqc_priv { struct platform_device *pdev; struct irq_chip_generic *gc; struct irq_domain *irq_domain; - struct clk *clk; + atomic_t wakeup_path; }; static struct irqc_priv *irq_data_to_priv(struct irq_data *data) @@ -111,14 +110,10 @@ static int irqc_irq_set_wake(struct irq_data *d, unsigned int on) int hw_irq = irqd_to_hwirq(d); irq_set_irq_wake(p->irq[hw_irq].requested_irq, on); - - if (!p->clk) - return 0; - if (on) - clk_enable(p->clk); + atomic_inc(&p->wakeup_path); else - clk_disable(p->clk); + atomic_dec(&p->wakeup_path); return 0; } @@ -159,12 +154,6 @@ static int irqc_probe(struct platform_device *pdev) p->pdev = pdev; platform_set_drvdata(pdev, p); - p->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(p->clk)) { - dev_warn(&pdev->dev, "unable to get clock\n"); - p->clk = NULL; - } - pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); @@ -276,6 +265,18 @@ static int irqc_remove(struct platform_device *pdev) return 0; } +static int __maybe_unused irqc_suspend(struct device *dev) +{ + struct irqc_priv *p = dev_get_drvdata(dev); + + if (atomic_read(&p->wakeup_path)) + device_set_wakeup_path(dev); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(irqc_pm_ops, irqc_suspend, NULL); + static const struct of_device_id irqc_dt_ids[] = { { .compatible = "renesas,irqc", }, {}, @@ -288,6 +289,7 @@ static struct platform_driver irqc_device_driver = { .driver = { .name = "renesas_irqc", .of_match_table = irqc_dt_ids, + .pm = &irqc_pm_ops, } }; -- cgit From f55c73aef8904ca41ff99618a3a9cf538eb88626 Mon Sep 17 00:00:00 2001 From: Archana Sathyakumar Date: Wed, 28 Feb 2018 10:27:29 -0700 Subject: irqchip/pdc: Add PDC interrupt controller for QCOM SoCs The Power Domain Controller (PDC) on QTI SoCs like SDM845 houses an interrupt controller along with other domain control functions to handle interrupt related functions like handle falling edge or active low which are not detected at the GIC and handle wakeup interrupts. The interrupt controller is on an always-on domain for the purpose of waking up the processor. Only a subset of the processor's interrupts are routed through the PDC to the GIC. The PDC powers on the processors' domain, when in low power mode and replays pending interrupts so the GIC may wake up the processor. Signed-off-by: Archana Sathyakumar Signed-off-by: Lina Iyer Signed-off-by: Marc Zyngier --- drivers/irqchip/Kconfig | 9 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/qcom-pdc.c | 311 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 drivers/irqchip/qcom-pdc.c (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index d913aec85109..030c7753d45b 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -351,4 +351,13 @@ config GOLDFISH_PIC Say yes here to enable Goldfish interrupt controller driver used for Goldfish based virtual platforms. +config QCOM_PDC + bool "QCOM PDC" + depends on ARCH_QCOM + select IRQ_DOMAIN + select IRQ_DOMAIN_HIERARCHY + help + Power Domain Controller driver to manage and configure wakeup + IRQs for Qualcomm Technologies Inc (QTI) mobile chips. + endmenu diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index d27e3e3619e0..c35ee5345a53 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -85,3 +85,4 @@ obj-$(CONFIG_IRQ_UNIPHIER_AIDET) += irq-uniphier-aidet.o obj-$(CONFIG_ARCH_SYNQUACER) += irq-sni-exiu.o obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o +obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c new file mode 100644 index 000000000000..b1b47a40a278 --- /dev/null +++ b/drivers/irqchip/qcom-pdc.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PDC_MAX_IRQS 126 + +#define CLEAR_INTR(reg, intr) (reg & ~(1 << intr)) +#define ENABLE_INTR(reg, intr) (reg | (1 << intr)) + +#define IRQ_ENABLE_BANK 0x10 +#define IRQ_i_CFG 0x110 + +struct pdc_pin_region { + u32 pin_base; + u32 parent_base; + u32 cnt; +}; + +static DEFINE_RAW_SPINLOCK(pdc_lock); +static void __iomem *pdc_base; +static struct pdc_pin_region *pdc_region; +static int pdc_region_cnt; + +static void pdc_reg_write(int reg, u32 i, u32 val) +{ + writel_relaxed(val, pdc_base + reg + i * sizeof(u32)); +} + +static u32 pdc_reg_read(int reg, u32 i) +{ + return readl_relaxed(pdc_base + reg + i * sizeof(u32)); +} + +static void pdc_enable_intr(struct irq_data *d, bool on) +{ + int pin_out = d->hwirq; + u32 index, mask; + u32 enable; + + index = pin_out / 32; + mask = pin_out % 32; + + raw_spin_lock(&pdc_lock); + enable = pdc_reg_read(IRQ_ENABLE_BANK, index); + enable = on ? ENABLE_INTR(enable, mask) : CLEAR_INTR(enable, mask); + pdc_reg_write(IRQ_ENABLE_BANK, index, enable); + raw_spin_unlock(&pdc_lock); +} + +static void qcom_pdc_gic_mask(struct irq_data *d) +{ + pdc_enable_intr(d, false); + irq_chip_mask_parent(d); +} + +static void qcom_pdc_gic_unmask(struct irq_data *d) +{ + pdc_enable_intr(d, true); + irq_chip_unmask_parent(d); +} + +/* + * GIC does not handle falling edge or active low. To allow falling edge and + * active low interrupts to be handled at GIC, PDC has an inverter that inverts + * falling edge into a rising edge and active low into an active high. + * For the inverter to work, the polarity bit in the IRQ_CONFIG register has to + * set as per the table below. + * Level sensitive active low LOW + * Rising edge sensitive NOT USED + * Falling edge sensitive LOW + * Dual Edge sensitive NOT USED + * Level sensitive active High HIGH + * Falling Edge sensitive NOT USED + * Rising edge sensitive HIGH + * Dual Edge sensitive HIGH + */ +enum pdc_irq_config_bits { + PDC_LEVEL_LOW = 0b000, + PDC_EDGE_FALLING = 0b010, + PDC_LEVEL_HIGH = 0b100, + PDC_EDGE_RISING = 0b110, + PDC_EDGE_DUAL = 0b111, +}; + +/** + * qcom_pdc_gic_set_type: Configure PDC for the interrupt + * + * @d: the interrupt data + * @type: the interrupt type + * + * If @type is edge triggered, forward that as Rising edge as PDC + * takes care of converting falling edge to rising edge signal + * If @type is level, then forward that as level high as PDC + * takes care of converting falling edge to rising edge signal + */ +static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) +{ + int pin_out = d->hwirq; + enum pdc_irq_config_bits pdc_type; + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + pdc_type = PDC_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + pdc_type = PDC_EDGE_FALLING; + type = IRQ_TYPE_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_BOTH: + pdc_type = PDC_EDGE_DUAL; + break; + case IRQ_TYPE_LEVEL_HIGH: + pdc_type = PDC_LEVEL_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + pdc_type = PDC_LEVEL_LOW; + type = IRQ_TYPE_LEVEL_HIGH; + break; + default: + WARN_ON(1); + return -EINVAL; + } + + pdc_reg_write(IRQ_i_CFG, pin_out, pdc_type); + + return irq_chip_set_type_parent(d, type); +} + +static struct irq_chip qcom_pdc_gic_chip = { + .name = "PDC", + .irq_eoi = irq_chip_eoi_parent, + .irq_mask = qcom_pdc_gic_mask, + .irq_unmask = qcom_pdc_gic_unmask, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_type = qcom_pdc_gic_set_type, + .flags = IRQCHIP_MASK_ON_SUSPEND | + IRQCHIP_SET_TYPE_MASKED | + IRQCHIP_SKIP_SET_WAKE, + .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, +}; + +static irq_hw_number_t get_parent_hwirq(int pin) +{ + int i; + struct pdc_pin_region *region; + + for (i = 0; i < pdc_region_cnt; i++) { + region = &pdc_region[i]; + if (pin >= region->pin_base && + pin < region->pin_base + region->cnt) + return (region->parent_base + pin - region->pin_base); + } + + WARN_ON(1); + return ~0UL; +} + +static int qcom_pdc_translate(struct irq_domain *d, struct irq_fwspec *fwspec, + unsigned long *hwirq, unsigned int *type) +{ + if (is_of_node(fwspec->fwnode)) { + if (fwspec->param_count != 2) + return -EINVAL; + + *hwirq = fwspec->param[0]; + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; + return 0; + } + + return -EINVAL; +} + +static int qcom_pdc_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *data) +{ + struct irq_fwspec *fwspec = data; + struct irq_fwspec parent_fwspec; + irq_hw_number_t hwirq, parent_hwirq; + unsigned int type; + int ret; + + ret = qcom_pdc_translate(domain, fwspec, &hwirq, &type); + if (ret) + return -EINVAL; + + parent_hwirq = get_parent_hwirq(hwirq); + if (parent_hwirq == ~0UL) + return -EINVAL; + + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &qcom_pdc_gic_chip, NULL); + if (ret) + return ret; + + if (type & IRQ_TYPE_EDGE_BOTH) + type = IRQ_TYPE_EDGE_RISING; + + if (type & IRQ_TYPE_LEVEL_MASK) + type = IRQ_TYPE_LEVEL_HIGH; + + parent_fwspec.fwnode = domain->parent->fwnode; + parent_fwspec.param_count = 3; + parent_fwspec.param[0] = 0; + parent_fwspec.param[1] = parent_hwirq; + parent_fwspec.param[2] = type; + + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, + &parent_fwspec); +} + +static const struct irq_domain_ops qcom_pdc_ops = { + .translate = qcom_pdc_translate, + .alloc = qcom_pdc_alloc, + .free = irq_domain_free_irqs_common, +}; + +static int pdc_setup_pin_mapping(struct device_node *np) +{ + int ret, n; + + n = of_property_count_elems_of_size(np, "qcom,pdc-ranges", sizeof(u32)); + if (n <= 0 || n % 3) + return -EINVAL; + + pdc_region_cnt = n / 3; + pdc_region = kcalloc(pdc_region_cnt, sizeof(*pdc_region), GFP_KERNEL); + if (!pdc_region) { + pdc_region_cnt = 0; + return -ENOMEM; + } + + for (n = 0; n < pdc_region_cnt; n++) { + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", + n * 3 + 0, + &pdc_region[n].pin_base); + if (ret) + return ret; + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", + n * 3 + 1, + &pdc_region[n].parent_base); + if (ret) + return ret; + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", + n * 3 + 2, + &pdc_region[n].cnt); + if (ret) + return ret; + } + + return 0; +} + +static int qcom_pdc_init(struct device_node *node, struct device_node *parent) +{ + struct irq_domain *parent_domain, *pdc_domain; + int ret; + + pdc_base = of_iomap(node, 0); + if (!pdc_base) { + pr_err("%pOF: unable to map PDC registers\n", node); + return -ENXIO; + } + + parent_domain = irq_find_host(parent); + if (!parent_domain) { + pr_err("%pOF: unable to find PDC's parent domain\n", node); + ret = -ENXIO; + goto fail; + } + + ret = pdc_setup_pin_mapping(node); + if (ret) { + pr_err("%pOF: failed to init PDC pin-hwirq mapping\n", node); + goto fail; + } + + pdc_domain = irq_domain_create_hierarchy(parent_domain, 0, PDC_MAX_IRQS, + of_fwnode_handle(node), + &qcom_pdc_ops, NULL); + if (!pdc_domain) { + pr_err("%pOF: GIC domain add failed\n", node); + ret = -ENOMEM; + goto fail; + } + + return 0; + +fail: + kfree(pdc_region); + iounmap(pdc_base); + return ret; +} + +IRQCHIP_DECLARE(pdc_sdm845, "qcom,sdm845-pdc", qcom_pdc_init); -- cgit From c5e1035c9687025373b7c48a08efb37f5329916b Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 9 Mar 2018 14:53:19 +0000 Subject: irqchip/gic-v2: Reset APRn registers at boot time Booting a crash kernel while in an interrupt handler is likely to leave the Active Priority Registers with some state that is not relevant to the new kernel, and is likely to lead to erratic behaviours such as interrupts not firing as their priority is already active. As a sanity measure, wipe the APRs clean on startup. Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 121af5cf688f..79801c24800b 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -453,15 +453,26 @@ static u8 gic_get_cpumask(struct gic_chip_data *gic) return mask; } +static bool gic_check_gicv2(void __iomem *base) +{ + u32 val = readl_relaxed(base + GIC_CPU_IDENT); + return (val & 0xff0fff) == 0x02043B; +} + static void gic_cpu_if_up(struct gic_chip_data *gic) { void __iomem *cpu_base = gic_data_cpu_base(gic); u32 bypass = 0; u32 mode = 0; + int i; if (gic == &gic_data[0] && static_key_true(&supports_deactivate)) mode = GIC_CPU_CTRL_EOImodeNS; + if (gic_check_gicv2(cpu_base)) + for (i = 0; i < 4; i++) + writel_relaxed(0, cpu_base + GIC_CPU_ACTIVEPRIO + i * 4); + /* * Preserve bypass disable bits to be written back later */ @@ -1264,12 +1275,6 @@ static int __init gicv2_force_probe_cfg(char *buf) } early_param("irqchip.gicv2_force_probe", gicv2_force_probe_cfg); -static bool gic_check_gicv2(void __iomem *base) -{ - u32 val = readl_relaxed(base + GIC_CPU_IDENT); - return (val & 0xff0fff) == 0x02043B; -} - static bool gic_check_eoimode(struct device_node *node, void __iomem **base) { struct resource cpuif_res; -- cgit From d6062a6d62c643a06c393745d032da3e6441d4bd Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 9 Mar 2018 14:53:19 +0000 Subject: irqchip/gic-v3: Reset APgRn registers at boot time Booting a crash kernel while in an interrupt handler is likely to leave the Active Priority Registers with some state that is not relevant to the new kernel, and is likely to lead to erratic behaviours such as interrupts not firing as their priority is already active. As a sanity measure, wipe the APRs clean on startup. We make sure to wipe both group 0 and 1 registers in order to avoid any surprise. Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index d99cc07903ec..0ea02504115d 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -532,6 +532,7 @@ static void gic_cpu_sys_reg_init(void) int i, cpu = smp_processor_id(); u64 mpidr = cpu_logical_map(cpu); u64 need_rss = MPIDR_RS(mpidr); + u32 val; /* * Need to check that the SRE bit has actually been set. If @@ -562,6 +563,28 @@ static void gic_cpu_sys_reg_init(void) gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop_dir); } + val = gic_read_ctlr(); + val &= ICC_CTLR_EL1_PRI_BITS_MASK; + val >>= ICC_CTLR_EL1_PRI_BITS_SHIFT; + + switch(val + 1) { + case 8: + case 7: + write_gicreg(0, ICC_AP0R3_EL1); + write_gicreg(0, ICC_AP1R3_EL1); + write_gicreg(0, ICC_AP0R2_EL1); + write_gicreg(0, ICC_AP1R2_EL1); + case 6: + write_gicreg(0, ICC_AP0R1_EL1); + write_gicreg(0, ICC_AP1R1_EL1); + case 5: + case 4: + write_gicreg(0, ICC_AP0R0_EL1); + write_gicreg(0, ICC_AP1R0_EL1); + } + + isb(); + /* ... and let's hit the road... */ gic_write_grpen1(1); -- cgit From f736d65df0acefcb50f7f7c6ad6070e7b954c79a Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Sun, 25 Feb 2018 11:27:04 +0000 Subject: irqchip/gic-v3: Allow LPIs to be disabled from the command line For most GICv3 implementations, enabling LPIs is a one way switch. Once they're on, there is no turning back, which completely kills kexec (pending tables will always be live, and we can't tell the secondary kernel where they are). This is really annoying if you plan to use Linux as a bootloader, as it pretty much guarantees that the secondary kernel won't be able to use MSIs, and may even see some memory corruption. Bad. A workaround for this unfortunate situation is to allow the kernel not to enable LPIs, even if the feature is present in the HW. This would allow Linux-as-a-bootloader to leave LPIs alone, and let the secondary kernel to do whatever it wants with them. Let's introduce a boolean "irqchip.gicv3_nolpi" command line option that serves that purpose. Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 0ea02504115d..3e9eeb6cb294 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -613,9 +613,17 @@ static void gic_cpu_sys_reg_init(void) pr_crit_once("RSS is required but GICD doesn't support it\n"); } +static bool gicv3_nolpi; + +static int __init gicv3_nolpi_cfg(char *buf) +{ + return strtobool(buf, &gicv3_nolpi); +} +early_param("irqchip.gicv3_nolpi", gicv3_nolpi_cfg); + static int gic_dist_supports_lpis(void) { - return !!(readl_relaxed(gic_data.dist_base + GICD_TYPER) & GICD_TYPER_LPIS); + return !!(readl_relaxed(gic_data.dist_base + GICD_TYPER) & GICD_TYPER_LPIS) && !gicv3_nolpi; } static void gic_cpu_init(void) -- cgit From dba0bc7b76dcf80f82f5a7542605d4abc52808f2 Mon Sep 17 00:00:00 2001 From: Derek Basehore Date: Wed, 28 Feb 2018 21:48:18 -0800 Subject: irqchip/gic-v3-its: Add ability to save/restore ITS state Some platforms power off GIC logic in suspend, so we need to save/restore state. The distributor and redistributor registers need to be handled in firmware code due to access permissions on those registers, but the ITS registers can be restored in the kernel. We limit this to systems where the ITS collections are implemented in HW (as opposed to being backed by memory tables), as they are the only ones that cannot be dealt with by the firmware. Signed-off-by: Derek Basehore [maz: fixed changelog, dropped DT property, limited to HCC being >0] Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3-its.c | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 1d3056f53747..06682c33acba 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,7 @@ #define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0) #define ITS_FLAGS_WORKAROUND_CAVIUM_22375 (1ULL << 1) #define ITS_FLAGS_WORKAROUND_CAVIUM_23144 (1ULL << 2) +#define ITS_FLAGS_SAVE_SUSPEND_STATE (1ULL << 3) #define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0) @@ -101,6 +103,8 @@ struct its_node { struct its_collection *collections; struct fwnode_handle *fwnode_handle; u64 (*get_msi_base)(struct its_device *its_dev); + u64 cbaser_save; + u32 ctlr_save; struct list_head its_device_list; u64 flags; unsigned long list_nr; @@ -3042,6 +3046,104 @@ static void its_enable_quirks(struct its_node *its) gic_enable_quirks(iidr, its_quirks, its); } +static int its_save_disable(void) +{ + struct its_node *its; + int err = 0; + + spin_lock(&its_lock); + list_for_each_entry(its, &its_nodes, entry) { + void __iomem *base; + + if (!(its->flags & ITS_FLAGS_SAVE_SUSPEND_STATE)) + continue; + + base = its->base; + its->ctlr_save = readl_relaxed(base + GITS_CTLR); + err = its_force_quiescent(base); + if (err) { + pr_err("ITS@%pa: failed to quiesce: %d\n", + &its->phys_base, err); + writel_relaxed(its->ctlr_save, base + GITS_CTLR); + goto err; + } + + its->cbaser_save = gits_read_cbaser(base + GITS_CBASER); + } + +err: + if (err) { + list_for_each_entry_continue_reverse(its, &its_nodes, entry) { + void __iomem *base; + + if (!(its->flags & ITS_FLAGS_SAVE_SUSPEND_STATE)) + continue; + + base = its->base; + writel_relaxed(its->ctlr_save, base + GITS_CTLR); + } + } + spin_unlock(&its_lock); + + return err; +} + +static void its_restore_enable(void) +{ + struct its_node *its; + int ret; + + spin_lock(&its_lock); + list_for_each_entry(its, &its_nodes, entry) { + void __iomem *base; + int i; + + if (!(its->flags & ITS_FLAGS_SAVE_SUSPEND_STATE)) + continue; + + base = its->base; + + /* + * Make sure that the ITS is disabled. If it fails to quiesce, + * don't restore it since writing to CBASER or BASER + * registers is undefined according to the GIC v3 ITS + * Specification. + */ + ret = its_force_quiescent(base); + if (ret) { + pr_err("ITS@%pa: failed to quiesce on resume: %d\n", + &its->phys_base, ret); + continue; + } + + gits_write_cbaser(its->cbaser_save, base + GITS_CBASER); + + /* + * Writing CBASER resets CREADR to 0, so make CWRITER and + * cmd_write line up with it. + */ + its->cmd_write = its->cmd_base; + gits_write_cwriter(0, base + GITS_CWRITER); + + /* Restore GITS_BASER from the value cache. */ + for (i = 0; i < GITS_BASER_NR_REGS; i++) { + struct its_baser *baser = &its->tables[i]; + + if (!(baser->val & GITS_BASER_VALID)) + continue; + + its_write_baser(its, baser, baser->val); + } + writel_relaxed(its->ctlr_save, base + GITS_CTLR); + } + spin_unlock(&its_lock); +} + +static struct syscore_ops its_syscore_ops = { + .suspend = its_save_disable, + .resume = its_restore_enable, +}; + static int its_init_domain(struct fwnode_handle *handle, struct its_node *its) { struct irq_domain *inner_domain; @@ -3261,6 +3363,9 @@ static int __init its_probe_one(struct resource *res, ctlr |= GITS_CTLR_ImDe; writel_relaxed(ctlr, its->base + GITS_CTLR); + if (GITS_TYPER_HCC(typer)) + its->flags |= ITS_FLAGS_SAVE_SUSPEND_STATE; + err = its_init_domain(handle, its); if (err) goto out_free_tables; @@ -3517,5 +3622,7 @@ int __init its_init(struct fwnode_handle *handle, struct rdists *rdists, } } + register_syscore_ops(&its_syscore_ops); + return 0; } -- cgit From 920181ce84692653094d9a3e04e05c06177edf5d Mon Sep 17 00:00:00 2001 From: Derek Basehore Date: Wed, 28 Feb 2018 21:48:20 -0800 Subject: irqchip/gic-v3-its: Add ability to resend MAPC on resume This adds functionality to resend the MAPC command to an ITS node on resume. If the ITS is powered down during suspend and the collections are not backed by memory, the ITS will lose that state. This just sets up the known state for the collections after the ITS is restored. Signed-off-by: Derek Basehore Reviewed-by: Brian Norris Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3-its.c | 86 ++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 38 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 06682c33acba..2c9006726450 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1942,52 +1942,53 @@ static void its_cpu_init_lpis(void) dsb(sy); } -static void its_cpu_init_collection(void) +static void its_cpu_init_collection(struct its_node *its) { - struct its_node *its; - int cpu; - - spin_lock(&its_lock); - cpu = smp_processor_id(); - - list_for_each_entry(its, &its_nodes, entry) { - u64 target; + int cpu = smp_processor_id(); + u64 target; - /* avoid cross node collections and its mapping */ - if (its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_23144) { - struct device_node *cpu_node; + /* avoid cross node collections and its mapping */ + if (its->flags & ITS_FLAGS_WORKAROUND_CAVIUM_23144) { + struct device_node *cpu_node; - cpu_node = of_get_cpu_node(cpu, NULL); - if (its->numa_node != NUMA_NO_NODE && - its->numa_node != of_node_to_nid(cpu_node)) - continue; - } + cpu_node = of_get_cpu_node(cpu, NULL); + if (its->numa_node != NUMA_NO_NODE && + its->numa_node != of_node_to_nid(cpu_node)) + return; + } + /* + * We now have to bind each collection to its target + * redistributor. + */ + if (gic_read_typer(its->base + GITS_TYPER) & GITS_TYPER_PTA) { /* - * We now have to bind each collection to its target + * This ITS wants the physical address of the * redistributor. */ - if (gic_read_typer(its->base + GITS_TYPER) & GITS_TYPER_PTA) { - /* - * This ITS wants the physical address of the - * redistributor. - */ - target = gic_data_rdist()->phys_base; - } else { - /* - * This ITS wants a linear CPU number. - */ - target = gic_read_typer(gic_data_rdist_rd_base() + GICR_TYPER); - target = GICR_TYPER_CPU_NUMBER(target) << 16; - } + target = gic_data_rdist()->phys_base; + } else { + /* This ITS wants a linear CPU number. */ + target = gic_read_typer(gic_data_rdist_rd_base() + GICR_TYPER); + target = GICR_TYPER_CPU_NUMBER(target) << 16; + } - /* Perform collection mapping */ - its->collections[cpu].target_address = target; - its->collections[cpu].col_id = cpu; + /* Perform collection mapping */ + its->collections[cpu].target_address = target; + its->collections[cpu].col_id = cpu; - its_send_mapc(its, &its->collections[cpu], 1); - its_send_invall(its, &its->collections[cpu]); - } + its_send_mapc(its, &its->collections[cpu], 1); + its_send_invall(its, &its->collections[cpu]); +} + +static void its_cpu_init_collections(void) +{ + struct its_node *its; + + spin_lock(&its_lock); + + list_for_each_entry(its, &its_nodes, entry) + its_cpu_init_collection(its); spin_unlock(&its_lock); } @@ -3135,6 +3136,15 @@ static void its_restore_enable(void) its_write_baser(its, baser, baser->val); } writel_relaxed(its->ctlr_save, base + GITS_CTLR); + + /* + * Reinit the collection if it's stored in the ITS. This is + * indicated by the col_id being less than the HCC field. + * CID < HCC as specified in the GIC v3 Documentation. + */ + if (its->collections[smp_processor_id()].col_id < + GITS_TYPER_HCC(gic_read_typer(base + GITS_TYPER))) + its_cpu_init_collection(its); } spin_unlock(&its_lock); } @@ -3401,7 +3411,7 @@ int its_cpu_init(void) return -ENXIO; } its_cpu_init_lpis(); - its_cpu_init_collection(); + its_cpu_init_collections(); } return 0; -- cgit From 83a86fbb5b56b5eed8a476cc3fe214077d7c4f49 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 16 Mar 2018 14:35:17 +0000 Subject: irqchip/gic: Loudly complain about the use of IRQ_TYPE_NONE There is a huge number of broken device trees out there. Just grepping through the tree for the use of IRQ_TYPE_NONE in conjunction with the GIC is scary. People just don't realise that IRQ_TYPE_NONE just doesn't exist, and you just get whatever junk was there before. So let's make them aware of the issue. Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 79801c24800b..ac2e62d613d1 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -1011,6 +1011,9 @@ static int gic_irq_domain_translate(struct irq_domain *d, *hwirq += 16; *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; + + /* Make it clear that broken DTs are... broken */ + WARN_ON(*type == IRQ_TYPE_NONE); return 0; } @@ -1020,6 +1023,8 @@ static int gic_irq_domain_translate(struct irq_domain *d, *hwirq = fwspec->param[0]; *type = fwspec->param[1]; + + WARN_ON(*type == IRQ_TYPE_NONE); return 0; } -- cgit From 6ef6386ef7c15bea21afce06f951c87de7e2a562 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 16 Mar 2018 14:35:17 +0000 Subject: irqchip/gic-v3: Loudly complain about the use of IRQ_TYPE_NONE There is a huge number of broken device trees out there. Just grepping through the tree for the use of IRQ_TYPE_NONE in conjunction with the GIC is scary. People just don't realise that IRQ_TYPE_NONE just doesn't exist, and you just get whatever junk was there before. So let's make them aware of the issue. Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 3e9eeb6cb294..5bb7bb22f1c1 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -916,6 +916,9 @@ static int gic_irq_domain_translate(struct irq_domain *d, } *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; + + /* Make it clear that broken DTs are... broken */ + WARN_ON(*type == IRQ_TYPE_NONE); return 0; } @@ -925,6 +928,8 @@ static int gic_irq_domain_translate(struct irq_domain *d, *hwirq = fwspec->param[0]; *type = fwspec->param[1]; + + WARN_ON(*type == IRQ_TYPE_NONE); return 0; } -- cgit From 65da7d1979c229b69d3fbec63350a6ae26232ad6 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 20 Mar 2018 13:44:09 +0000 Subject: irqchip/gic-v3: Do not check trigger configuration of partitionned LPIs We cannot know the trigger of partitionned PPIs ahead of time (when we instanciate the partitions), so let's not check them early. Reported-by: JeffyChen Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 5bb7bb22f1c1..0d8e326ebf19 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -892,6 +892,8 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, return 0; } +#define GIC_IRQ_TYPE_PARTITION (GIC_IRQ_TYPE_LPI + 1) + static int gic_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, @@ -906,6 +908,7 @@ static int gic_irq_domain_translate(struct irq_domain *d, *hwirq = fwspec->param[1] + 32; break; case 1: /* PPI */ + case GIC_IRQ_TYPE_PARTITION: *hwirq = fwspec->param[1] + 16; break; case GIC_IRQ_TYPE_LPI: /* LPI */ @@ -917,8 +920,12 @@ static int gic_irq_domain_translate(struct irq_domain *d, *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; - /* Make it clear that broken DTs are... broken */ - WARN_ON(*type == IRQ_TYPE_NONE); + /* + * Make it clear that broken DTs are... broken. + * Partitionned PPIs are an unfortunate exception. + */ + WARN_ON(*type == IRQ_TYPE_NONE && + fwspec->param[0] != GIC_IRQ_TYPE_PARTITION); return 0; } @@ -1176,7 +1183,7 @@ static void __init gic_populate_ppi_partitions(struct device_node *gic_node) .fwnode = gic_data.fwnode, .param_count = 3, .param = { - [0] = 1, + [0] = GIC_IRQ_TYPE_PARTITION, [1] = i, [2] = IRQ_TYPE_NONE, }, -- cgit From 66569052fe5328729ff696c1ec17913907d00b5f Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 20 Mar 2018 18:21:37 +0000 Subject: irqchip/gic-v3: Don't try to reset AP0Rn Clearing AP0Rn has created a number of regressions, due to systems that have SCR_EL3.FIQ set. Even when addressing some obvious bugs, GIC500 platforms seem to act bizarrely (we are supposed to have 5 bits of priority, but PMR seems to behave as if we had 6...). Drop the AP0Rn reset for the time being, it is unlikely to have any effect if kexec-ing. Fixes: d6062a6d62c6 irqchip/gic-v3: Reset APgRn registers at boot time Reported-by: Shawn Lin Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 0d8e326ebf19..e2b90bec1473 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -570,16 +570,12 @@ static void gic_cpu_sys_reg_init(void) switch(val + 1) { case 8: case 7: - write_gicreg(0, ICC_AP0R3_EL1); write_gicreg(0, ICC_AP1R3_EL1); - write_gicreg(0, ICC_AP0R2_EL1); write_gicreg(0, ICC_AP1R2_EL1); case 6: - write_gicreg(0, ICC_AP0R1_EL1); write_gicreg(0, ICC_AP1R1_EL1); case 5: case 4: - write_gicreg(0, ICC_AP0R0_EL1); write_gicreg(0, ICC_AP1R0_EL1); } -- cgit From 33625282adaaba93d37aa437ae9688bf0cc024a9 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Tue, 20 Mar 2018 09:46:42 +0000 Subject: irqchip/gic-v3: Probe for SCR_EL3 being clear before resetting AP0Rn We would like to reset the Group-0 Active Priority Registers at boot time if they are available to us. They would be available if SCR_EL3.FIQ was not set, but we cannot directly probe this bit, and short of checking, we may end-up trapping to EL3, and the firmware may not be please to get such an exception. Yes, this is dumb. Instead, let's use PMR to find out if its value gets affected by SCR_EL3.FIQ being set. We use the fact that when SCR_EL3.FIQ is set, the LSB of the priority is lost due to the shifting back and forth of the actual priority. If we read back a 0, we know that Group0 is unavailable. In case we read a non-zero value, we can safely reset the AP0Rn register. Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3.c | 46 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index e2b90bec1473..56c8de84a72b 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -532,7 +532,8 @@ static void gic_cpu_sys_reg_init(void) int i, cpu = smp_processor_id(); u64 mpidr = cpu_logical_map(cpu); u64 need_rss = MPIDR_RS(mpidr); - u32 val; + bool group0; + u32 val, pribits; /* * Need to check that the SRE bit has actually been set. If @@ -544,8 +545,28 @@ static void gic_cpu_sys_reg_init(void) if (!gic_enable_sre()) pr_err("GIC: unable to set SRE (disabled at EL2), panic ahead\n"); + pribits = gic_read_ctlr(); + pribits &= ICC_CTLR_EL1_PRI_BITS_MASK; + pribits >>= ICC_CTLR_EL1_PRI_BITS_SHIFT; + pribits++; + + /* + * Let's find out if Group0 is under control of EL3 or not by + * setting the highest possible, non-zero priority in PMR. + * + * If SCR_EL3.FIQ is set, the priority gets shifted down in + * order for the CPU interface to set bit 7, and keep the + * actual priority in the non-secure range. In the process, it + * looses the least significant bit and the actual priority + * becomes 0x80. Reading it back returns 0, indicating that + * we're don't have access to Group0. + */ + write_gicreg(BIT(8 - pribits), ICC_PMR_EL1); + val = read_gicreg(ICC_PMR_EL1); + group0 = val != 0; + /* Set priority mask register */ - gic_write_pmr(DEFAULT_PMR_VALUE); + write_gicreg(DEFAULT_PMR_VALUE, ICC_PMR_EL1); /* * Some firmwares hand over to the kernel with the BPR changed from @@ -563,11 +584,24 @@ static void gic_cpu_sys_reg_init(void) gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop_dir); } - val = gic_read_ctlr(); - val &= ICC_CTLR_EL1_PRI_BITS_MASK; - val >>= ICC_CTLR_EL1_PRI_BITS_SHIFT; + /* Always whack Group0 before Group1 */ + if (group0) { + switch(pribits) { + case 8: + case 7: + write_gicreg(0, ICC_AP0R3_EL1); + write_gicreg(0, ICC_AP0R2_EL1); + case 6: + write_gicreg(0, ICC_AP0R1_EL1); + case 5: + case 4: + write_gicreg(0, ICC_AP0R0_EL1); + } + + isb(); + } - switch(val + 1) { + switch(pribits) { case 8: case 7: write_gicreg(0, ICC_AP1R3_EL1); -- cgit From 19d99164480a34db66941cf995bdd996cc266fc0 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 22 Mar 2018 16:15:24 +0100 Subject: irqchip: Add a driver for the Microsemi Ocelot controller The Microsemi Ocelot SoC has a pretty simple IRQ controller in its ICPU block. Add a driver for it. Signed-off-by: Alexandre Belloni Signed-off-by: Marc Zyngier --- drivers/irqchip/Kconfig | 5 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-mscc-ocelot.c | 118 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 drivers/irqchip/irq-mscc-ocelot.c (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 030c7753d45b..60d5982d8234 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -286,6 +286,11 @@ config IRQ_MXS select IRQ_DOMAIN select STMP_DEVICE +config MSCC_OCELOT_IRQ + bool + select IRQ_DOMAIN + select GENERIC_IRQ_CHIP + config MVEBU_GICP bool diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c35ee5345a53..935868c3292e 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o obj-$(CONFIG_PIC32_EVIC) += irq-pic32-evic.o +obj-$(CONFIG_MSCC_OCELOT_IRQ) += irq-mscc-ocelot.o obj-$(CONFIG_MVEBU_GICP) += irq-mvebu-gicp.o obj-$(CONFIG_MVEBU_ICU) += irq-mvebu-icu.o obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o diff --git a/drivers/irqchip/irq-mscc-ocelot.c b/drivers/irqchip/irq-mscc-ocelot.c new file mode 100644 index 000000000000..b63e40c00a02 --- /dev/null +++ b/drivers/irqchip/irq-mscc-ocelot.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* + * Microsemi Ocelot IRQ controller driver + * + * Copyright (c) 2017 Microsemi Corporation + */ +#include +#include +#include +#include +#include +#include +#include + +#define ICPU_CFG_INTR_INTR_STICKY 0x10 +#define ICPU_CFG_INTR_INTR_ENA 0x18 +#define ICPU_CFG_INTR_INTR_ENA_CLR 0x1c +#define ICPU_CFG_INTR_INTR_ENA_SET 0x20 +#define ICPU_CFG_INTR_DST_INTR_IDENT(x) (0x38 + 0x4 * (x)) +#define ICPU_CFG_INTR_INTR_TRIGGER(x) (0x5c + 0x4 * (x)) + +#define OCELOT_NR_IRQ 24 + +static void ocelot_irq_unmask(struct irq_data *data) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); + struct irq_chip_type *ct = irq_data_get_chip_type(data); + unsigned int mask = data->mask; + u32 val; + + irq_gc_lock(gc); + val = irq_reg_readl(gc, ICPU_CFG_INTR_INTR_TRIGGER(0)) | + irq_reg_readl(gc, ICPU_CFG_INTR_INTR_TRIGGER(1)); + if (!(val & mask)) + irq_reg_writel(gc, mask, ICPU_CFG_INTR_INTR_STICKY); + + *ct->mask_cache &= ~mask; + irq_reg_writel(gc, mask, ICPU_CFG_INTR_INTR_ENA_SET); + irq_gc_unlock(gc); +} + +static void ocelot_irq_handler(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct irq_domain *d = irq_desc_get_handler_data(desc); + struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, 0); + u32 reg = irq_reg_readl(gc, ICPU_CFG_INTR_DST_INTR_IDENT(0)); + + chained_irq_enter(chip, desc); + + while (reg) { + u32 hwirq = __fls(reg); + + generic_handle_irq(irq_find_mapping(d, hwirq)); + reg &= ~(BIT(hwirq)); + } + + chained_irq_exit(chip, desc); +} + +static int __init ocelot_irq_init(struct device_node *node, + struct device_node *parent) +{ + struct irq_domain *domain; + struct irq_chip_generic *gc; + int parent_irq, ret; + + parent_irq = irq_of_parse_and_map(node, 0); + if (!parent_irq) + return -EINVAL; + + domain = irq_domain_add_linear(node, OCELOT_NR_IRQ, + &irq_generic_chip_ops, NULL); + if (!domain) { + pr_err("%s: unable to add irq domain\n", node->name); + return -ENOMEM; + } + + ret = irq_alloc_domain_generic_chips(domain, OCELOT_NR_IRQ, 1, + "icpu", handle_level_irq, + 0, 0, 0); + if (ret) { + pr_err("%s: unable to alloc irq domain gc\n", node->name); + goto err_domain_remove; + } + + gc = irq_get_domain_generic_chip(domain, 0); + gc->reg_base = of_iomap(node, 0); + if (!gc->reg_base) { + pr_err("%s: unable to map resource\n", node->name); + ret = -ENOMEM; + goto err_gc_free; + } + + gc->chip_types[0].regs.ack = ICPU_CFG_INTR_INTR_STICKY; + gc->chip_types[0].regs.mask = ICPU_CFG_INTR_INTR_ENA_CLR; + gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; + gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit; + gc->chip_types[0].chip.irq_unmask = ocelot_irq_unmask; + + /* Mask and ack all interrupts */ + irq_reg_writel(gc, 0, ICPU_CFG_INTR_INTR_ENA); + irq_reg_writel(gc, 0xffffffff, ICPU_CFG_INTR_INTR_STICKY); + + irq_set_chained_handler_and_data(parent_irq, ocelot_irq_handler, + domain); + + return 0; + +err_gc_free: + irq_free_generic_chip(gc); + +err_domain_remove: + irq_domain_remove(domain); + + return ret; +} +IRQCHIP_DECLARE(ocelot_icpu, "mscc,ocelot-icpu-intr", ocelot_irq_init); -- cgit From 6eb486b66a3094cdcd68dc39c9df3a29d6a51dd5 Mon Sep 17 00:00:00 2001 From: Shanker Donthineni Date: Wed, 21 Mar 2018 20:58:49 -0500 Subject: irqchip/gic-v3: Ensure GICR_CTLR.EnableLPI=0 is observed before enabling Booting with GICR_CTLR.EnableLPI=1 is usually a bad idea, and may result in subtle memory corruption. Detecting this is thus pretty important. On detecting that LPIs are still enabled, we taint the kernel (because we're not sure of anything anymore), and try to disable LPIs. This can fail, as implementations are allowed to implement GICR_CTLR.EnableLPI as a one-way enable, meaning the redistributors cannot be reprogrammed with new tables. Should this happen, we fail probing the redistributor and warn the user that things are pretty dire. Signed-off-by: Shanker Donthineni [maz: reworded changelog, minor comment and message changes] Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3-its.c | 74 ++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 14 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 2c9006726450..cb952c073d77 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1879,16 +1879,6 @@ static void its_cpu_init_lpis(void) gic_data_rdist()->pend_page = pend_page; } - /* Disable LPIs */ - val = readl_relaxed(rbase + GICR_CTLR); - val &= ~GICR_CTLR_ENABLE_LPIS; - writel_relaxed(val, rbase + GICR_CTLR); - - /* - * Make sure any change to the table is observable by the GIC. - */ - dsb(sy); - /* set PROPBASE */ val = (page_to_phys(gic_rdists->prop_page) | GICR_PROPBASER_InnerShareable | @@ -3403,13 +3393,69 @@ static bool gic_rdists_supports_plpis(void) return !!(gic_read_typer(gic_data_rdist_rd_base() + GICR_TYPER) & GICR_TYPER_PLPIS); } +static int redist_disable_lpis(void) +{ + void __iomem *rbase = gic_data_rdist_rd_base(); + u64 timeout = USEC_PER_SEC; + u64 val; + + if (!gic_rdists_supports_plpis()) { + pr_info("CPU%d: LPIs not supported\n", smp_processor_id()); + return -ENXIO; + } + + val = readl_relaxed(rbase + GICR_CTLR); + if (!(val & GICR_CTLR_ENABLE_LPIS)) + return 0; + + pr_warn("CPU%d: Booted with LPIs enabled, memory probably corrupted\n", + smp_processor_id()); + add_taint(TAINT_CRAP, LOCKDEP_STILL_OK); + + /* Disable LPIs */ + val &= ~GICR_CTLR_ENABLE_LPIS; + writel_relaxed(val, rbase + GICR_CTLR); + + /* Make sure any change to GICR_CTLR is observable by the GIC */ + dsb(sy); + + /* + * Software must observe RWP==0 after clearing GICR_CTLR.EnableLPIs + * from 1 to 0 before programming GICR_PEND{PROP}BASER registers. + * Error out if we time out waiting for RWP to clear. + */ + while (readl_relaxed(rbase + GICR_CTLR) & GICR_CTLR_RWP) { + if (!timeout) { + pr_err("CPU%d: Timeout while disabling LPIs\n", + smp_processor_id()); + return -ETIMEDOUT; + } + udelay(1); + timeout--; + } + + /* + * After it has been written to 1, it is IMPLEMENTATION + * DEFINED whether GICR_CTLR.EnableLPI becomes RES1 or can be + * cleared to 0. Error out if clearing the bit failed. + */ + if (readl_relaxed(rbase + GICR_CTLR) & GICR_CTLR_ENABLE_LPIS) { + pr_err("CPU%d: Failed to disable LPIs\n", smp_processor_id()); + return -EBUSY; + } + + return 0; +} + int its_cpu_init(void) { if (!list_empty(&its_nodes)) { - if (!gic_rdists_supports_plpis()) { - pr_info("CPU%d: LPIs not supported\n", smp_processor_id()); - return -ENXIO; - } + int ret; + + ret = redist_disable_lpis(); + if (ret) + return ret; + its_cpu_init_lpis(); its_cpu_init_collections(); } -- cgit From d01d327406d9c36e066181240ac078b636871de8 Mon Sep 17 00:00:00 2001 From: Davidlohr Bueso Date: Mon, 26 Mar 2018 14:09:25 -0700 Subject: irqchip/gic: Update supports_deactivate static key to modern api No changes in semantics -- key init is true; replace static_key_slow_dec with static_branch_disable static_key_true with static_branch_likely The first is because we never actually do any couterpart incs, thus there is really no reference counting semantics going on. Use the more proper static_branch_disable() construct. Also added a '_key' suffix to supports_deactivate, for better self documentation. Cc: Thomas Gleixner Cc: Jason Cooper Signed-off-by: Davidlohr Bueso Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-v3.c | 20 ++++++++++---------- drivers/irqchip/irq-gic.c | 22 +++++++++++----------- 2 files changed, 21 insertions(+), 21 deletions(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 56c8de84a72b..e5d101418390 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -61,7 +61,7 @@ struct gic_chip_data { }; static struct gic_chip_data gic_data __read_mostly; -static struct static_key supports_deactivate = STATIC_KEY_INIT_TRUE; +static DEFINE_STATIC_KEY_TRUE(supports_deactivate_key); static struct gic_kvm_info gic_v3_kvm_info; static DEFINE_PER_CPU(bool, has_rss); @@ -354,7 +354,7 @@ static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) { int err; - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) gic_write_eoir(irqnr); else isb(); @@ -362,7 +362,7 @@ static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs err = handle_domain_irq(gic_data.domain, irqnr, regs); if (err) { WARN_ONCE(true, "Unexpected interrupt received!\n"); - if (static_key_true(&supports_deactivate)) { + if (static_branch_likely(&supports_deactivate_key)) { if (irqnr < 8192) gic_write_dir(irqnr); } else { @@ -373,7 +373,7 @@ static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs } if (irqnr < 16) { gic_write_eoir(irqnr); - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) gic_write_dir(irqnr); #ifdef CONFIG_SMP /* @@ -576,7 +576,7 @@ static void gic_cpu_sys_reg_init(void) */ gic_write_bpr1(0); - if (static_key_true(&supports_deactivate)) { + if (static_branch_likely(&supports_deactivate_key)) { /* EOI drops priority only (mode 1) */ gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop); } else { @@ -884,7 +884,7 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, { struct irq_chip *chip = &gic_chip; - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) chip = &gic_eoimode1_chip; /* SGIs are private to the core kernel */ @@ -1075,9 +1075,9 @@ static int __init gic_init_bases(void __iomem *dist_base, int err; if (!is_hyp_mode_available()) - static_key_slow_dec(&supports_deactivate); + static_branch_disable(&supports_deactivate_key); - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) pr_info("GIC: Using split EOI/Deactivate mode\n"); gic_data.fwnode = handle; @@ -1312,7 +1312,7 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare gic_populate_ppi_partitions(node); - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) gic_of_setup_kvm_info(node); return 0; @@ -1614,7 +1614,7 @@ gic_acpi_init(struct acpi_subtable_header *header, const unsigned long end) acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, domain_handle); - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) gic_acpi_setup_kvm_info(); return 0; diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index ac2e62d613d1..ced10c44b68a 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -121,7 +121,7 @@ static DEFINE_RAW_SPINLOCK(cpu_map_lock); #define NR_GIC_CPU_IF 8 static u8 gic_cpu_map[NR_GIC_CPU_IF] __read_mostly; -static struct static_key supports_deactivate = STATIC_KEY_INIT_TRUE; +static DEFINE_STATIC_KEY_TRUE(supports_deactivate_key); static struct gic_chip_data gic_data[CONFIG_ARM_GIC_MAX_NR] __read_mostly; @@ -361,7 +361,7 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) irqnr = irqstat & GICC_IAR_INT_ID_MASK; if (likely(irqnr > 15 && irqnr < 1020)) { - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); isb(); handle_domain_irq(gic->domain, irqnr, regs); @@ -369,7 +369,7 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) } if (irqnr < 16) { writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE); #ifdef CONFIG_SMP /* @@ -466,7 +466,7 @@ static void gic_cpu_if_up(struct gic_chip_data *gic) u32 mode = 0; int i; - if (gic == &gic_data[0] && static_key_true(&supports_deactivate)) + if (gic == &gic_data[0] && static_branch_likely(&supports_deactivate_key)) mode = GIC_CPU_CTRL_EOImodeNS; if (gic_check_gicv2(cpu_base)) @@ -1219,11 +1219,11 @@ static int __init __gic_init_bases(struct gic_chip_data *gic, "irqchip/arm/gic:starting", gic_starting_cpu, NULL); set_handle_irq(gic_handle_irq); - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) pr_info("GIC: Using split EOI/Deactivate mode\n"); } - if (static_key_true(&supports_deactivate) && gic == &gic_data[0]) { + if (static_branch_likely(&supports_deactivate_key) && gic == &gic_data[0]) { name = kasprintf(GFP_KERNEL, "GICv2"); gic_init_chip(gic, NULL, name, true); } else { @@ -1250,7 +1250,7 @@ void __init gic_init(unsigned int gic_nr, int irq_start, * Non-DT/ACPI systems won't run a hypervisor, so let's not * bother with these... */ - static_key_slow_dec(&supports_deactivate); + static_branch_disable(&supports_deactivate_key); gic = &gic_data[gic_nr]; gic->raw_dist_base = dist_base; @@ -1430,7 +1430,7 @@ static void __init gic_of_setup_kvm_info(struct device_node *node) if (ret) return; - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) gic_set_kvm_info(&gic_v2_kvm_info); } @@ -1457,7 +1457,7 @@ gic_of_init(struct device_node *node, struct device_node *parent) * or the CPU interface is too small. */ if (gic_cnt == 0 && !gic_check_eoimode(node, &gic->raw_cpu_base)) - static_key_slow_dec(&supports_deactivate); + static_branch_disable(&supports_deactivate_key); ret = __gic_init_bases(gic, -1, &node->fwnode); if (ret) { @@ -1638,7 +1638,7 @@ static int __init gic_v2_acpi_init(struct acpi_subtable_header *header, * interface will always be the right size. */ if (!is_hyp_mode_available()) - static_key_slow_dec(&supports_deactivate); + static_branch_disable(&supports_deactivate_key); /* * Initialize GIC instance zero (no multi-GIC support). @@ -1663,7 +1663,7 @@ static int __init gic_v2_acpi_init(struct acpi_subtable_header *header, if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) gicv2m_init(NULL, gic_data[0].domain); - if (static_key_true(&supports_deactivate)) + if (static_branch_likely(&supports_deactivate_key)) gic_acpi_setup_kvm_info(); return 0; -- cgit From aa08192a254d362a4d5317647a81de6996961aef Mon Sep 17 00:00:00 2001 From: Aniruddha Banerjee Date: Wed, 28 Mar 2018 19:12:00 +0530 Subject: irqchip/gic: Take lock when updating irq type Most MMIO GIC register accesses use a 1-hot bit scheme that avoids requiring any form of locking. This isn't true for the GICD_ICFGRn registers, which require a RMW sequence. Unfortunately, we seem to be missing a lock for these particular accesses, which could result in a race condition if changing the trigger type on any two interrupts within the same set of 16 interrupts (and thus controlled by the same CFGR register). Introduce a private lock in the GIC common comde for this particular case, making it cover both GIC implementations in one go. Cc: stable@vger.kernel.org Signed-off-by: Aniruddha Banerjee [maz: updated changelog] Signed-off-by: Marc Zyngier --- drivers/irqchip/irq-gic-common.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers/irqchip') diff --git a/drivers/irqchip/irq-gic-common.c b/drivers/irqchip/irq-gic-common.c index 30017df5b54c..01e673c680cd 100644 --- a/drivers/irqchip/irq-gic-common.c +++ b/drivers/irqchip/irq-gic-common.c @@ -21,6 +21,8 @@ #include "irq-gic-common.h" +static DEFINE_RAW_SPINLOCK(irq_controller_lock); + static const struct gic_kvm_info *gic_kvm_info; const struct gic_kvm_info *gic_get_kvm_info(void) @@ -53,11 +55,13 @@ int gic_configure_irq(unsigned int irq, unsigned int type, u32 confoff = (irq / 16) * 4; u32 val, oldval; int ret = 0; + unsigned long flags; /* * Read current configuration register, and insert the config * for "irq", depending on "type". */ + raw_spin_lock_irqsave(&irq_controller_lock, flags); val = oldval = readl_relaxed(base + GIC_DIST_CONFIG + confoff); if (type & IRQ_TYPE_LEVEL_MASK) val &= ~confmask; @@ -65,8 +69,10 @@ int gic_configure_irq(unsigned int irq, unsigned int type, val |= confmask; /* If the current configuration is the same, then we are done */ - if (val == oldval) + if (val == oldval) { + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); return 0; + } /* * Write back the new configuration, and possibly re-enable @@ -84,6 +90,7 @@ int gic_configure_irq(unsigned int irq, unsigned int type, pr_warn("GIC: PPI%d is secure or misconfigured\n", irq - 16); } + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); if (sync_access) sync_access(); -- cgit