/* 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 #include #include #include #include #include #include #include #include #include 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. * * 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. * * 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_monitor_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; }