|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [PATCH v1 10/14] xen/riscv: implementation of aplic and imsic operations
On 16.04.2025 21:05, Oleksii Kurochko wrote:
> On 4/15/25 2:46 PM, Jan Beulich wrote:
>> On 08.04.2025 17:57, Oleksii Kurochko wrote:
>>> Introduce interrupt controller descriptor for host APLIC to describe
>>> the low-lovel hardare. It includes implementation of the following
>>> functions:
>>> - aplic_irq_startup()
>>> - aplic_irq_shutdown()
>>> - aplic_irq_enable()
>>> - aplic_irq_disable()
>>> - aplic_irq_ack()
>>> - aplic_host_irq_end()
>>> - aplic_set_irq_affinity()
>>>
>>> As APLIC is used in MSI mode it requires to enable/disable interrupts not
>>> only for APLIC but also for IMSIC. Thereby for the purpose of
>>> aplic_irq_{enable,disable}() it is introduced imsic_irq_{enable,disable)().
>>>
>>> For the purpose of aplic_set_irq_affinity() aplic_get_cpu_from_mask() is
>>> introduced to get hart id.
>>>
>>> Also, introduce additional interrupt controller h/w operations and
>>> host_irq_type for APLIC:
>>> - aplic_host_irq_type
>>> - aplic_set_irq_priority()
>>> - aplic_set_irq_type()
>> Yet these two functions nor the hooks they're used to populate are entirely
>> unused here. Since they're also outside of the common IRQ handling machinery,
>> it's unclear how one would sanely ack such a change.
>
> They will be called by intc_route_irq_to_xen() from setup_irq() during firt
> time
> the IRQ is setup.
Perhaps move their introduction to there then? We don't do any Misra checking
yet lon RISC-V, but imo it's still good practice to avoid introducing new
violations, even if only temporarily.
>>> --- a/xen/arch/riscv/aplic.c
>>> +++ b/xen/arch/riscv/aplic.c
>>> @@ -15,6 +15,7 @@
>>> #include <xen/irq.h>
>>> #include <xen/mm.h>
>>> #include <xen/sections.h>
>>> +#include <xen/spinlock.h>
>>> #include <xen/types.h>
>>> #include <xen/vmap.h>
>>>
>>> @@ -110,9 +111,173 @@ static int __init aplic_init(void)
>>> return 0;
>>> }
>>>
>>> -static const struct intc_hw_operations __ro_after_init aplic_ops = {
>>> +static void aplic_set_irq_type(struct irq_desc *desc, unsigned int type)
>>> +{
>>> + unsigned int irq = desc->irq - 1;
>> Why this adjustment by 1 (and yet both items being named "irq")?
>
> Interrupt 0 isn't possible based on the spec:
> ```
> Each of an APLIC’s interrupt sources has a fixed unique identity number
> in the range 1 to N, where N is the total number of sources at the
> APLIC. The number zero is not a valid interrupt identity number at an
> APLIC. The maximum number of interrupt sources an APLIC may support is
> 1023. ``` and interrupt 1 will correspond to bit 0 in sourcecfg[] register,
> interrupt
> 2 ->sourcecfg[1] and so on. And that is the reason why we need -1.
Okay, fine. But what about the part of the question in parentheses?
>>> + aplic.regs->sourcecfg[irq] = APLIC_SOURCECFG_SM_EDGE_FALL;
>>> + break;
>>> + case IRQ_TYPE_LEVEL_HIGH:
>>> + aplic.regs->sourcecfg[irq] = APLIC_SOURCECFG_SM_LEVEL_HIGH;
>>> + break;
>>> + case IRQ_TYPE_LEVEL_LOW:
>>> + aplic.regs->sourcecfg[irq] = APLIC_SOURCECFG_SM_LEVEL_LOW;
>>> + break;
>>> + default:
>>> + aplic.regs->sourcecfg[irq] = APLIC_SOURCECFG_SM_INACTIVE;
>>> + break;
>> Is the default: label legitimate to be reached?
>
> From the spec:
> ```
> 0 Inactive Inactive in this domain (and not delegated) 1 Detached
> Active, detached from the source wire 2–3 — Reserved 4 Edge1 Active,
> edge-sensitive; interrupt asserted on rising edge 5 Edge0 Active,
> edge-sensitive; interrupt asserted on falling edge 6 Level1 Active,
> level-sensitive; interrupt asserted when high 7 Level0 Active,
> level-sensitive; interrupt asserted when low ``` It seems to me like
> APLIC_SOURCECFG_SM_INACTIVE just covers cases (0-3) and inactive IRQ
> pretty safe to as a default value.
I fear this doesn't answer my question, which is to a large part related
to the Xen code, and only to some degree to the spec.
>>> +static void aplic_set_irq_priority(struct irq_desc *desc,
>>> + unsigned int priority)
>>> +{
>>> + /* No priority, do nothing */
>>> +}
>> Since the function dopes nothing, wouldn't it be better to omit it and have
>> the (future) caller check for a NULL pointer ahead of making the (indirect)
>> call? Same remark for other handlers (below) which also do nothing.
>
> I thought about that too, but it could be some cases when the stub is
> introduced
> with temporary BUG_ON("unimplemented") inside just to not miss to implement it
> when it will be necessary.
> If we will have only the caller check then we could miss to implement such
> stubs.
I guess I don't understand the concern.
>>> +static void aplic_irq_enable(struct irq_desc *desc)
>>> +{
>>> + unsigned long flags;
>>> +
>>> + /*
>>> + * TODO: Currently, APLIC is supported only with MSI interrupts.
>>> + * If APLIC without MSI interrupts is required in the future,
>>> + * this function will need to be updated accordingly.
>>> + */
>>> + ASSERT(aplic.imsic_cfg->is_used);
>> Such an extra field, used only for assertions, is pretty odd. Can't you
>> use any of the other fields to achieve the same effect?
>
> in aplic_init() there is:
> /* check for associated imsic node */
> rc = dt_property_read_u32(node, "msi-parent", &imsic_phandle);
> if ( !rc )
> panic("%s: IDC mode not supported\n", node->full_name);
>
> So we will have panic() anyway if MSI mode isn't supported. As an option we
> can just drop the ASSERT.
Since they serve primarily as a reminder where changes would need making,
I'd prefer if they could be kept.
> Or introduce static variable in aplic.c `aplic_mode`, init it in aplic_init()
> and use it in ASSERT().
This would then again be used solely for assertions, aiui? As said, I
think it would be preferable if some already existing indicator could be
used for this purpose.
>>> + ASSERT(spin_is_locked(&desc->lock));
>> If this lock (which is an IRQ-safe one) is necessarily held, ...
>>
>>> + spin_lock_irqsave(&aplic.lock, flags);
>> ... you can use just spin_lock() here.
>>
>>> + clear_bit(_IRQ_DISABLED, &desc->status);
>> Why an atomic bitop when desc is locked? (And yes, I ought to raise the same
>> question on Arm code also doing so.)
>
> I haven't thought about that. Likely non-atomic bitop could be used here.
And then - does it need to be a bitop? Aiui that's what Arm uses, while x86
doesn't. And I see no reason to use other than plain C operators here. If
Arm was switched, presumably all the redundant (and misnamed) _IRQ_*
constants could go away, with just the IRQ_* ones left.
>> I'm uncertain about this bit setting anyway - on x86 we would only fiddle
>> with it for IRQs not in use, not while enabling/disabling one.
What about this part?
>> In any event this can be done outside of the APLIC-locked region, I think.
>
> Considering that we are doing that under desc->lock, agree we can move that
> outside
> the APLIC-locked region.
>
>>> + imsic_irq_enable(desc->irq);
>>> +
>>> + /* enable interrupt in APLIC */
>>> + aplic.regs->setienum = desc->irq;
>> Are you sure you want to use plain assignments for MMIO accesses? I'd have
>> expected writel() to be used here. (And only later I realized that I didn't
>> spot the same already higher up from here.)
>
> Good point. I have to update that with writel()...
>
>>
>> From the vague understanding I've gained so far: Isn't the APLIC closer to
>> the CPU and the IMSIC closer to the device? If so, wouldn't you want to
>> enable at the APLIC before enabling at the IMSIC? But of course that also
>> depends on what exactly happens in the window while one is already enabled
>> and the other is still disabled. (Later) From the code you add to imsic.c
>> it looks like it's the other way around, as the IMSIC is accessed through
>> CSRs.
>
> From the AIA spec:
> ```
> An Incoming MSI Controller (IMSIC) is an optional RISC-V hardware component
> that is closely coupled with a hart, one IMSIC per hart. An IMSIC receives
> and records incoming message-signaled interrupts (MSIs) for a hart, and
> signals to the hart when there are pending and enabled interrupts to be
> serviced.
> ```
>
> Based on the figure 2 (Interrupt delivery by MSIs when harts have IMSICs for
> receiving them)
> of AIA
> spechttps://github.com/riscv/riscv-aia/blob/main/src/intrsWithIMSICs.png
> IMSIC is more close to CPU and APLIC is more close to the device. The
> external interrupt
> controller is APLIC and it only sends a MSI message for a CPU.
>
> The logical flow of an interrupt to a hart with an IMSIC would be:
> 1. A physical interrupt signal arrives at the APLIC.
> 2. The APLIC, if configured for MSI delivery mode (domaincfg.DM = 1) and if
> the specific
> interrupt source is active and enabled within its domain (controlled by
> sourcecfg[i]
> and the global Interrupt Enable bit IE in domaincfg), will generate an
> MSI.
> 3. This MSI is then sent to the target hart's IMSIC. The APLIC needs to know
> the MSI
> target address for each hart, which can be hardwired or configured
> through registers
> like mmsiaddrcfg and mmsiaddrcfgh.
> 4. The receiving hart's IMSIC records this MSI as a pending interrupt.
> 5. If the corresponding interrupt identity is enabled within the IMSIC's
> interrupt file,
> the IMSIC will signal the hart, typically by setting the MEIP or SEIP bit
> in the mip
> CSR (or sip CSR).
>
> Generally, I think that the order in which enable interrupts doesn't really
> matter as
> if you were to enable the IMSIC to receive a certain interrupt before the
> APLIC was
> configured to send it (or had a pending interrupt from the device), the IMSIC
> would
> simply be waiting for an MSI that wouldn't arrive.
> Similarly, if the APLIC sends an MSI for an interrupt that is not enabled in
> the IMSIC,
> the interrupt would remain pending in the IMSIC but wouldn't trigger an
> interrupt at
> the hart.
>
> IMO, the order which is used now in the code is pretty logical.
>
> Does it make sense?
Except for the "doesn't really matter" - yes. In a reply to a later patch I
indicated I realized that IMSIC is what's closer to the CPU (and hence later
in the chain of interrupt delivery actions).
>>> + spin_unlock_irqrestore(&aplic.lock, flags);
>>> +}
>>> +
>>> +static void aplic_irq_disable(struct irq_desc *desc)
>>> +{
>>> + unsigned long flags;
>>> +
>>> + /*
>>> + * TODO: Currently, APLIC is supported only with MSI interrupts.
>>> + * If APLIC without MSI interrupts is required in the future,
>>> + * this function will need to be updated accordingly.
>>> + */
>>> + ASSERT(aplic.imsic_cfg->is_used);
>>> +
>>> + ASSERT(spin_is_locked(&desc->lock));
>>> +
>>> + spin_lock_irqsave(&aplic.lock, flags);
>>> +
>>> + set_bit(_IRQ_DISABLED, &desc->status);
>>> +
>>> + /* disable interrupt in APLIC */
>>> + aplic.regs->clrienum = desc->irq;
>>> +
>>> + /* disable interrupt in IMSIC */
>>> + imsic_irq_disable(desc->irq);
>>> +
>>> + spin_unlock_irqrestore(&aplic.lock, flags);
>>> +}
>>> +
>>> +static unsigned int aplic_irq_startup(struct irq_desc *desc)
>>> +{
>>> + aplic_irq_enable(desc);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void aplic_irq_shutdown(struct irq_desc *desc)
>>> +{
>>> + aplic_irq_disable(desc);
>>> +}
>> You don't really need a separate hook function here, do you?
>
> With such implementation it is really not needed to have a hook so
> I will drop it.
>
>>> +static void aplic_irq_ack(struct irq_desc *desc)
>>> +{
>>> + /* nothing to do */
>>> +}
>>> +
>>> +static void aplic_host_irq_end(struct irq_desc *desc)
>> What's the "host" in the identifier about?
>
> It was copied that from Arm and my understanding that it means
> Xen-related IRQ as they also have:
> ```
> /* XXX different for level vs edge */
> static hw_irq_controller gicv2_host_irq_type = {
> ...
> .end = gicv2_host_irq_end,
> ...
> };
>
> static hw_irq_controller gicv2_guest_irq_type = {
> ...
> .end = gicv2_guest_irq_end,
> ...
> };
> ```
And you expect to end up with a similar distinction on RISC-V? There's
nothing like that on x86, just to mention it.
>>> +static void aplic_set_irq_affinity(struct irq_desc *desc, const cpumask_t
>>> *mask)
>>> +{
>>> + unsigned int cpu;
>>> + uint64_t group_index, base_ppn;
>>> + uint32_t hhxw, lhxw ,hhxs, value;
>>> + const struct imsic_config *imsic = aplic.imsic_cfg;
>>> +
>>> + /*
>>> + * TODO: Currently, APLIC is supported only with MSI interrupts.
>>> + * If APLIC without MSI interrupts is required in the future,
>>> + * this function will need to be updated accordingly.
>>> + */
>>> + ASSERT(aplic.imsic_cfg->is_used);
>> Use the local variable you have made yourself?
>
> What do you mean by local here?
Just a few lines up you latch aplic.imsic_cfg into the local "imsic".
>>> + ASSERT(!cpumask_empty(mask));
>>> +
>>> + spin_lock(&aplic.lock);
>> Aiui the lock can be acquired quite a bit later. It ought to be needed only
>> around the actual write to the hardware register.
>>
>>> + cpu = cpuid_to_hartid(aplic_get_cpu_from_mask(mask));
>>> + hhxw = imsic->group_index_bits;
>>> + lhxw = imsic->hart_index_bits;
>>> + hhxs = imsic->group_index_shift - IMSIC_MMIO_PAGE_SHIFT * 2;
>>> + base_ppn = imsic->msi[cpu].base_addr >> IMSIC_MMIO_PAGE_SHIFT;
>>> +
>>> + /* update hart and EEID in the target register */
>>> + group_index = (base_ppn >> (hhxs + 12)) & (BIT(hhxw, UL) - 1);
>> What's this magic 12 in here? Not IMSIC_MMIO_PAGE_SHIFT I suppose?
>
> In the AIA spec they are using 12
> explicitly:https://github.com/riscv/riscv-aia/blob/main/src/AdvPLIC.adoc#AdvPLIC-MSIAddrs
In the spec that's fine, but please make yourself a constant with a suitable
name then, to be used here. Just consider what would happen if we used literal
12 everywhere PAGE_SHIFT was meant.
>>> +void imsic_irq_enable(unsigned int hwirq)
>>> +{
>>> + unsigned long flags;
>>> +
>>> + spin_lock_irqsave(&imsic_cfg.lock, flags);
>>> + imsic_local_eix_update(hwirq, 1, false, true);
>> No subtraction of 1 here? Also, why "hwirq" and not just "irq"?
>
> From the spec:
> ```
>
> When an interrupt file supports distinct interrupt identities, valid identity
> numbers are between 1
> and inclusive. The identity numbers within this range are said to be
> implemented by the interrupt
> file; numbers outside this range are not implemented. The number zero is
> never a valid interrupt
> identity.
> ...
>
> Bit positions in a valid eiek register that don’t correspond to a
> supported interrupt identity (such as bit 0 of eie0) are read-only zeros.
>
>
> ```
>
> So in EIx registers interrupt i corresponds to bit i in comparison wiht
> APLIC's sourcecfg which starts from 0.
Confusing, but what do you do.
>>> @@ -277,6 +333,13 @@ int __init imsic_init(struct dt_device_node *node)
>>> goto imsic_init_err;
>>> }
>>>
>>> + spin_lock_init(&imsic_cfg.lock);
>>> +
>>> + /* Enable local interrupt delivery */
>>> + imsic_ids_local_delivery(true);
>> What's this? I can't find the function/macro here, nor in patch 08, nor in
>> staging.
>
> It is defined in imsic.c:
> ```
> void imsic_ids_local_delivery(bool enable)
> {
> if ( enable )
> {
> imsic_csr_write(IMSIC_EITHRESHOLD, IMSIC_ENABLE_EITHRESHOLD);
> imsic_csr_write(IMSIC_EIDELIVERY, IMSIC_ENABLE_EIDELIVERY);
> }
> else
> {
> imsic_csr_write(IMSIC_EITHRESHOLD, IMSIC_DISABLE_EITHRESHOLD);
> imsic_csr_write(IMSIC_EIDELIVERY, IMSIC_DISABLE_EIDELIVERY);
> }
> }
> ```
No, it's not. As noted in the reply to a later patch, it's only introduced
there. Hence the build will break between the two patches.
Jan
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |