diff mbox series

[v1,12/14] xen/riscv: implement setup_irq()

Message ID 2bc37c3996978129a5b2c888917307ea32448651.1744126720.git.oleksii.kurochko@gmail.com (mailing list archive)
State New
Headers show
Series riscv: introduce basic UART support and interrupts for hypervisor mode | expand

Commit Message

Oleksii Kurochko April 8, 2025, 3:57 p.m. UTC
Introduce support for IRQ setup on RISC-V by implementing `setup_irq()` and
`__setup_irq()`, adapted and extended from an initial implementation by [1].

__setup_irq() does the following:
  - Sets up an IRQ action.
  - Validates that shared IRQs have non-NULL `dev_id` and are only used when
    existing handlers allow sharing.
  - Uses `smp_mb()` to enforce memory ordering after assigning `desc->action`
    to ensure visibility before enabling the IRQ.
  - Supports multi-action setups via `CONFIG_IRQ_HAS_MULTIPLE_ACTION`.

setup_irq() does the following:
  - Converts IRQ number to descriptor and acquires its lock.
  - Rejects registration if the IRQ is already assigned to a guest domain,
    printing an error.
  - Delegates the core setup to `__setup_irq()`.
  - On first-time setup, disables the IRQ, routes it to Xen using
    `intc_route_irq_to_xen()`, sets default CPU affinity (current CPU),
    calls the handler’s startup routine, and finally enables the IRQ.

irq_set_affinity() invokes `set_affinity` callback from the IRQ handler
if present.

Defined `IRQ_NO_PRIORITY` as default priority used when routing IRQs to Xen.

[1] https://gitlab.com/xen-project/people/olkur/xen/-/commit/7390e2365828b83e27ead56b03114a56e3699dd5

Co-developed-by: Romain Caritey <Romain.Caritey@microchip.com>
Signed-off-by: Oleksii Kurochko <oleksii.kurochko@gmail.com>
---
 xen/arch/riscv/include/asm/irq.h |  6 ++
 xen/arch/riscv/irq.c             | 95 ++++++++++++++++++++++++++++++++
 2 files changed, 101 insertions(+)

Comments

Jan Beulich April 15, 2025, 3:55 p.m. UTC | #1
On 08.04.2025 17:57, Oleksii Kurochko wrote:
> --- a/xen/arch/riscv/include/asm/irq.h
> +++ b/xen/arch/riscv/include/asm/irq.h
> @@ -26,6 +26,8 @@
>  #define IRQ_TYPE_SENSE_MASK     DT_IRQ_TYPE_SENSE_MASK
>  #define IRQ_TYPE_INVALID        DT_IRQ_TYPE_INVALID
>  
> +#define IRQ_NO_PRIORITY 0
> +
>  /* TODO */
>  #define nr_static_irqs 0
>  #define arch_hwdom_irqs(domid) 0U
> @@ -54,6 +56,10 @@ void init_IRQ(void);
>  struct cpu_user_regs;

Seeing this and ...

>  void do_IRQ(struct cpu_user_regs *regs, unsigned int irq);
>  
> +struct irq_desc;
> +struct cpumask_t;

... now these - all of such forward decls may want to collectively live in a
central place higher up in the file.

> @@ -57,6 +59,99 @@ int platform_get_irq(const struct dt_device_node *device, int index)
>      return dt_irq.irq;
>  }
>  
> +static int __setup_irq(struct irq_desc *desc, unsigned int irqflags,
> +                       struct irqaction *new)

Irrespective of you possibly having found it like this elsewhere, may I
suggest that in new code we avoid leading double underscores? A single one
will do here.

> +{
> +    bool shared = irqflags & IRQF_SHARED;
> +
> +    ASSERT(new != NULL);
> +
> +    /* Sanity checks:

Nit: Comment style (and there are many more issues below).

> +     *  - if the IRQ is marked as shared
> +     *  - dev_id is not NULL when IRQF_SHARED is set
> +     */
> +    if ( desc->action != NULL && (!test_bit(_IRQF_SHARED, &desc->status)
> +         || !shared) )

Nit: Operator placement and indentation.

You're probably better off this way anyway:

    if ( desc->action != NULL &&
         (!test_bit(_IRQF_SHARED, &desc->status) || !shared) )

> +        return -EINVAL;
> +    if ( shared && new->dev_id == NULL )
> +        return -EINVAL;
> +
> +    if ( shared )
> +        set_bit(_IRQF_SHARED, &desc->status);

See comments on earlier patches.

> +#ifdef CONFIG_IRQ_HAS_MULTIPLE_ACTION
> +    new->next = desc->action;
> +    smp_mb();
> +#endif
> +
> +    desc->action = new;
> +    smp_mb();

Aren't smp_wmb() sufficient on both places? If not, I think comments
want adding.

> +    return 0;
> +}
> +
> +void irq_set_affinity(struct irq_desc *desc, const cpumask_t *cpu_mask)
> +{
> +    if ( desc != NULL )

Can desc really be NULL here? Isn't desc->lock required to be held?

> +        desc->handler->set_affinity(desc, cpu_mask);
> +}
> +
> +int setup_irq(unsigned int irq, unsigned int irqflags, struct irqaction *new)
> +{
> +    int rc;
> +    unsigned long flags;
> +    struct irq_desc *desc;
> +    bool disabled;
> +
> +    desc = irq_to_desc(irq);

Make this the variable's initializer?

> +    spin_lock_irqsave(&desc->lock, flags);
> +
> +    disabled = (desc->action == NULL);
> +
> +    if ( test_bit(_IRQ_GUEST, &desc->status) )
> +    {
> +        spin_unlock_irqrestore(&desc->lock, flags);
> +        /*
> +         * TODO: would be nice to have functionality to print which domain owns
> +         *       an IRQ.
> +         */
> +        printk(XENLOG_ERR "ERROR: IRQ %u is already in use by a domain\n", irq);
> +        return -EBUSY;
> +    }
> +
> +    rc = __setup_irq(desc, irqflags, new);
> +    if ( rc )
> +        goto err;
> +
> +    /* First time the IRQ is setup */
> +    if ( disabled )
> +    {
> +        /* disable irq by default */
> +        set_bit(_IRQ_DISABLED, &desc->status);

Shouldn't this be set when we make it here?

> +        /* route interrupt to xen */
> +        intc_route_irq_to_xen(desc, IRQ_NO_PRIORITY);
> +
> +        /*
> +         * we don't care for now which CPU will receive the
> +         * interrupt
> +         *
> +         * TODO: Handle case where IRQ is setup on different CPU than
> +         * the targeted CPU and the priority.
> +         */
> +        irq_set_affinity(desc, cpumask_of(smp_processor_id()));
> +        desc->handler->startup(desc);
> +        /* enable irq */
> +        clear_bit(_IRQ_DISABLED, &desc->status);

Now it turns out this is really done twice: Once in aplic_irq_enable(),
and once here.

> +    }
> +
> +err:

Nit (yet once again).

Jan
Oleksii Kurochko April 17, 2025, 10:10 a.m. UTC | #2
On 4/15/25 5:55 PM, Jan Beulich wrote
>> +#ifdef CONFIG_IRQ_HAS_MULTIPLE_ACTION
>> +    new->next = desc->action;
>> +    smp_mb();
>> +#endif
>> +
>> +    desc->action = new;
>> +    smp_mb();
> Aren't smp_wmb() sufficient on both places? If not, I think comments
> want adding.

smp_wmb() will be sufficient but I think the barrier could be dropped at all
as __setup_irq() is called only in setup_irq() and __setup_irq() call is wrapped
by spinlock_{un}lock_irqsave() where spinlock_unlock_*() will put barrier.

>
>> +    return 0;
>> +}
>> +
>> +void irq_set_affinity(struct irq_desc *desc, const cpumask_t *cpu_mask)
>> +{
>> +    if ( desc != NULL )
> Can desc really be NULL here?

It can't as irq_desc[] isn't dynamically allocated.

>   Isn't desc->lock required to be held?

It is and it is called in setup_irq() which calls spin_lock_irqsave().
Anyway, I think it could be dropped at all and use 'desc->handler->set_affinity(desc, cpu_mask);'
explicitly in setup_irq().

>> +    spin_lock_irqsave(&desc->lock, flags);
>> +
>> +    disabled = (desc->action == NULL);
>> +
>> +    if ( test_bit(_IRQ_GUEST, &desc->status) )
>> +    {
>> +        spin_unlock_irqrestore(&desc->lock, flags);
>> +        /*
>> +         * TODO: would be nice to have functionality to print which domain owns
>> +         *       an IRQ.
>> +         */
>> +        printk(XENLOG_ERR "ERROR: IRQ %u is already in use by a domain\n", irq);
>> +        return -EBUSY;
>> +    }
>> +
>> +    rc = __setup_irq(desc, irqflags, new);
>> +    if ( rc )
>> +        goto err;
>> +
>> +    /* First time the IRQ is setup */
>> +    if ( disabled )
>> +    {
>> +        /* disable irq by default */
>> +        set_bit(_IRQ_DISABLED, &desc->status);
> Shouldn't this be set when we make it here?

It should be. I'll drop the setting of _IRQ_DISABLED.

>
>> +        /* route interrupt to xen */
>> +        intc_route_irq_to_xen(desc, IRQ_NO_PRIORITY);
>> +
>> +        /*
>> +         * we don't care for now which CPU will receive the
>> +         * interrupt
>> +         *
>> +         * TODO: Handle case where IRQ is setup on different CPU than
>> +         * the targeted CPU and the priority.
>> +         */
>> +        irq_set_affinity(desc, cpumask_of(smp_processor_id()));
>> +        desc->handler->startup(desc);
>> +        /* enable irq */
>> +        clear_bit(_IRQ_DISABLED, &desc->status);
> Now it turns out this is really done twice: Once in aplic_irq_enable(),
> and once here.

Agree, this is a job of *_startup()->*_aplic_irq_enable(). I'll drop that too.

~ Oleksii
Jan Beulich April 17, 2025, 11:51 a.m. UTC | #3
On 17.04.2025 12:10, Oleksii Kurochko wrote:
> On 4/15/25 5:55 PM, Jan Beulich wrote
>>> +        /*
>>> +         * we don't care for now which CPU will receive the
>>> +         * interrupt
>>> +         *
>>> +         * TODO: Handle case where IRQ is setup on different CPU than
>>> +         * the targeted CPU and the priority.
>>> +         */
>>> +        irq_set_affinity(desc, cpumask_of(smp_processor_id()));
>>> +        desc->handler->startup(desc);
>>> +        /* enable irq */
>>> +        clear_bit(_IRQ_DISABLED, &desc->status);
>> Now it turns out this is really done twice: Once in aplic_irq_enable(),
>> and once here.
> 
> Agree, this is a job of *_startup()->*_aplic_irq_enable(). I'll drop that too.

Wait - see my comment there. I think it belongs here, not there.

Jan
diff mbox series

Patch

diff --git a/xen/arch/riscv/include/asm/irq.h b/xen/arch/riscv/include/asm/irq.h
index 9558d3fa61..bba3a97e3e 100644
--- a/xen/arch/riscv/include/asm/irq.h
+++ b/xen/arch/riscv/include/asm/irq.h
@@ -26,6 +26,8 @@ 
 #define IRQ_TYPE_SENSE_MASK     DT_IRQ_TYPE_SENSE_MASK
 #define IRQ_TYPE_INVALID        DT_IRQ_TYPE_INVALID
 
+#define IRQ_NO_PRIORITY 0
+
 /* TODO */
 #define nr_static_irqs 0
 #define arch_hwdom_irqs(domid) 0U
@@ -54,6 +56,10 @@  void init_IRQ(void);
 struct cpu_user_regs;
 void do_IRQ(struct cpu_user_regs *regs, unsigned int irq);
 
+struct irq_desc;
+struct cpumask_t;
+void irq_set_affinity(struct irq_desc *desc, const cpumask_t *cpu_mask);
+
 #endif /* ASM__RISCV__IRQ_H */
 
 /*
diff --git a/xen/arch/riscv/irq.c b/xen/arch/riscv/irq.c
index 3c0b95220a..1e937d4306 100644
--- a/xen/arch/riscv/irq.c
+++ b/xen/arch/riscv/irq.c
@@ -6,7 +6,9 @@ 
  * Copyright (c) 2024 Vates
  */
 
+#include <xen/bitops.h>
 #include <xen/bug.h>
+#include <xen/cpumask.h>
 #include <xen/device_tree.h>
 #include <xen/errno.h>
 #include <xen/init.h>
@@ -57,6 +59,99 @@  int platform_get_irq(const struct dt_device_node *device, int index)
     return dt_irq.irq;
 }
 
+static int __setup_irq(struct irq_desc *desc, unsigned int irqflags,
+                       struct irqaction *new)
+{
+    bool shared = irqflags & IRQF_SHARED;
+
+    ASSERT(new != NULL);
+
+    /* Sanity checks:
+     *  - if the IRQ is marked as shared
+     *  - dev_id is not NULL when IRQF_SHARED is set
+     */
+    if ( desc->action != NULL && (!test_bit(_IRQF_SHARED, &desc->status)
+         || !shared) )
+        return -EINVAL;
+    if ( shared && new->dev_id == NULL )
+        return -EINVAL;
+
+    if ( shared )
+        set_bit(_IRQF_SHARED, &desc->status);
+
+#ifdef CONFIG_IRQ_HAS_MULTIPLE_ACTION
+    new->next = desc->action;
+    smp_mb();
+#endif
+
+    desc->action = new;
+    smp_mb();
+
+    return 0;
+}
+
+void irq_set_affinity(struct irq_desc *desc, const cpumask_t *cpu_mask)
+{
+    if ( desc != NULL )
+        desc->handler->set_affinity(desc, cpu_mask);
+}
+
+int setup_irq(unsigned int irq, unsigned int irqflags, struct irqaction *new)
+{
+    int rc;
+    unsigned long flags;
+    struct irq_desc *desc;
+    bool disabled;
+
+    desc = irq_to_desc(irq);
+
+    spin_lock_irqsave(&desc->lock, flags);
+
+    disabled = (desc->action == NULL);
+
+    if ( test_bit(_IRQ_GUEST, &desc->status) )
+    {
+        spin_unlock_irqrestore(&desc->lock, flags);
+        /*
+         * TODO: would be nice to have functionality to print which domain owns
+         *       an IRQ.
+         */
+        printk(XENLOG_ERR "ERROR: IRQ %u is already in use by a domain\n", irq);
+        return -EBUSY;
+    }
+
+    rc = __setup_irq(desc, irqflags, new);
+    if ( rc )
+        goto err;
+
+    /* First time the IRQ is setup */
+    if ( disabled )
+    {
+        /* disable irq by default */
+        set_bit(_IRQ_DISABLED, &desc->status);
+
+        /* route interrupt to xen */
+        intc_route_irq_to_xen(desc, IRQ_NO_PRIORITY);
+
+        /*
+         * we don't care for now which CPU will receive the
+         * interrupt
+         *
+         * TODO: Handle case where IRQ is setup on different CPU than
+         * the targeted CPU and the priority.
+         */
+        irq_set_affinity(desc, cpumask_of(smp_processor_id()));
+        desc->handler->startup(desc);
+        /* enable irq */
+        clear_bit(_IRQ_DISABLED, &desc->status);
+    }
+
+err:
+    spin_unlock_irqrestore(&desc->lock, flags);
+
+    return rc;
+}
+
 int arch_init_one_irq_desc(struct irq_desc *desc)
 {
     desc->arch.type = IRQ_TYPE_INVALID;