| /* task kernel services */ |
| |
| /* |
| * Copyright (c) 1997-2010, 2013-2015 Wind River Systems, Inc. |
| * |
| * Licensed 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. |
| */ |
| |
| |
| #include <microkernel.h> |
| #include <nanokernel.h> |
| #include <arch/cpu.h> |
| #include <string.h> |
| #include <toolchain.h> |
| #include <sections.h> |
| |
| #include <micro_private.h> |
| #include <nano_private.h> |
| #include <start_task_arch.h> |
| #include <misc/debug/object_tracing_common.h> |
| |
| extern ktask_t _k_task_ptr_start[]; |
| extern ktask_t _k_task_ptr_end[]; |
| |
| |
| ktask_t task_id_get(void) |
| { |
| return _k_current_task->id; |
| } |
| |
| /** |
| * @brief Reset the specified task state bits |
| * |
| * This routine resets the specified task state bits. When a task's state bits |
| * are zero, the task may be scheduled to run. The tasks's state bits are a |
| * bitmask of the TF_xxx bits. Each TF_xxx bit indicates a reason why the task |
| * must not be scheduled to run. |
| * |
| * @param X Pointer to task |
| * @param bits Bitmask of TF_xxx bits to reset |
| * @return N/A |
| * |
| * @internal |
| * When operating on microkernel objects, this routine is invoked in the |
| * context of the microkernel server fiber. However, since microkernel tasks |
| * may pend/unpend on nanokernel objects, interrupts must be locked to |
| * prevent data corruption. |
| * @endinternal |
| */ |
| void _k_state_bit_reset(struct k_task *X, uint32_t bits) |
| { |
| unsigned int key = irq_lock(); |
| uint32_t f_old = X->state; /* old state bits */ |
| uint32_t f_new = f_old & ~bits; /* new state bits */ |
| |
| X->state = f_new; /* Update task's state bits */ |
| |
| if ((f_old != 0) && (f_new == 0)) { |
| /* |
| * The task may now be scheduled to run (but could not |
| * previously) as all the TF_xxx bits are clear. It must |
| * be added to the list of schedulable tasks. |
| */ |
| |
| struct k_tqhd *H = _k_task_priority_list + X->priority; |
| |
| X->next = NULL; |
| H->tail->next = X; |
| H->tail = X; |
| _k_task_priority_bitmap[X->priority >> 5] |= |
| (1 << (X->priority & 0x1F)); |
| } |
| |
| irq_unlock(key); |
| |
| #ifdef CONFIG_TASK_MONITOR |
| f_new ^= f_old; |
| if ((_k_monitor_mask & MON_STATE) && (f_new)) { |
| /* |
| * Task monitoring is enabled and the new state bits are |
| * different than the old state bits. |
| * |
| * <f_new> now contains the bits that are different. |
| */ |
| |
| _k_task_monitor(X, f_new | MO_STBIT0); |
| } |
| #endif |
| } |
| |
| /** |
| * @brief Set specified task state bits |
| * |
| * This routine sets the specified task state bits. When a task's state bits |
| * are non-zero, the task will not be scheduled to run. The task's state bits |
| * are a bitmask of the TF_xxx bits. Each TF_xxx bit indicates a reason why |
| * the task must not be scheduled to run. |
| * @param task_ptr Task pointer |
| * @param bitmask of TF_xxx bits to set |
| * @return N/A |
| * |
| * @internal |
| * When operating on microkernel objects, this routine is invoked in the |
| * context of the microkernel server fiber. However, since microkernel tasks |
| * may pend/unpend on nanokernel objects, interrupts must be locked to |
| * prevent data corruption. |
| * @endinternal |
| */ |
| void _k_state_bit_set(struct k_task *task_ptr, uint32_t bits) |
| { |
| unsigned int key = irq_lock(); |
| uint32_t old_state_bits = task_ptr->state; |
| uint32_t new_state_bits = old_state_bits | bits; |
| |
| task_ptr->state = new_state_bits; |
| |
| if ((old_state_bits == 0) && (new_state_bits != 0)) { |
| /* |
| * The task could have been scheduled to run ([state] was 0) |
| * but can not be scheduled to run anymore at least one TF_xxx |
| * bit has been set. Remove it from the list of schedulable |
| * tasks. |
| */ |
| #if defined(__GNUC__) |
| #if defined(CONFIG_ARM) |
| /* |
| * Avoid bad code generation by certain gcc toolchains for ARM |
| * when an optimization setting of -O2 or above is used. |
| * |
| * Specifically, this issue has been seen with ARM gcc version |
| * 4.6.3 (Sourcery CodeBench Lite 2012.03-56): The 'volatile' |
| * attribute is added to the following variable to prevent it |
| * from being lost--otherwise the register that holds its value |
| * is reused, but the compiled code uses it later on as if it |
| * was still that variable. |
| */ |
| volatile |
| #endif |
| #endif |
| struct k_tqhd *task_queue = _k_task_priority_list + |
| task_ptr->priority; |
| struct k_task *cur_task = (struct k_task *)(&task_queue->head); |
| |
| /* |
| * Search in the list for this task priority level, |
| * and remove the task. |
| */ |
| while (cur_task->next != task_ptr) { |
| cur_task = cur_task->next; |
| } |
| |
| cur_task->next = task_ptr->next; |
| |
| if (task_queue->tail == task_ptr) { |
| task_queue->tail = cur_task; |
| } |
| |
| /* |
| * If there are no more tasks of this priority that are |
| * runnable, then clear that bit in the global priority bit map. |
| */ |
| if (task_queue->head == NULL) { |
| _k_task_priority_bitmap[task_ptr->priority >> 5] &= |
| ~(1 << (task_ptr->priority & 0x1F)); |
| } |
| } |
| |
| irq_unlock(key); |
| |
| #ifdef CONFIG_TASK_MONITOR |
| new_state_bits ^= old_state_bits; |
| if ((_k_monitor_mask & MON_STATE) && (new_state_bits)) { |
| /* |
| * Task monitoring is enabled and the new state bits are |
| * different than the old state bits. |
| * |
| * <new_state_bits> now contains the bits that are different. |
| */ |
| |
| _k_task_monitor(task_ptr, new_state_bits | MO_STBIT1); |
| } |
| #endif |
| } |
| |
| /** |
| * @brief Initialize and start a task |
| * |
| * @param X Pointer to task control block |
| * @param func Entry point for task |
| * @return N/A |
| */ |
| static void start_task(struct k_task *X, void (*func)(void)) |
| { |
| unsigned int task_options; |
| void *parameter1; |
| |
| /* Note: the field X->worksize now represents the task size in bytes */ |
| |
| task_options = 0; |
| _START_TASK_ARCH(X, &task_options); |
| |
| /* |
| * The 'func' argument to _new_thread() represents the entry point of |
| * the |
| * kernel task. The 'parameter1', 'parameter2', & 'parameter3' |
| * arguments |
| * are not applicable to such tasks. A 'priority' of -1 indicates that |
| * the thread is a task, rather than a fiber. |
| */ |
| |
| #ifdef CONFIG_THREAD_MONITOR |
| parameter1 = (void *)X; |
| #else |
| parameter1 = (void *)0; |
| #endif |
| |
| _new_thread((char *)X->workspace, /* pStackMem */ |
| X->worksize, /* stackSize */ |
| X, /* microkernel task pointer */ |
| (_thread_entry_t)func, /* pEntry */ |
| parameter1, /* parameter1 */ |
| (void *)0, /* parameter2 */ |
| (void *)0, /* parameter3 */ |
| -1, /* priority */ |
| task_options /* options */ |
| ); |
| |
| X->fn_abort = NULL; |
| |
| _k_state_bit_reset(X, TF_STOP | TF_TERM); |
| } |
| |
| /** |
| * @brief Abort a task |
| * |
| * This routine aborts the specified task. |
| * @param X Task pointer |
| * @return N/A |
| */ |
| static void abort_task(struct k_task *X) |
| { |
| |
| /* Do normal thread exit cleanup */ |
| |
| _thread_exit((struct tcs *)X->workspace); |
| |
| /* Set TF_TERM and TF_STOP state flags */ |
| |
| _k_state_bit_set(X, TF_STOP | TF_TERM); |
| |
| /* Invoke abort function, if there is one */ |
| |
| if (X->fn_abort != NULL) { |
| X->fn_abort(); |
| } |
| } |
| |
| #ifndef CONFIG_ARCH_HAS_TASK_ABORT |
| /** |
| * @brief Microkernel handler for fatal task errors |
| * |
| * To be invoked when a task aborts implicitly, either by returning from its |
| * entry point or due to a software or hardware fault. |
| * |
| * @return does not return |
| */ |
| FUNC_NORETURN void _TaskAbort(void) |
| { |
| _task_ioctl(_k_current_task->id, TASK_ABORT); |
| |
| /* |
| * Compiler can't tell that _task_ioctl() won't return and issues |
| * a warning unless we explicitly tell it that control never gets this |
| * far. |
| */ |
| |
| CODE_UNREACHABLE; |
| } |
| #endif |
| |
| |
| void task_abort_handler_set(void (*func)(void)) |
| { |
| _k_current_task->fn_abort = func; |
| } |
| |
| /** |
| * @brief Handle a task operation request |
| * |
| * This routine handles any one of the following task operation requests: |
| * starting either a kernel or user task, aborting a task, suspending a task, |
| * resuming a task, blocking a task or unblocking a task |
| * @param A Arguments |
| * @return N/A |
| */ |
| void _k_task_op(struct k_args *A) |
| { |
| ktask_t Tid = A->args.g1.task; |
| struct k_task *X = (struct k_task *)Tid; |
| |
| switch (A->args.g1.opt) { |
| case TASK_START: |
| start_task(X, X->fn_start); |
| SYS_TRACING_OBJ_INIT(micro_task, X); |
| break; |
| case TASK_ABORT: |
| abort_task(X); |
| break; |
| case TASK_SUSPEND: |
| _k_state_bit_set(X, TF_SUSP); |
| break; |
| case TASK_RESUME: |
| _k_state_bit_reset(X, TF_SUSP); |
| break; |
| case TASK_BLOCK: |
| _k_state_bit_set(X, TF_BLCK); |
| break; |
| case TASK_UNBLOCK: |
| _k_state_bit_reset(X, TF_BLCK); |
| break; |
| } |
| } |
| |
| /** |
| * @brief Task operations |
| * @param task Task on which to operate |
| * @param opt Task operation |
| * @return N/A |
| */ |
| void _task_ioctl(ktask_t task, int opt) |
| { |
| struct k_args A; |
| |
| A.Comm = _K_SVC_TASK_OP; |
| A.args.g1.task = task; |
| A.args.g1.opt = opt; |
| KERNEL_ENTRY(&A); |
| } |
| |
| /** |
| * @brief Handle task group operation request |
| * |
| * This routine handles any one of the following task group operations requests: |
| * starting either kernel or user tasks, aborting tasks, suspending tasks, |
| * resuming tasks, blocking tasks or unblocking tasks |
| * @param A Arguments |
| * @return N/A |
| */ |
| void _k_task_group_op(struct k_args *A) |
| { |
| ktask_group_t grp = A->args.g1.group; |
| int opt = A->args.g1.opt; |
| struct k_task *X; |
| ktask_t *task_id; |
| |
| #ifdef CONFIG_TASK_DEBUG |
| if (opt == TASK_GROUP_BLOCK) |
| _k_debug_halt = 1; |
| if (opt == TASK_GROUP_UNBLOCK) |
| _k_debug_halt = 0; |
| #endif |
| |
| for (task_id = _k_task_ptr_start; task_id < _k_task_ptr_end; |
| task_id++) { |
| X = (struct k_task *)(*task_id); |
| if (X->group & grp) { |
| switch (opt) { |
| case TASK_GROUP_START: |
| start_task(X, X->fn_start); |
| SYS_TRACING_OBJ_INIT(micro_task, X); |
| break; |
| case TASK_GROUP_ABORT: |
| abort_task(X); |
| break; |
| case TASK_GROUP_SUSPEND: |
| _k_state_bit_set(X, TF_SUSP); |
| break; |
| case TASK_GROUP_RESUME: |
| _k_state_bit_reset(X, TF_SUSP); |
| break; |
| case TASK_GROUP_BLOCK: |
| _k_state_bit_set(X, TF_BLCK); |
| break; |
| case TASK_GROUP_UNBLOCK: |
| _k_state_bit_reset(X, TF_BLCK); |
| break; |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * @brief Task group operations |
| * @param group Task group |
| * @param opt Operation |
| * @return N/A |
| */ |
| void _task_group_ioctl(ktask_group_t group, int opt) |
| { |
| struct k_args A; |
| |
| A.Comm = _K_SVC_TASK_GROUP_OP; |
| A.args.g1.group = group; |
| A.args.g1.opt = opt; |
| KERNEL_ENTRY(&A); |
| } |
| |
| |
| kpriority_t task_group_mask_get(void) |
| { |
| return _k_current_task->group; |
| } |
| |
| void task_group_join(uint32_t groups) |
| { |
| _k_current_task->group |= groups; |
| } |
| |
| void task_group_leave(uint32_t groups) |
| { |
| _k_current_task->group &= ~groups; |
| } |
| |
| /** |
| * @brief Get task priority |
| * |
| * @return priority of current task |
| */ |
| kpriority_t task_priority_get(void) |
| { |
| return _k_current_task->priority; |
| } |
| |
| /** |
| * @brief Handle task set priority request |
| * @param A Arguments |
| * @return N/A |
| */ |
| void _k_task_priority_set(struct k_args *A) |
| { |
| ktask_t Tid = A->args.g1.task; |
| struct k_task *X = (struct k_task *)Tid; |
| |
| _k_state_bit_set(X, TF_PRIO); |
| X->priority = A->args.g1.prio; |
| _k_state_bit_reset(X, TF_PRIO); |
| |
| if (A->alloc) |
| FREEARGS(A); |
| } |
| |
| |
| void task_priority_set(ktask_t task, kpriority_t prio) |
| { |
| struct k_args A; |
| |
| A.Comm = _K_SVC_TASK_PRIORITY_SET; |
| A.args.g1.task = task; |
| A.args.g1.prio = prio; |
| KERNEL_ENTRY(&A); |
| } |
| |
| /** |
| * @brief Handle task yield request |
| * |
| * @param A Arguments |
| * @return N/A |
| */ |
| void _k_task_yield(struct k_args *A) |
| { |
| struct k_tqhd *H = _k_task_priority_list + _k_current_task->priority; |
| struct k_task *X = _k_current_task->next; |
| |
| ARG_UNUSED(A); |
| if (X && H->head == _k_current_task) { |
| _k_current_task->next = NULL; |
| H->tail->next = _k_current_task; |
| H->tail = _k_current_task; |
| H->head = X; |
| } |
| } |
| |
| |
| void task_yield(void) |
| { |
| struct k_args A; |
| |
| A.Comm = _K_SVC_TASK_YIELD; |
| KERNEL_ENTRY(&A); |
| } |
| |
| |
| void task_entry_set(ktask_t task, void (*func)(void)) |
| { |
| struct k_task *X = (struct k_task *)task; |
| |
| X->fn_start = func; |
| } |
| |