/* SPDX-License-Identifier: BSD-2-Clause */ /** * @file * * @ingroup bsp_interrupt * * @brief This source file contains the interrupt server implementation. */ /* * Copyright (C) 2009, 2020 embedded brains GmbH (http://www.embedded-brains.de) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #define BSP_INTERRUPT_SERVER_MANAGEMENT_VECTOR BSP_INTERRUPT_VECTOR_COUNT static rtems_interrupt_server_control bsp_interrupt_server_default; static rtems_chain_control bsp_interrupt_server_chain = RTEMS_CHAIN_INITIALIZER_EMPTY(bsp_interrupt_server_chain); static rtems_interrupt_server_control *bsp_interrupt_server_get_context( uint32_t server_index, rtems_status_code *sc ) { rtems_chain_node *node; bsp_interrupt_lock(); node = rtems_chain_first(&bsp_interrupt_server_chain); while (node != rtems_chain_tail(&bsp_interrupt_server_chain)) { rtems_interrupt_server_control *s; s = RTEMS_CONTAINER_OF(node, rtems_interrupt_server_control, node); if (s->index == server_index) { bsp_interrupt_unlock(); return s; } node = rtems_chain_next(node); } bsp_interrupt_unlock(); *sc = RTEMS_INVALID_ID; return NULL; } static void bsp_interrupt_server_trigger(void *arg) { rtems_interrupt_lock_context lock_context; rtems_interrupt_server_entry *e = arg; rtems_interrupt_server_control *s = e->server; if (bsp_interrupt_is_valid_vector(e->vector)) { bsp_interrupt_vector_disable(e->vector); } rtems_interrupt_lock_acquire(&s->lock, &lock_context); if (rtems_chain_is_node_off_chain(&e->node)) { rtems_chain_append_unprotected(&s->entries, &e->node); } else { ++s->errors; } rtems_interrupt_lock_release(&s->lock, &lock_context); rtems_event_system_send(s->server, RTEMS_EVENT_SYSTEM_SERVER); } typedef struct { rtems_interrupt_server_entry *entry; rtems_option *options; } bsp_interrupt_server_iterate_entry; static void bsp_interrupt_server_per_handler_routine( void *iterate_arg, const char *info, rtems_option options, rtems_interrupt_handler handler, void *handler_arg ) { if (handler == bsp_interrupt_server_trigger) { bsp_interrupt_server_iterate_entry *ie = iterate_arg; ie->entry = handler_arg; *ie->options = options; } } static rtems_interrupt_server_entry *bsp_interrupt_server_query_entry( rtems_vector_number vector, rtems_option *trigger_options ) { bsp_interrupt_server_iterate_entry ie = { .entry = NULL, .options = trigger_options }; rtems_interrupt_handler_iterate( vector, bsp_interrupt_server_per_handler_routine, &ie ); return ie.entry; } typedef struct { rtems_interrupt_server_control *server; rtems_vector_number vector; rtems_option options; rtems_interrupt_handler handler; void *arg; rtems_id task; rtems_status_code sc; } bsp_interrupt_server_helper_data; static void bsp_interrupt_server_install_helper(void *arg) { bsp_interrupt_server_helper_data *hd = arg; rtems_status_code sc; rtems_interrupt_server_entry *e; rtems_interrupt_server_action *a; rtems_option trigger_options; a = calloc(1, sizeof(*a)); if (a == NULL) { hd->sc = RTEMS_NO_MEMORY; rtems_event_transient_send(hd->task); return; } a->handler = hd->handler; a->arg = hd->arg; bsp_interrupt_lock(); e = bsp_interrupt_server_query_entry(hd->vector, &trigger_options); if (e == NULL) { e = calloc(1, sizeof(*e)); if (e != NULL) { e->server = hd->server; e->vector = hd->vector; e->actions = a; sc = rtems_interrupt_handler_install( hd->vector, "IRQS", hd->options & RTEMS_INTERRUPT_UNIQUE, bsp_interrupt_server_trigger, e ); if (sc != RTEMS_SUCCESSFUL) { free(e); } } else { sc = RTEMS_NO_MEMORY; } #if defined(RTEMS_SMP) } else if (e->server != hd->server) { sc = RTEMS_RESOURCE_IN_USE; #endif } else if ( RTEMS_INTERRUPT_IS_UNIQUE(hd->options) || RTEMS_INTERRUPT_IS_UNIQUE(trigger_options) ) { sc = RTEMS_RESOURCE_IN_USE; } else { rtems_interrupt_server_action **link = &e->actions; rtems_interrupt_server_action *c; sc = RTEMS_SUCCESSFUL; while ((c = *link) != NULL) { if (c->handler == hd->handler && c->arg == hd->arg) { sc = RTEMS_TOO_MANY; break; } link = &c->next; } if (sc == RTEMS_SUCCESSFUL) { *link = a; } } bsp_interrupt_unlock(); if (sc != RTEMS_SUCCESSFUL) { free(a); } hd->sc = sc; rtems_event_transient_send(hd->task); } static void bsp_interrupt_server_remove_helper(void *arg) { bsp_interrupt_server_helper_data *hd = arg; rtems_status_code sc; rtems_interrupt_server_entry *e; rtems_option trigger_options; bsp_interrupt_lock(); e = bsp_interrupt_server_query_entry(hd->vector, &trigger_options); if (e != NULL) { rtems_interrupt_server_action **link = &e->actions; rtems_interrupt_server_action *c; while ((c = *link) != NULL) { if (c->handler == hd->handler && c->arg == hd->arg) { break; } link = &c->next; } if (c != NULL) { bool remove_last = e->actions->next == NULL; if (remove_last) { rtems_interrupt_handler_remove( hd->vector, bsp_interrupt_server_trigger, e ); } *link = c->next; free(c); if (remove_last) { free(e); } sc = RTEMS_SUCCESSFUL; } else { sc = RTEMS_UNSATISFIED; } } else { sc = RTEMS_INVALID_ID; } bsp_interrupt_unlock(); hd->sc = sc; rtems_event_transient_send(hd->task); } static rtems_status_code bsp_interrupt_server_call_helper( rtems_interrupt_server_control *s, rtems_vector_number vector, rtems_option options, rtems_interrupt_handler handler, void *arg, void (*helper)(void *) ) { bsp_interrupt_server_helper_data hd = { .server = s, .vector = vector, .options = options, .handler = handler, .arg = arg, .task = rtems_task_self() }; rtems_interrupt_server_action a = { .handler = helper, .arg = &hd }; rtems_interrupt_server_entry e = { .server = s, .vector = BSP_INTERRUPT_SERVER_MANAGEMENT_VECTOR, .actions = &a }; bsp_interrupt_server_trigger(&e); rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT); return hd.sc; } static rtems_interrupt_server_entry *bsp_interrupt_server_get_entry( rtems_interrupt_server_control *s ) { rtems_interrupt_lock_context lock_context; rtems_interrupt_server_entry *e; rtems_interrupt_lock_acquire(&s->lock, &lock_context); if (!rtems_chain_is_empty(&s->entries)) { e = (rtems_interrupt_server_entry *) rtems_chain_get_first_unprotected(&s->entries); rtems_chain_set_off_chain(&e->node); } else { e = NULL; } rtems_interrupt_lock_release(&s->lock, &lock_context); return e; } static void bsp_interrupt_server_task(rtems_task_argument arg) { rtems_interrupt_server_control *s = (rtems_interrupt_server_control *) arg; while (true) { rtems_event_set events; rtems_interrupt_server_entry *e; rtems_event_system_receive( RTEMS_EVENT_SYSTEM_SERVER, RTEMS_EVENT_ALL | RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events ); while ((e = bsp_interrupt_server_get_entry(s)) != NULL) { rtems_interrupt_server_action *action = e->actions; rtems_vector_number vector = e->vector; do { rtems_interrupt_server_action *current = action; action = action->next; (*current->handler)(current->arg); } while (action != NULL); if (bsp_interrupt_is_valid_vector(vector)) { bsp_interrupt_vector_enable(vector); } } } } rtems_status_code rtems_interrupt_server_handler_install( uint32_t server_index, rtems_vector_number vector, const char *info, rtems_option options, rtems_interrupt_handler handler, void *arg ) { rtems_status_code sc; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } return bsp_interrupt_server_call_helper( s, vector, options, handler, arg, bsp_interrupt_server_install_helper ); } rtems_status_code rtems_interrupt_server_handler_remove( uint32_t server_index, rtems_vector_number vector, rtems_interrupt_handler handler, void *arg ) { rtems_status_code sc; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } return bsp_interrupt_server_call_helper( s, vector, 0, handler, arg, bsp_interrupt_server_remove_helper ); } typedef struct { rtems_interrupt_per_handler_routine routine; void *arg; } bsp_interrupt_server_handler_iterate_helper_data; static void bsp_interrupt_server_handler_iterate_helper(void *arg) { bsp_interrupt_server_helper_data *hd = arg; bsp_interrupt_server_handler_iterate_helper_data *hihd = hd->arg; rtems_status_code sc; rtems_interrupt_server_entry *e; rtems_option trigger_options; bsp_interrupt_lock(); e = bsp_interrupt_server_query_entry(hd->vector, &trigger_options); if (e != NULL) { rtems_interrupt_server_action **link = &e->actions; rtems_interrupt_server_action *c; while ((c = *link) != NULL) { (*hihd->routine)(hihd->arg, NULL, trigger_options, c->handler, c->arg); link = &c->next; } sc = RTEMS_SUCCESSFUL; } else { sc = RTEMS_UNSATISFIED; } bsp_interrupt_unlock(); hd->sc = sc; rtems_event_transient_send(hd->task); } rtems_status_code rtems_interrupt_server_handler_iterate( uint32_t server_index, rtems_vector_number vector, rtems_interrupt_per_handler_routine routine, void *arg ) { rtems_status_code sc; bsp_interrupt_server_handler_iterate_helper_data hihd; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } if (!bsp_interrupt_is_valid_vector(vector)) { return RTEMS_INVALID_ID; } hihd.routine = routine; hihd.arg = arg; return bsp_interrupt_server_call_helper( s, vector, 0, NULL, &hihd, bsp_interrupt_server_handler_iterate_helper ); } /* * The default server is statically allocated. Just clear the structure so * that it can be re-initialized. */ static void bsp_interrupt_server_destroy_default( rtems_interrupt_server_control *s ) { memset(s, 0, sizeof(*s)); } #if defined(RTEMS_SMP) static void bsp_interrupt_server_destroy_secondary( rtems_interrupt_server_control *s ) { free(s); } #endif static rtems_status_code bsp_interrupt_server_create( rtems_interrupt_server_control *s, rtems_task_priority priority, size_t stack_size, rtems_mode modes, rtems_attribute attributes, uint32_t cpu_index ) { rtems_status_code sc; #if defined(RTEMS_SMP) rtems_id scheduler; cpu_set_t cpu; #endif sc = rtems_task_create( rtems_build_name('I', 'R', 'Q', 'S'), priority, stack_size, modes, attributes, &s->server ); if (sc != RTEMS_SUCCESSFUL) { (*s->destroy)(s); return sc; } rtems_interrupt_lock_initialize(&s->lock, "Interrupt Server"); rtems_chain_initialize_empty(&s->entries); #if defined(RTEMS_SMP) sc = rtems_scheduler_ident_by_processor(cpu_index, &scheduler); /* * If a scheduler exists for the processor, then move it to this scheduler * and try to set the affinity to the processor, otherwise keep the scheduler * of the executing thread. */ if (sc == RTEMS_SUCCESSFUL) { sc = rtems_task_set_scheduler(s->server, scheduler, priority); _Assert(sc == RTEMS_SUCCESSFUL); /* Set the task to processor affinity on a best-effort basis */ CPU_ZERO(&cpu); CPU_SET(cpu_index, &cpu); (void) rtems_task_set_affinity(s->server, sizeof(cpu), &cpu); } #else (void) cpu_index; #endif rtems_chain_append_unprotected(&bsp_interrupt_server_chain, &s->node); sc = rtems_task_start( s->server, bsp_interrupt_server_task, (rtems_task_argument) s ); _Assert(sc == RTEMS_SUCCESSFUL); return sc; } rtems_status_code rtems_interrupt_server_initialize( rtems_task_priority priority, size_t stack_size, rtems_mode modes, rtems_attribute attributes, uint32_t *server_count ) { rtems_status_code sc; rtems_interrupt_server_control *s; uint32_t cpu_index; #if defined(RTEMS_SMP) uint32_t cpu_count; #endif cpu_index = 0; s = &bsp_interrupt_server_default; bsp_interrupt_lock(); if (s->server != 0) { sc = RTEMS_INCORRECT_STATE; goto done; } s->destroy = bsp_interrupt_server_destroy_default; sc = bsp_interrupt_server_create( s, priority, stack_size, modes, attributes, cpu_index ); if (sc != RTEMS_SUCCESSFUL) { goto done; } cpu_index = 1; #if defined(RTEMS_SMP) cpu_count = rtems_scheduler_get_processor_maximum(); while (cpu_index < cpu_count) { s = calloc(1, sizeof(*s)); if (s == NULL) { sc = RTEMS_NO_MEMORY; goto done; } s->destroy = bsp_interrupt_server_destroy_secondary; s->index = cpu_index; sc = bsp_interrupt_server_create( s, priority, stack_size, modes, attributes, cpu_index ); if (sc != RTEMS_SUCCESSFUL) { goto done; } ++cpu_index; } #endif done: bsp_interrupt_unlock(); if (server_count != NULL) { *server_count = cpu_index; } return sc; } rtems_status_code rtems_interrupt_server_create( rtems_interrupt_server_control *s, const rtems_interrupt_server_config *config, uint32_t *server_index ) { rtems_status_code sc; sc = rtems_task_create( config->name, config->priority, config->storage_size, config->modes, config->attributes, &s->server ); if (sc != RTEMS_SUCCESSFUL) { return sc; } rtems_interrupt_lock_initialize(&s->lock, "Interrupt Server"); rtems_chain_initialize_empty(&s->entries); s->destroy = config->destroy; s->index = rtems_object_id_get_index(s->server) + rtems_scheduler_get_processor_maximum(); *server_index = s->index; bsp_interrupt_lock(); rtems_chain_initialize_node(&s->node); rtems_chain_append_unprotected(&bsp_interrupt_server_chain, &s->node); bsp_interrupt_unlock(); sc = rtems_task_start( s->server, bsp_interrupt_server_task, (rtems_task_argument) s ); _Assert(sc == RTEMS_SUCCESSFUL); return sc; } static void bsp_interrupt_server_destroy_helper(void *arg) { bsp_interrupt_server_helper_data *hd = arg; rtems_interrupt_server_control *s = hd->server; rtems_status_code sc; bsp_interrupt_lock(); rtems_chain_extract_unprotected(&s->node); bsp_interrupt_unlock(); rtems_interrupt_lock_destroy(&s->lock); if (s->destroy != NULL) { (*s->destroy)(s); } sc = rtems_event_transient_send(hd->task); _Assert(sc == RTEMS_SUCCESSFUL); (void) sc; rtems_task_exit(); } rtems_status_code rtems_interrupt_server_delete(uint32_t server_index) { rtems_status_code sc; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } bsp_interrupt_server_call_helper( s, BSP_INTERRUPT_SERVER_MANAGEMENT_VECTOR, 0, NULL, NULL, bsp_interrupt_server_destroy_helper ); return RTEMS_SUCCESSFUL; } static void bsp_interrupt_server_entry_initialize( rtems_interrupt_server_entry *entry, rtems_interrupt_server_control *s ) { rtems_chain_set_off_chain(&entry->node); entry->server = s; entry->vector = BSP_INTERRUPT_SERVER_MANAGEMENT_VECTOR; entry->actions = NULL; } static void bsp_interrupt_server_action_prepend( rtems_interrupt_server_entry *entry, rtems_interrupt_server_action *action, rtems_interrupt_handler handler, void *arg ) { action->handler = handler; action->arg = arg; action->next = entry->actions; entry->actions = action; } rtems_status_code rtems_interrupt_server_entry_initialize( uint32_t server_index, rtems_interrupt_server_entry *entry ) { rtems_status_code sc; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } bsp_interrupt_server_entry_initialize(entry, s); return RTEMS_SUCCESSFUL; } void rtems_interrupt_server_action_prepend( rtems_interrupt_server_entry *entry, rtems_interrupt_server_action *action, rtems_interrupt_handler handler, void *arg ) { bsp_interrupt_server_action_prepend(entry, action, handler, arg); } void rtems_interrupt_server_entry_submit( rtems_interrupt_server_entry *entry ) { bsp_interrupt_server_trigger(entry); } rtems_status_code rtems_interrupt_server_entry_move( rtems_interrupt_server_entry *entry, uint32_t destination_server_index ) { rtems_status_code sc; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(destination_server_index, &sc); if (s == NULL) { return sc; } entry->server = s; return RTEMS_SUCCESSFUL; } static void bsp_interrupt_server_entry_synchronize_helper(void *arg) { bsp_interrupt_server_helper_data *hd = arg; rtems_event_transient_send(hd->task); } void rtems_interrupt_server_entry_destroy( rtems_interrupt_server_entry *entry ) { rtems_interrupt_server_control *s; rtems_interrupt_lock_context lock_context; s = entry->server; rtems_interrupt_lock_acquire(&s->lock, &lock_context); if (!rtems_chain_is_node_off_chain(&entry->node)) { rtems_chain_extract_unprotected(&entry->node); rtems_chain_set_off_chain(&entry->node); } rtems_interrupt_lock_release(&s->lock, &lock_context); bsp_interrupt_server_call_helper( s, BSP_INTERRUPT_SERVER_MANAGEMENT_VECTOR, 0, NULL, NULL, bsp_interrupt_server_entry_synchronize_helper ); } rtems_status_code rtems_interrupt_server_request_initialize( uint32_t server_index, rtems_interrupt_server_request *request, rtems_interrupt_handler handler, void *arg ) { rtems_status_code sc; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } bsp_interrupt_server_entry_initialize(&request->entry, s); bsp_interrupt_server_action_prepend( &request->entry, &request->action, handler, arg ); return RTEMS_SUCCESSFUL; } static void bsp_interrupt_server_handler_move_helper(void *arg) { bsp_interrupt_server_helper_data *hd = arg; bsp_interrupt_server_handler_iterate_helper_data *hihd = hd->arg; rtems_interrupt_server_entry *e; rtems_option trigger_options; bsp_interrupt_lock(); e = bsp_interrupt_server_query_entry(hd->vector, &trigger_options); if (e != NULL) { rtems_interrupt_lock_context lock_context; rtems_interrupt_server_control *src = e->server; rtems_interrupt_server_control *dst = hihd->arg; bool pending; /* The source server is only used in SMP configurations for the lock */ (void) src; rtems_interrupt_lock_acquire(&src->lock, &lock_context); pending = !rtems_chain_is_node_off_chain(&e->node); if (pending) { rtems_chain_extract_unprotected(&e->node); rtems_chain_set_off_chain(&e->node); } rtems_interrupt_lock_release(&src->lock, &lock_context); e->server = dst; if (pending) { bsp_interrupt_server_trigger(e); } } bsp_interrupt_unlock(); rtems_event_transient_send(hd->task); } rtems_status_code rtems_interrupt_server_move( uint32_t source_server_index, rtems_vector_number vector, uint32_t destination_server_index ) { rtems_status_code sc; rtems_interrupt_server_control *src; rtems_interrupt_server_control *dst; bsp_interrupt_server_handler_iterate_helper_data hihd; src = bsp_interrupt_server_get_context(source_server_index, &sc); if (src == NULL) { return sc; } dst = bsp_interrupt_server_get_context(destination_server_index, &sc); if (dst == NULL) { return sc; } if (!bsp_interrupt_is_valid_vector(vector)) { return RTEMS_INVALID_ID; } hihd.arg = dst; bsp_interrupt_server_call_helper( src, vector, 0, NULL, &hihd, bsp_interrupt_server_handler_move_helper ); return RTEMS_SUCCESSFUL; } static void bsp_interrupt_server_entry_suspend_helper(void *arg) { bsp_interrupt_server_helper_data *hd = arg; rtems_event_set events; rtems_event_transient_send(hd->task); rtems_event_system_receive( RTEMS_EVENT_SYSTEM_SERVER_RESUME, RTEMS_WAIT, RTEMS_NO_TIMEOUT, &events ); } rtems_status_code rtems_interrupt_server_suspend(uint32_t server_index) { rtems_status_code sc; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } bsp_interrupt_server_call_helper( s, BSP_INTERRUPT_SERVER_MANAGEMENT_VECTOR, 0, NULL, NULL, bsp_interrupt_server_entry_suspend_helper ); return RTEMS_SUCCESSFUL; } rtems_status_code rtems_interrupt_server_resume(uint32_t server_index) { rtems_status_code sc; rtems_interrupt_server_control *s; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } rtems_event_system_send(s->server, RTEMS_EVENT_SYSTEM_SERVER_RESUME); bsp_interrupt_server_call_helper( s, BSP_INTERRUPT_SERVER_MANAGEMENT_VECTOR, 0, NULL, NULL, bsp_interrupt_server_entry_synchronize_helper ); return RTEMS_SUCCESSFUL; } rtems_status_code rtems_interrupt_server_set_affinity( uint32_t server_index, size_t affinity_size, const cpu_set_t *affinity, rtems_task_priority priority ) { rtems_status_code sc; rtems_interrupt_server_control *s; rtems_id scheduler; s = bsp_interrupt_server_get_context(server_index, &sc); if (s == NULL) { return sc; } sc = rtems_scheduler_ident_by_processor_set( affinity_size, affinity, &scheduler ); if (sc != RTEMS_SUCCESSFUL) { return sc; } sc = rtems_task_set_scheduler(s->server, scheduler, priority); if (sc != RTEMS_SUCCESSFUL) { return sc; } return rtems_task_set_affinity(s->server, affinity_size, affinity); }