[PATCH 2/2] irqchip/apple-aic: Add support for A7-A11 SoCs
Sven Peter
sven at svenpeter.dev
Tue Jul 12 13:17:48 PDT 2022
On Tue, Jul 12, 2022, at 21:23, Konrad Dybcio wrote:
> On 12.07.2022 21:12, Marc Zyngier wrote:
>> Hi Konrad,
>>
>> Please add a cover letter when sending more than a single patch.
>>
>> On Tue, 12 Jul 2022 17:09:19 +0100,
>> Konrad Dybcio <konrad.dybcio at somainline.org> wrote:
>>>
>>> Add support for A7-A11 SoCs by if-ing out some features only present on
>>> A12 & newer (UNCORE2 registers) or M1 & newer (EL2 registers - the
>>> older SoCs don't implement EL2).
>>>
>>> Also, annotate IPI regs support (A11 and newer*) so that the driver can
>>> tell whether the SoC supports these (they are written to even if fast
>>> IPI is disabled, when the registers are there of course).
>>>
>>> *A11 is supposed to use this feature, but it is currently not working.
>>> That said, it is not yet necessary, especially with only one core up,
>>> and it works a-ok using the same featureset as earlier SoCs.
>>>
>>> Signed-off-by: Konrad Dybcio <konrad.dybcio at somainline.org>
>>> ---
>>> drivers/irqchip/irq-apple-aic.c | 54 +++++++++++++++++++++++----------
>>> 1 file changed, 38 insertions(+), 16 deletions(-)
>>>
>>> diff --git a/drivers/irqchip/irq-apple-aic.c b/drivers/irqchip/irq-apple-aic.c
>>> index 12dd48727a15..36f4b52addc2 100644
>>> --- a/drivers/irqchip/irq-apple-aic.c
>>> +++ b/drivers/irqchip/irq-apple-aic.c
>>> @@ -245,7 +245,10 @@ struct aic_info {
>>> u32 die_stride;
>>>
>>> /* Features */
>>> + bool el2_regs;
>>> bool fast_ipi;
>>> + bool ipi_regs;
>>> + bool uncore2_regs;
>>> };
>>>
>>> static const struct aic_info aic1_info = {
>>> @@ -261,7 +264,10 @@ static const struct aic_info aic1_fipi_info = {
>>> .event = AIC_EVENT,
>>> .target_cpu = AIC_TARGET_CPU,
>>>
>>> + .el2_regs = true,
>>> .fast_ipi = true,
>>> + .ipi_regs = true,
>>> + .uncore2_regs = true,
>>> };
>>>
>>> static const struct aic_info aic2_info = {
>>> @@ -269,7 +275,10 @@ static const struct aic_info aic2_info = {
>>>
>>> .irq_cfg = AIC2_IRQ_CFG,
>>>
>>> + .el2_regs = true,
>>> .fast_ipi = true,
>>> + .ipi_regs = true,
>>> + .uncore2_regs = true,
>>
>> So to sum it up, all recent cores have all the cool features, and the
>> older ones have none of them. Surely we can do better than adding 3
>> fields that have the same value. Turn 'fast_ipi' into something that
>> means 'full_fat', and key everything on that.
>>
>> And if this is meant to evolve into a more differentiated set of
>> features, the usual idiom is to have a set of flags as part of an
>> unsigned long instead of a set of booleans.
> The latter would be true if a bootrom exploit or any equivalent means
> of booting Linux would be found for A12 (M1 is family with A14 for context).
>
> We can think of 4 feature levels, I think:
>
> A7-A10: 'nothing fancy'
> A11: fast_ipi (broken currently, need to investigate)
> A12: A11 + UNCORE2 regs
> M1+: A12 + EL2
>
> We *could* squash the A12-A14 case into M1, but then if a means of booting
> Linux appears, this would have to be untangled again..
>
>>
>>> };
>>>
>>> static const struct of_device_id aic_info_match[] = {
>>> @@ -452,6 +461,9 @@ static unsigned long aic_fiq_get_idx(struct irq_data *d)
>>>
>>> static void aic_fiq_set_mask(struct irq_data *d)
>>> {
>>> + if (!aic_irqc->info.el2_regs)
>>> + return;
>>
>> Why? AIC_TMR_EL02_PHYS is defined as the interrupt that fires in the
>> context of a guest. There is no guest here (no EL2 either), so what
>> you should have as interrupt number is AIC_TMR_EL0_{PHYS,VIRT}, and
>> this change becomes irrelevant (nothing to mask). Which is also what
>> happens when running an M1 under the m1n1 hypervisor.
> This func accesses impl-defined regs that are not present on earlier SoCs.
>
>>
>>> +
>>> /* Only the guest timers have real mask bits, unfortunately. */
>>> switch (aic_fiq_get_idx(d)) {
>>> case AIC_TMR_EL02_PHYS:
>>> @@ -469,6 +481,9 @@ static void aic_fiq_set_mask(struct irq_data *d)
>>>
>>> static void aic_fiq_clear_mask(struct irq_data *d)
>>> {
>>> + if (!aic_irqc->info.el2_regs)
>>> + return;
>>> +
>>> switch (aic_fiq_get_idx(d)) {
>>> case AIC_TMR_EL02_PHYS:
>>> sysreg_clear_set_s(SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2, 0, VM_TMR_FIQ_ENABLE_P);
>>> @@ -524,12 +539,14 @@ static void __exception_irq_entry aic_handle_fiq(struct pt_regs *regs)
>>> * we check for everything here, even things we don't support yet.
>>> */
>>>
>>> - if (read_sysreg_s(SYS_IMP_APL_IPI_SR_EL1) & IPI_SR_PENDING) {
>>> - if (static_branch_likely(&use_fast_ipi)) {
>>> - aic_handle_ipi(regs);
>>> - } else {
>>> - pr_err_ratelimited("Fast IPI fired. Acking.\n");
>>> - write_sysreg_s(IPI_SR_PENDING, SYS_IMP_APL_IPI_SR_EL1);
>>> + if (aic_irqc->info.ipi_regs) {
>>
>> This is probably the hottest path in the whole kernel. Do we want an
>> extra read here? Absolutely not. At the very least, this should be a
>> static key.
> Yeah, makes sense..
>
>
>>
>>> + if (read_sysreg_s(SYS_IMP_APL_IPI_SR_EL1) & IPI_SR_PENDING) {
>>> + if (static_branch_likely(&use_fast_ipi)) {
>>> + aic_handle_ipi(regs);
>>> + } else {
>>> + pr_err_ratelimited("Fast IPI fired. Acking.\n");
>>> + write_sysreg_s(IPI_SR_PENDING, SYS_IMP_APL_IPI_SR_EL1);
>>> + }
>>> }
>>> }
>>>
>>> @@ -566,12 +583,14 @@ static void __exception_irq_entry aic_handle_fiq(struct pt_regs *regs)
>>> AIC_FIQ_HWIRQ(irq));
>>> }
>>>
>>> - if (FIELD_GET(UPMCR0_IMODE, read_sysreg_s(SYS_IMP_APL_UPMCR0_EL1)) == UPMCR0_IMODE_FIQ &&
>>> - (read_sysreg_s(SYS_IMP_APL_UPMSR_EL1) & UPMSR_IACT)) {
>>> - /* Same story with uncore PMCs */
>>> - pr_err_ratelimited("Uncore PMC FIQ fired. Masking.\n");
>>> - sysreg_clear_set_s(SYS_IMP_APL_UPMCR0_EL1, UPMCR0_IMODE,
>>> - FIELD_PREP(UPMCR0_IMODE, UPMCR0_IMODE_OFF));
>>> + if (aic_irqc->info.uncore2_regs) {
>>
>> Same thing.
>>
>>> + if (FIELD_GET(UPMCR0_IMODE, read_sysreg_s(SYS_IMP_APL_UPMCR0_EL1)) == UPMCR0_IMODE_FIQ &&
>>> + (read_sysreg_s(SYS_IMP_APL_UPMSR_EL1) & UPMSR_IACT)) {
>>> + /* Same story with uncore PMCs */
>>> + pr_err_ratelimited("Uncore PMC FIQ fired. Masking.\n");
>>> + sysreg_clear_set_s(SYS_IMP_APL_UPMCR0_EL1, UPMCR0_IMODE,
>>> + FIELD_PREP(UPMCR0_IMODE, UPMCR0_IMODE_OFF));
>>> + }
>>> }
>>> }
>>>
>>> @@ -676,7 +695,8 @@ static int aic_irq_domain_translate(struct irq_domain *id,
>>> break;
>>> case AIC_TMR_HV_PHYS:
>>> case AIC_TMR_HV_VIRT:
>>> - return -ENOENT;
>>> + if (aic_irqc->info.el2_regs)
>>> + return -ENOENT;
>>
>> See my comment above about the use of these interrupt numbers.
> `if (!is_kernel_in_hyp_mode()) {` always evaluates to true, since there's
> no EL2. Hence, accessing AIC_TMR_HV_{VIRT,PHYS} makes this return ENOENT,
> which means timer can't probe and that's no bueno.
Sounds like an issue with your device tree. There should be no reference to
AIC_TMR_HV_{VIRT,PHYS} in there.
Sven
More information about the linux-arm-kernel
mailing list