/**************************************************************************** * arch/rgmp/src/nuttx.c * * Copyright (C) 2011 Yu Qiang. All rights reserved. * Author: Yu Qiang * * This file is a part of NuttX: * * Copyright (C) 2011 Gregory Nutt. All rights reserved. * * 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. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * 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 #include #include #include #include #include #include #include #include #include struct tcb_s *current_task = NULL; /** * This function is called in non-interrupt context * to switch tasks. * Assumption: global interrupt is disabled. */ static inline void up_switchcontext(struct tcb_s *ctcb, struct tcb_s *ntcb) { // do nothing if two tasks are the same if (ctcb == ntcb) return; // this function can not be called in interrupt if (up_interrupt_context()) { panic("%s: try to switch context in interrupt\n", __func__); } // start switch current_task = ntcb; rgmp_context_switch(ctcb ? &ctcb->xcp.ctx : NULL, &ntcb->xcp.ctx); } void up_initialize(void) { extern pidhash_t g_pidhash[]; extern void vdev_init(void); extern void nuttx_arch_init(void); // intialize the current_task to g_idletcb current_task = g_pidhash[PIDHASH(0)].tcb; // OS memory alloc system is ready use_os_kmalloc = 1; // rgmp vdev init vdev_init(); nuttx_arch_init(); // enable interrupt local_irq_enable(); } void up_idle(void) { arch_hlt(); } void up_allocate_heap(void **heap_start, size_t *heap_size) { void *boot_freemem = boot_alloc(0, sizeof(int)); *heap_start = boot_freemem; *heap_size = KERNBASE + kmem_size - (uint32_t)boot_freemem; } int up_create_stack(struct tcb_s *tcb, size_t stack_size, uint8_t ttype) { uint32_t *stack_alloc_ptr; int ret = ERROR; size_t *adj_stack_ptr; /* Move up to next even word boundary if necessary */ size_t adj_stack_size = (stack_size + 3) & ~3; size_t adj_stack_words = adj_stack_size >> 2; /* Allocate the memory for the stack */ #if defined(CONFIG_NUTTX_KERNEL) && defined(CONFIG_MM_KERNEL_HEAP) /* Use the kernel allocator if this is a kernel thread */ if (ttype == TCB_FLAG_TTYPE_KERNEL) { stack_alloc_ptr = (uint32_t *)kmalloc(stack_size); } else #endif { stack_alloc_ptr = (uint32_t*)kumalloc(adj_stack_size); } if (stack_alloc_ptr) { /* This is the address of the last word in the allocation */ adj_stack_ptr = &stack_alloc_ptr[adj_stack_words - 1]; /* Save the values in the TCB */ tcb->adj_stack_size = adj_stack_size; tcb->stack_alloc_ptr = stack_alloc_ptr; tcb->adj_stack_ptr = (void *)((unsigned int)adj_stack_ptr & ~7); ret = OK; } return ret; } int up_use_stack(struct tcb_s *tcb, void *stack, size_t stack_size) { /* Move up to next even word boundary if necessary */ size_t adj_stack_size = stack_size & ~3; size_t adj_stack_words = adj_stack_size >> 2; /* This is the address of the last word in the allocation */ size_t *adj_stack_ptr = &((size_t*)stack)[adj_stack_words - 1]; /* Save the values in the TCB */ tcb->adj_stack_size = adj_stack_size; tcb->stack_alloc_ptr = stack; tcb->adj_stack_ptr = (void *)((unsigned int)adj_stack_ptr & ~7); return OK; } #ifdef CONFIG_NUTTX_KERNEL FAR void *up_stack_frame(FAR struct tcb_s *tcb, size_t frame_size) { uintptr_t topaddr; /* Align the frame_size */ frame_size = (frame_size + 3) & ~3; /* Is there already a stack allocated? Is it big enough? */ if (!tcb->stack_alloc_ptr || tcb->adj_stack_size <= frame_size) { return NULL; } /* Save the adjusted stack values in the struct tcb_s */ topaddr = (uintptr_t)tcb->adj_stack_ptr - frame_size; tcb->adj_stack_ptr = (FAR void *)topaddr; tcb->adj_stack_size -= frame_size; /* Reset the initial state */ up_initial_state(tcb); /* And return a pointer to the allocated memory region */ return (FAR void *)(topaddr + sizeof(uint32_t)); } #endif void up_release_stack(struct tcb_s *dtcb, uint8_t ttype) { /* Is there a stack allocated? */ if (dtcb->stack_alloc_ptr) { #if defined(CONFIG_NUTTX_KERNEL) && defined(CONFIG_MM_KERNEL_HEAP) /* Use the kernel allocator if this is a kernel thread */ if (ttype == TCB_FLAG_TTYPE_KERNEL) { kfree(dtcb->stack_alloc_ptr); } else #endif { /* Use the user-space allocator if this is a task or pthread */ kufree(dtcb->stack_alloc_ptr); } } /* Mark the stack freed */ dtcb->stack_alloc_ptr = NULL; dtcb->adj_stack_size = 0; dtcb->adj_stack_ptr = NULL; } /**************************************************************************** * Name: up_block_task * * Description: * The currently executing task at the head of * the ready to run list must be stopped. Save its context * and move it to the inactive list specified by task_state. * * This function is called only from the NuttX scheduling * logic. Interrupts will always be disabled when this * function is called. * * Inputs: * tcb: Refers to a task in the ready-to-run list (normally * the task at the head of the list). It most be * stopped, its context saved and moved into one of the * waiting task lists. It it was the task at the head * of the ready-to-run list, then a context to the new * ready to run task must be performed. * task_state: Specifies which waiting task list should be * hold the blocked task TCB. * ****************************************************************************/ void up_block_task(struct tcb_s *tcb, tstate_t task_state) { /* Verify that the context switch can be performed */ if ((tcb->task_state < FIRST_READY_TO_RUN_STATE) || (tcb->task_state > LAST_READY_TO_RUN_STATE)) { warn("%s: task sched error\n", __func__); return; } else { struct tcb_s *rtcb = current_task; bool switch_needed; /* Remove the tcb task from the ready-to-run list. If we * are blocking the task at the head of the task list (the * most likely case), then a context switch to the next * ready-to-run task is needed. In this case, it should * also be true that rtcb == tcb. */ switch_needed = sched_removereadytorun(tcb); /* Add the task to the specified blocked task list */ sched_addblocked(tcb, (tstate_t)task_state); /* Now, perform the context switch if one is needed */ if (switch_needed) { struct tcb_s *nexttcb; // this part should not be executed in interrupt context if (up_interrupt_context()) { panic("%s: %d\n", __func__, __LINE__); } // If there are any pending tasks, then add them to the g_readytorun // task list now. It should be the up_realease_pending() called from // sched_unlock() to do this for disable preemption. But it block // itself, so it's OK. if (g_pendingtasks.head) { warn("Disable preemption failed for task block itself\n"); sched_mergepending(); } nexttcb = (struct tcb_s*)g_readytorun.head; // context switch up_switchcontext(rtcb, nexttcb); } } } /**************************************************************************** * Name: up_unblock_task * * Description: * A task is currently in an inactive task list * but has been prepped to execute. Move the TCB to the * ready-to-run list, restore its context, and start execution. * * Inputs: * tcb: Refers to the tcb to be unblocked. This tcb is * in one of the waiting tasks lists. It must be moved to * the ready-to-run list and, if it is the highest priority * ready to run taks, executed. * ****************************************************************************/ void up_unblock_task(struct tcb_s *tcb) { /* Verify that the context switch can be performed */ if ((tcb->task_state < FIRST_BLOCKED_STATE) || (tcb->task_state > LAST_BLOCKED_STATE)) { warn("%s: task sched error\n", __func__); return; } else { struct tcb_s *rtcb = current_task; /* Remove the task from the blocked task list */ sched_removeblocked(tcb); /* Reset its timeslice. This is only meaningful for round * robin tasks but it doesn't here to do it for everything */ #if CONFIG_RR_INTERVAL > 0 tcb->timeslice = CONFIG_RR_INTERVAL / MSEC_PER_TICK; #endif // Add the task in the correct location in the prioritized // g_readytorun task list. if (sched_addreadytorun(tcb) && !up_interrupt_context()) { /* The currently active task has changed! */ struct tcb_s *nexttcb = (struct tcb_s*)g_readytorun.head; // context switch up_switchcontext(rtcb, nexttcb); } } } /** * This function is called from sched_unlock() which will check not * in interrupt context and disable interrupt. */ void up_release_pending(void) { struct tcb_s *rtcb = current_task; /* Merge the g_pendingtasks list into the g_readytorun task list */ if (sched_mergepending()) { /* The currently active task has changed! */ struct tcb_s *nexttcb = (struct tcb_s*)g_readytorun.head; // context switch up_switchcontext(rtcb, nexttcb); } } void up_reprioritize_rtr(struct tcb_s *tcb, uint8_t priority) { /* Verify that the caller is sane */ if (tcb->task_state < FIRST_READY_TO_RUN_STATE || tcb->task_state > LAST_READY_TO_RUN_STATE #if SCHED_PRIORITY_MIN > UINT8_MIN || priority < SCHED_PRIORITY_MIN #endif #if SCHED_PRIORITY_MAX < UINT8_MAX || priority > SCHED_PRIORITY_MAX #endif ) { warn("%s: task sched error\n", __func__); return; } else { struct tcb_s *rtcb = current_task; bool switch_needed; /* Remove the tcb task from the ready-to-run list. * sched_removereadytorun will return true if we just * remove the head of the ready to run list. */ switch_needed = sched_removereadytorun(tcb); /* Setup up the new task priority */ tcb->sched_priority = (uint8_t)priority; /* Return the task to the specified blocked task list. * sched_addreadytorun will return true if the task was * added to the new list. We will need to perform a context * switch only if the EXCLUSIVE or of the two calls is non-zero * (i.e., one and only one the calls changes the head of the * ready-to-run list). */ switch_needed ^= sched_addreadytorun(tcb); /* Now, perform the context switch if one is needed */ if (switch_needed && !up_interrupt_context()) { struct tcb_s *nexttcb; // If there are any pending tasks, then add them to the g_readytorun // task list now. It should be the up_realease_pending() called from // sched_unlock() to do this for disable preemption. But it block // itself, so it's OK. if (g_pendingtasks.head) { warn("Disable preemption failed for reprioritize task\n"); sched_mergepending(); } nexttcb = (struct tcb_s*)g_readytorun.head; // context switch up_switchcontext(rtcb, nexttcb); } } } void _exit(int status) { struct tcb_s* tcb; /* Destroy the task at the head of the ready to run list. */ (void)task_exit(); /* Now, perform the context switch to the new ready-to-run task at the * head of the list. */ tcb = (struct tcb_s*)g_readytorun.head; /* Then switch contexts */ up_switchcontext(NULL, tcb); } void up_assert(const uint8_t *filename, int line) { fprintf(stderr, "Assertion failed at file:%s line: %d\n", filename, line); // in interrupt context or idle task means kernel error // which will stop the OS // if in user space just terminate the task if (up_interrupt_context() || current_task->pid == 0) { panic("%s: %d\n", __func__, __LINE__); } else { exit(EXIT_FAILURE); } } void up_assert_code(const uint8_t *filename, int line, int code) { fprintf(stderr, "Assertion failed at file:%s line: %d error code: %d\n", filename, line, code); // in interrupt context or idle task means kernel error // which will stop the OS // if in user space just terminate the task if (up_interrupt_context() || current_task->pid == 0) { panic("%s: %d\n", __func__, __LINE__); } else { exit(EXIT_FAILURE); } } #ifndef CONFIG_DISABLE_SIGNALS void up_schedule_sigaction(struct tcb_s *tcb, sig_deliver_t sigdeliver) { /* Refuse to handle nested signal actions */ if (!tcb->xcp.sigdeliver) { int flags; /* Make sure that interrupts are disabled */ local_irq_save(flags); // First, handle some special cases when the signal is // being delivered to the currently executing task. if (tcb == current_task) { // CASE 1: We are not in an interrupt handler and // a task is signalling itself for some reason. if (!up_interrupt_context()) { // In this case just deliver the signal now. sigdeliver(tcb); } // CASE 2: We are in an interrupt handler AND the // interrupted task is the same as the one that // must receive the signal. else { tcb->xcp.sigdeliver = sigdeliver; } } // Otherwise, we are (1) signaling a task is not running // from an interrupt handler or (2) we are not in an // interrupt handler and the running task is signalling // some non-running task. else { tcb->xcp.sigdeliver = sigdeliver; push_xcptcontext(&tcb->xcp); } local_irq_restore(flags); } } #endif /* !CONFIG_DISABLE_SIGNALS */ bool up_interrupt_context(void) { if (nest_irq) return true; return false; } #ifndef CONFIG_ARCH_NOINTC void up_disable_irq(int irq) { } void up_enable_irq(int irq) { } #endif #ifdef CONFIG_ARCH_IRQPRIO int up_prioritize_irq(int irq, int priority) { } #endif void up_sigdeliver(struct Trapframe *tf) { sig_deliver_t sigdeliver; pop_xcptcontext(¤t_task->xcp); sigdeliver = current_task->xcp.sigdeliver; current_task->xcp.sigdeliver = NULL; local_irq_enable(); sigdeliver(current_task); local_irq_disable(); } #if defined(CONFIG_HAVE_CXX) && defined(CONFIG_HAVE_CXXINITIALIZE) void up_cxxinitialize(void) { rgmp_cxx_init(); } #endif