/** * @copyright (c) 2024-2025, MacRsh * * @license SPDX-License-Identifier: Apache-2.0 * * @date 2024-09-06 MacRsh First version */ #include #if defined(MR_USE_IRQ) #include #include /* Irq clazz(dynamic & static) */ static void irq_release_dyn(mr_object_t *obj); static void irq_release(mr_object_t *obj); MR_CLAZZ_DYNAMIC_DEFINE(irq, irq_release_dyn); MR_CLAZZ_DEFINE(irq, irq_release); #if defined(MR_USE_IRQ_DEFER_HOOK) static const mr_irq_defer_hook_t *__hook = MR_NULL; #endif /* defined(MR_USE_IRQ_DEFER_HOOK) */ /* Irq lock */ static mr_spinlock_t __lock = MR_SPINLOCK_INIT(); /* Irq descriptor table */ #if !defined(MR_CFG_IRQ_TABLE_SIZE) #define MR_CFG_IRQ_TABLE_SIZE (32) #endif /* !defined(MR_CFG_IRQ_TABLE_SIZE) */ static mr_irq_desc_t __table[MR_CFG_IRQ_TABLE_SIZE]; #if defined(MR_USE_IRQ_DEFER) /* Irq defer workqueue */ static mr_workqueue_t __defer_queue; #endif /* defined(MR_USE_IRQ_DEFER) */ /* Irq action pending definition */ #define IRQ_ACTION_PENDING (1U << 31) /* Irq flags definition */ #define IRQ_FLAG_TYPE_MASK (0x0fU) /* Irq priority default definition */ #define IRQ_PRIORITY_DEFAULT (0) static mr_err_t irq_table_insert(mr_irq_t *irq, mr_uint32_t irq_start, mr_uint32_t irq_end) { mr_uint32_t i; /* Add irq to table */ for (i = irq_start; i <= irq_end; i++) { /* Check if irq is conflict */ if (__table[i].irq) { /* Irq conflict */ for (irq_end = i, i = irq_start; i < irq_end; i++) { __table[i].irq = MR_NULL; } return -MR_EEXIST; } /* Init irq descriptor */ mr_atomic_init(&__table[i].depth, 1); __table[i].irq = irq; } return 0; } static void irq_action_free(mr_irq_action_t *action) { #if defined(MR_USE_IRQ_DEFER) /* Delete irq action defer work */ mr_work_del(&action->defer); #endif /* defined(MR_USE_IRQ_DEFER) */ /* Free irq action */ mr_free(action); } static void irq_depth_disable(mr_uint32_t irq, mr_irq_desc_t *desc) { mr_atomic_t last; /* Increase depth */ last = mr_atomic_fetch_add(&desc->depth, 1); if (last != 0) { /* Depth does not change from 0 to 1 */ if (!desc->action) { /* Reset depth if no irq action */ mr_atomic_store(&desc->depth, 1); } return; } /* Disable irq */ desc->irq->ops->disable(irq, desc->irq->priv); desc->irq->ops->mask(irq, desc->irq->priv); } static void irq_table_remove(mr_irq_t *irq) { mr_irq_action_t *action; mr_uint32_t i; /* Remove irq from table */ for (i = irq->istart; i <= irq->iend; i++) { /* Free irq actions */ while (__table[i].action) { /* Remove irq action from irq descriptor */ action = __table[i].action; __table[i].action = action->next; irq_action_free(action); } /* Disable irq */ irq_depth_disable(i, &__table[i]); __table[i].irq = MR_NULL; } } static mr_err_t irq_init(mr_irq_t *irq, const mr_clazz_t *clazz, mr_uint32_t irq_start, mr_uint32_t irq_end, const mr_irq_ops_t *ops, mr_ptr_t priv) { mr_err_t ret; int mask; /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Insert irq to table */ ret = irq_table_insert(irq, irq_start, irq_end); if (ret != 0) { goto _exit; } /* Init irq */ mr_atomic_init(&irq->dying, MR_FALSE); irq->istart = irq_start; irq->iend = irq_end; irq->ops = ops; irq->priv = priv; /* Init object */ ret = mr_object_init((mr_object_t *)irq, clazz); if (ret != 0) { irq_table_remove(irq); goto _exit; } _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } mr_err_t mr_irq_init(mr_irq_t *irq, mr_uint32_t irq_start, mr_uint32_t irq_end, const mr_irq_ops_t *ops, void *priv) { const mr_clazz_t *clazz = MR_CLAZZ_FIND(irq); /* Check parameter */ MR_ASSERT((irq != MR_NULL) && (!MR_OBJECT_IS_INITED(irq))); MR_ASSERT(irq_start < MR_CFG_IRQ_TABLE_SIZE); MR_ASSERT(irq_end < MR_CFG_IRQ_TABLE_SIZE); MR_ASSERT(irq_start <= irq_end); MR_ASSERT(ops != MR_NULL); /* Init workqueue */ return irq_init(irq, clazz, irq_start, irq_end, ops, priv); } mr_irq_t *mr_irq_create(mr_uint32_t irq_start, mr_uint32_t irq_end, const mr_irq_ops_t *ops, void *priv) { const mr_clazz_t *clazz = MR_CLAZZ_DYNAMIC_FIND(irq); mr_irq_t *irq; mr_err_t ret; /* Check parameter */ MR_ASSERT(irq_start < MR_CFG_IRQ_TABLE_SIZE); MR_ASSERT(irq_end < MR_CFG_IRQ_TABLE_SIZE); MR_ASSERT(irq_start <= irq_end); MR_ASSERT(ops != MR_NULL); /* Create irq */ irq = mr_malloc(sizeof(mr_irq_t)); if (!irq) { return MR_NULL; } /* Init irq */ ret = irq_init(irq, clazz, irq_start, irq_end, ops, priv); if (ret != 0) { mr_free(irq); return MR_NULL; } return irq; } mr_err_t mr_irq_del(mr_irq_t *irq) { /* Check parameter */ MR_ASSERT((irq != MR_NULL) && MR_OBJECT_IS_INITED(irq)); MR_ASSERT(MR_OBJECT_CLAZZ_IS(irq, irq)); /* Mark irq as dying */ mr_atomic_store(&irq->dying, MR_TRUE); /* Delete object */ mr_object_del((mr_object_t *)irq); return 0; } #if defined(MR_USE_IRQ_DEFER) static void irq_action_defer_entry(mr_work_t *work, void *param) { mr_irq_action_t *action; mr_irq_entry_t *entry; mr_uint32_t irq; void *owner; int mask; MR_UNUSED(work); /* Get irq action */ action = (mr_irq_action_t *)param; /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Save irq action entry, owner and irq */ entry = (mr_irq_entry_t *)action->defer_entry; owner = (void *)action->owner; irq = action->irq; /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); /* Check if irq action was freed */ if (!owner) { return; } /* Call irq action defer entry */ entry(irq, owner); } #endif /* defined(MR_USE_IRQ_DEFER) */ static mr_irq_action_t *irq_action_alloc(mr_uint32_t irq, mr_irq_entry_t *entry, mr_ptr_t owner, mr_irq_entry_t *defer_entry) { mr_irq_action_t *action; mr_err_t ret; /* Create irq action */ action = mr_malloc(sizeof(mr_irq_action_t)); if (!action) { return MR_NULL; } #if defined(MR_USE_IRQ_DEFER) /* Init irq action defer work */ ret = mr_work_init(&action->defer, irq_action_defer_entry, action); if (ret != 0) { mr_free(action); return MR_NULL; } action->defer_entry = defer_entry; #else MR_UNUSED(defer_entry); MR_UNUSED(ret); #endif /* MR_USE_IRQ_DEFER */ /* Init irq action */ action->next = MR_NULL; action->irq = irq; action->entry = entry; action->owner = owner; return action; } static void irq_type_set(mr_uint32_t irq, mr_irq_desc_t *desc, mr_uint8_t type) { /* Set irq type */ desc->irq->ops->type_set(irq, type, desc->irq->priv); } static void irq_priority_set(mr_uint32_t irq, mr_irq_desc_t *desc, mr_uint8_t priority) { /* Set irq priority */ desc->irq->ops->priority_set(irq, priority, desc->irq->priv); desc->priority = priority; } static void irq_depth_enable(mr_uint32_t irq, mr_irq_desc_t *desc) { mr_atomic_t last; /* Decrease depth */ last = mr_atomic_fetch_sub(&desc->depth, 1); if (last != 1) { /* Depth does not change from 1 to 0 */ if (last <= 0) { /* No depth when enabled */ mr_atomic_store(&desc->depth, 0); } return; } /* Enable irq */ desc->irq->ops->unmask(irq, desc->irq->priv); desc->irq->ops->enable(irq, desc->irq->priv); } static mr_err_t irq_request(mr_uint32_t irq, mr_uint16_t flags, mr_irq_action_t *action) { mr_irq_action_t **a; mr_irq_desc_t *desc; mr_err_t ret; int mask; /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Check if irq is dying */ desc = &__table[irq]; if ((!desc->irq) || mr_atomic_load(&desc->irq->dying)) { ret = -MR_ENOENT; goto _exit; } /* Check if irq descriptor action is exist */ if (!desc->action) { /* Set irq type */ irq_type_set(irq, desc, flags & IRQ_FLAG_TYPE_MASK); /* Set irq default priority */ irq_priority_set(irq, desc, IRQ_PRIORITY_DEFAULT); /* Set irq descriptor flags */ desc->flags = flags; } else { /* Check irq share flag */ if (!(desc->flags & MR_IRQ_SHARED)) { /* If the share isn't set up, it can't be configured */ ret = -MR_EBUSY; goto _exit; } /* Check irq mode(shared must be the same) */ if (desc->flags != flags) { ret = -MR_EINVAL; goto _exit; } } /* Insert irq action to irq descriptor */ for (a = &desc->action; *a; a = &(*a)->next) { /* Check if irq action is exist */ if ((*a)->owner != action->owner) { continue; } /* Irq action is exist */ ret = -MR_EEXIST; goto _exit; } *a = action; /* Enable irq */ irq_depth_enable(irq, desc); ret = 0; _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } static mr_err_t irq_defer_request(mr_uint32_t irq, mr_irq_entry_t *entry, mr_ptr_t owner, mr_uint16_t flags, mr_irq_entry_t *defer_entry) { mr_irq_action_t *action; mr_err_t ret; /* Create irq action */ action = irq_action_alloc(irq, entry, owner, defer_entry); if (!action) { return -MR_ENOMEM; } /* Request irq */ ret = irq_request(irq, flags, action); if (ret != 0) { irq_action_free(action); return ret; } return 0; } mr_err_t mr_irq_request(mr_uint32_t irq, mr_irq_entry_t *entry, void *owner, mr_uint16_t flags) { /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); MR_ASSERT((entry != MR_NULL) && (owner != MR_NULL)); MR_ASSERT(flags != 0); /* Request irq */ return irq_defer_request(irq, entry, owner, flags, MR_NULL); } mr_err_t mr_irq_defer_request(mr_uint32_t irq, mr_irq_entry_t *entry, void *owner, mr_uint16_t flags, mr_irq_entry_t *defer_entry) { /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); MR_ASSERT((entry != MR_NULL) && (owner != MR_NULL)); MR_ASSERT(flags != 0); MR_ASSERT(defer_entry != MR_NULL); /* Request defer irq */ return irq_defer_request(irq, entry, owner, flags, defer_entry); } mr_err_t mr_irq_free(mr_uint32_t irq, void *owner) { mr_irq_action_t *action, **a; mr_irq_desc_t *desc; mr_err_t ret; int mask; /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); MR_ASSERT(owner != MR_NULL); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Check if irq is dying */ desc = &__table[irq]; if ((!desc->irq) || mr_atomic_load(&desc->irq->dying)) { ret = -MR_ENOENT; goto _exit; } /* Remove irq action from irq descriptor */ for (action = MR_NULL, a = &desc->action; *a; a = &(*a)->next) { /* Match owner */ if (owner != (*a)->owner) { continue; } /* Check if irq action is pending */ if ((*a)->irq & IRQ_ACTION_PENDING) { /* Mark irq action, defer free */ (*a)->owner = MR_NULL; ret = 0; goto _exit; } /* Remove irq action from irq descriptor */ action = *a; *a = action->next; break; } /* Check irq action */ if (!action) { /* Irq action is not exist */ ret = -MR_ENOENT; goto _exit; } /* Free irq action */ irq_action_free(action); /* Disable irq if no irq action */ if (!desc->action) { irq_depth_disable(irq, desc); } ret = 0; _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } mr_err_t mr_irq_enable(mr_uint32_t irq) { mr_irq_desc_t *desc; mr_err_t ret; int mask; /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Check if irq is dying */ desc = &__table[irq]; if ((!desc->irq) || mr_atomic_load(&desc->irq->dying)) { ret = -MR_ENOENT; goto _exit; } /* Enable irq */ irq_depth_enable(irq, desc); ret = 0; _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } mr_err_t mr_irq_disable(mr_uint32_t irq) { mr_irq_desc_t *desc; mr_err_t ret; int mask; /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Check if irq is dying */ desc = &__table[irq]; if ((!desc->irq) || mr_atomic_load(&desc->irq->dying)) { ret = -MR_ENOENT; goto _exit; } /* Disable irq */ irq_depth_disable(irq, desc); ret = 0; _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } int mr_irq_type(mr_uint32_t irq) { mr_irq_desc_t *desc; int mask, ret; /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Check if irq is not exist */ desc = &__table[irq]; if (!desc->irq) { ret = -MR_ENOENT; goto _exit; } /* Get irq type */ ret = (mr_uint8_t)(desc->flags & IRQ_FLAG_TYPE_MASK); _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } mr_err_t mr_irq_priority_set(mr_uint32_t irq, mr_uint8_t priority) { mr_irq_desc_t *desc; mr_err_t ret; int mask; /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Check if irq is dying */ desc = &__table[irq]; if ((!desc->irq) || mr_atomic_load(&desc->irq->dying)) { ret = -MR_ENOENT; goto _exit; } /* Set irq priority */ irq_priority_set(irq, desc, priority); ret = 0; _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } int mr_irq_priority(mr_uint32_t irq) { mr_irq_desc_t *desc; int mask, ret; /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Check if irq is not exist */ desc = &__table[irq]; if (!desc->irq) { ret = -MR_ENOENT; goto _exit; } /* Get irq priority */ ret = (mr_uint8_t)desc->priority; _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } mr_irq_return_t mr_irq_handle(mr_uint32_t irq) { mr_irq_action_t **a, *action, action2; mr_irq_entry_t *entry; mr_irq_return_t ret; mr_irq_desc_t *desc; void *owner; int mask; /* Check parameter */ MR_ASSERT(irq < MR_CFG_IRQ_TABLE_SIZE); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Check if irq is dying */ desc = &__table[irq]; if ((!desc->irq) || mr_atomic_load(&desc->irq->dying)) { ret = MR_IRQ_NONE; goto _exit; } /* Get irq run-time */ mr_irq_get(desc->irq); /* Mask irq */ desc->irq->ops->mask(irq, desc->irq->priv); /* Handle irq action */ for (ret = MR_IRQ_NONE, action = desc->action; action; action = action->next) { /* Check if irq action is being freed */ if (!action->owner) { continue; } /* Mark irq action pending */ action->irq |= IRQ_ACTION_PENDING; /* Save irq action entry and owner */ entry = (mr_irq_entry_t *)action->entry; owner = (void *)action->owner; /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); /* Call irq action entry */ ret = entry(irq, owner); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Mark irq action not pending */ action->irq &= ~IRQ_ACTION_PENDING; /* Check if irq action is being freed */ if (!action->owner) { /* Free irq action */ for (a = &desc->action; *a; a = &(*a)->next) { /* Match irq action */ if (*a != action) { continue; } /* Remove irq action from irq descriptor(copy to keep the * structure unchanged) */ action2.next = *a = action->next; #if defined(MR_USE_IRQ_DEFER) /* To prevent entering the defer branch after a break */ action2.defer_entry = MR_NULL; #endif /* defined(MR_USE_IRQ_DEFER) */ irq_action_free(action); action = &action2; break; } } /* Check if irq action is handled */ if (ret == MR_IRQ_HANDLED) { break; } /* Check if irq action is deferred */ if (ret == MR_IRQ_DEFERRED) { #if defined(MR_USE_IRQ_DEFER) /* Check if irq action supports deferring */ if (action->defer_entry) { /* Add irq action to the defer workqueue */ mr_workqueue_work(&__defer_queue, &action->defer); } else #endif /* defined(MR_USE_IRQ_DEFER) */ { /* Not support deferring */ ret = MR_IRQ_HANDLED; } break; } } /* Unmask irq if irq action is enabled */ if (mr_atomic_load(&desc->depth) == 0) { desc->irq->ops->unmask(irq, desc->irq->priv); } /* Put irq run-time */ mr_irq_put(desc->irq); _exit: /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); return ret; } #if defined(MR_USE_IRQ_DEFER) void mr_irq_defer_execute(void) { mr_workqueue_t *queue; /* Get irq workqueue */ queue = mr_workqueue_get(&__defer_queue); if (!queue) { return; } /* Execute irq workqueue */ mr_workqueue_execute(queue); /* Put irq workqueue */ mr_workqueue_put(queue); } #endif /* defined(MR_USE_IRQ_DEFER) */ #if defined(MR_USE_IRQ_DEFER_HOOK) static void irq_workqueue_hook_wakeup(mr_workqueue_t *queue, void *param) { const mr_irq_defer_hook_t *hook; MR_UNUSED(queue); /* Check irq defer wakeup hook */ hook = *(const mr_irq_defer_hook_t **)param; if (!hook->wakeup) { return; } /* Call irq defer wakeup hook */ hook->wakeup(hook->param); } static void irq_workqueue_hook_suspend(mr_workqueue_t *queue, void *param) { const mr_irq_defer_hook_t *hook; MR_UNUSED(queue); /* Check irq defer suspend hook */ hook = *(const mr_irq_defer_hook_t **)param; if (!hook->suspend) { return; } /* Call irq defer suspend hook */ hook->suspend(hook->param); } mr_err_t mr_irq_defer_hook_set(const mr_irq_defer_hook_t *hook) { /* Check parameter */ MR_ASSERT(hook != MR_NULL); /* Set irq defer hook */ __hook = hook; return 0; } #endif /* defined(MR_USE_IRQ_DEFER_HOOK) */ static void irq_del(mr_irq_t *irq) { int mask; /* Mark irq as dying */ mr_atomic_store(&irq->dying, MR_TRUE); /* Lock */ mask = mr_spinlock_lock_irqsave(&__lock); /* Remove irq from table */ irq_table_remove(irq); /* Unlock */ mr_spinlock_unlock_irqrestore(&__lock, mask); } static void irq_release(mr_object_t *obj) { mr_irq_t *irq; /* Get irq */ irq = MR_CONTAINER_OF(obj, mr_irq_t, parent); /* Delete irq */ irq_del(irq); } static void irq_release_dyn(mr_object_t *obj) { /* Release irq */ irq_release(obj); /* Free irq */ mr_free(obj); } void mr_kernel_irq_init(void) { #if defined(MR_USE_IRQ_DEFER_HOOK) static const mr_workqueue_hook_t queue_hook = {.wakeup = irq_workqueue_hook_wakeup, .suspend = irq_workqueue_hook_suspend, .param = &__hook}; #endif /* defined(MR_USE_IRQ_DEFER_HOOK) */ #if defined(MR_USE_IRQ_DEFER) /* Init irq defer workqueue */ mr_workqueue_init(&__defer_queue); #endif /* defined(MR_USE_IRQ_DEFER) */ #if defined(MR_USE_IRQ_DEFER_HOOK) mr_workqueue_hook_set(&__defer_queue, &queue_hook); #endif /* defined(MR_USE_IRQ_DEFER_HOOK) */ } #endif /* defined(MR_USE_IRQ) */