lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Sun, 3 Jan 2021 21:46:10 -0600
From:   Samuel Holland <samuel@...lland.org>
To:     Marc Zyngier <maz@...nel.org>
Cc:     Thomas Gleixner <tglx@...utronix.de>,
        Rob Herring <robh+dt@...nel.org>,
        Maxime Ripard <mripard@...nel.org>,
        Chen-Yu Tsai <wens@...e.org>,
        Jernej Skrabec <jernej.skrabec@...l.net>,
        Russell King <linux@...linux.org.uk>,
        Catalin Marinas <catalin.marinas@....com>,
        Will Deacon <will@...nel.org>,
        Ondrej Jirman <megous@...ous.com>, devicetree@...r.kernel.org,
        linux-arm-kernel@...ts.infradead.org, linux-kernel@...r.kernel.org
Subject: Re: [PATCH v3 03/10] irqchip/sun6i-r: Use a stacked irqchip driver

On 1/3/21 7:10 AM, Marc Zyngier wrote:
> On Sun, 03 Jan 2021 12:08:43 +0000,
> Samuel Holland <samuel@...lland.org> wrote:
>>
>> On 1/3/21 5:27 AM, Marc Zyngier wrote:
>>> On Sun, 03 Jan 2021 10:30:54 +0000,
>>> Samuel Holland <samuel@...lland.org> wrote:
>>>>
>>>> The R_INTC in the A31 and newer sun8i/sun50i SoCs is more similar to the
>>>> original sun4i interrupt controller than the sun7i/sun9i NMI controller.
>>>> It is used for two distinct purposes:
>>>>  - To control the trigger, latch, and mask for the NMI input pin
>>>>  - To provide the interrupt input for the ARISC coprocessor
>>>>
>>>> As this interrupt controller is not documented, information about it
>>>> comes from vendor-provided firmware blobs and from experimentation.
>>>>
>>>> Like the original sun4i interrupt controller, it has:
>>>>  - A VECTOR_REG at 0x00 (configurable via the BASE_ADDR_REG at 0x04)
>>>>  - A NMI_CTRL_REG, PENDING_REG, and ENABLE_REG as used by both the
>>>>    sun4i and sunxi-nmi drivers
>>>>  - A MASK_REG at 0x50
>>>>  - A RESP_REG at 0x60
>>>>
>>>> Differences from the sun4i interrupt controller appear to be:
>>>>  - It only has one or two registers of each kind (max 32 or 64 IRQs)
>>>>  - Multiplexing logic is added to support additional inputs
>>>>  - There is no FIQ-related logic
>>>>  - There is no interrupt priority logic
>>>>
>>>> In order to fulfill its two purposes, this hardware block combines four
>>>> types of IRQs. First, the NMI pin is routed to the "IRQ 0" input on this
>>>> chip, with a trigger type controlled by the NMI_CTRL_REG. The "IRQ 0
>>>> pending" output from this chip, if enabled, is then routed to a SPI IRQ
>>>> input on the GIC. In other words, bit 0 of IRQ_ENABLE_REG *does* affect
>>>> the NMI IRQ seen at the GIC.
>>>>
>>>> The NMI is followed by a contiguous block of 15 "direct" (my name for
>>>> them) IRQ inputs that are connected in parallel to both R_INTC and the
>>>> GIC. Or in other words, these bits of IRQ_ENABLE_REG *do not* affect the
>>>> IRQs seen at the GIC.
>>>>
>>>> Following the direct IRQs are the ARISC's copy of banked IRQs for shared
>>>> peripherals. These are not relevant to Linux. The remaining IRQs are
>>>> connected to a multiplexer and provide access to the first (up to) 128
>>>> SPIs from the ARISC. This range of SPIs overlaps with the direct IRQs.
>>>>
>>>> Finally, the global "IRQ pending" output from R_INTC, after being masked
>>>> by MASK_REG and RESP_REG, is connected to the "external interrupt" input
>>>> of the ARISC CPU. This path is also irrelevant to Linux.
>>>
>>> An ASCII-art version of this would help a lot, and would look good in
>>> the driver code...
>>
>> There is this diagram which I forgot to include in the cover letter:
>>
>>   https://linux-sunxi.org/images/5/5c/R_INTC.png
>>
>> I can try to come up with some ASCII art.
>>
>>>> Because of the 1:1 correspondence between R_INTC and GIC inputs, this is
>>>> a perfect scenario for using a stacked irqchip driver. We want to hook
>>>> into enabling/disabling IRQs to add more features to the GIC
>>>> (specifically to allow masking the NMI and setting its trigger type),
>>>> but we don't need to actually handle the IRQ in this driver.
>>>>
>>>> And since R_INTC is in the always-on power domain, and its output is
>>>> connected directly in to the power management coprocessor, a stacked
>>>> irqchip driver provides a simple way to add wakeup support to this set
>>>> of IRQs. That is the next patch; for now, just the NMI is moved over.
>>>>
>>>> To allow access to all multiplexed IRQs, this driver requires a new
>>>> binding where the interrupt number matches the GIC interrupt number.
>>>> (This moves the NMI number from 0 to 32 or 96, depending on the SoC.)
>>>> For simplicity, copy the three-cell GIC binding; this disambiguates
>>>> interrupt 0 in the old binding (the NMI) from interrupt 0 in the new
>>>> binding (SPI 0) by the number of cells.
>>>>
>>>> This commit mostly reverts commit 173bda53b340 ("irqchip/sunxi-nmi:
>>>> Support sun6i-a31-r-intc compatible").
>>>>
>>>> Signed-off-by: Samuel Holland <samuel@...lland.org>
>>>> ---
>>>>  arch/arm/mach-sunxi/Kconfig     |   1 +
>>>>  arch/arm64/Kconfig.platforms    |   1 +
>>>>  drivers/irqchip/Makefile        |   1 +
>>>>  drivers/irqchip/irq-sun6i-r.c   | 267 ++++++++++++++++++++++++++++++++
>>>>  drivers/irqchip/irq-sunxi-nmi.c |  26 +---
>>>>  5 files changed, 273 insertions(+), 23 deletions(-)
>>>>  create mode 100644 drivers/irqchip/irq-sun6i-r.c
>>>>
>>>
>>> [...]
>>>
>>>> diff --git a/drivers/irqchip/irq-sun6i-r.c b/drivers/irqchip/irq-sun6i-r.c
>>>> new file mode 100644
>>>> index 000000000000..7490ade7b254
>>>> --- /dev/null
>>>> +++ b/drivers/irqchip/irq-sun6i-r.c
>>>> @@ -0,0 +1,267 @@
>>>> +// SPDX-License-Identifier: GPL-2.0-only
>>>> +//
>>>> +// R_INTC driver for Allwinner A31 and newer SoCs
>>>> +//
>>>> +
>>>> +#include <linux/irq.h>
>>>> +#include <linux/irqchip.h>
>>>> +#include <linux/irqdomain.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_address.h>
>>>> +#include <linux/of_irq.h>
>>>> +
>>>> +#include <dt-bindings/interrupt-controller/arm-gic.h>
>>>> +
>>>> +/*
>>>> + * The R_INTC manages between 32 and 64 IRQs, divided into four groups. Example
>>>> + * bit numbers are for the original variant in the A31:
>>>> + *
>>>> + *   Bit      0: The "External NMI" input, connected in series to a GIC SPI.
>>>> + *   Bits  1-15: "Direct" IRQs for ARISC peripherals, connected in parallel to
>>>> + *               the GIC and mapped 1:1 to SPIs numerically following the NMI.
>>>> + *   Bits 16-18: "Banked" IRQs for peripherals that have separate interfaces
>>>> + *               for the ARM CPUs and ARISC. These do not map to any GIC SPI.
>>>> + *   Bits 19-31: "Muxed" IRQs, each corresponding to a group of up to 8 SPIs.
>>>> + *               Later variants added a second PENDING and ENABLE register to
>>>> + *               make use of all 128 mux inputs (16 IRQ lines).
>>>> + *
>>>> + * Since the direct IRQs are inside the muxed IRQ range, they do not increase
>>>> + * the number of HWIRQs needed.
>>>> + */
>>>> +#define SUN6I_NR_IRQS			64
>>>> +#define SUN6I_NR_DIRECT_IRQS		16
>>>> +#define SUN6I_NR_MUX_INPUTS		128
>>>> +#define SUN6I_NR_HWIRQS			SUN6I_NR_MUX_INPUTS
>>>> +
>>>> +#define SUN6I_NMI_CTRL			(0x0c)
>>>> +#define SUN6I_IRQ_PENDING(n)		(0x10 + 4 * (n))
>>>> +#define SUN6I_IRQ_ENABLE(n)		(0x40 + 4 * (n))
>>>> +#define SUN6I_MUX_ENABLE(n)		(0xc0 + 4 * (n))
>>>> +
>>>> +#define SUN6I_NMI_IRQ_BIT		BIT(0)
>>>> +
>>>> +static void __iomem *base;
>>>> +static irq_hw_number_t nmi_hwirq;
>>>> +static u32 nmi_type;
>>>> +
>>>> +static struct irq_chip sun6i_r_intc_edge_chip;
>>>> +static struct irq_chip sun6i_r_intc_level_chip;
>>>> +
>>>> +static void sun6i_r_intc_nmi_ack(void)
>>>> +{
>>>> +	/*
>>>> +	 * The NMI channel has a latch separate from its trigger type.
>>>> +	 * This latch must be cleared to clear the signal to the GIC.
>>>> +	 */
>>>> +	writel_relaxed(SUN6I_NMI_IRQ_BIT, base + SUN6I_IRQ_PENDING(0));
>>>> +}
>>>> +
>>>> +static void sun6i_r_intc_irq_mask(struct irq_data *data)
>>>> +{
>>>> +	if (data->hwirq == nmi_hwirq)
>>>> +		sun6i_r_intc_nmi_ack();
>>>
>>> I'm a bit worried by this. I can see it working with level interrupts
>>> (you can clear the input, and if the interrupt is asserted, it will
>>> fire again), but I'm worried that it will simply result in lost
>>> interrupts for edge signalling.
>>
>> For edge interrupts, don't you want to ack as early as possible,
>> before the handler clears the source of the interrupt? That way if a
>> second interrupt comes in while you're handling the first one, you
>> don't ack the second one without handling it?
> 
> It completely depends on what this block does. If, as I expect, it
> latches the interrupt, then it needs clearing after the GIC has acked
> the incoming interrupt.

Yes, there is an internal S/R latch.
 - For edge interrupts, the latch is set once for each pulse.
 - For level interrupts, it gets set continuously as long as the
   pin is high/low.
 - Writing a "1" to bit 0 of PENDING resets the latch.
 - The output of the latch goes to the GIC.

>>> It also begs the question: why would you want to clear the signal to
>>> the GIC on mask (or unmask)? The expectations are that a pending
>>> interrupt is preserved across a mask/unmask sequence.
>>
>> I hadn't thought about anything masking the IRQ outside of the
>> handler; but you're right, this breaks that case. I'm trying to work
>> within the constraints of stacking the GIC driver, which assumes
>> handle_fasteoi_irq, so it sounds like I should switch back to
>> handle_fasteoi_ack_irq and use .irq_ack. Or based on your previous
>> paragraph, maybe I'm missing some other consideration?
> 
> handle_fasteoi_ack_irq() sounds like a good match for edge
> interrupts. Do you actually need to do anything for level signals? If
> you do, piggybacking on .irq_eoi would do the trick.

For level interrupts, I have to reset the latch (see above) after the source of
the interrupt is cleared.

That was the bug with v2: I set IRQ_EOI_THREADED so .irq_eoi would run after the
thread. But with GICv2 EOImode==0, that blocked other interrupts from being
received during the IRQ thread. Which is why I moved it to .irq_unmask and
removed the flag: so .irq_eoi runs at the end of the hardirq (unblocking further
interrupts at the GIC), and .irq_unmask resets the latch at the end of the thread.

With the flag removed, but still clearing the latch in .irq_eoi, every edge IRQ
was followed by a second, spurious IRQ after the thread finished.

Does that make sense?

>>>> +
>>>> +	irq_chip_mask_parent(data);
>>>> +}
>>>> +
>>>> +static void sun6i_r_intc_irq_unmask(struct irq_data *data)
>>>> +{
>>>> +	if (data->hwirq == nmi_hwirq)
>>>> +		sun6i_r_intc_nmi_ack();
>>>> +
>>>> +	irq_chip_unmask_parent(data);
>>>> +}
>>>> +
>>>> +static int sun6i_r_intc_irq_set_type(struct irq_data *data, unsigned int type)
>>>> +{
>>>> +	/*
>>>> +	 * The GIC input labeled "External NMI" connects to bit 0 of the R_INTC
>>>> +	 * PENDING register, not to the pin directly. So the trigger type of the
>>>> +	 * GIC input does not depend on the trigger type of the NMI pin itself.
>>>> +	 *
>>>> +	 * Only the NMI channel is routed through this interrupt controller on
>>>> +	 * its way to the GIC. Other IRQs are routed to the GIC and R_INTC in
>>>> +	 * parallel; they must have a trigger type appropriate for the GIC.
>>>> +	 */
>>>> +	if (data->hwirq == nmi_hwirq) {
>>>> +		struct irq_chip *chip;
>>>> +		u32 nmi_src_type;
>>>> +
>>>> +		switch (type) {
>>>> +		case IRQ_TYPE_LEVEL_LOW:
>>>> +			chip = &sun6i_r_intc_level_chip;
>>>> +			nmi_src_type = 0;
>>>
>>> Please add symbolic names for these types.
>>
>> I removed them based on your previous comment:
>>
>> https://lkml.org/lkml/2020/1/20/278
>>> It is unusual to use an enum for values that get directly programmed into the HW.
>>
>> Do you want them to be specifically #defines?
> 
> Yes please.

Will do.

>>
>>>> +			break;
>>>> +		case IRQ_TYPE_EDGE_FALLING:
>>>> +			chip = &sun6i_r_intc_edge_chip;
>>>> +			nmi_src_type = 1;
>>>> +			break;
>>>> +		case IRQ_TYPE_LEVEL_HIGH:
>>>> +			chip = &sun6i_r_intc_level_chip;
>>>> +			nmi_src_type = 2;
>>>> +			break;
>>>> +		case IRQ_TYPE_EDGE_RISING:
>>>> +			chip = &sun6i_r_intc_edge_chip;
>>>> +			nmi_src_type = 3;
>>>> +			break;
>>>> +		default:
>>>> +			pr_err("%pOF: invalid trigger type %d for IRQ %d\n",
>>>> +			       irq_domain_get_of_node(data->domain), type,
>>>> +			       data->irq);
>>>
>>> A failing set_type already triggers a kernel message.
>>
>> I'll remove this.
>>
>>>> +			return -EBADR;
>>>
>>> That's a pretty odd error. I see it used in 3 drivers (including the
>>> one this driver replaces), but the canonical error code is -EINVAL.
>>
>> I'll change it to -EINVAL.
>>
>>>> +		}
>>>> +
>>>> +		irq_set_chip_handler_name_locked(data, chip,
>>>> +						 handle_fasteoi_irq, NULL);
>>>> +
>>>> +		writel_relaxed(nmi_src_type, base + SUN6I_NMI_CTRL);
>>>> +
>>>> +		/*
>>>> +		 * Use the trigger type from the OF node for the NMI's
>>>> +		 * R_INTC to GIC connection.
>>>> +		 */
>>>> +		type = nmi_type;
>>>
>>> This looks wrong. The GIC only supports level-high and edge-rising, so
>>> half of the possible settings will result in an error. I assume the
>>> R_INTC has an inverter controlled by nmi_src_type, and only outputs
>>> something the GIC can grok.
>>
>> nmi_type isn't the setting from the incoming IRQ. nmi_type is from
>> the interrupts property in the irqchip OF node itself. So this is a
>> fwspec for `interrupt-parent = <&gic>;`, i.e. already assumed to be
>> GIC-compatible. I'm not sure what you mean by "half of the possible
>> settings".
> 
> Ah, I see. I misread where nmi_type was coming from. Please ignore me.
> 
>>
>> Maybe I should remove the `interrupts` property and hardcode the
>> number and type connecting to the GIC? If I did that, I'm not quite
>> sure whether it would be high or rising.
> 
> I guess that the output of this block is a level signal, so you should
> probably enforce that.

Okay, I'll do that.

>> The output to the GIC is literally the bit in the pending register:
>> 1 for pending, 0 for not. Since that is after a latch (regardless of
>> the input trigger type) it sounds like it should be level-high,
>> because a pulse would have already been converted to a steady
>> signal. Or does it depend on when I clear the latch?
> 
> Level and edge will have different latch clearing requirements, as
> outlined above. For level, it needs to be cleared after handling the
> interrupt, at the point where you would deactivate it in the GIC
> (.irq_eoi). For edge, that's an Ack.
> 
> [...]
> 
>>>> +static int sun6i_r_intc_domain_alloc(struct irq_domain *domain,
>>>> +				     unsigned int virq,
>>>> +				     unsigned int nr_irqs, void *arg)
>>>> +{
>>>> +	struct irq_fwspec *fwspec = arg;
>>>> +	struct irq_fwspec gic_fwspec;
>>>> +	unsigned long hwirq;
>>>> +	unsigned int type;
>>>> +	int i, ret;
>>>> +
>>>> +	ret = sun6i_r_intc_domain_translate(domain, fwspec, &hwirq, &type);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +	if (hwirq + nr_irqs > SUN6I_NR_HWIRQS)
>>>> +		return -EINVAL;
>>>> +
>>>> +	/* Construct a GIC-compatible fwspec from this fwspec. */
>>>> +	gic_fwspec = (struct irq_fwspec) {
>>>> +		.fwnode      = domain->parent->fwnode,
>>>> +		.param_count = 3,
>>>> +		.param       = { GIC_SPI, hwirq, type },
>>>> +	};
>>>> +
>>>> +	for (i = 0; i < nr_irqs; ++i)
>>>> +		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
>>>> +					      &sun6i_r_intc_level_chip, NULL);
>>>
>>> Unconditionally level, without looking at the requested type?
>>
>> __setup_irq calls __irq_set_trigger if any trigger is provided, which calls
>> chip->irq_set_type unconditionally. So I don't think the chip here matters,
>> because .irq_set_type will be called before the IRQ is enabled anyway. Again,
>> maybe I'm missing something.
> 
> It doesn't really matter, but it is a bit odd to have the information
> at hand, and ignore it. This at least deserves a comment.
> 
>> For non-NMI IRQs, the choice of chip doesn't matter, because this
>> driver handles nothing but .irq_set_wake for them (should I provide
>> a third chip for this that doesn't provide its own
>> ack/mask/unmask?).
> 
> I think you could handle the NMI with its own single irqchip (for both
> level and edge, only checking for the configuration in ack/eoi), and
> have a non-NMI chip for the rest.

I will try that.

>>
>>>> +
>>>> +	return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &gic_fwspec);
>>>> +}
>>>> +
>>>> +static const struct irq_domain_ops sun6i_r_intc_domain_ops = {
>>>> +	.translate	= sun6i_r_intc_domain_translate,
>>>> +	.alloc		= sun6i_r_intc_domain_alloc,
>>>> +	.free		= irq_domain_free_irqs_common,
>>>> +};
>>>> +
>>>> +static void sun6i_r_intc_resume(void)
>>>> +{
>>>> +	int i;
>>>> +
>>>> +	/* Only the NMI is relevant during normal operation. */
>>>> +	writel_relaxed(SUN6I_NMI_IRQ_BIT, base + SUN6I_IRQ_ENABLE(0));
>>>> +	for (i = 1; i < BITS_TO_U32(SUN6I_NR_IRQS); ++i)
>>>> +		writel_relaxed(0, base + SUN6I_IRQ_ENABLE(i));
>>>
>>> If only the NMI is relevant, why are the other interrupts enabled?
>>> Shouldn't this be moved to the following patch (I presume this is
>>> wake-up related...).
>>
>> The other IRQs aren't enabled? I'm writing all zeroes to the enable register.
> 
> Duh. -ECANTREAD.
> 
>>>> +}
>>>> +
>>>> +static int __init sun6i_r_intc_init(struct device_node *node,
>>>> +				    struct device_node *parent)
>>>> +{
>>>> +	struct irq_domain *domain, *parent_domain;
>>>> +	struct of_phandle_args parent_irq;
>>>> +	int ret;
>>>> +
>>>> +	/* Extract the NMI's R_INTC to GIC mapping from the OF node. */
>>>> +	ret = of_irq_parse_one(node, 0, &parent_irq);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +	if (parent_irq.args_count < 3 || parent_irq.args[0] != GIC_SPI)
>>>> +		return -EINVAL;
>>>> +	nmi_hwirq = parent_irq.args[1];
>>>> +	nmi_type = parent_irq.args[2];
>>>
>>> This looks a lot like the translate callback.
>>
>> Yes, I could use that here.
>>
>>>> +
>>>> +	parent_domain = irq_find_host(parent);
>>>> +	if (!parent_domain) {
>>>> +		pr_err("%pOF: Failed to obtain parent domain\n", node);
>>>> +		return -ENXIO;
>>>> +	}
>>>> +
>>>> +	base = of_io_request_and_map(node, 0, NULL);
>>>> +	if (IS_ERR(base)) {
>>>> +		pr_err("%pOF: Failed to map MMIO region\n", node);
>>>> +		return PTR_ERR(base);
>>>> +	}
>>>> +
>>>> +	sun6i_r_intc_nmi_ack();
>>>> +	sun6i_r_intc_resume();
>>>> +
>>>> +	domain = irq_domain_add_hierarchy(parent_domain, 0,
>>>> +					  SUN6I_NR_HWIRQS, node,
>>>> +					  &sun6i_r_intc_domain_ops, NULL);
>>>> +	if (!domain) {
>>>> +		pr_err("%pOF: Failed to allocate domain\n", node);
>>>> +		iounmap(base);
>>>> +		return -ENOMEM;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +IRQCHIP_DECLARE(sun6i_r_intc, "allwinner,sun6i-a31-r-intc", sun6i_r_intc_init);
>>>
>>> Thanks,
>>>
>>> 	M.
>>>
>>
>> Thank you for your (extremely quick, I must say) review!
> 
> Locked up home until Wednesday. I try to keep myself busy...
> 
> Thanks,
> 
> 	M.
> 

Thanks,
Samuel

Powered by blists - more mailing lists