drivers: intc: irqstr: refactor level 1 interrupt recounting
So far, it has been assumed that only level 2 interrupts can be shared via the `CONFIG_SHARED_INTERRUPTS` option, but this is not true. In the case of i.MX95, for instance, level 1 interrupt 143 is shared among EDMA channels 30 and 31. Due to the previous assumption, the irqsteer driver currently performs reference counting for all level 2 interrupts aggregated by each dispatcher and, of course, for the level 1 interrupts the dispatchers are attached to. For instance, assuming a machine with 100 level 1 interrupts and 1 irqsteer dispatcher attached to line 50 this would mean reference counting is performed solely for line 50 (and the level 2 interrupts MUX'd into this line). Going back to i.MX95, since there's no dispatcher attached to IRQ line 143 that means there's no reference counting for it. In turn, this means that the IRQ line can be disabled accidentally on a channel release() operation while the other channel is active. To protect against such cases, refactor the level1 interrupt reference counting. Now, reference counting is performed for _all_ level 1 interrupts. Additionally, simplify the locking logic. Ideally, there would be a lock for each dispatcher protecting the level 2 interrupts and 1 global lock protecting the level 1 interrupts. Instead of this approach (which is a bit more complex), simply use a global lock for all interrupts. If finer granularity is required then it can be added later on. Signed-off-by: Laurentiu Mihalcea <laurentiu.mihalcea@nxp.com>
This commit is contained in:
parent
bb7efcf03b
commit
eaa1addb5e
1 changed files with 77 additions and 45 deletions
|
@ -291,12 +291,12 @@ struct irqsteer_dispatcher {
|
|||
uint32_t irq;
|
||||
/* reference count for all IRQs aggregated by dispatcher */
|
||||
uint8_t irq_refcnt[CONFIG_MAX_IRQ_PER_AGGREGATOR];
|
||||
/* dispatcher lock */
|
||||
struct k_spinlock lock;
|
||||
/* reference count for dispatcher */
|
||||
uint8_t refcnt;
|
||||
};
|
||||
|
||||
static struct k_spinlock irqstr_lock;
|
||||
|
||||
static uint8_t l1_irq_refcnt[CONFIG_2ND_LVL_ISR_TBL_OFFSET];
|
||||
|
||||
static struct irqsteer_dispatcher dispatchers[] = {
|
||||
IRQSTEER_DECLARE_DISPATCHERS(DT_NODELABEL(irqsteer))
|
||||
};
|
||||
|
@ -342,68 +342,95 @@ static int from_zephyr_irq(uint32_t regmap, uint32_t irq, uint32_t master_index)
|
|||
return idx + FSL_FEATURE_IRQSTEER_IRQ_START_INDEX;
|
||||
}
|
||||
|
||||
static void _irqstr_disp_enable_disable(struct irqsteer_dispatcher *disp,
|
||||
bool enable)
|
||||
/* note: if disp != NULL that means there's several level2 interrupts
|
||||
* multiplexed into this single level 1 line via irqsteer. In such cases,
|
||||
* the interrupt needs to also be enable/disabled at the irqsteer level.
|
||||
*
|
||||
* brief: disp == NULL => IRQ is not managed by IRQSTEER
|
||||
* disp != NULL => IRQ is managed by IRQSTEER
|
||||
*/
|
||||
static void irqstr_l1_irq_enable_disable(uint32_t irq,
|
||||
struct irqsteer_dispatcher *disp,
|
||||
bool enable)
|
||||
{
|
||||
uint32_t regmap = DISPATCHER_REGMAP(disp);
|
||||
|
||||
if (enable) {
|
||||
irqstr_l1_irq_enable_raw(disp->irq);
|
||||
IRQSTEER_EnableMasterInterrupt(UINT_TO_IRQSTEER(regmap), disp->irq);
|
||||
irqstr_l1_irq_enable_raw(irq);
|
||||
|
||||
if (disp) {
|
||||
IRQSTEER_EnableMasterInterrupt(UINT_TO_IRQSTEER(DISPATCHER_REGMAP(disp)),
|
||||
irq);
|
||||
}
|
||||
} else {
|
||||
IRQSTEER_DisableMasterInterrupt(UINT_TO_IRQSTEER(regmap), disp->irq);
|
||||
irqstr_l1_irq_disable_raw(disp->irq);
|
||||
if (disp) {
|
||||
IRQSTEER_DisableMasterInterrupt(UINT_TO_IRQSTEER(DISPATCHER_REGMAP(disp)),
|
||||
irq);
|
||||
}
|
||||
|
||||
irqstr_l1_irq_disable_raw(irq);
|
||||
}
|
||||
}
|
||||
|
||||
static void _irqstr_disp_get_unlocked(struct irqsteer_dispatcher *disp)
|
||||
static void irqstr_request_l1_irq_unlocked(uint32_t irq,
|
||||
struct irqsteer_dispatcher *disp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (disp->refcnt == UINT8_MAX) {
|
||||
LOG_WRN("disp for irq %d reference count reached limit", disp->irq);
|
||||
#ifndef CONFIG_SHARED_INTERRUPTS
|
||||
if (l1_irq_refcnt[irq]) {
|
||||
LOG_WRN("L1 IRQ %d already requested", irq);
|
||||
return;
|
||||
}
|
||||
#endif /* CONFIG_SHARED_INTERRUPTS */
|
||||
|
||||
if (l1_irq_refcnt[irq] == UINT8_MAX) {
|
||||
LOG_WRN("L1 IRQ %d reference count reached limit", irq);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!disp->refcnt) {
|
||||
ret = pm_device_runtime_get(disp->dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("failed to enable PM resources: %d", ret);
|
||||
return;
|
||||
if (!l1_irq_refcnt[irq]) {
|
||||
if (disp) {
|
||||
ret = pm_device_runtime_get(disp->dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("failed to enable PM resources: %d", ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_irqstr_disp_enable_disable(disp, true);
|
||||
irqstr_l1_irq_enable_disable(irq, disp, true);
|
||||
}
|
||||
|
||||
disp->refcnt++;
|
||||
l1_irq_refcnt[irq]++;
|
||||
|
||||
LOG_DBG("get on disp for irq %d results in refcnt: %d",
|
||||
disp->irq, disp->refcnt);
|
||||
LOG_DBG("request for L1 IRQ %d results in refcnt: %d",
|
||||
irq, l1_irq_refcnt[irq]);
|
||||
}
|
||||
|
||||
static void _irqstr_disp_put_unlocked(struct irqsteer_dispatcher *disp)
|
||||
static void irqstr_release_l1_irq_unlocked(uint32_t irq,
|
||||
struct irqsteer_dispatcher *disp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!disp->refcnt) {
|
||||
LOG_WRN("disp for irq %d already put", disp->irq);
|
||||
if (!l1_irq_refcnt[irq]) {
|
||||
LOG_WRN("L1 IRQ %d already released", irq);
|
||||
return;
|
||||
}
|
||||
|
||||
disp->refcnt--;
|
||||
l1_irq_refcnt[irq]--;
|
||||
|
||||
if (!disp->refcnt) {
|
||||
_irqstr_disp_enable_disable(disp, false);
|
||||
if (!l1_irq_refcnt[irq]) {
|
||||
irqstr_l1_irq_enable_disable(irq, disp, false);
|
||||
|
||||
ret = pm_device_runtime_put(disp->dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("failed to disable PM resources: %d", ret);
|
||||
return;
|
||||
if (disp) {
|
||||
ret = pm_device_runtime_put(disp->dev);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("failed to disable PM resources: %d", ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("put on disp for irq %d results in refcnt: %d",
|
||||
disp->irq, disp->refcnt);
|
||||
LOG_DBG("release on L1 IRQ %d results in refcnt: %d",
|
||||
irq, l1_irq_refcnt[irq]);
|
||||
}
|
||||
|
||||
static void _irqstr_enable_disable_irq(struct irqsteer_dispatcher *disp,
|
||||
|
@ -437,7 +464,7 @@ static void irqstr_request_irq_unlocked(struct irqsteer_dispatcher *disp,
|
|||
}
|
||||
|
||||
if (!disp->irq_refcnt[zephyr_irq]) {
|
||||
_irqstr_disp_get_unlocked(disp);
|
||||
irqstr_request_l1_irq_unlocked(disp->irq, disp);
|
||||
_irqstr_enable_disable_irq(disp, system_irq, true);
|
||||
}
|
||||
|
||||
|
@ -462,7 +489,7 @@ static void irqstr_release_irq_unlocked(struct irqsteer_dispatcher *disp,
|
|||
|
||||
if (!disp->irq_refcnt[zephyr_irq]) {
|
||||
_irqstr_enable_disable_irq(disp, system_irq, false);
|
||||
_irqstr_disp_put_unlocked(disp);
|
||||
irqstr_release_l1_irq_unlocked(disp->irq, disp);
|
||||
}
|
||||
|
||||
LOG_DBG("released irq %d has refcount %d",
|
||||
|
@ -476,10 +503,12 @@ void z_soc_irq_enable_disable(uint32_t irq, bool enable)
|
|||
|
||||
if (irq_get_level(irq) == 1) {
|
||||
/* LEVEL 1 interrupts are DSP direct */
|
||||
if (enable) {
|
||||
irqstr_l1_irq_enable_raw(irq);
|
||||
} else {
|
||||
irqstr_l1_irq_disable_raw(irq);
|
||||
K_SPINLOCK(&irqstr_lock) {
|
||||
if (enable) {
|
||||
irqstr_request_l1_irq_unlocked(irq, NULL);
|
||||
} else {
|
||||
irqstr_release_l1_irq_unlocked(irq, NULL);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -493,7 +522,7 @@ void z_soc_irq_enable_disable(uint32_t irq, bool enable)
|
|||
continue;
|
||||
}
|
||||
|
||||
K_SPINLOCK(&dispatchers[i].lock) {
|
||||
K_SPINLOCK(&irqstr_lock) {
|
||||
if (enable) {
|
||||
irqstr_request_irq_unlocked(&dispatchers[i], level2_irq);
|
||||
} else {
|
||||
|
@ -523,7 +552,10 @@ int z_soc_irq_is_enabled(unsigned int irq)
|
|||
bool enabled;
|
||||
|
||||
if (irq_get_level(irq) == 1) {
|
||||
return irqsteer_level1_irq_is_enabled(irq);
|
||||
K_SPINLOCK(&irqstr_lock) {
|
||||
enabled = l1_irq_refcnt[irq];
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
|
||||
parent_irq = irq_parent_level_2(irq);
|
||||
|
@ -537,7 +569,7 @@ int z_soc_irq_is_enabled(unsigned int irq)
|
|||
|
||||
cfg = dispatchers[i].dev->config;
|
||||
|
||||
K_SPINLOCK(&dispatchers[i].lock) {
|
||||
K_SPINLOCK(&irqstr_lock) {
|
||||
enabled = dispatchers[i].irq_refcnt[irq_from_level_2(irq)];
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue