The ICU is an IRQ chip found in Armada CP110. It currently has 207 wired
inputs. Its purpose is to aggregate all CP interrupts and report them to
the AP through MSIs. The ICU writes into GIC registers (AP side) by way
of the interconnect. These interrupts can be of several groups:
- SecuRe (SR);
- Non-SecuRe (NSR);
- System Error Interrupts (SEI);
- RAM Error Interrupts (REI);
- ...
Each ICU wired interrupt can be of any of these groups. The group is
encoded in the MSI payload.
Until now, only the non-secure interrupts (NSR) were handled by the ICU
driver. Interrupts of another group could work by chance because the
ICU driver does not erase all ATF configuration; it only erases the
configuration for NSR interrupts.
This series aims at adding support for the System Error Interrupts
(SEI). For this purpose, the ICU driver is a bit reworked to separate
the ICU 'generic' configuration from the NSR-related handling. Then,
the SEI driver (part of the GIC) is introduced and finally, support for
SEI interrupts are also added to the ICU driver.
The SEI driver is a bit different than its cousin the GICP because it
must handle MSIs from the CPs, as well as wired interrupts from the AP
itself. MSIs and wired interrupts will automatically update two
registers (GICP_SECR0/GICP_SECR1) that will trigger a single top-level
interrupt (SPI #32).
As this is my first contribution in the IRQ subsystem I might have
missed some specificities or misunderstood the API, please do not
hesitate to correct me if I'm wrong.
Also, for the sake of understandability (and because I love ASCII art),
this is a try to explain the ICU/SEI architecture:
+----------------------------------------------------------------------+
| |
| |
| SPIa SPIb SPIz SPI 32 |
| ^ ^ ^ ^ |
| | | . . . | | |
| | | | | |
| | | . . . | | |
| +------------------------+ +---------------------------------+ |
| | | | | | | | | |
| | | | | | | SEI | | |
| | | | . . . | | | ________|_______ | |
| | | | | | | /___SEI_SECR_____\ | |
| | |____|___________| | | / | \\ | |
| | \_GICP_SETSPI _/ | | / | \\ | |
| | || | | / ... | \\ | |
| | GICP || | | | | \\ | |
| +----------------||------+ +--|----------|------------||-----+ |
| || | | || |
| || | ... | || |
| || | | || |
| || | | || |
| \\_______ int 0 ... int 20 // |
| \_NSR__ \ // |
| \\ ____________________// |
| \\ /________SEI_________/ |
| AP 806 \\// |
| || |
+---------------------------------||-----------------------------------+
||
|| Interconnect
||\
||\\______
|| \______ <---> Others CP 110
||
+---------------------------------||-----------------------------------+
| || |
| CP 110 || |
| || |
| +-------------------------||------------------------+ |
| | || MSI | |
| | ICU || | |
| | /--------------/ \------\ | |
| | / /-------/ \ | |
| | / / / \ | |
| | / / / . . . \ | |
| | / / / \ | |
| | NSR NSR SEI NSR | |
| | | | | | | |
| +----^-------^-------^-----------------^------------+ |
| | | | | |
| | | | . . . | |
| | | | | |
| int 0 int 1 int 2 int 206 |
| |
| |
+----------------------------------------------------------------------+
Thank you,
Miquèl
Changes since v2:
=================
* Rebased on top of v4.18-rc1
platform-msi:
-------------
* New patch to allow using MSI tree domains.
irqchip/irq-mvebu-sei: add new driver for Marvell SEI
------------------------------------------------------
* Updated commit message with Marc comments
* Wrote two functions to fill ->irq_set_type() in the irq_chip
structures, one accepting only rising edge interrupts (for MSI),
another one accepting only high level interrupts (for wired IRQ).
* Changed the spin lock protecting the allocated SEIs bitmap into a
mutex.
* Changed the bitmap allocation line to respect the actual number of
MSIs instead of pretending having SEI_IRQ_COUNT (64) MSI available.
* I did not split the code to have one function per domain because it
would duplicate a _lot_ of code. Requested some advices instead.
* Stopped using the fwnode when creating the AP (wired) IRQ domain.
* Implemented the AP IRQ domain ->match() hook.
* Used marvell,sei-xx-ranges properties to get the relevant IRQ numbers
from DT. 'xx' is either 'ap' or 'cp'.
irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
---------------------------------------------------------------------
* Added a patch to ease the creation of tree domains (changes in the
core).
* Changed the code accordingly to use tree domains.
* Created a couple of helpers to do the bitmap allocation/release.
* Removed the .offset_clr_a[hl] entries of the sei_subset_data
structure to avoid confusion. These registers actually exist, but
are not used here because the upper block (SEI) only supports
edge-MSI and not level-MSI like the NSR one.
dt-bindings/interrupt-controller: update Marvell ICU bindings
--------------------------------------------------------------
* Explained better in the commit message that backward compatibility
is not broken.
* Changed subnodes names to be 'interrupt-controller' as requested.
* Added a range associated to each sub-node (as well as in the DT).
* Replaced spaces by tabs.
* Merged the SEI's subnodes so that there is only one SEI node and no
subnodes anymore.
Changes since v1:
=================
General
-------
* Spelling/function names/comments.
* Added Reviewed-by tags.
* Rebased on top of Marc Zyngier level-MSI series (tip:irq/core).
SEI
---
* Change the license for GPL-2.0 only in irq-mvebu-sei.c C file.
* Used alphabetic ordering when adding SEI driver in Makefile.
* Re-ordered register definitions by increasing offset.
* s/NB/COUNT/ in register definitions.
* avoid enabling all interrupt by default.
* fixed mask/unmask functions using the wrong hwirq number.
* removed hackish doorbell mechanism.
* Removed the ->xlate hook assigned for CP MSIs.
* Used devm_*() helpers.
* s/top_level_spi/parent_irq/ in probe.
* Added forgotten of_node_put(child).
* Reset the SEI registers before registering the IRQ domains.
* Introduced new DT property "marvell,sei-ranges" instead of using
"reg" to declare the range of MSI interrupts vs. wired interrupts in
the SEI subnodes.
* Finally did not change the ->alloc() about the fwspec->param[1]
line (to be checked by Marc).
ICU
---
* Updated the ICU documentation so the legacy bindings are still
documented somewhere.
* Added stable tags on the commit fixing the CP110 ICU node size.
* Removed the "syscon" compatible from the ICU node, instead the
syscon is created at probe time.
* s/user data/private data/ in the title of commit
"irqchip/irq-mvebu-icu: fix wrong user data retrieval"
Miquel Raynal (17):
platform-msi: allow creation of MSI domain without interrupt number
dt-bindings/interrupt-controller: fix Marvell ICU length in the
example
arm64: dts: marvell: fix CP110 ICU node size
irqchip/irq-mvebu-icu: fix wrong private data retrieval
irqchip/irq-mvebu-icu: clarify the reset operation of configured
interrupts
irqchip/irq-mvebu-icu: switch to regmap
irqchip/irq-mvebu-icu: make irq_domain local
irqchip/irq-mvebu-icu: disociate ICU and NSR
irqchip/irq-mvebu-icu: support ICU subnodes
irqchip/irq-mvebu-sei: add new driver for Marvell SEI
arm64: marvell: enable SEI driver
irqchip/irq-mvebu-icu: add support for System Error Interrupts (SEI)
dt-bindings/interrupt-controller: update Marvell ICU bindings
dt-bindings/interrupt-controller: add documentation for Marvell SEI
controller
arm64: dts: marvell: add AP806 SEI subnode
arm64: dts: marvell: use new bindings for CP110 interrupts
arm64: dts: marvell: add CP110 ICU SEI subnode
.../bindings/interrupt-controller/marvell,icu.txt | 85 +++-
.../bindings/interrupt-controller/marvell,sei.txt | 39 ++
arch/arm64/Kconfig.platforms | 1 +
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 11 +
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 127 +++---
drivers/base/platform-msi.c | 2 +-
drivers/irqchip/Kconfig | 3 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-mvebu-icu.c | 319 ++++++++++++---
drivers/irqchip/irq-mvebu-sei.c | 444 +++++++++++++++++++++
10 files changed, 899 insertions(+), 133 deletions(-)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
create mode 100644 drivers/irqchip/irq-mvebu-sei.c
--
2.14.1
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
platform_msi_alloc_priv_data() checks that a number of interrupts is
always given. This extra-check has no real impact and just prevents
uselessly the user to create an MSI tree domain: remove it.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/base/platform-msi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
ICU size in CP110 is not 0x10 but at least 0x440 bytes long (from the
specification).
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Rob Herring <robh@kernel.org>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
Documentation/devicetree/bindings/interrupt-controller/marvell,icu.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
The irq_domain structure has an host_data pointer that just stores
private data. It is meant to not be touched by the IRQ core. However,
when it comes to MSI, the MSI layer adds its own private data there
with a structure that also has a host_data pointer.
Because this IRQ domain is an MSI domain, to access private data we
should do a d->host_data->host_data, also wrapped as
'platform_msi_get_host_data()'.
This bug was lying there silently because the 'icu' structure retrieved
this way was just called by dev_err(), only producing a
'(NULL device *):' output on the console.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
@@ -105,7 +105,7 @@ static intmvebu_icu_irq_domain_translate(structirq_domain*d,structirq_fwspec*fwspec,unsignedlong*hwirq,unsignedint*type){-structmvebu_icu*icu=d->host_data;+structmvebu_icu*icu=platform_msi_get_host_data(d);unsignedinticu_group;/* Check the count of the parameters in dt */
Rewrite a small section to clarify the reset operation of interrupts
already configured by ATF that we want to handle in the driver. This
will simplify the introduction of System Error Interrupts support.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
Before splitting the code to support multiple platform devices to
be probed (one for the ICU, one per interrupt group), let's switch to
regmap first by creating one in the ->probe().
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 45 +++++++++++++++++++++++++++--------------
1 file changed, 30 insertions(+), 15 deletions(-)
@@ -220,12 +230,17 @@ static int mvebu_icu_probe(struct platform_device *pdev)icu->dev=&pdev->dev;res=platform_get_resource(pdev,IORESOURCE_MEM,0);-icu->base=devm_ioremap_resource(&pdev->dev,res);-if(IS_ERR(icu->base)){+regs=devm_ioremap_resource(&pdev->dev,res);+if(IS_ERR(regs)){dev_err(&pdev->dev,"Failed to map icu base address.\n");-returnPTR_ERR(icu->base);+returnPTR_ERR(regs);}+icu->regmap=devm_regmap_init_mmio(icu->dev,regs,+&mvebu_icu_regmap_config);+if(IS_ERR(icu->regmap))+returnPTR_ERR(icu->regmap);+icu->irq_chip.name=devm_kasprintf(&pdev->dev,GFP_KERNEL,"ICU.%x",(unsignedint)res->start);
@@ -260,11 +275,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)for(i=0;i<ICU_MAX_IRQS;i++){u32icu_int,icu_grp;-icu_int=readl_relaxed(icu->base+ICU_INT_CFG(i));+regmap_read(icu->regmap,ICU_INT_CFG(i),&icu_int);icu_grp=icu_int>>ICU_GROUP_SHIFT;if(icu_grp==ICU_GRP_NSR)-writel_relaxed(0x0,icu->base+ICU_INT_CFG(i));+regmap_write(icu->regmap,ICU_INT_CFG(i),0);}icu->domain=
Make the current MSI irq_domain local to ease the split between ICU
platform device code and NSR platform device code.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
NSR (non-secure interrupts) are handled in the ICU driver like if there
was only this type of interrupt in the ICU. Change this behavior to
prepare the introduction of SEI (System Error Interrupts) support by
moving the NSR code in a separate function. This is done under the form
of a 'probe' function to ease future migration to NSR/SEI being platform
devices part of the ICU.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 58 +++++++++++++++++++++++------------------
1 file changed, 33 insertions(+), 25 deletions(-)
The ICU can handle several type of interrupt, each of them being handled
differently on AP side. On CP side, the ICU should be able to make the
distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU
node as the parent of multiple ICU sub-nodes, each of them being an
interrupt type with a different interrupt parent. ICU interrupt 'clients'
now directly point to the right sub-node, avoiding the need for the extra
ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If
the node as no child, the probe function for NSRs will still be called
'manually' in order to preserve backward compatibility with DT using the
old binding.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++-------
1 file changed, 74 insertions(+), 14 deletions(-)
@@ -107,24 +132,28 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,unsignedlong*hwirq,unsignedint*type){structmvebu_icu*icu=platform_msi_get_host_data(d);-unsignedinticu_group;+unsignedintparam_count=icu->legacy_bindings?3:2;/* Check the count of the parameters in dt */-if(WARN_ON(fwspec->param_count<3)){+if(WARN_ON(fwspec->param_count!=param_count)){dev_err(icu->dev,"wrong ICU parameter count %d\n",fwspec->param_count);return-EINVAL;}-/* Only ICU group type is handled */-icu_group=fwspec->param[0];-if(icu_group!=ICU_GRP_NSR&&icu_group!=ICU_GRP_SR&&-icu_group!=ICU_GRP_SEI&&icu_group!=ICU_GRP_REI){-dev_err(icu->dev,"wrong ICU group type %x\n",icu_group);-return-EINVAL;+if(icu->legacy_bindings){+*hwirq=fwspec->param[1];+*type=fwspec->param[2];+if(fwspec->param[0]!=ICU_GRP_NSR){+dev_err(icu->dev,"wrong ICU group type %x\n",+fwspec->param[0]);+return-EINVAL;+}+}else{+*hwirq=fwspec->param[0];+*type=fwspec->param[1];}-*hwirq=fwspec->param[1];if(*hwirq>=ICU_MAX_IRQS){dev_err(icu->dev,"invalid interrupt number %ld\n",*hwirq);return-EINVAL;
@@ -157,7 +186,10 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,gotofree_irqd;}-icu_irqd->icu_group=fwspec->param[0];+if(icu->legacy_bindings)+icu_irqd->icu_group=fwspec->param[0];+else+icu_irqd->icu_group=ICU_GRP_NSR;icu_irqd->icu=icu;err=platform_msi_domain_alloc(domain,virq,nr_irqs);
@@ -211,9 +243,9 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)structirq_domain*irq_domain;structmvebu_icu*icu;-icu=dev_get_drvdata(&pdev->dev);-if(!icu)-return-ENODEV;+icu=mvebu_icu_dev_get_drvdata(pdev);+if(IS_ERR(icu))+returnPTR_ERR(icu);pdev->dev.msi_domain=of_msi_get_domain(&pdev->dev,pdev->dev.of_node,DOMAIN_BUS_PLATFORM_MSI);
@@ -236,6 +268,22 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev)return0;}+staticconststructof_device_idmvebu_icu_subset_of_match[]={+{+.compatible="marvell,cp110-icu-nsr",+},+{},+};++staticstructplatform_drivermvebu_icu_subset_driver={+.probe=mvebu_icu_subset_probe,+.driver={+.name="mvebu-icu-subset",+.of_match_table=mvebu_icu_subset_of_match,+},+};+builtin_platform_driver(mvebu_icu_subset_driver);+staticstructregmap_configmvebu_icu_regmap_config={.reg_bits=32,.val_bits=32,
@@ -275,6 +323,15 @@ static int mvebu_icu_probe(struct platform_device *pdev)if(!icu->irq_chip.name)return-ENOMEM;+/*+*Legacybindings:ICUisonenodewithoneMSIparent:forcemanually+*theprobeoftheNSRinterruptsside.+*Newbindings:ICUnodehaschildren,oneperinterruptcontroller+*havingitsownMSIparent:callplatform_populate().+*/+if(!of_get_child_count(pdev->dev.of_node))+icu->legacy_bindings=true;+icu->irq_chip.irq_mask=irq_chip_mask_parent;icu->irq_chip.irq_unmask=irq_chip_unmask_parent;icu->irq_chip.irq_eoi=irq_chip_eoi_parent;
@@ -299,7 +356,10 @@ static int mvebu_icu_probe(struct platform_device *pdev)platform_set_drvdata(pdev,icu);-returnmvebu_icu_subset_probe(pdev);+if(icu->legacy_bindings)+returnmvebu_icu_subset_probe(pdev);+else+returndevm_of_platform_populate(&pdev->dev);}staticconststructof_device_idmvebu_icu_of_match[]={
This is a cascaded interrupt controller in the AP806 GIC that collapses
SEIs (System Error Interrupt) coming from the AP and the CPs (through
the ICU).
The SEI handles up to 64 interrupts. The first 21 interrupts are wired
from the AP. The next 43 interrupts are from the CPs and are triggered
through MSI messages. To handle this complexity, the driver has to
declare to the upper layer: one IRQ domain for the wired interrupts,
one IRQ domain for the MSIs; and acts as a MSI controller ('parent')
by declaring an MSI domain.
Suggested-by: Haim Boot <redacted>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/Kconfig | 3 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-mvebu-sei.c | 444 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 448 insertions(+)
create mode 100644 drivers/irqchip/irq-mvebu-sei.c
@@ -0,0 +1,444 @@+// SPDX-License-Identifier: GPL-2.0++#define pr_fmt(fmt) "mvebu-sei: " fmt++#include<linux/irq.h>+#include<linux/interrupt.h>+#include<linux/irqchip/chained_irq.h>+#include<linux/irqdomain.h>+#include<linux/kernel.h>+#include<linux/of_address.h>+#include<linux/of_irq.h>+#include<linux/of_platform.h>+#include<linux/msi.h>+#include<linux/platform_device.h>+#include<linux/irqchip.h>++#include<dt-bindings/interrupt-controller/arm-gic.h>++/* Cause register */+#define GICP_SECR(idx) (0x0 + ((idx) * 0x4))+/* Mask register */+#define GICP_SEMR(idx) (0x20 + ((idx) * 0x4))+#define GICP_SET_SEI_OFFSET 0x30++#define SEI_IRQ_COUNT_PER_REG 32+#define SEI_IRQ_REG_COUNT 2+#define SEI_IRQ_COUNT (SEI_IRQ_COUNT_PER_REG * SEI_IRQ_REG_COUNT)+#define SEI_IRQ_REG_IDX(irq_id) ((irq_id) / SEI_IRQ_COUNT_PER_REG)+#define SEI_IRQ_REG_BIT(irq_id) ((irq_id) % SEI_IRQ_COUNT_PER_REG)++structmvebu_sei_interrupt_range{+u32first;+u32number;+};++structmvebu_sei{+structdevice*dev;+void__iomem*base;+structresource*res;+structirq_domain*ap_domain;+structirq_domain*cp_domain;+structmvebu_sei_interrupt_rangeap_interrupts;+structmvebu_sei_interrupt_rangecp_interrupts;+/* Lock on MSI allocations/releases */+structmutexcp_msi_lock;+DECLARE_BITMAP(cp_msi_bitmap,SEI_IRQ_COUNT);+};++staticintmvebu_sei_domain_to_sei_irq(structmvebu_sei*sei,+structirq_domain*domain,+irq_hw_number_thwirq)+{+if(domain==sei->ap_domain)+returnsei->ap_interrupts.first+hwirq;+else+returnsei->cp_interrupts.first+hwirq;+}++staticvoidmvebu_sei_reset(structmvebu_sei*sei)+{+u32reg_idx;++/* Clear IRQ cause registers */+for(reg_idx=0;reg_idx<SEI_IRQ_REG_COUNT;reg_idx++)+writel(0xFFFFFFFF,sei->base+GICP_SECR(reg_idx));+}++staticvoidmvebu_sei_mask_irq(structirq_data*d)+{+structmvebu_sei*sei=irq_data_get_irq_chip_data(d);+u32sei_irq=mvebu_sei_domain_to_sei_irq(sei,d->domain,d->hwirq);+u32reg_idx=SEI_IRQ_REG_IDX(sei_irq);+u32reg;++/* 1 disables the interrupt */+reg=readl(sei->base+GICP_SEMR(reg_idx));+reg|=BIT(SEI_IRQ_REG_BIT(sei_irq));+writel(reg,sei->base+GICP_SEMR(reg_idx));+}++staticvoidmvebu_sei_unmask_irq(structirq_data*d)+{+structmvebu_sei*sei=irq_data_get_irq_chip_data(d);+u32sei_irq=mvebu_sei_domain_to_sei_irq(sei,d->domain,d->hwirq);+u32reg_idx=SEI_IRQ_REG_IDX(sei_irq);+u32reg;++/* 0 enables the interrupt */+reg=readl(sei->base+GICP_SEMR(reg_idx));+reg&=~BIT(SEI_IRQ_REG_BIT(sei_irq));+writel(reg,sei->base+GICP_SEMR(reg_idx));+}++staticvoidmvebu_sei_compose_msi_msg(structirq_data*data,+structmsi_msg*msg)+{+structmvebu_sei*sei=data->chip_data;+phys_addr_tset=sei->res->start+GICP_SET_SEI_OFFSET;++msg->data=mvebu_sei_domain_to_sei_irq(sei,data->domain,data->hwirq);+msg->address_lo=lower_32_bits(set);+msg->address_hi=upper_32_bits(set);+}++staticintmvebu_sei_ap_set_type(structirq_data*data,unsignedinttype)+{+if(!(type&IRQ_TYPE_LEVEL_HIGH))+return-EINVAL;++return0;+}++staticintmvebu_sei_cp_set_type(structirq_data*data,unsignedinttype)+{+if(!(type&IRQ_TYPE_EDGE_RISING))+return-EINVAL;++return0;+}++staticstructirq_chipmvebu_sei_ap_wired_irq_chip={+.name="AP wired SEI",+.irq_mask=mvebu_sei_mask_irq,+.irq_unmask=mvebu_sei_unmask_irq,+.irq_eoi=irq_chip_eoi_parent,+.irq_set_affinity=irq_chip_set_affinity_parent,+.irq_set_type=mvebu_sei_ap_set_type,+};++staticstructirq_chipmvebu_sei_cp_msi_irq_chip={+.name="CP MSI SEI",+.irq_mask=mvebu_sei_mask_irq,+.irq_unmask=mvebu_sei_unmask_irq,+.irq_eoi=irq_chip_eoi_parent,+.irq_set_affinity=irq_chip_set_affinity_parent,+.irq_set_type=mvebu_sei_cp_set_type,+.irq_compose_msi_msg=mvebu_sei_compose_msi_msg,+};++staticintmvebu_sei_irq_domain_alloc(structirq_domain*domain,+unsignedintvirq,unsignedintnr_irqs,+void*args)+{+structmvebu_sei*sei=domain->host_data;+structirq_fwspec*fwspec=args;+structirq_chip*irq_chip;+intsei_hwirq,hwirq;+intret;++/* The software only supports single allocations for now */+if(nr_irqs!=1)+return-ENOTSUPP;++if(domain==sei->ap_domain){+irq_chip=&mvebu_sei_ap_wired_irq_chip;+hwirq=fwspec->param[0];+}else{+irq_chip=&mvebu_sei_cp_msi_irq_chip;+mutex_lock(&sei->cp_msi_lock);+hwirq=bitmap_find_free_region(sei->cp_msi_bitmap,+sei->cp_interrupts.number,0);+mutex_unlock(&sei->cp_msi_lock);+if(hwirq<0)+return-ENOSPC;+}++sei_hwirq=mvebu_sei_domain_to_sei_irq(sei,domain,hwirq);++fwspec->fwnode=domain->parent->fwnode;+fwspec->param_count=3;+fwspec->param[0]=GIC_SPI;+fwspec->param[1]=sei_hwirq;+fwspec->param[2]=IRQ_TYPE_EDGE_RISING;++ret=irq_domain_alloc_irqs_parent(domain,virq,nr_irqs,fwspec);+if(ret)+gotorelease_region;++ret=irq_domain_set_hwirq_and_chip(domain,virq,hwirq,irq_chip,sei);+if(ret)+gotofree_irq_parents;++return0;++free_irq_parents:+irq_domain_free_irqs_parent(domain,virq,nr_irqs);+release_region:+if(domain==sei->cp_domain){+mutex_lock(&sei->cp_msi_lock);+bitmap_release_region(sei->cp_msi_bitmap,hwirq,0);+mutex_unlock(&sei->cp_msi_lock);+}++returnret;+}++staticvoidmvebu_sei_irq_domain_free(structirq_domain*domain,+unsignedintvirq,unsignedintnr_irqs)+{+structmvebu_sei*sei=domain->host_data;+structirq_data*d=irq_domain_get_irq_data(domain,virq);+u32irq_nb=sei->ap_interrupts.number+sei->cp_interrupts.number;++if(nr_irqs!=1||d->hwirq>=irq_nb){+dev_err(sei->dev,"Invalid hwirq %lu\n",d->hwirq);+return;+}++irq_domain_free_irqs_parent(domain,virq,nr_irqs);++mutex_lock(&sei->cp_msi_lock);+bitmap_release_region(sei->cp_msi_bitmap,d->hwirq,0);+mutex_unlock(&sei->cp_msi_lock);+}++staticintmvebu_sei_ap_match(structirq_domain*d,structdevice_node*node,+enumirq_domain_bus_tokenbus_token)+{+structmvebu_sei*sei=d->host_data;++if(!sei)+return0;++if(sei->dev->of_node!=node)+return0;++if(!d||!sei->ap_domain)+return0;++if(d==sei->ap_domain)+return1;++return0;+}++staticconststructirq_domain_opsmvebu_sei_ap_domain_ops={+.match=mvebu_sei_ap_match,+.xlate=irq_domain_xlate_onecell,+.alloc=mvebu_sei_irq_domain_alloc,+.free=mvebu_sei_irq_domain_free,+};++staticconststructirq_domain_opsmvebu_sei_cp_domain_ops={+.alloc=mvebu_sei_irq_domain_alloc,+.free=mvebu_sei_irq_domain_free,+};++staticstructirq_chipmvebu_sei_msi_irq_chip={+.name="SEI MSI controller",+.irq_set_type=mvebu_sei_cp_set_type,+};++staticstructmsi_domain_opsmvebu_sei_msi_ops={+};++staticstructmsi_domain_infomvebu_sei_msi_domain_info={+.flags=MSI_FLAG_USE_DEF_DOM_OPS|MSI_FLAG_USE_DEF_CHIP_OPS,+.ops=&mvebu_sei_msi_ops,+.chip=&mvebu_sei_msi_irq_chip,+};++staticvoidmvebu_sei_handle_cascade_irq(structirq_desc*desc)+{+structmvebu_sei*sei=irq_desc_get_handler_data(desc);+structirq_chip*chip=irq_desc_get_chip(desc);+unsignedlongirqmap,irq_bit;+u32reg_idx,virq,irqn;++chained_irq_enter(chip,desc);++/* Read both SEI cause registers (64 bits) */+for(reg_idx=0;reg_idx<SEI_IRQ_REG_COUNT;reg_idx++){+irqmap=readl_relaxed(sei->base+GICP_SECR(reg_idx));++/* Call handler for each set bit */+for_each_set_bit(irq_bit,&irqmap,SEI_IRQ_COUNT_PER_REG){+/* Cause Register gives the SEI number */+irqn=irq_bit+reg_idx*SEI_IRQ_COUNT_PER_REG;+/*+*FindingLinuxmapping(virq)needstherightdomain+*andtherelativehwirq(whichstartat0inboth+*cases,whileirqnisrelativetoallSEIinterrupts).+*/+if(irqn<sei->ap_interrupts.number){+virq=irq_find_mapping(sei->ap_domain,irqn);+}else{+irqn-=sei->ap_interrupts.number;+virq=irq_find_mapping(sei->cp_domain,irqn);+}++/* Call IRQ handler */+generic_handle_irq(virq);+}++/* Clear interrupt indication by writing 1 to it */+writel(irqmap,sei->base+GICP_SECR(reg_idx));+}++chained_irq_exit(chip,desc);+}++staticintmvebu_sei_probe(structplatform_device*pdev)+{+structdevice_node*node=pdev->dev.of_node,*parent;+structirq_domain*parent_domain,*plat_domain;+structmvebu_sei*sei;+const__be32*property;+u32parent_irq,size;+intret;++sei=devm_kzalloc(&pdev->dev,sizeof(*sei),GFP_KERNEL);+if(!sei)+return-ENOMEM;++sei->dev=&pdev->dev;++mutex_init(&sei->cp_msi_lock);++sei->res=platform_get_resource(pdev,IORESOURCE_MEM,0);+sei->base=devm_ioremap_resource(sei->dev,sei->res);+if(!sei->base){+dev_err(sei->dev,"Failed to remap SEI resource\n");+return-ENODEV;+}++mvebu_sei_reset(sei);++/*+*Reservethesingle(top-level)parentSPIIRQfromwhichallthe+*interruptshandledbythisdriverwillbesignaled.+*/+parent_irq=irq_of_parse_and_map(node,0);+if(parent_irq<=0){+dev_err(sei->dev,"Failed to retrieve top-level SPI IRQ\n");+return-ENODEV;+}++irq_set_chained_handler(parent_irq,mvebu_sei_handle_cascade_irq);+irq_set_handler_data(parent_irq,sei);++/*+*SEIscanbetriggeredfromtheAPthroughwiredinterruptsandfrom+*theCPsthroughMSIs.+*/++/* Get a reference to the parent domain to create a hierarchy */+parent=of_irq_find_parent(node);+if(!parent){+dev_err(sei->dev,"Failed to find parent IRQ node\n");+ret=-ENODEV;+gotodispose_irq;+}++parent_domain=irq_find_host(parent);+if(!parent_domain){+dev_err(sei->dev,"Failed to find parent IRQ domain\n");+ret=-ENODEV;+gotodispose_irq;+}++/*+*RetrievetheIRQorganization(AP/CP):theindexofthefirstoneand+*thenumberofthemforeachdomain.+*/+property=of_get_property(node,"marvell,sei-ap-ranges",&size);+if(!property||(size!=(2*sizeof(u32)))){+dev_err(sei->dev,"Missing 'marvell,sei-ap-ranges' property\n");+ret=-ENODEV;+gotodispose_irq;+}++sei->ap_interrupts.first=be32_to_cpu(property[0]);+sei->ap_interrupts.number=be32_to_cpu(property[1]);++property=of_get_property(node,"marvell,sei-cp-ranges",&size);+if(!property||(size!=(2*sizeof(u32)))){+dev_err(sei->dev,"Missing 'marvell,sei-cp-ranges' property\n");+ret=-ENODEV;+gotodispose_irq;+}++sei->cp_interrupts.first=be32_to_cpu(property[0]);+sei->cp_interrupts.number=be32_to_cpu(property[1]);++/* Create the 'wired' hierarchy */+sei->ap_domain=irq_domain_create_hierarchy(parent_domain,0,+sei->ap_interrupts.number,+of_node_to_fwnode(node),+&mvebu_sei_ap_domain_ops,+sei);+if(!sei->ap_domain){+dev_err(sei->dev,"Failed to create AP IRQ domain\n");+ret=-ENOMEM;+gotodispose_irq;+}++/* Create the 'MSI' hierarchy */+sei->cp_domain=irq_domain_create_hierarchy(parent_domain,0,+sei->cp_interrupts.number,+of_node_to_fwnode(node),+&mvebu_sei_cp_domain_ops,+sei);+if(!sei->cp_domain){+pr_err("Failed to create CPs IRQ domain\n");+ret=-ENOMEM;+gotoremove_ap_domain;+}++plat_domain=platform_msi_create_irq_domain(of_node_to_fwnode(node),+&mvebu_sei_msi_domain_info,+sei->cp_domain);+if(!plat_domain){+pr_err("Failed to create CPs MSI domain\n");+ret=-ENOMEM;+gotoremove_cp_domain;+}++platform_set_drvdata(pdev,sei);++return0;++remove_cp_domain:+irq_domain_remove(sei->cp_domain);+remove_ap_domain:+irq_domain_remove(sei->ap_domain);+dispose_irq:+irq_dispose_mapping(parent_irq);++returnret;+}++staticconststructof_device_idmvebu_sei_of_match[]={+{.compatible="marvell,armada-8k-sei",},+{},+};++staticstructplatform_drivermvebu_sei_driver={+.probe=mvebu_sei_probe,+.driver={+.name="mvebu-sei",+.of_match_table=mvebu_sei_of_match,+},+};+builtin_platform_driver(mvebu_sei_driver);
An SEI driver provides an MSI domain through which it is possible to
raise SEIs.
Handle the NSR probe function in a more generic way to support other
type of interrupts (ie. the SEIs).
For clarity we do not use tree IRQ domains for now but linear ones
instead, allocating the 207 ICU lines for each interrupt group.
Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 152 ++++++++++++++++++++++++++++++++++------
1 file changed, 129 insertions(+), 23 deletions(-)
@@ -76,16 +96,25 @@ static struct mvebu_icu *mvebu_icu_dev_get_drvdata(struct platform_device *pdev)returnicu;}-staticvoidmvebu_icu_init(structmvebu_icu*icu,structmsi_msg*msg)+staticvoidmvebu_icu_init(structmvebu_icu*icu,structirq_domain*d,+structmsi_msg*msg){-if(atomic_cmpxchg(&icu->initialized,false,true))+structmvebu_icu_msi_data*msi_data=platform_msi_get_host_data(d);+conststructmvebu_icu_subset_data*subset=msi_data->subset_data;++if(atomic_cmpxchg(&msi_data->initialized,false,true))+return;++/* Set 'SET' ICU SPI message address in AP */+regmap_write(icu->regmap,subset->offset_set_ah,msg[0].address_hi);+regmap_write(icu->regmap,subset->offset_set_al,msg[0].address_lo);++if(subset->icu_group!=ICU_GRP_NSR)return;-/* Set Clear/Set ICU SPI message address in AP */-regmap_write(icu->regmap,ICU_SETSPI_NSR_AH,msg[0].address_hi);-regmap_write(icu->regmap,ICU_SETSPI_NSR_AL,msg[0].address_lo);-regmap_write(icu->regmap,ICU_CLRSPI_NSR_AH,msg[1].address_hi);-regmap_write(icu->regmap,ICU_CLRSPI_NSR_AL,msg[1].address_lo);+/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */+regmap_write(icu->regmap,subset->offset_clr_ah,msg[1].address_hi);+regmap_write(icu->regmap,subset->offset_clr_al,msg[1].address_lo);}staticvoidmvebu_icu_write_msg(structmsi_desc*desc,structmsi_msg*msg)
@@ -96,8 +125,8 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)unsignedinticu_int;if(msg->address_lo||msg->address_hi){-/* One off initialization */-mvebu_icu_init(icu,msg);+/* One off initialization per domain */+mvebu_icu_init(icu,d->domain,msg);/* Configure the ICU with irq number & type */icu_int=msg->data|ICU_INT_ENABLE;if(icu_irqd->type&IRQ_TYPE_EDGE_RISING)
@@ -131,7 +160,8 @@ static intmvebu_icu_irq_domain_translate(structirq_domain*d,structirq_fwspec*fwspec,unsignedlong*hwirq,unsignedint*type){-structmvebu_icu*icu=platform_msi_get_host_data(d);+structmvebu_icu_msi_data*msi_data=platform_msi_get_host_data(d);+structmvebu_icu*icu=msi_data->icu;unsignedintparam_count=icu->legacy_bindings?3:2;/* Check the count of the parameters in dt */
@@ -143,7 +173,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,if(icu->legacy_bindings){*hwirq=fwspec->param[1];-*type=fwspec->param[2];+*type=fwspec->param[2]&IRQ_TYPE_SENSE_MASK;if(fwspec->param[0]!=ICU_GRP_NSR){dev_err(icu->dev,"wrong ICU group type %x\n",fwspec->param[0]);
@@ -159,12 +189,36 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,return-EINVAL;}-/* Mask the type to prevent wrong DT configuration */-*type=fwspec->param[2]&IRQ_TYPE_SENSE_MASK;+/*+*TheICUreceiveslevel-interrupts.MSISEIare+*edge-interruptswhileMSINSRarelevel-interrupts.Updatethetype+*accordinglyfortheparentirqchip.+*/+if(msi_data->subset_data->icu_group==ICU_GRP_SEI)+*type=IRQ_TYPE_EDGE_RISING;return0;}+staticintmvebu_icu_msi_bitmap_region_alloc(structmvebu_icu*icu,inthwirq)+{+intret;++mutex_lock(&icu->msi_lock);+ret=bitmap_allocate_region(icu->msi_bitmap,hwirq,0);+mutex_unlock(&icu->msi_lock);++returnret;+}++staticvoidmvebu_icu_msi_bitmap_region_release(structmvebu_icu*icu,+inthwirq)+{+mutex_lock(&icu->msi_lock);+bitmap_release_region(icu->msi_bitmap,hwirq,0);+mutex_unlock(&icu->msi_lock);+}+staticintmvebu_icu_irq_domain_alloc(structirq_domain*domain,unsignedintvirq,unsignedintnr_irqs,void*args)
@@ -172,7 +226,9 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,interr;unsignedlonghwirq;structirq_fwspec*fwspec=args;-structmvebu_icu*icu=platform_msi_get_host_data(domain);+structmvebu_icu_msi_data*msi_data=+platform_msi_get_host_data(domain);+structmvebu_icu*icu=msi_data->icu;structmvebu_icu_irq_data*icu_irqd;icu_irqd=kmalloc(sizeof(*icu_irqd),GFP_KERNEL);
@@ -186,16 +242,20 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,gotofree_irqd;}+err=mvebu_icu_msi_bitmap_region_alloc(icu,hwirq);+if(err)+gotofree_irqd;+if(icu->legacy_bindings)icu_irqd->icu_group=fwspec->param[0];else-icu_irqd->icu_group=ICU_GRP_NSR;+icu_irqd->icu_group=msi_data->subset_data->icu_group;icu_irqd->icu=icu;err=platform_msi_domain_alloc(domain,virq,nr_irqs);if(err){dev_err(icu->dev,"failed to allocate ICU interrupt in parent domain\n");-gotofree_irqd;+gotofree_bitmap;}/* Make sure there is no interrupt left pending by the firmware */
@@ -214,6 +274,8 @@ mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,free_msi:platform_msi_domain_free(domain,virq,nr_irqs);+free_bitmap:+mvebu_icu_msi_bitmap_region_release(icu,hwirq);free_irqd:kfree(icu_irqd);returnerr;
Change the documentation to reflect the new bindings used for Marvell
ICU. This involves describing each interrupt group as a subnode of the
ICU node. Each of them having their own compatible.
The DT binding documentation still documents the legacy binding, where
there was a single node with no subnode.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
.../bindings/interrupt-controller/marvell,icu.txt | 83 ++++++++++++++++++----
1 file changed, 71 insertions(+), 12 deletions(-)
@@ -5,6 +5,8 @@ The Marvell ICU (Interrupt Consolidation Unit) controller is responsible for collecting all wired-interrupt sources in the CP and communicating them to the GIC in the AP, the unit translates interrupt requests on input wires to MSG memory mapped transactions to the GIC.+These messages will access a different GIC memory area depending on+their type (NSR, SR, SEI, REI, etc). Required properties:
@@ -12,20 +14,19 @@ Required properties: - reg: Should contain ICU registers location and length.+Subnodes: Each group of interrupt is declared as a subnode of the ICU,+with their own compatible.++Required properties for the icu_nsr/icu_sei subnodes:++- compatible: Should be "marvell,cp110-icu-nsr" or "marvell,cp110-icu-sei".+ - #interrupt-cells: Specifies the number of cells needed to encode an- interrupt source. The value shall be 3.+ interrupt source. The value shall be 2.- The 1st cell is the group type of the ICU interrupt. Possible group- types are:+ The 1st cell is the index of the interrupt in the ICU unit.- ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure- ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure- ICU_GRP_SEI (0x4) : System error interrupt- ICU_GRP_REI (0x5) : RAM error interrupt-- The 2nd cell is the index of the interrupt in the ICU unit.-- The 3rd cell is the type of the interrupt. See arm,gic.txt for+ The 2nd cell is the type of the interrupt. See arm,gic.txt for details. - interrupt-controller: Identifies the node as an interrupt
@@ -35,17 +36,75 @@ Required properties: that allows to trigger interrupts using MSG memory mapped transactions.+Note: each 'interrupts' property referring to any 'icu_xxx' node shall+ have a different number within [0:206].+ Example: icu: interrupt-controller@1e0000 { compatible = "marvell,cp110-icu"; reg = <0x1e0000 0x440>;++ CP110_LABEL(icu_nsr): interrupt-controller@10 {+ compatible = "marvell,cp110-icu-nsr";+ reg = <0x10 0x20>;+ #interrupt-cells = <2>;+ interrupt-controller;+ msi-parent = <&gicp>;+ };++ CP110_LABEL(icu_sei): interrupt-controller@50 {+ compatible = "marvell,cp110-icu-sei";+ reg = <0x50 0x10>;+ #interrupt-cells = <2>;+ interrupt-controller;+ msi-parent = <&sei>;+ };+};++node1 {+ interrupt-parent = <&icu_nsr>;+ interrupts = <106 IRQ_TYPE_LEVEL_HIGH>;+};++node2 {+ interrupt-parent = <&icu_sei>;+ interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;+};++/* Would not work with the above nodes */+node3 {+ interrupt-parent = <&icu_nsr>;+ interrupts = <107 IRQ_TYPE_LEVEL_HIGH>;+};++Note on legacy bindings:+Before using a subnode for each domain, only NSR were+supported. Bindings were different in this way:++- #interrupt-cells: The value was 3.+ The 1st cell was the group type of the ICU interrupt. Possible+ group types were:+ ICU_GRP_NSR (0x0) : Shared peripheral interrupt, non-secure+ ICU_GRP_SR (0x1) : Shared peripheral interrupt, secure+ ICU_GRP_SEI (0x4) : System error interrupt+ ICU_GRP_REI (0x5) : RAM error interrupt+ The 2nd cell was the index of the interrupt in the ICU unit.+ The 3rd cell was the type of the interrupt. See arm,gic.txt for+ details.++Example:++icu: interrupt-controller@1e0000 {+ compatible = "marvell,cp110-icu";+ reg = <0x1e0000 0x440>;+ #interrupt-cells = <3>; interrupt-controller; msi-parent = <&gicp>; };-usb3h0: usb3@500000 {+node1 { interrupt-parent = <&icu>; interrupts = <ICU_GRP_NSR 106 IRQ_TYPE_LEVEL_HIGH>; };
Describe the System Error Interrupt (SEI) controller. It aggregates two
types of interrupts, wired and MSIs from respectively the AP and the
CPs, into a single SPI interrupt.
Suggested-by: Haim Boot <redacted>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
.../bindings/interrupt-controller/marvell,sei.txt | 39 ++++++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/marvell,sei.txt
@@ -0,0 +1,39 @@+Marvell SEI (System Error Interrupt) Controller+-----------------------------------------------++Marvell SEI (System Error Interrupt) controller is an interrupt+aggregator. It receives interrupts from several sources and aggregates+them to a single interrupt line (an SPI) on the parent interrupt+controller.++This interrupt controller can handle up to 64 SEIs, a set comes from the+AP and is wired while a second set comes from the CPs by the mean of+MSIs. Each 'domain' is represented as a subnode.++Required properties:++- compatible: should be "marvell,armada-8k-sei".+- reg: SEI registers location and length.+- interrupts: identifies the parent IRQ that will be triggered.+- marvell,sei-ranges: two ranges, the first one describe wired+ interrupts coming from the AP, the second one+ non-wired interrupts (MSI) from the CPs.+- #interrupt-cells: number of cells to define an SEI wired interrupt+ coming from the AP, should be 1. The cell is the IRQ+ number.+- interrupt-controller: identifies the node as an interrupt controller+ (AP only).+- msi-controller: identifies the node as an MSI controller (CPs only).++Example:++ sei: interrupt-controller@3f0200 {+ compatible = "marvell,armada-8k-sei";+ reg = <0x3f0200 0x40>;+ interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;+ marvell,sei-ap-ranges = <0 21>;+ marvell,sei-cp-ranges = <21 43>;+ #interrupt-cells = <1>;+ interrupt-controller;+ msi-controller;+ };
Add the System Error Interrupt node, representing an IRQ chip which is
part of the GIC. The SEI node has two subnodes, one for each interrupt
domain: wired (from the AP) and not-wired (MSIs from the CPs).
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
arch/arm64/boot/dts/marvell/armada-ap806.dtsi | 11 +++++++++++
1 file changed, 11 insertions(+)
Create an ICU subnode for the NSR interrupts. This subnode becomes the
CP110 interrupt parent, removing the need for the ICU_GRP_NSR parameter.
Move all DT110 nodes to use these new bindings.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 117 ++++++++++++++------------
1 file changed, 62 insertions(+), 55 deletions(-)
The ICU handles several interrupt groups, each of them being a subpart
of the ICU node.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 8 ++++++++
1 file changed, 8 insertions(+)
ICU size in CP110 is not 0x10 but at least 0x440 bytes long (from the
specification).
Fixes: 6ef84a827c37 ("arm64: dts: marvell: enable GICP and ICU on Armada 7K/8K")
Cc: stable@vger.kernel.org
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
arch/arm64/boot/dts/marvell/armada-cp110.dtsi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Marc Zyngier <hidden> Date: 2018-06-28 11:12:04
On 22/06/18 16:14, Miquel Raynal wrote:
quoted hunk
platform_msi_alloc_priv_data() checks that a number of interrupts is
always given. This extra-check has no real impact and just prevents
uselessly the user to create an MSI tree domain: remove it.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/base/platform-msi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,*accordingly(whichwouldimpactthemaxnumberofMSI*capabledevices).*/-if(!dev->msi_domain||!write_msi_msg||!nvec||nvec>MAX_DEV_MSIS)+if(!dev->msi_domain||!write_msi_msg||nvec>MAX_DEV_MSIS)returnERR_PTR(-EINVAL);if(dev->msi_domain->bus_token!=DOMAIN_BUS_PLATFORM_MSI){
Huh... It's not that simple.
Yes, it allows you to get a tree via platform_msi_create_device_domain
(assuming that's why you're changing it -- your commit message doesn't
say much) but it also has some impact on the way msi_domain_prepare_irqs
works (see how it is called from platform_msi_create_device_domain).
Importantly, the msi_prepare callback takes nvec as a parameter, and
that ends up trickling down to the irqchip, or whatever will setup the
MSI domain. Things like the GICv3 ITS do rely on that to carve out the
LPI space that subsequently gets used to populate the domain.
So no, you can't do it like that. If you really want a tree, add a
helper that does so.
Thanks,
M.
--
Jazz is not dead. It just smells funny...
From: Marc Zyngier <hidden> Date: 2018-06-28 12:05:05
On 22/06/18 16:14, Miquel Raynal wrote:
Before splitting the code to support multiple platform devices to
be probed (one for the ICU, one per interrupt group), let's switch to
regmap first by creating one in the ->probe().
What's the benefit of doing so? I assume that has to do with supporting
multiple devices that share an MMIO range?
@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return;/* Set Clear/Set ICU SPI message address in AP */-writel_relaxed(msg[0].address_hi,icu->base+ICU_SETSPI_NSR_AH);-writel_relaxed(msg[0].address_lo,icu->base+ICU_SETSPI_NSR_AL);-writel_relaxed(msg[1].address_hi,icu->base+ICU_CLRSPI_NSR_AH);-writel_relaxed(msg[1].address_lo,icu->base+ICU_CLRSPI_NSR_AL);+regmap_write(icu->regmap,ICU_SETSPI_NSR_AH,msg[0].address_hi);+regmap_write(icu->regmap,ICU_SETSPI_NSR_AL,msg[0].address_lo);+regmap_write(icu->regmap,ICU_CLRSPI_NSR_AH,msg[1].address_hi);+regmap_write(icu->regmap,ICU_CLRSPI_NSR_AL,msg[1].address_lo);
Isn't this a change in the way we write things to the MMIO registers?
You're now trading a writel_relaxed for a writel, plus some locking...
Talking about which: Are you always in a context where you can take that
lock? The bit of documentation I've just read seems to imply that the
default lock is a mutex. Is that always safe? My guess is that it isn't,
and any callback that can end-up here after having taken something like
the desc lock is going to blow in your face.
Have you tried lockdep?
From: Marc Zyngier <hidden> Date: 2018-06-28 12:10:05
On 22/06/18 16:14, Miquel Raynal wrote:
quoted hunk
Make the current MSI irq_domain local to ease the split between ICU
platform device code and NSR platform device code.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
@@ -218,6 +217,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)structmvebu_icu*icu;structdevice_node*node=pdev->dev.of_node;structdevice_node*gicp_dn;+structirq_domain*irq_domain;structresource*res;void__iomem*regs;inti;
@@ -282,11 +282,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write(icu->regmap,ICU_INT_CFG(i),0);}-icu->domain=+irq_domain=platform_msi_create_device_domain(&pdev->dev,ICU_MAX_IRQS,mvebu_icu_write_msg,&mvebu_icu_domain_ops,icu);
nit: this really hurts my eyes. Please put the = sign and the function
call on a single line. I really don't care if it checkpatch is having a
fit because of that.
quoted hunk
- if (!icu->domain) {+ if (!irq_domain) { dev_err(&pdev->dev, "Failed to create ICU domain\n"); return -ENOMEM; }
But looking at the next patch, you might as well fold the two. On its
own, this patches is pretty pointless.
Thanks,
M.
--
Jazz is not dead. It just smells funny...
From: Marc Zyngier <hidden> Date: 2018-06-28 12:24:05
On 22/06/18 16:14, Miquel Raynal wrote:
quoted hunk
NSR (non-secure interrupts) are handled in the ICU driver like if there
was only this type of interrupt in the ICU. Change this behavior to
prepare the introduction of SEI (System Error Interrupts) support by
moving the NSR code in a separate function. This is done under the form
of a 'probe' function to ease future migration to NSR/SEI being platform
devices part of the ICU.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 58 +++++++++++++++++++++++------------------
1 file changed, 33 insertions(+), 25 deletions(-)
@@ -255,19 +283,6 @@ static int mvebu_icu_probe(struct platform_device *pdev)icu->irq_chip.irq_set_affinity=irq_chip_set_affinity_parent;#endif-/*-*We'reprobedafterMSIdomainshavebeenresolved,soforce-*resolutionhere.-*/-pdev->dev.msi_domain=of_msi_get_domain(&pdev->dev,node,-DOMAIN_BUS_PLATFORM_MSI);-if(!pdev->dev.msi_domain)-return-EPROBE_DEFER;--gicp_dn=irq_domain_get_of_node(pdev->dev.msi_domain);-if(!gicp_dn)-return-ENODEV;-/**CleanallICUinterruptswithtypeSPI_NSR,requiredto*avoidunpredictableSPIassignmentsdonebyfirmware.
@@ -282,16 +297,9 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write(icu->regmap,ICU_INT_CFG(i),0);}-irq_domain=-platform_msi_create_device_domain(&pdev->dev,ICU_MAX_IRQS,-mvebu_icu_write_msg,-&mvebu_icu_domain_ops,icu);-if(!irq_domain){-dev_err(&pdev->dev,"Failed to create ICU domain\n");-return-ENOMEM;-}+platform_set_drvdata(pdev,icu);
What is the upshot of passing this icu pointer as part of the platform
device structure instead of simply passing it as a parameter? Is
anything else using it?
Thanks,
M.
--
Jazz is not dead. It just smells funny...
From: Marc Zyngier <hidden> Date: 2018-06-28 12:45:09
On 22/06/18 16:14, Miquel Raynal wrote:
quoted hunk
The ICU can handle several type of interrupt, each of them being handled
differently on AP side. On CP side, the ICU should be able to make the
distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU
node as the parent of multiple ICU sub-nodes, each of them being an
interrupt type with a different interrupt parent. ICU interrupt 'clients'
now directly point to the right sub-node, avoiding the need for the extra
ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If
the node as no child, the probe function for NSRs will still be called
'manually' in order to preserve backward compatibility with DT using the
old binding.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++-------
1 file changed, 74 insertions(+), 14 deletions(-)
Doesn't this make legacy_bindings completely redundant? Either the
pointer is !NULL in the device, and this is using a legacy binging, or
it is stored in the parent, and this is a new binding. You could even
have a helper for that:
static bool is_legacy(struct platform_device *pdev)
{
return !dev_get_drvdata(&pdev->dev);
}
The driver really doesn't need to be defending against itself, if
anything, and it would save you quite a bit of error handling in the
callers of this function.
@@ -275,6 +323,15 @@ static int mvebu_icu_probe(struct platform_device *pdev) if (!icu->irq_chip.name) return -ENOMEM;+ /*+ * Legacy bindings: ICU is one node with one MSI parent: force manually+ * the probe of the NSR interrupts side.+ * New bindings: ICU node has children, one per interrupt controller+ * having its own MSI parent: call platform_populate().+ */+ if (!of_get_child_count(pdev->dev.of_node))+ icu->legacy_bindings = true;+ icu->irq_chip.irq_mask = irq_chip_mask_parent; icu->irq_chip.irq_unmask = irq_chip_unmask_parent; icu->irq_chip.irq_eoi = irq_chip_eoi_parent;
From: Marc Zyngier <hidden> Date: 2018-06-28 14:54:30
On 22/06/18 16:14, Miquel Raynal wrote:
quoted hunk
This is a cascaded interrupt controller in the AP806 GIC that collapses
SEIs (System Error Interrupt) coming from the AP and the CPs (through
the ICU).
The SEI handles up to 64 interrupts. The first 21 interrupts are wired
from the AP. The next 43 interrupts are from the CPs and are triggered
through MSI messages. To handle this complexity, the driver has to
declare to the upper layer: one IRQ domain for the wired interrupts,
one IRQ domain for the MSIs; and acts as a MSI controller ('parent')
by declaring an MSI domain.
Suggested-by: Haim Boot <redacted>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/Kconfig | 3 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-mvebu-sei.c | 444 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 448 insertions(+)
create mode 100644 drivers/irqchip/irq-mvebu-sei.c
I think it'd be a lot clearer if you just read both 32bit registers and
treat the resulting 64bit quantity as a single bitmap, rather than
having this outer loop.
quoted hunk
++ /* Call handler for each set bit */+ for_each_set_bit(irq_bit, &irqmap, SEI_IRQ_COUNT_PER_REG) {+ /* Cause Register gives the SEI number */+ irqn = irq_bit + reg_idx * SEI_IRQ_COUNT_PER_REG;+ /*+ * Finding Linux mapping (virq) needs the right domain+ * and the relative hwirq (which start at 0 in both+ * cases, while irqn is relative to all SEI interrupts).+ */+ if (irqn < sei->ap_interrupts.number) {+ virq = irq_find_mapping(sei->ap_domain, irqn);+ } else {+ irqn -= sei->ap_interrupts.number;+ virq = irq_find_mapping(sei->cp_domain, irqn);+ }++ /* Call IRQ handler */+ generic_handle_irq(virq);+ }++ /* Clear interrupt indication by writing 1 to it */+ writel(irqmap, sei->base + GICP_SECR(reg_idx));
write_relaxed. It would probably make sense not to write to that
register if no bits were set to 1 the first place.
quoted hunk
+ }++ chained_irq_exit(chip, desc);+}++static int mvebu_sei_probe(struct platform_device *pdev)+{+ struct device_node *node = pdev->dev.of_node, *parent;+ struct irq_domain *parent_domain, *plat_domain;+ struct mvebu_sei *sei;+ const __be32 *property;+ u32 parent_irq, size;+ int ret;++ sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL);+ if (!sei)+ return -ENOMEM;++ sei->dev = &pdev->dev;++ mutex_init(&sei->cp_msi_lock);++ sei->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);+ sei->base = devm_ioremap_resource(sei->dev, sei->res);+ if (!sei->base) {+ dev_err(sei->dev, "Failed to remap SEI resource\n");+ return -ENODEV;+ }++ mvebu_sei_reset(sei);++ /*+ * Reserve the single (top-level) parent SPI IRQ from which all the+ * interrupts handled by this driver will be signaled.+ */+ parent_irq = irq_of_parse_and_map(node, 0);+ if (parent_irq <= 0) {+ dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n");+ return -ENODEV;+ }++ irq_set_chained_handler(parent_irq, mvebu_sei_handle_cascade_irq);+ irq_set_handler_data(parent_irq, sei);++ /*+ * SEIs can be triggered from the AP through wired interrupts and from+ * the CPs through MSIs.+ */++ /* Get a reference to the parent domain to create a hierarchy */+ parent = of_irq_find_parent(node);+ if (!parent) {+ dev_err(sei->dev, "Failed to find parent IRQ node\n");+ ret = -ENODEV;+ goto dispose_irq;+ }
Wouldn't it make sense to check this before having configured things
with a chained handler? i.e. check everything that can be checked, and
once you have everything in order, do the plumbing?
quoted hunk
++ parent_domain = irq_find_host(parent);+ if (!parent_domain) {+ dev_err(sei->dev, "Failed to find parent IRQ domain\n");+ ret = -ENODEV;+ goto dispose_irq;+ }++ /*+ * Retrieve the IRQ organization (AP/CP): the index of the first one and+ * the number of them for each domain.+ */+ property = of_get_property(node, "marvell,sei-ap-ranges", &size);+ if (!property || (size != (2 * sizeof(u32)))) {+ dev_err(sei->dev, "Missing 'marvell,sei-ap-ranges' property\n");+ ret = -ENODEV;+ goto dispose_irq;+ }++ sei->ap_interrupts.first = be32_to_cpu(property[0]);+ sei->ap_interrupts.number = be32_to_cpu(property[1]);++ property = of_get_property(node, "marvell,sei-cp-ranges", &size);+ if (!property || (size != (2 * sizeof(u32)))) {+ dev_err(sei->dev, "Missing 'marvell,sei-cp-ranges' property\n");+ ret = -ENODEV;+ goto dispose_irq;+ }++ sei->cp_interrupts.first = be32_to_cpu(property[0]);+ sei->cp_interrupts.number = be32_to_cpu(property[1]);++ /* Create the 'wired' hierarchy */+ sei->ap_domain = irq_domain_create_hierarchy(parent_domain, 0,+ sei->ap_interrupts.number,+ of_node_to_fwnode(node),+ &mvebu_sei_ap_domain_ops,+ sei);+ if (!sei->ap_domain) {+ dev_err(sei->dev, "Failed to create AP IRQ domain\n");+ ret = -ENOMEM;+ goto dispose_irq;+ }++ /* Create the 'MSI' hierarchy */+ sei->cp_domain = irq_domain_create_hierarchy(parent_domain, 0,+ sei->cp_interrupts.number,+ of_node_to_fwnode(node),+ &mvebu_sei_cp_domain_ops,+ sei);+ if (!sei->cp_domain) {+ pr_err("Failed to create CPs IRQ domain\n");+ ret = -ENOMEM;+ goto remove_ap_domain;+ }++ plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),+ &mvebu_sei_msi_domain_info,+ sei->cp_domain);+ if (!plat_domain) {+ pr_err("Failed to create CPs MSI domain\n");+ ret = -ENOMEM;+ goto remove_cp_domain;+ }++ platform_set_drvdata(pdev, sei);++ return 0;++remove_cp_domain:+ irq_domain_remove(sei->cp_domain);+remove_ap_domain:+ irq_domain_remove(sei->ap_domain);+dispose_irq:+ irq_dispose_mapping(parent_irq);++ return ret;+}++static const struct of_device_id mvebu_sei_of_match[] = {+ { .compatible = "marvell,armada-8k-sei", },+ {},+};++static struct platform_driver mvebu_sei_driver = {+ .probe = mvebu_sei_probe,+ .driver = {+ .name = "mvebu-sei",+ .of_match_table = mvebu_sei_of_match,+ },+};+builtin_platform_driver(mvebu_sei_driver);
Thanks,
M.
--
Jazz is not dead. It just smells funny...
From: Marc Zyngier <hidden> Date: 2018-06-28 16:49:55
On 22/06/18 16:14, Miquel Raynal wrote:
An SEI driver provides an MSI domain through which it is possible to
raise SEIs.
Handle the NSR probe function in a more generic way to support other
type of interrupts (ie. the SEIs).
For clarity we do not use tree IRQ domains for now but linear ones
instead, allocating the 207 ICU lines for each interrupt group.
Is that still the truth? or just a stale comment?
quoted hunk
Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 152 ++++++++++++++++++++++++++++++++++------
1 file changed, 129 insertions(+), 23 deletions(-)
I'm a bit lost here. Can you have more than a single mvebu_icu_msi_data
per subset_data? I though you'd only have one. If so, what's the benefit
of having a separate structure? Am I missing something?
Since you don't use the domain for anything else than obtaining the
subset_data, why don't you directly pass that subset_data as a
parameter? Here, you are mixing two different abstraction levels, and
that makes the whole thing really bizarre.
quoted hunk
++ if (atomic_cmpxchg(&msi_data->initialized, false, true))+ return;++ /* Set 'SET' ICU SPI message address in AP */+ regmap_write(icu->regmap, subset->offset_set_ah, msg[0].address_hi);+ regmap_write(icu->regmap, subset->offset_set_al, msg[0].address_lo);++ if (subset->icu_group != ICU_GRP_NSR) return;- /* Set Clear/Set ICU SPI message address in AP */- regmap_write(icu->regmap, ICU_SETSPI_NSR_AH, msg[0].address_hi);- regmap_write(icu->regmap, ICU_SETSPI_NSR_AL, msg[0].address_lo);- regmap_write(icu->regmap, ICU_CLRSPI_NSR_AH, msg[1].address_hi);- regmap_write(icu->regmap, ICU_CLRSPI_NSR_AL, msg[1].address_lo);+ /* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */+ regmap_write(icu->regmap, subset->offset_clr_ah, msg[1].address_hi);+ regmap_write(icu->regmap, subset->offset_clr_al, msg[1].address_lo); } static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
@@ -96,8 +125,8 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) unsigned int icu_int; if (msg->address_lo || msg->address_hi) {- /* One off initialization */- mvebu_icu_init(icu, msg);+ /* One off initialization per domain */+ mvebu_icu_init(icu, d->domain, msg); /* Configure the ICU with irq number & type */ icu_int = msg->data | ICU_INT_ENABLE; if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
@@ -131,7 +160,8 @@ static int mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) {- struct mvebu_icu *icu = platform_msi_get_host_data(d);+ struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);+ struct mvebu_icu *icu = msi_data->icu; unsigned int param_count = icu->legacy_bindings ? 3 : 2; /* Check the count of the parameters in dt */
@@ -143,7 +173,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, if (icu->legacy_bindings) { *hwirq = fwspec->param[1];- *type = fwspec->param[2];+ *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; if (fwspec->param[0] != ICU_GRP_NSR) { dev_err(icu->dev, "wrong ICU group type %x\n", fwspec->param[0]);
@@ -341,7 +444,7 @@ static int mvebu_icu_probe(struct platform_device *pdev) #endif /*- * Clean all ICU interrupts with type SPI_NSR, required to+ * Clean all ICU interrupts of type NSR and SEI, required to * avoid unpredictable SPI assignments done by firmware. */ for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
I'm close to loosing the plot with this driver, so I'll stop here and
see what you have to say... ;-)
Thanks,
M.
--
Jazz is not dead. It just smells funny...
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 17:49:55
+0100:
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
An SEI driver provides an MSI domain through which it is possible to
raise SEIs.
Handle the NSR probe function in a more generic way to support other
type of interrupts (ie. the SEIs).
For clarity we do not use tree IRQ domains for now but linear ones
instead, allocating the 207 ICU lines for each interrupt group.
Is that still the truth? or just a stale comment?
Stale comment, will remove it.
quoted
Reallocating an ICU slot is prevented by the use of an ICU-wide bitmap.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 152 ++++++++++++++++++++++++++++++++++------
1 file changed, 129 insertions(+), 23 deletions(-)
I'm a bit lost here. Can you have more than a single mvebu_icu_msi_data
per subset_data? I though you'd only have one. If so, what's the benefit
of having a separate structure? Am I missing something?
The reason for having a pointer on another structure is because I use
of_device_get_match_data() to retrieve a different structure per compatible
(ie. per subset).
Since you don't use the domain for anything else than obtaining the
subset_data, why don't you directly pass that subset_data as a
parameter? Here, you are mixing two different abstraction levels, and
that makes the whole thing really bizarre.
Indeed. Will edit this.
quoted
++ if (atomic_cmpxchg(&msi_data->initialized, false, true))+ return;++ /* Set 'SET' ICU SPI message address in AP */+ regmap_write(icu->regmap, subset->offset_set_ah, msg[0].address_hi);+ regmap_write(icu->regmap, subset->offset_set_al, msg[0].address_lo);++ if (subset->icu_group != ICU_GRP_NSR) return;- /* Set Clear/Set ICU SPI message address in AP */- regmap_write(icu->regmap, ICU_SETSPI_NSR_AH, msg[0].address_hi);- regmap_write(icu->regmap, ICU_SETSPI_NSR_AL, msg[0].address_lo);- regmap_write(icu->regmap, ICU_CLRSPI_NSR_AH, msg[1].address_hi);- regmap_write(icu->regmap, ICU_CLRSPI_NSR_AL, msg[1].address_lo);+ /* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */+ regmap_write(icu->regmap, subset->offset_clr_ah, msg[1].address_hi);+ regmap_write(icu->regmap, subset->offset_clr_al, msg[1].address_lo); } static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
@@ -96,8 +125,8 @@ static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) unsigned int icu_int; if (msg->address_lo || msg->address_hi) {- /* One off initialization */- mvebu_icu_init(icu, msg);+ /* One off initialization per domain */+ mvebu_icu_init(icu, d->domain, msg); /* Configure the ICU with irq number & type */ icu_int = msg->data | ICU_INT_ENABLE; if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
@@ -131,7 +160,8 @@ static int mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type) {- struct mvebu_icu *icu = platform_msi_get_host_data(d);+ struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);+ struct mvebu_icu *icu = msi_data->icu; unsigned int param_count = icu->legacy_bindings ? 3 : 2; /* Check the count of the parameters in dt */
@@ -143,7 +173,7 @@ mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, if (icu->legacy_bindings) { *hwirq = fwspec->param[1];- *type = fwspec->param[2];+ *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; if (fwspec->param[0] != ICU_GRP_NSR) { dev_err(icu->dev, "wrong ICU group type %x\n", fwspec->param[0]);
@@ -341,7 +444,7 @@ static int mvebu_icu_probe(struct platform_device *pdev) #endif /*- * Clean all ICU interrupts with type SPI_NSR, required to+ * Clean all ICU interrupts of type NSR and SEI, required to * avoid unpredictable SPI assignments done by firmware. */ for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
I'm close to loosing the plot with this driver, so I'll stop here and
see what you have to say... ;-)
I'll check your previous comments tomorrow, I'll probably have a few
questions. Thanks for reviewing :-)
Regards,
Miquèl
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 12:12:04
+0100:
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
platform_msi_alloc_priv_data() checks that a number of interrupts is
always given. This extra-check has no real impact and just prevents
uselessly the user to create an MSI tree domain: remove it.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/base/platform-msi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,*accordingly(whichwouldimpactthemaxnumberofMSI*capabledevices).*/-if(!dev->msi_domain||!write_msi_msg||!nvec||nvec>MAX_DEV_MSIS)+if(!dev->msi_domain||!write_msi_msg||nvec>MAX_DEV_MSIS)returnERR_PTR(-EINVAL);if(dev->msi_domain->bus_token!=DOMAIN_BUS_PLATFORM_MSI){
Huh... It's not that simple.
Yes, it allows you to get a tree via platform_msi_create_device_domain
(assuming that's why you're changing it -- your commit message doesn't
say much)
Indeed. That was exactly my intention.
but it also has some impact on the way msi_domain_prepare_irqs
works (see how it is called from platform_msi_create_device_domain).
Importantly, the msi_prepare callback takes nvec as a parameter, and
that ends up trickling down to the irqchip, or whatever will setup the
MSI domain. Things like the GICv3 ITS do rely on that to carve out the
LPI space that subsequently gets used to populate the domain.
So no, you can't do it like that. If you really want a tree, add a
helper that does so.
So if I understand correctly, what should be done is writting a new
helper that would do something similar to
platform_msi_create_device_domain(), but creating instead a tree domain
and still giving msi_domain_prepare_irqs() a meaningful number (as
nvec) that would be the maximum number of MSIs that could be allocated.
Am I right here?
Thanks,
Miquèl
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
- /*- * We're probed after MSI domains have been resolved, so force- * resolution here.- */- pdev->dev.msi_domain = of_msi_get_domain(&pdev->dev, node,- DOMAIN_BUS_PLATFORM_MSI);- if (!pdev->dev.msi_domain)- return -EPROBE_DEFER;-- gicp_dn = irq_domain_get_of_node(pdev->dev.msi_domain);- if (!gicp_dn)- return -ENODEV;- /* * Clean all ICU interrupts with type SPI_NSR, required to * avoid unpredictable SPI assignments done by firmware.
What is the upshot of passing this icu pointer as part of the platform
device structure instead of simply passing it as a parameter? Is
anything else using it?
Not at this stage of the series, but right after the patch
"irqchip/irq-mvebu-icu: support ICU subnodes" does the following:
Using driver data in both cases was, from my point of view, the easiest
(meanwhile, I'll simplify it thanks to another comment you made).
Thanks,
Miquèl
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:10:05
+0100:
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
Make the current MSI irq_domain local to ease the split between ICU
platform device code and NSR platform device code.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
@@ -218,6 +217,7 @@ static int mvebu_icu_probe(struct platform_device *pdev)structmvebu_icu*icu;structdevice_node*node=pdev->dev.of_node;structdevice_node*gicp_dn;+structirq_domain*irq_domain;structresource*res;void__iomem*regs;inti;
@@ -282,11 +282,11 @@ static int mvebu_icu_probe(struct platform_device *pdev)regmap_write(icu->regmap,ICU_INT_CFG(i),0);}-icu->domain=+irq_domain=platform_msi_create_device_domain(&pdev->dev,ICU_MAX_IRQS,mvebu_icu_write_msg,&mvebu_icu_domain_ops,icu);
nit: this really hurts my eyes. Please put the = sign and the function
call on a single line. I really don't care if it checkpatch is having a
fit because of that.
I also dislike. Will change.
quoted
- if (!icu->domain) {+ if (!irq_domain) { dev_err(&pdev->dev, "Failed to create ICU domain\n"); return -ENOMEM; }
But looking at the next patch, you might as well fold the two. On its
own, this patches is pretty pointless.
Most often people ask to split logical changes as much as possible. But
I have no problem squashing these two patches.
Thanks,
Miquèl
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:45:09
+0100:
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
The ICU can handle several type of interrupt, each of them being handled
differently on AP side. On CP side, the ICU should be able to make the
distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU
node as the parent of multiple ICU sub-nodes, each of them being an
interrupt type with a different interrupt parent. ICU interrupt 'clients'
now directly point to the right sub-node, avoiding the need for the extra
ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If
the node as no child, the probe function for NSRs will still be called
'manually' in order to preserve backward compatibility with DT using the
old binding.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++-------
1 file changed, 74 insertions(+), 14 deletions(-)
Doesn't this make legacy_bindings completely redundant? Either the
pointer is !NULL in the device, and this is using a legacy binging, or
it is stored in the parent, and this is a new binding. You could even
have a helper for that:
static bool is_legacy(struct platform_device *pdev)
{
return !dev_get_drvdata(&pdev->dev);
}
The driver really doesn't need to be defending against itself, if
anything, and it would save you quite a bit of error handling in the
callers of this function.
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 15:54:30
+0100:
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
This is a cascaded interrupt controller in the AP806 GIC that collapses
SEIs (System Error Interrupt) coming from the AP and the CPs (through
the ICU).
The SEI handles up to 64 interrupts. The first 21 interrupts are wired
from the AP. The next 43 interrupts are from the CPs and are triggered
through MSI messages. To handle this complexity, the driver has to
declare to the upper layer: one IRQ domain for the wired interrupts,
one IRQ domain for the MSIs; and acts as a MSI controller ('parent')
by declaring an MSI domain.
Suggested-by: Haim Boot <redacted>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/Kconfig | 3 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-mvebu-sei.c | 444 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 448 insertions(+)
create mode 100644 drivers/irqchip/irq-mvebu-sei.c
++ if (!sei)+ return 0;++ if (sei->dev->of_node != node)+ return 0;++ if (!d || !sei->ap_domain)
... how can it be NULL here? Actually, how can it *ever* be NULL?
Over-sanitization indeed. d cannot be NULL.
Also, it is not clear to me how can sei or sei->ap_domain be NULL.
I misread some internal functions of the core and after a second look
you are right that there is no possibility for both of them to be NULL.
I'll simplify.
I think it'd be a lot clearer if you just read both 32bit registers and
treat the resulting 64bit quantity as a single bitmap, rather than
having this outer loop.
True, there is no need for this outer loop once the bitmap is 64-bit
wide.
quoted
++ /* Call handler for each set bit */+ for_each_set_bit(irq_bit, &irqmap, SEI_IRQ_COUNT_PER_REG) {+ /* Cause Register gives the SEI number */+ irqn = irq_bit + reg_idx * SEI_IRQ_COUNT_PER_REG;+ /*+ * Finding Linux mapping (virq) needs the right domain+ * and the relative hwirq (which start at 0 in both+ * cases, while irqn is relative to all SEI interrupts).+ */+ if (irqn < sei->ap_interrupts.number) {+ virq = irq_find_mapping(sei->ap_domain, irqn);+ } else {+ irqn -= sei->ap_interrupts.number;+ virq = irq_find_mapping(sei->cp_domain, irqn);+ }++ /* Call IRQ handler */+ generic_handle_irq(virq);+ }++ /* Clear interrupt indication by writing 1 to it */+ writel(irqmap, sei->base + GICP_SECR(reg_idx));
write_relaxed. It would probably make sense not to write to that
register if no bits were set to 1 the first place.
Will do it.
quoted
+ }++ chained_irq_exit(chip, desc);+}++static int mvebu_sei_probe(struct platform_device *pdev)+{+ struct device_node *node = pdev->dev.of_node, *parent;+ struct irq_domain *parent_domain, *plat_domain;+ struct mvebu_sei *sei;+ const __be32 *property;+ u32 parent_irq, size;+ int ret;++ sei = devm_kzalloc(&pdev->dev, sizeof(*sei), GFP_KERNEL);+ if (!sei)+ return -ENOMEM;++ sei->dev = &pdev->dev;++ mutex_init(&sei->cp_msi_lock);++ sei->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);+ sei->base = devm_ioremap_resource(sei->dev, sei->res);+ if (!sei->base) {+ dev_err(sei->dev, "Failed to remap SEI resource\n");+ return -ENODEV;+ }++ mvebu_sei_reset(sei);++ /*+ * Reserve the single (top-level) parent SPI IRQ from which all the+ * interrupts handled by this driver will be signaled.+ */+ parent_irq = irq_of_parse_and_map(node, 0);+ if (parent_irq <= 0) {+ dev_err(sei->dev, "Failed to retrieve top-level SPI IRQ\n");+ return -ENODEV;+ }++ irq_set_chained_handler(parent_irq, mvebu_sei_handle_cascade_irq);+ irq_set_handler_data(parent_irq, sei);++ /*+ * SEIs can be triggered from the AP through wired interrupts and from+ * the CPs through MSIs.+ */++ /* Get a reference to the parent domain to create a hierarchy */+ parent = of_irq_find_parent(node);+ if (!parent) {+ dev_err(sei->dev, "Failed to find parent IRQ node\n");+ ret = -ENODEV;+ goto dispose_irq;+ }
Wouldn't it make sense to check this before having configured things
with a chained handler? i.e. check everything that can be checked, and
once you have everything in order, do the plumbing?
Ok
quoted
++ parent_domain = irq_find_host(parent);+ if (!parent_domain) {+ dev_err(sei->dev, "Failed to find parent IRQ domain\n");+ ret = -ENODEV;+ goto dispose_irq;+ }++ /*+ * Retrieve the IRQ organization (AP/CP): the index of the first one and+ * the number of them for each domain.+ */+ property = of_get_property(node, "marvell,sei-ap-ranges", &size);+ if (!property || (size != (2 * sizeof(u32)))) {+ dev_err(sei->dev, "Missing 'marvell,sei-ap-ranges' property\n");+ ret = -ENODEV;+ goto dispose_irq;+ }++ sei->ap_interrupts.first = be32_to_cpu(property[0]);+ sei->ap_interrupts.number = be32_to_cpu(property[1]);++ property = of_get_property(node, "marvell,sei-cp-ranges", &size);+ if (!property || (size != (2 * sizeof(u32)))) {+ dev_err(sei->dev, "Missing 'marvell,sei-cp-ranges' property\n");+ ret = -ENODEV;+ goto dispose_irq;+ }++ sei->cp_interrupts.first = be32_to_cpu(property[0]);+ sei->cp_interrupts.number = be32_to_cpu(property[1]);++ /* Create the 'wired' hierarchy */+ sei->ap_domain = irq_domain_create_hierarchy(parent_domain, 0,+ sei->ap_interrupts.number,+ of_node_to_fwnode(node),+ &mvebu_sei_ap_domain_ops,+ sei);+ if (!sei->ap_domain) {+ dev_err(sei->dev, "Failed to create AP IRQ domain\n");+ ret = -ENOMEM;+ goto dispose_irq;+ }++ /* Create the 'MSI' hierarchy */+ sei->cp_domain = irq_domain_create_hierarchy(parent_domain, 0,+ sei->cp_interrupts.number,+ of_node_to_fwnode(node),+ &mvebu_sei_cp_domain_ops,+ sei);+ if (!sei->cp_domain) {+ pr_err("Failed to create CPs IRQ domain\n");+ ret = -ENOMEM;+ goto remove_ap_domain;+ }++ plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node),+ &mvebu_sei_msi_domain_info,+ sei->cp_domain);+ if (!plat_domain) {+ pr_err("Failed to create CPs MSI domain\n");+ ret = -ENOMEM;+ goto remove_cp_domain;+ }++ platform_set_drvdata(pdev, sei);++ return 0;++remove_cp_domain:+ irq_domain_remove(sei->cp_domain);+remove_ap_domain:+ irq_domain_remove(sei->ap_domain);+dispose_irq:+ irq_dispose_mapping(parent_irq);++ return ret;+}++static const struct of_device_id mvebu_sei_of_match[] = {+ { .compatible = "marvell,armada-8k-sei", },+ {},+};++static struct platform_driver mvebu_sei_driver = {+ .probe = mvebu_sei_probe,+ .driver = {+ .name = "mvebu-sei",+ .of_match_table = mvebu_sei_of_match,+ },+};+builtin_platform_driver(mvebu_sei_driver);
From: Marc Zyngier <hidden> Date: 2018-06-29 14:38:31
On Fri, 29 Jun 2018 08:40:35 +0100,
Miquel Raynal [off-list ref] wrote:
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 12:12:04
+0100:
quoted
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
platform_msi_alloc_priv_data() checks that a number of interrupts is
always given. This extra-check has no real impact and just prevents
uselessly the user to create an MSI tree domain: remove it.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/base/platform-msi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,*accordingly(whichwouldimpactthemaxnumberofMSI*capabledevices).*/-if(!dev->msi_domain||!write_msi_msg||!nvec||nvec>MAX_DEV_MSIS)+if(!dev->msi_domain||!write_msi_msg||nvec>MAX_DEV_MSIS)returnERR_PTR(-EINVAL);if(dev->msi_domain->bus_token!=DOMAIN_BUS_PLATFORM_MSI){
Huh... It's not that simple.
Yes, it allows you to get a tree via platform_msi_create_device_domain
(assuming that's why you're changing it -- your commit message doesn't
say much)
Indeed. That was exactly my intention.
quoted
but it also has some impact on the way msi_domain_prepare_irqs
works (see how it is called from platform_msi_create_device_domain).
Importantly, the msi_prepare callback takes nvec as a parameter, and
that ends up trickling down to the irqchip, or whatever will setup the
MSI domain. Things like the GICv3 ITS do rely on that to carve out the
LPI space that subsequently gets used to populate the domain.
So no, you can't do it like that. If you really want a tree, add a
helper that does so.
So if I understand correctly, what should be done is writting a new
helper that would do something similar to
platform_msi_create_device_domain(), but creating instead a tree domain
and still giving msi_domain_prepare_irqs() a meaningful number (as
nvec) that would be the maximum number of MSIs that could be allocated.
Am I right here?
What I have in mind is the following (untested) patch. Let me know if
that works for you.
Thanks,
M.
From f9bc9e331503b0d7f44fe9771fedb8bcd4a2c979 Mon Sep 17 00:00:00 2001
From: Marc Zyngier <redacted>
Date: Fri, 29 Jun 2018 15:29:11 +0100
Subject: [PATCH] genirq/msi: Allow creation of a tree-based irqdomain for
platform-msi
platform_msi_create_device_domain() always creates a revmap-based
irqdomain, which has the drawback of requiring the number of MSIs
that can be allocated ahead of time. This is not always possible,
and we sometimes need to use a tree-based irqdomain instead.
Add a new platform_msi_create_device_tree_domain() helper to
that effect.
Reported-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Marc Zyngier <redacted>
---
drivers/base/platform-msi.c | 14 ++++++++------
include/linux/msi.h | 17 ++++++++++++-----
2 files changed, 20 insertions(+), 11 deletions(-)
Hi Marc,
Marc Zyngier [off-list ref] wrote on Fri, 29 Jun 2018 15:38:31
+0100:
On Fri, 29 Jun 2018 08:40:35 +0100,
Miquel Raynal [off-list ref] wrote:
quoted
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 12:12:04
+0100:
quoted
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
platform_msi_alloc_priv_data() checks that a number of interrupts is
always given. This extra-check has no real impact and just prevents
uselessly the user to create an MSI tree domain: remove it.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/base/platform-msi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
@@ -203,7 +203,7 @@ platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec,*accordingly(whichwouldimpactthemaxnumberofMSI*capabledevices).*/-if(!dev->msi_domain||!write_msi_msg||!nvec||nvec>MAX_DEV_MSIS)+if(!dev->msi_domain||!write_msi_msg||nvec>MAX_DEV_MSIS)returnERR_PTR(-EINVAL);if(dev->msi_domain->bus_token!=DOMAIN_BUS_PLATFORM_MSI){
Huh... It's not that simple.
Yes, it allows you to get a tree via platform_msi_create_device_domain
(assuming that's why you're changing it -- your commit message doesn't
say much)
Indeed. That was exactly my intention.
quoted
but it also has some impact on the way msi_domain_prepare_irqs
works (see how it is called from platform_msi_create_device_domain).
Importantly, the msi_prepare callback takes nvec as a parameter, and
that ends up trickling down to the irqchip, or whatever will setup the
MSI domain. Things like the GICv3 ITS do rely on that to carve out the
LPI space that subsequently gets used to populate the domain.
So no, you can't do it like that. If you really want a tree, add a
helper that does so.
So if I understand correctly, what should be done is writting a new
helper that would do something similar to
platform_msi_create_device_domain(), but creating instead a tree domain
and still giving msi_domain_prepare_irqs() a meaningful number (as
nvec) that would be the maximum number of MSIs that could be allocated.
Am I right here?
What I have in mind is the following (untested) patch. Let me know if
that works for you.
Ok, that's really close to what I wrote this morning, cool. I was not
sure that was the only thing to modify.
Thanks,
Miquèl
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:05:05
+0100:
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
Before splitting the code to support multiple platform devices to
be probed (one for the ICU, one per interrupt group), let's switch to
regmap first by creating one in the ->probe().
What's the benefit of doing so? I assume that has to do with supporting
multiple devices that share an MMIO range?
Yes, the ICU subnodes will share the same MMIO range.
@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return;/* Set Clear/Set ICU SPI message address in AP */-writel_relaxed(msg[0].address_hi,icu->base+ICU_SETSPI_NSR_AH);-writel_relaxed(msg[0].address_lo,icu->base+ICU_SETSPI_NSR_AL);-writel_relaxed(msg[1].address_hi,icu->base+ICU_CLRSPI_NSR_AH);-writel_relaxed(msg[1].address_lo,icu->base+ICU_CLRSPI_NSR_AL);+regmap_write(icu->regmap,ICU_SETSPI_NSR_AH,msg[0].address_hi);+regmap_write(icu->regmap,ICU_SETSPI_NSR_AL,msg[0].address_lo);+regmap_write(icu->regmap,ICU_CLRSPI_NSR_AH,msg[1].address_hi);+regmap_write(icu->regmap,ICU_CLRSPI_NSR_AL,msg[1].address_lo);
Isn't this a change in the way we write things to the MMIO registers?
You're now trading a writel_relaxed for a writel, plus some locking...
Is the "writel_relaxed" -> "writel" thing really an issue?
Talking about which: Are you always in a context where you can take that
lock? The bit of documentation I've just read seems to imply that the
default lock is a mutex. Is that always safe? My guess is that it isn't,
and any callback that can end-up here after having taken something like
the desc lock is going to blow in your face.
Have you tried lockdep?
Just did -- thanks for pointing it, it failed once the overheat
interrupt fired. I'm not sure if it is because of the regmap-locking
mechanism. There is definitely something to fix there, but I don't know
what for now; I'll come back on it.
[for reference: logs below]
Thanks,
Miquèl
---
[ 91.128615]
[ 91.130120] ================================
[ 91.134408] WARNING: inconsistent lock state
[ 91.138698] 4.18.0-rc1-00047-g7f7c805c2b69-dirty #1580 Not tainted
[ 91.144905] --------------------------------
[ 91.149193] inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W} usage.
[ 91.155227] swapper/0/0 [HC1[1]:SC0[0]:HE0:SE1] takes:
[ 91.160388] (____ptrval____) (&tz->lock){?.+.}, at: thermal_zone_get_temp+0x60/0x158
[ 91.168186] {HARDIRQ-ON-W} state was registered at:
[ 91.173089] lock_acquire+0x44/0x60
[ 91.176683] __mutex_lock+0x74/0x880
[ 91.180362] mutex_lock_nested+0x1c/0x28
[ 91.184390] thermal_zone_of_sensor_register+0x198/0x250
[ 91.189812] devm_thermal_zone_of_sensor_register+0x5c/0xc0
[ 91.195498] armada_thermal_probe+0x220/0x5f0
[ 91.199963] platform_drv_probe+0x50/0xb0
[ 91.204078] driver_probe_device+0x224/0x308
[ 91.208454] __driver_attach+0xe8/0xf0
[ 91.212309] bus_for_each_dev+0x68/0xc8
[ 91.216248] driver_attach+0x20/0x28
[ 91.219926] bus_add_driver+0x108/0x228
[ 91.223866] driver_register+0x60/0x110
[ 91.227806] __platform_driver_register+0x44/0x50
[ 91.232622] armada_thermal_driver_init+0x18/0x20
[ 91.237436] do_one_initcall+0x58/0x170
[ 91.241378] kernel_init_freeable+0x1d4/0x27c
[ 91.245843] kernel_init+0x10/0x108
[ 91.249434] ret_from_fork+0x10/0x18
[ 91.253111] irq event stamp: 61312
[ 91.256530] hardirqs last enabled at (61309): [<ffff000008086ab0>] arch_cpu_idle+0x10/0x20
[ 91.264918] hardirqs last disabled at (61310): [<ffff000008083134>] el1_irq+0x74/0x130
[ 91.272871] softirqs last enabled at (61312): [<ffff0000080b7fa0>] _local_bh_enable+0x20/0x40
[ 91.281520] softirqs last disabled at (61311): [<ffff0000080b8398>] irq_enter+0x58/0x78
[ 91.289559]
[ 91.289559] other info that might help us debug this:
[ 91.296114] Possible unsafe locking scenario:
[ 91.296114]
[ 91.302058] CPU0
[ 91.304514] ----
[ 91.306970] lock(&tz->lock);
[ 91.310041] <Interrupt>
[ 91.312670] lock(&tz->lock);
[ 91.315916]
[ 91.315916] *** DEADLOCK ***
[ 91.315916]
[ 91.321863] no locks held by swapper/0/0.
[ 91.325890]
[ 91.325890] stack backtrace:
[ 91.330269] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 4.18.0-rc1-00047-g7f7c805c2b69-dirty #1580
[ 91.339091] Hardware name: Marvell Armada 7040 DB board (DT)
[ 91.344775] Call trace:
[ 91.347234] dump_backtrace+0x0/0x1a0
[ 91.350913] show_stack+0x14/0x20
[ 91.354244] dump_stack+0xb8/0xf4
[ 91.357574] print_usage_bug.part.24+0x264/0x27c
[ 91.362211] mark_lock+0x150/0x6b0
[ 91.365628] __lock_acquire+0x6d0/0x18f8
[ 91.369568] lock_acquire+0x44/0x60
[ 91.373073] __mutex_lock+0x74/0x880
[ 91.376666] mutex_lock_nested+0x1c/0x28
[ 91.380606] thermal_zone_get_temp+0x60/0x158
[ 91.384982] thermal_zone_device_update.part.4+0x34/0xe0
[ 91.390318] thermal_zone_device_update+0x28/0x38
[ 91.395043] armada_overheat_isr+0xb0/0xb8
[ 91.399159] __handle_irq_event_percpu+0x9c/0x128
[ 91.403883] handle_irq_event_percpu+0x34/0x88
[ 91.408346] handle_irq_event+0x48/0x78
[ 91.412201] handle_fasteoi_irq+0xa0/0x180
[ 91.416316] generic_handle_irq+0x24/0x38
[ 91.420346] mvebu_sei_handle_cascade_irq+0xc8/0x180
[ 91.425332] generic_handle_irq+0x24/0x38
[ 91.429360] __handle_domain_irq+0x5c/0xb8
[ 91.433473] gic_handle_irq+0x58/0xb0
[ 91.437151] el1_irq+0xb4/0x130
[ 91.440306] arch_cpu_idle+0x14/0x20
[ 91.443898] default_idle_call+0x18/0x30
[ 91.447840] do_idle+0x1c4/0x278
[ 91.451082] cpu_startup_entry+0x24/0x28
[ 91.455023] rest_init+0x254/0x268
[ 91.458440] start_kernel+0x3f0/0x41c
[ 91.462126] thermal thermal_zone5: critical temperature reached (54 C), shutting down
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
From: Marc Zyngier <hidden> Date: 2018-06-29 17:17:21
On Fri, 29 Jun 2018 16:27:51 +0100,
Miquel Raynal [off-list ref] wrote:
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:05:05
+0100:
quoted
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
Before splitting the code to support multiple platform devices to
be probed (one for the ICU, one per interrupt group), let's switch to
regmap first by creating one in the ->probe().
What's the benefit of doing so? I assume that has to do with supporting
multiple devices that share an MMIO range?
Yes, the ICU subnodes will share the same MMIO range.
So, one MMIO range, shared by multiple devices managed by the same
driver. Why the complexity?
@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return;/* Set Clear/Set ICU SPI message address in AP */-writel_relaxed(msg[0].address_hi,icu->base+ICU_SETSPI_NSR_AH);-writel_relaxed(msg[0].address_lo,icu->base+ICU_SETSPI_NSR_AL);-writel_relaxed(msg[1].address_hi,icu->base+ICU_CLRSPI_NSR_AH);-writel_relaxed(msg[1].address_lo,icu->base+ICU_CLRSPI_NSR_AL);+regmap_write(icu->regmap,ICU_SETSPI_NSR_AH,msg[0].address_hi);+regmap_write(icu->regmap,ICU_SETSPI_NSR_AL,msg[0].address_lo);+regmap_write(icu->regmap,ICU_CLRSPI_NSR_AH,msg[1].address_hi);+regmap_write(icu->regmap,ICU_CLRSPI_NSR_AL,msg[1].address_lo);
Isn't this a change in the way we write things to the MMIO registers?
You're now trading a writel_relaxed for a writel, plus some locking...
Is the "writel_relaxed" -> "writel" thing really an issue?
If you're happy with system-wide barriers (dsb sy) being issued,
synchronising unrelated accesses, and generally slowing down the whole
system in a completely unnecessary way, then there is absolutely no
issue whatsoever. Performance is completely overrated anyway, let's
embrace slow computing.
quoted
Talking about which: Are you always in a context where you can take that
lock? The bit of documentation I've just read seems to imply that the
default lock is a mutex. Is that always safe? My guess is that it isn't,
and any callback that can end-up here after having taken something like
the desc lock is going to blow in your face.
Have you tried lockdep?
Just did -- thanks for pointing it, it failed once the overheat
interrupt fired. I'm not sure if it is because of the regmap-locking
mechanism. There is definitely something to fix there, but I don't know
what for now; I'll come back on it.
Taking a mutex in an interrupt handler is... great! On the other hand,
that armada_overheat_isr function doesn't seem to exist in 4.18-rc2,
so that's absolutely fine.
Nonetheless, regmap usage in this context is still suspect, and my gut
feeling is that you really don't need it at all.
Thanks,
M.
--
Jazz is not dead, it just smell funny.
Hi Marc,
Marc Zyngier [off-list ref] wrote on Fri, 29 Jun 2018 18:17:21
+0100:
On Fri, 29 Jun 2018 16:27:51 +0100,
Miquel Raynal [off-list ref] wrote:
quoted
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:05:05
+0100:
quoted
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
Before splitting the code to support multiple platform devices to
be probed (one for the ICU, one per interrupt group), let's switch to
regmap first by creating one in the ->probe().
What's the benefit of doing so? I assume that has to do with supporting
multiple devices that share an MMIO range?
Yes, the ICU subnodes will share the same MMIO range.
So, one MMIO range, shared by multiple devices managed by the same
driver. Why the complexity?
@@ -56,10 +58,10 @@ static void mvebu_icu_init(struct mvebu_icu *icu, struct msi_msg *msg)return;/* Set Clear/Set ICU SPI message address in AP */-writel_relaxed(msg[0].address_hi,icu->base+ICU_SETSPI_NSR_AH);-writel_relaxed(msg[0].address_lo,icu->base+ICU_SETSPI_NSR_AL);-writel_relaxed(msg[1].address_hi,icu->base+ICU_CLRSPI_NSR_AH);-writel_relaxed(msg[1].address_lo,icu->base+ICU_CLRSPI_NSR_AL);+regmap_write(icu->regmap,ICU_SETSPI_NSR_AH,msg[0].address_hi);+regmap_write(icu->regmap,ICU_SETSPI_NSR_AL,msg[0].address_lo);+regmap_write(icu->regmap,ICU_CLRSPI_NSR_AH,msg[1].address_hi);+regmap_write(icu->regmap,ICU_CLRSPI_NSR_AL,msg[1].address_lo);
Isn't this a change in the way we write things to the MMIO registers?
You're now trading a writel_relaxed for a writel, plus some locking...
Is the "writel_relaxed" -> "writel" thing really an issue?
If you're happy with system-wide barriers (dsb sy) being issued,
synchronising unrelated accesses, and generally slowing down the whole
system in a completely unnecessary way, then there is absolutely no
issue whatsoever. Performance is completely overrated anyway, let's
embrace slow computing.
8-D
quoted
quoted
Talking about which: Are you always in a context where you can take that
lock? The bit of documentation I've just read seems to imply that the
default lock is a mutex. Is that always safe? My guess is that it isn't,
and any callback that can end-up here after having taken something like
the desc lock is going to blow in your face.
Have you tried lockdep?
Just did -- thanks for pointing it, it failed once the overheat
interrupt fired. I'm not sure if it is because of the regmap-locking
mechanism. There is definitely something to fix there, but I don't know
what for now; I'll come back on it.
Taking a mutex in an interrupt handler is... great! On the other hand,
that armada_overheat_isr function doesn't seem to exist in 4.18-rc2,
so that's absolutely fine.
I had no troubles with NSRs so I wanted to check with SEIs too.
The only device using SEIs for now is this thermal driver which I just
contributed, it is still under review. The ISR is making a thermal-core
call to update on an overheat situation. This call is synchronous and
the core checks for the current temperature, but first locks its
tz->lock mutex, which is kind of bad in interrupt context. I just
realized it. Sad.
Nonetheless, regmap usage in this context is still suspect, and my gut
feeling is that you really don't need it at all.
You are right on this and I probably over-looked at the initial
problem. I'm pretty sure I can get rid of it as long as only one driver
touches this MMIO region.
Thanks for all your thoughts, I'll resend a version next week.
Thank you very much,
Miquèl
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:45:09
+0100:
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
The ICU can handle several type of interrupt, each of them being handled
differently on AP side. On CP side, the ICU should be able to make the
distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU
node as the parent of multiple ICU sub-nodes, each of them being an
interrupt type with a different interrupt parent. ICU interrupt 'clients'
now directly point to the right sub-node, avoiding the need for the extra
ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If
the node as no child, the probe function for NSRs will still be called
'manually' in order to preserve backward compatibility with DT using the
old binding.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++-------
1 file changed, 74 insertions(+), 14 deletions(-)
Doesn't this make legacy_bindings completely redundant? Either the
pointer is !NULL in the device, and this is using a legacy binging, or
it is stored in the parent, and this is a new binding. You could even
have a helper for that:
static bool is_legacy(struct platform_device *pdev)
{
return !dev_get_drvdata(&pdev->dev);
}
The driver really doesn't need to be defending against itself, if
anything, and it would save you quite a bit of error handling in the
callers of this function.
I simplified the sanity checks but I had to keep an icu->is_legacy
boolean because the above function would not have worked, for instance,
in the *_translate() hook. As this hook does not receive a struct
device * (or platform_device) as parameter, I tried to use icu->dev
instead. This cannot work as it always points to the device having the
driver data attached.
Please tell me if the next version fits what you expect.
Thanks,
Miquèl
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
From: Marc Zyngier <hidden> Date: 2018-07-04 12:43:58
On 04/07/18 10:09, Miquel Raynal wrote:
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:45:09
+0100:
quoted
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
The ICU can handle several type of interrupt, each of them being handled
differently on AP side. On CP side, the ICU should be able to make the
distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU
node as the parent of multiple ICU sub-nodes, each of them being an
interrupt type with a different interrupt parent. ICU interrupt 'clients'
now directly point to the right sub-node, avoiding the need for the extra
ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If
the node as no child, the probe function for NSRs will still be called
'manually' in order to preserve backward compatibility with DT using the
old binding.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++-------
1 file changed, 74 insertions(+), 14 deletions(-)
Doesn't this make legacy_bindings completely redundant? Either the
pointer is !NULL in the device, and this is using a legacy binging, or
it is stored in the parent, and this is a new binding. You could even
have a helper for that:
static bool is_legacy(struct platform_device *pdev)
{
return !dev_get_drvdata(&pdev->dev);
}
The driver really doesn't need to be defending against itself, if
anything, and it would save you quite a bit of error handling in the
callers of this function.
I simplified the sanity checks but I had to keep an icu->is_legacy
boolean because the above function would not have worked, for instance,
in the *_translate() hook. As this hook does not receive a struct
device * (or platform_device) as parameter, I tried to use icu->dev
instead. This cannot work as it always points to the device having the
driver data attached.
You could still have the pdev as part of the domain host_data, right?
Isn't that just a matter of having a pointer to the pdev as part of the
icu data structure?
Thanks,
M.
--
Jazz is not dead. It just smells funny...
Hi Marc,
Marc Zyngier [off-list ref] wrote on Wed, 4 Jul 2018 13:43:58
+0100:
On 04/07/18 10:09, Miquel Raynal wrote:
quoted
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:45:09
+0100:
quoted
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
The ICU can handle several type of interrupt, each of them being handled
differently on AP side. On CP side, the ICU should be able to make the
distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU
node as the parent of multiple ICU sub-nodes, each of them being an
interrupt type with a different interrupt parent. ICU interrupt 'clients'
now directly point to the right sub-node, avoiding the need for the extra
ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If
the node as no child, the probe function for NSRs will still be called
'manually' in order to preserve backward compatibility with DT using the
old binding.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++-------
1 file changed, 74 insertions(+), 14 deletions(-)
Doesn't this make legacy_bindings completely redundant? Either the
pointer is !NULL in the device, and this is using a legacy binging, or
it is stored in the parent, and this is a new binding. You could even
have a helper for that:
static bool is_legacy(struct platform_device *pdev)
{
return !dev_get_drvdata(&pdev->dev);
}
The driver really doesn't need to be defending against itself, if
anything, and it would save you quite a bit of error handling in the
callers of this function.
I simplified the sanity checks but I had to keep an icu->is_legacy
boolean because the above function would not have worked, for instance,
in the *_translate() hook. As this hook does not receive a struct
device * (or platform_device) as parameter, I tried to use icu->dev
instead. This cannot work as it always points to the device having the
driver data attached.
You could still have the pdev as part of the domain host_data, right?
Isn't that just a matter of having a pointer to the pdev as part of the
icu data structure?
There is already a 'struct device *dev' pointer in the ICU data
structure. This pointer is set to "&pdev->dev" in the main probe
function.
Legacy: dev is the unique ICU device, the driver data is attached to it.
Now: dev is the 'host' ICU device that is the parent of two other
devices, ICU NSR controller and SEI NSR controller. The driver data is
also attached to this device.
So in both situations checking dev_get_drvdata(icu->dev) would return a
valid pointer.
Does this answer your question?
Kind regards,
Miquèl
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
From: Marc Zyngier <hidden> Date: 2018-07-05 08:19:24
On 04/07/18 16:16, Miquel Raynal wrote:
Hi Marc,
Marc Zyngier [off-list ref] wrote on Wed, 4 Jul 2018 13:43:58
+0100:
quoted
On 04/07/18 10:09, Miquel Raynal wrote:
quoted
Hi Marc,
Marc Zyngier [off-list ref] wrote on Thu, 28 Jun 2018 13:45:09
+0100:
quoted
On 22/06/18 16:14, Miquel Raynal wrote:
quoted
The ICU can handle several type of interrupt, each of them being handled
differently on AP side. On CP side, the ICU should be able to make the
distinction between each interrupt group by pointing to the right parent.
This is done through the introduction of new bindings, presenting the ICU
node as the parent of multiple ICU sub-nodes, each of them being an
interrupt type with a different interrupt parent. ICU interrupt 'clients'
now directly point to the right sub-node, avoiding the need for the extra
ICU_GRP_* parameter.
ICU subnodes are probed automatically with devm_platform_populate(). If
the node as no child, the probe function for NSRs will still be called
'manually' in order to preserve backward compatibility with DT using the
old binding.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
drivers/irqchip/irq-mvebu-icu.c | 88 ++++++++++++++++++++++++++++++++++-------
1 file changed, 74 insertions(+), 14 deletions(-)
Doesn't this make legacy_bindings completely redundant? Either the
pointer is !NULL in the device, and this is using a legacy binging, or
it is stored in the parent, and this is a new binding. You could even
have a helper for that:
static bool is_legacy(struct platform_device *pdev)
{
return !dev_get_drvdata(&pdev->dev);
}
The driver really doesn't need to be defending against itself, if
anything, and it would save you quite a bit of error handling in the
callers of this function.
I simplified the sanity checks but I had to keep an icu->is_legacy
boolean because the above function would not have worked, for instance,
in the *_translate() hook. As this hook does not receive a struct
device * (or platform_device) as parameter, I tried to use icu->dev
instead. This cannot work as it always points to the device having the
driver data attached.
You could still have the pdev as part of the domain host_data, right?
Isn't that just a matter of having a pointer to the pdev as part of the
icu data structure?
There is already a 'struct device *dev' pointer in the ICU data
structure. This pointer is set to "&pdev->dev" in the main probe
function.
Legacy: dev is the unique ICU device, the driver data is attached to it.
Now: dev is the 'host' ICU device that is the parent of two other
devices, ICU NSR controller and SEI NSR controller. The driver data is
also attached to this device.
So in both situations checking dev_get_drvdata(icu->dev) would return a
valid pointer.
Does this answer your question?
I think I need to see the full picture again. Post the updated series,
and we'll see what we can do.
Thanks,
M.
--
Jazz is not dead. It just smells funny...