/**************************************************************************** * sched/irq/irq_chain.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include "irq/irq.h" /**************************************************************************** * Private Types ****************************************************************************/ struct irqchain_s { FAR struct irqchain_s *next; xcpt_t handler; /* Address of the interrupt handler */ FAR void *arg; /* The argument provided to the interrupt handler. */ }; /**************************************************************************** * Private Data ****************************************************************************/ /* g_irqchainpool is a list of pre-allocated irq chain. The number of irq * chains in the pool is a configuration item. */ static struct irqchain_s g_irqchainpool[CONFIG_PREALLOC_IRQCHAIN]; /* The g_irqchainfreelist data structure is a single linked list of irqchains * available to the system for delayed function use. */ static sq_queue_t g_irqchainfreelist; /**************************************************************************** * Private Functions ****************************************************************************/ static void irqchain_detach_all(int ndx) { FAR struct irqchain_s *curr; FAR struct irqchain_s *prev; g_irqvector[ndx].handler = irq_unexpected_isr; curr = g_irqvector[ndx].arg; while (curr != NULL) { prev = curr; curr = curr->next; sq_addlast((FAR struct sq_entry_s *)prev, &g_irqchainfreelist); } } static int irqchain_dispatch(int irq, FAR void *context, FAR void *arg) { FAR struct irqchain_s *curr; FAR struct irqchain_s *prev; int ndx; int ret = 0; #ifdef CONFIG_ARCH_MINIMAL_VECTORTABLE ndx = g_irqmap[irq]; #else ndx = irq; #endif curr = g_irqvector[ndx].arg; while (curr != NULL) { prev = curr; curr = curr->next; ret |= prev->handler(irq, context, prev->arg); } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ void irqchain_initialize(void) { FAR struct irqchain_s *irqchain = g_irqchainpool; int i; /* Initialize irqchain free lists */ sq_init(&g_irqchainfreelist); /* The g_irqchainfreelist must be loaded at initialization time to hold the * configured number of irqchain. */ for (i = 0; i < CONFIG_PREALLOC_IRQCHAIN; i++) { sq_addlast((FAR struct sq_entry_s *)irqchain++, &g_irqchainfreelist); } } bool is_irqchain(int ndx, xcpt_t isr) { if (g_irqvector[ndx].handler == irq_unexpected_isr || g_irqvector[ndx].handler == NULL) { return false; } else if (g_irqvector[ndx].handler == irqchain_dispatch) { return true; } else { return isr != irq_unexpected_isr; } } int irqchain_attach(int ndx, xcpt_t isr, FAR void *arg) { FAR struct irqchain_s *node; FAR struct irqchain_s *curr; if (isr != irq_unexpected_isr) { if (g_irqvector[ndx].handler != irqchain_dispatch) { if (sq_count(&g_irqchainfreelist) < 2) { return -ENOMEM; } node = (FAR struct irqchain_s *)sq_remfirst(&g_irqchainfreelist); DEBUGASSERT(node != NULL); node->handler = g_irqvector[ndx].handler; node->arg = g_irqvector[ndx].arg; node->next = NULL; g_irqvector[ndx].handler = irqchain_dispatch; g_irqvector[ndx].arg = node; } node = (FAR struct irqchain_s *)sq_remfirst(&g_irqchainfreelist); if (node == NULL) { return -ENOMEM; } node->handler = isr; node->arg = arg; node->next = NULL; curr = g_irqvector[ndx].arg; while (curr->next != NULL) { curr = curr->next; } curr->next = node; } else { irqchain_detach_all(ndx); } return OK; } int irqchain_detach(int irq, xcpt_t isr, FAR void *arg) { #if NR_IRQS > 0 FAR struct irqchain_s *prev; FAR struct irqchain_s *curr; FAR struct irqchain_s *first; int ret = -EINVAL; if ((unsigned)irq < NR_IRQS) { irqstate_t flags; int ndx; #ifdef CONFIG_ARCH_MINIMAL_VECTORTABLE /* Is there a mapping for this IRQ number? */ ndx = g_irqmap[irq]; if ((unsigned)ndx >= CONFIG_ARCH_NUSER_INTERRUPTS) { /* No.. then return failure. */ return ret; } #else ndx = irq; #endif flags = spin_lock_irqsave(NULL); if (g_irqvector[ndx].handler == irqchain_dispatch) { first = g_irqvector[ndx].arg; for (prev = NULL, curr = first; curr != NULL; prev = curr, curr = curr->next) { if (curr->handler == isr && curr->arg == arg) { if (curr == first) { g_irqvector[ndx].arg = curr->next; } else if (curr->next == NULL) { prev->next = NULL; } else { prev->next = curr->next; } sq_addlast((FAR struct sq_entry_s *)curr, &g_irqchainfreelist); first = g_irqvector[ndx].arg; if (first->next == NULL) { g_irqvector[ndx].handler = first->handler; g_irqvector[ndx].arg = first->arg; sq_addlast((FAR struct sq_entry_s *)first, &g_irqchainfreelist); } ret = OK; break; } } } else { ret = irq_detach(irq); } spin_unlock_irqrestore(NULL, flags); } return ret; #else return OK; #endif }