interrupt: support arbitrary interrupt controller cascading

When registering a cascading interrupt controller as a child of
another interrupt controller, no new child descriptor has to be
allocated, instead the embedded into the cascading interrupt
controller object descriptor has to be used.

Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
This commit is contained in:
Guennadi Liakhovetski 2019-02-26 18:33:33 +01:00 committed by Liam Girdwood
parent f0e5094c4a
commit ac456d0a13
1 changed files with 83 additions and 42 deletions

View File

@ -26,6 +26,12 @@ static union {
uint8_t bytes[PLATFORM_DCACHE_ALIGN];
} cascade_root;
static int interrupt_register_internal(uint32_t irq, int unmask,
void (*handler)(void *arg),
void *arg, struct irq_desc *desc);
static void interrupt_unregister_internal(uint32_t irq, const void *arg,
struct irq_desc *desc);
int interrupt_cascade_register(const struct irq_cascade_tmpl *tmpl)
{
struct irq_cascade_desc **cascade;
@ -142,7 +148,7 @@ void interrupt_init(void)
}
static int irq_register_child(struct irq_desc *parent, int irq, int unmask,
void (*handler)(void *arg), void *arg)
void (*handler)(void *arg), void *arg, struct irq_desc *desc)
{
unsigned int core = cpu_get_id();
struct irq_desc *child;
@ -155,14 +161,10 @@ static int irq_register_child(struct irq_desc *parent, int irq, int unmask,
cascade = container_of(parent, struct irq_cascade_desc, desc);
spin_lock(&cascade->lock);
hw_irq = irq - cascade->irq_base;
if (hw_irq < 0 || cascade->irq_base + PLATFORM_IRQ_CHILDREN <= irq) {
ret = -EINVAL;
goto finish;
}
if (hw_irq < 0 || cascade->irq_base + PLATFORM_IRQ_CHILDREN <= irq)
return -EINVAL;
head = &cascade->child[hw_irq].list;
@ -173,51 +175,52 @@ static int irq_register_child(struct irq_desc *parent, int irq, int unmask,
trace_error(TRACE_CLASS_IRQ,
"error: IRQ 0x%x handler argument re-used!",
irq);
ret = -EEXIST;
goto finish;
return -EEXIST;
}
if (child->unmask != unmask) {
trace_error(TRACE_CLASS_IRQ,
"error: IRQ 0x%x flags differ!", irq);
ret = -EINVAL;
goto finish;
return -EINVAL;
}
}
/* init child from run-time, may be registered and unregistered
* many times at run-time
*/
child = rzalloc(RZONE_SYS_RUNTIME | RZONE_FLAG_UNCACHED,
SOF_MEM_CAPS_RAM, sizeof(struct irq_desc));
if (!child) {
ret = -ENOMEM;
goto finish;
if (!desc) {
/* init child from run-time, may be registered and unregistered
* many times at run-time
*/
child = rzalloc(RZONE_SYS_RUNTIME | RZONE_FLAG_UNCACHED,
SOF_MEM_CAPS_RAM, sizeof(struct irq_desc));
if (!child)
return -ENOMEM;
child->handler = handler;
child->handler_arg = arg;
child->irq = irq;
} else {
child = desc;
child->cpu_mask = 0;
}
child->handler = handler;
child->handler_arg = arg;
child->unmask = unmask;
child->irq = irq;
list_item_append(&child->irq_list, head);
/* do we need to register parent on this CPU? */
if (!cascade->num_children[core])
ret = interrupt_register(parent->irq, IRQ_AUTO_UNMASK,
parent->handler, parent);
ret = interrupt_register_internal(parent->irq, IRQ_AUTO_UNMASK,
parent->handler, parent,
parent);
/* increment number of children */
if (!ret)
cascade->num_children[core]++;
finish:
spin_unlock(&cascade->lock);
return ret;
}
static void irq_unregister_child(struct irq_desc *parent, int irq,
const void *arg)
const void *arg, struct irq_desc *desc)
{
struct irq_desc *child;
struct irq_cascade_desc *cascade = container_of(parent,
@ -226,28 +229,26 @@ static void irq_unregister_child(struct irq_desc *parent, int irq,
struct list_item *list, *head = &cascade->child[hw_irq].list;
unsigned int core = cpu_get_id();
spin_lock(&cascade->lock);
list_for_item(list, head) {
child = container_of(list, struct irq_desc, irq_list);
if (child->handler_arg == arg) {
list_item_del(&child->irq_list);
cascade->num_children[core]--;
rfree(child);
if (!desc)
rfree(child);
/*
* unregister the root interrupt if this l2 is the last
* registered child.
*/
if (!cascade->num_children[core])
interrupt_unregister(parent->irq, parent);
interrupt_unregister_internal(parent->irq,
parent, parent);
break;
}
}
spin_unlock(&cascade->lock);
}
static uint32_t irq_enable_child(struct irq_desc *parent, int irq, void *arg)
@ -259,8 +260,14 @@ static uint32_t irq_enable_child(struct irq_desc *parent, int irq, void *arg)
struct irq_child *child;
unsigned int child_idx;
struct list_item *list;
unsigned long flags;
spin_lock(&cascade->lock);
/*
* Locking is child to parent: when called recursively we are already
* holding the child's lock and then also taking the parent's lock. The
* same holds for the interrupt_(un)register() paths.
*/
spin_lock_irq(&cascade->lock, flags);
child = cascade->child + hw_irq;
child_idx = cascade->global_mask ? 0 : core;
@ -284,7 +291,7 @@ static uint32_t irq_enable_child(struct irq_desc *parent, int irq, void *arg)
interrupt_unmask(irq, core);
}
spin_unlock(&cascade->lock);
spin_unlock_irq(&cascade->lock, flags);
return 0;
}
@ -298,8 +305,9 @@ static uint32_t irq_disable_child(struct irq_desc *parent, int irq, void *arg)
struct irq_child *child;
unsigned int child_idx;
struct list_item *list;
unsigned long flags;
spin_lock(&cascade->lock);
spin_lock_irq(&cascade->lock, flags);
child = cascade->child + hw_irq;
child_idx = cascade->global_mask ? 0 : core;
@ -327,7 +335,7 @@ static uint32_t irq_disable_child(struct irq_desc *parent, int irq, void *arg)
interrupt_disable(parent->irq, parent->handler_arg);
}
spin_unlock(&cascade->lock);
spin_unlock_irq(&cascade->lock, flags);
return 0;
}
@ -335,26 +343,59 @@ static uint32_t irq_disable_child(struct irq_desc *parent, int irq, void *arg)
int interrupt_register(uint32_t irq, int unmask, void (*handler)(void *arg),
void *arg)
{
return interrupt_register_internal(irq, unmask, handler, arg, NULL);
}
static int interrupt_register_internal(uint32_t irq, int unmask,
void (*handler)(void *arg),
void *arg, struct irq_desc *desc)
{
struct irq_cascade_desc *cascade;
struct irq_desc *parent;
/* Avoid a bogus compiler warning */
unsigned long flags = 0;
int ret;
/* no parent means we are registering DSP internal IRQ */
parent = interrupt_get_parent(irq);
if (parent == NULL)
return arch_interrupt_register(irq, handler, arg);
else
return irq_register_child(parent, irq, unmask, handler, arg);
cascade = container_of(parent, struct irq_cascade_desc, desc);
spin_lock_irq(&cascade->lock, flags);
ret = irq_register_child(parent, irq, unmask, handler, arg,
desc);
spin_unlock_irq(&cascade->lock, flags);
return ret;
}
void interrupt_unregister(uint32_t irq, const void *arg)
{
interrupt_unregister_internal(irq, arg, NULL);
}
static void interrupt_unregister_internal(uint32_t irq, const void *arg,
struct irq_desc *desc)
{
struct irq_cascade_desc *cascade;
struct irq_desc *parent;
/* Avoid a bogus compiler warning */
unsigned long flags = 0;
/* no parent means we are unregistering DSP internal IRQ */
parent = interrupt_get_parent(irq);
if (parent == NULL)
if (!parent) {
arch_interrupt_unregister(irq);
else
irq_unregister_child(parent, irq, arg);
return;
}
cascade = container_of(parent, struct irq_cascade_desc, desc);
spin_lock_irq(&cascade->lock, flags);
irq_unregister_child(parent, irq, arg, desc);
spin_unlock_irq(&cascade->lock, flags);
}
uint32_t interrupt_enable(uint32_t irq, void *arg)