From 8ac699b63d54fc4e3ba6a9767b9f06682452529a Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Sat, 13 Feb 2016 09:04:54 -0600 Subject: [PATCH] SMP: Need to perform all IDLE task memory allocations on initializatin thread prior to starting the other CPUs --- include/nuttx/arch.h | 4 +- sched/init/init.h | 19 ++++ sched/init/os_smpstart.c | 176 +-------------------------------- sched/init/os_start.c | 206 ++++++++++++++++++++++++++------------- 4 files changed, 159 insertions(+), 246 deletions(-) diff --git a/include/nuttx/arch.h b/include/nuttx/arch.h index de85b99d13..a6a30e69f0 100644 --- a/include/nuttx/arch.h +++ b/include/nuttx/arch.h @@ -101,7 +101,6 @@ #include -#include #include #include #include @@ -1700,7 +1699,6 @@ int up_cpundx(void); * value in the range of from one to (CONFIG_SMP_NCPUS-1). (CPU * 0 is already active) * idletask - The entry point to the IDLE task. - * pid - Task ID of the IDLE task * * Returned Value: * Zero on success; a negated errno value on failure. @@ -1708,7 +1706,7 @@ int up_cpundx(void); ****************************************************************************/ #ifdef CONFIG_SMP -int up_cpustart(int cpu, main_t idletask, pid_t pid); +int up_cpustart(int cpu, main_t idletask); #endif /**************************************************************************** diff --git a/sched/init/init.h b/sched/init/init.h index d87b0341f3..1d95df1f6c 100644 --- a/sched/init/init.h +++ b/sched/init/init.h @@ -85,6 +85,25 @@ void os_start(void); int os_smpstart(void); #endif +/**************************************************************************** + * Name: os_idletask + * + * Description: + * This is the common IDLE task for CPUs 1 through (CONFIG_SMP_NCPUS-1). + * It is equivalent to the CPU 0 IDLE logic in os_start.c + * + * Input Parameters: + * Standard task arguments. + * + * Returned Value: + * This function does not return. + * + ****************************************************************************/ + +#ifdef CONFIG_SMP +int os_idletask(int argc, FAR char *argv[]); +#endif + /**************************************************************************** * Name: os_bringup * diff --git a/sched/init/os_smpstart.c b/sched/init/os_smpstart.c index 4dce113d1d..03f3b0992d 100644 --- a/sched/init/os_smpstart.c +++ b/sched/init/os_smpstart.c @@ -94,40 +94,6 @@ static const char g_idlename[] = "CPUn Idle" int os_idletask(int argc, FAR char *argv[]) { -#if CONFIG_NFILE_DESCRIPTORS > 0 || CONFIG_NSOCKET_DESCRIPTORS > 0 - /* Finish TCB initialization */ - - FAR struct task_tcb_s *rtcb = (FAR struct task_tcb_s *)this_task(); - int ret; - - /* Create stdout, stderr, stdin on the IDLE task. These will be - * inherited by all of the threads created by the IDLE task. - * - * We must have exclusive access to the memory manager to do this BUT the - * idle task cannot wait on a semaphore (at least not later in the - * initialzation sequency when this thread is started). So loop until - * we do finally get access - * - */ - - do - { - ret = kmm_trysemaphore(); - if (ret >= 0) - { - DEBUGVERIFY(group_setupidlefiles(rtcb)); - kmm_givesemaphore(); - } - else - { - /* Perform any processor-specific idle state operations */ - - up_idle(); - } - } - while (ret < 0); -#endif - /* Enter the IDLE loop */ sdbg("CPU%d: Beginning Idle Loop\n", this_cpu()); @@ -165,122 +131,6 @@ int os_idletask(int argc, FAR char *argv[]) } } -/**************************************************************************** - * Name: os_idletcb_setup - * - * Description: - * Initialize the IDLE task TCB for this CPU and add it to the correct OS - * task list. The calling sequence here is: - * - * 1. os_start() - Initializes the system and calls os_smpstart() - * 2. os_smpstart() - Calls up_cpustart() for each CPU 1..(CONFIG_SMP_NCPUS-1) - * 3. up_cpustart() - Calls os_idletcb_setup() when appropriate to configure - * the OS data structures. - * 4. up_cpustart() - may also call os_idletcb_teardown() to recover from - * from any errors. - * - * Input Parameters: - * cpu - The CPU to undel - * idle - Memory allocated by os_idletcb_setup - * pid - Task ID of the IDLE task - * - * Returned Value: - * Memory allocated by os_idletcb_setup (for error recovery). NULL is - * returned only a a failure to allocate memory. - * - ****************************************************************************/ - -static FAR void *os_idletcb_setup(int cpu, main_t idletask, pid_t pid) -{ - FAR struct os_tcballoc_s *alloc; - FAR struct task_tcb_s *itcb; - dq_queue_t *tasklist; - - /* IDLE TCB Initialization ************************************************/ - /* Allocate and clear the IDLE TCB */ - - alloc = (FAR struct os_tcballoc_s *)kmm_zalloc(sizeof(struct os_tcballoc_s)); - if (alloc == NULL) - { - return NULL; - } - - /* Initialize the TCB for the IDLE task on the stack. - * REVISIT: We should be able to use task_schedsetup() to do most of this - */ - - itcb = &alloc->tcb; - itcb->cmn.task_state = TSTATE_TASK_RUNNING; - itcb->cmn.entry.main = idletask; - itcb->cmn.flags = (TCB_FLAG_TTYPE_KERNEL | TCB_FLAG_CPU_ASSIGNED); - itcb->cmn.cpu = cpu; - -#if CONFIG_TASK_NAME_SIZE > 0 - snprintf(itcb->cmn.name, CONFIG_TASK_NAME_SIZE, "CPU%d IDLE", cpu); - alloc->idleargv[0] = itcb->cmn.name; -#else - alloc->idleargv[0] = (FAR char *)g_idlename; -#endif - alloc->idleargv[1] = NULL; - itcb->argv = alloc->idleargv; - - /* Add the IDLE task TCB to the end of the assigned task list */ - - tasklist = TLIST_HEAD(TSTATE_TASK_RUNNING, cpu); - dq_addfirst((FAR dq_entry_t *)itcb, tasklist); - - /* Initialize the processor-specific portion of the TCB */ - - up_initial_state(&itcb->cmn); - - /* PID assignment *********************************************************/ - - g_pidhash[PIDHASH(pid)].tcb = &itcb->cmn; - - /* IDLE Group Initialization **********************************************/ -#ifdef HAVE_TASK_GROUP - /* Allocate the IDLE group */ - - DEBUGVERIFY(group_allocate(itcb, itcb->cmn.flags)); -#endif - -#ifdef HAVE_TASK_GROUP - /* Complete initialization of the IDLE group. Suppress retention - * of child status in the IDLE group. - */ - - DEBUGVERIFY(group_initialize(itcb)); - itcb->cmn.group->tg_flags = GROUP_FLAG_NOCLDWAIT; -#endif - - return alloc; -} - -/**************************************************************************** - * Name: os_idletcb_teardown - * - * Description: - * Undo what os_idletcb_setup() did. Necessary only for error recovery. - * - * Input Parameters: - * cpu - The CPU to undel - * alloc - Memory allocated by os_idletcb_setup - * - * Returned Value: - * None - * - ****************************************************************************/ - -static void os_idletcb_teardown(int cpu, FAR void *alloc) -{ - /* Undo what os_idletcb_setup() did */ - - FAR dq_queue_t *tasklist= TLIST_HEAD(TSTATE_TASK_RUNNING, 0); - dq_init(tasklist); - - kmm_free(alloc); -} - /**************************************************************************** * Public Functions ****************************************************************************/ @@ -308,41 +158,19 @@ static void os_idletcb_teardown(int cpu, FAR void *alloc) int os_smpstart(void) { - FAR void *alloc; - pid_t pid; int ret; int cpu; - /* Reserve PIDs for IDLE tasks 1..(CONFIG_SMP_NCPUS-1). We do not have to - * be careful here because so far because there is only one CPU running and - * we have not yet started multi-tasking. - */ - - pid = g_lastpid + 1; /* Should be 1 */ - g_lastpid += CONFIG_SMP_NCPUS; /* Should be CONFIG_SMP_NCPUS */ - /* CPU0 is already running. Start the remaining CPUs */ - for (cpu = 1; cpu < CONFIG_SMP_NCPUS; cpu++, pid++) + for (cpu = 1; cpu < CONFIG_SMP_NCPUS; cpu++) { - /* Assign a PID to the IDLE task and set up the IDLE thread TCB */ - - alloc = os_idletcb_setup(cpu, os_idletask, pid); - if (alloc == NULL) - { - return -ENOMEM; - } - /* And start the CPU. */ - ret = up_cpustart(cpu, os_idletask, pid); + ret = up_cpustart(cpu, os_idletask); if (ret < 0) { sdbg("ERROR: Failed to start CPU%d: %d\n", cpu, ret); - - /* Undo what os_idletcb_setup() did and return the failure*/ - - os_idletcb_teardown(cpu, alloc); return ret; } } diff --git a/sched/init/os_start.c b/sched/init/os_start.c index d201553387..efac6fcc78 100644 --- a/sched/init/os_start.c +++ b/sched/init/os_start.c @@ -39,13 +39,14 @@ #include #include +#include #include #include #include #include #include -#include +#include #include #include #include @@ -296,27 +297,40 @@ const struct tasklist_s g_tasklisttable[NUM_TASK_STATES] = uint8_t g_os_initstate; /* See enum os_initstate_e */ /**************************************************************************** - * Private Variables + * Private Data ****************************************************************************/ -/* This is the task control block for this thread of execution. This thread - * of execution is the IDLE task. NOTE: the system boots into the IDLE - * task. The IDLE task spawns the user initialization task and that user - * initialization task is responsible for bringing up the rest of the system. +/* This is an arry of task control block (TCB) for the IDLE thread of each + * CPU. For the non-SMP case, this is a a single TCB; For the SMP case, + * there is one TCB per CPU. NOTE: The system boots on CPU0 into the IDLE + * task. The IDLE task later starts the other CPUs and spawns the user + * initialization task. That user initialization task is responsible for + * bringing up the rest of the system. */ -static struct task_tcb_s g_idletcb; +#ifdef CONFIG_SMP +static struct task_tcb_s g_idletcb[CONFIG_SMP_NCPUS]; +#else +static struct task_tcb_s g_idletcb[1]; +#endif /* This is the name of the idle task */ #ifdef CONFIG_SMP -static const char g_idlename[] = "CPU0 Idle"; +static const char g_idlename[] = "CPU Idle"; #else static const char g_idlename[] = "Idle Task"; #endif -/* This the IDLE idle threads argument list. */ +/* This the IDLE idle threads argument list. NOTE: Normally the argument + * list is created on the stack prior to starting the task. We have to + * do things s little differently here for the IDLE tasks. + */ +#ifdef CONFIG_SMP +static FAR char *g_idleargv[CONFIG_SMP_NCPUS][2]; +#else static FAR char *g_idleargv[2]; +#endif /**************************************************************************** * Public Functions @@ -340,7 +354,11 @@ static FAR char *g_idleargv[2]; void os_start(void) { - FAR dq_queue_t *tasklist; +#ifdef CONFIG_SMP + int cpu; +#else +# define cpu 0 +#endif int i; slldbg("Entry\n"); @@ -390,68 +408,100 @@ void os_start(void) g_pidhash[i].pid = INVALID_PROCESS_ID; } - /* Assign the process ID of ZERO to the idle task */ - - g_pidhash[PIDHASH(0)].tcb = &g_idletcb.cmn; - g_pidhash[PIDHASH(0)].pid = 0; - /* Initialize the IDLE task TCB *******************************************/ - /* Initialize a TCB for this thread of execution. NOTE: The default - * value for most components of the g_idletcb are zero. The entire - * structure is set to zero. Then only the (potentially) non-zero - * elements are initialized. NOTE: The idle task is the only task in - * that has pid == 0 and sched_priority == 0. - */ - - bzero((void *)&g_idletcb, sizeof(struct task_tcb_s)); - g_idletcb.cmn.task_state = TSTATE_TASK_RUNNING; - g_idletcb.cmn.entry.main = (main_t)os_start; - - /* Set the task flags to indicate that this is a kernel thread and, if - * configured for SMP, that this task is assigned to CPU0. - */ #ifdef CONFIG_SMP - g_idletcb.cmn.flags = (TCB_FLAG_TTYPE_KERNEL | TCB_FLAG_CPU_ASSIGNED); -#else - g_idletcb.cmn.flags = TCB_FLAG_TTYPE_KERNEL; + for (cpu = 0; cpu < CONFIG_SMP_NCPUS; cpu++, g_lastpid++) #endif + { + FAR dq_queue_t *tasklist; - /* Set the IDLE task name */ + /* Assign the process ID(s) of ZERO to the idle task(s) */ -#if CONFIG_TASK_NAME_SIZE > 0 - strncpy(g_idletcb.cmn.name, g_idlename, CONFIG_TASK_NAME_SIZE); - g_idletcb.cmn.name[CONFIG_TASK_NAME_SIZE] = '\0'; -#endif /* CONFIG_TASK_NAME_SIZE */ + g_pidhash[PIDHASH(0)].tcb = &g_idletcb[cpu].cmn; + g_pidhash[PIDHASH(0)].pid = g_lastpid; - /* Configure the task name in the argument list. The IDLE task does - * not really have an argument list, but this name is still useful - * for things like the NSH PS command. - * - * In the kernel mode build, the arguments are saved on the task's stack - * and there is no support that yet. - */ + /* Initialize a TCB for this thread of execution. NOTE: The default + * value for most components of the g_idletcb are zero. The entire + * structure is set to zero. Then only the (potentially) non-zero + * elements are initialized. NOTE: The idle task is the only task in + * that has pid == 0 and sched_priority == 0. + */ -#if CONFIG_TASK_NAME_SIZE > 0 - g_idleargv[0] = g_idletcb.cmn.name; -#else - g_idleargv[0] = (FAR char *)g_idlename; -#endif /* CONFIG_TASK_NAME_SIZE */ - g_idleargv[1] = NULL; - g_idletcb.argv = g_idleargv; + bzero((void *)&g_idletcb[cpu], sizeof(struct task_tcb_s)); + g_idletcb[cpu].cmn.task_state = TSTATE_TASK_RUNNING; - /* Then add the idle task's TCB to the head of the ready to run list */ + /* Set the entry point. This is only for debug purposes. NOTE: that + * the start_t entry point is not saved. That is acceptable, however, + * becaue it can be used only for restarting a task: The IDLE task + * cannot be restarted. + */ #ifdef CONFIG_SMP - tasklist = TLIST_HEAD(TSTATE_TASK_RUNNING, 0); + if (cpu > 0) + { + g_idletcb[cpu].cmn.entry.main = os_idletask; + } + else #else - tasklist = TLIST_HEAD(TSTATE_TASK_RUNNING); + { + g_idletcb[cpu].cmn.entry.main = (main_t)os_start; + } #endif - dq_addfirst((FAR dq_entry_t *)&g_idletcb, tasklist); - /* Initialize the processor-specific portion of the TCB */ + /* Set the task flags to indicate that this is a kernel thread and, if + * configured for SMP, that this task is assigned to the correct CPU. + */ - up_initial_state(&g_idletcb.cmn); +#ifdef CONFIG_SMP + g_idletcb[cpu].cmn.flags = (TCB_FLAG_TTYPE_KERNEL | TCB_FLAG_CPU_ASSIGNED); + g_idletcb[cpu].cmn.cpu = cpu; +#else + g_idletcb[cpu].cmn.flags = TCB_FLAG_TTYPE_KERNEL; +#endif + + /* Set the IDLE task name */ + +#if CONFIG_TASK_NAME_SIZE > 0 +# ifdef CONFIG_SMP + snprintf(g_idletcb[cpu].cmn.name, CONFIG_TASK_NAME_SIZE, "CPU%d IDLE", cpu); +# else + strncpy(g_idletcb[cpu].cmn.name, g_idlename, CONFIG_TASK_NAME_SIZE); + g_idletcb[cpu].cmn.name[CONFIG_TASK_NAME_SIZE] = '\0'; +# endif +#endif + + /* Configure the task name in the argument list. The IDLE task does + * not really have an argument list, but this name is still useful + * for things like the NSH PS command. + * + * In the kernel mode build, the arguments are saved on the task's + * stack and there is no support that yet. + */ + +#if CONFIG_TASK_NAME_SIZE > 0 + g_idleargv[cpu][0] = g_idletcb[cpu].cmn.name; +#else + g_idleargv[cpu][0] = (FAR char *)g_idlename; +#endif /* CONFIG_TASK_NAME_SIZE */ + g_idleargv[cpu][1] = NULL; + g_idletcb[cpu].argv = &g_idleargv[cpu][0]; + + /* Then add the idle task's TCB to the head of the corrent ready to + * run list. + */ + +#ifdef CONFIG_SMP + tasklist = TLIST_HEAD(TSTATE_TASK_RUNNING, cpu); +#else + tasklist = TLIST_HEAD(TSTATE_TASK_RUNNING); +#endif + dq_addfirst((FAR dq_entry_t *)&g_idletcb[cpu], tasklist); + + /* Initialize the processor-specific portion of the TCB */ + + up_initial_state(&g_idletcb[cpu].cmn); + } /* Initialize RTOS facilities *********************************************/ /* Initialize the semaphore facility. This has to be done very early @@ -632,28 +682,46 @@ void os_start(void) lib_initialize(); /* IDLE Group Initialization **********************************************/ -#ifdef HAVE_TASK_GROUP - /* Allocate the IDLE group */ - DEBUGVERIFY(group_allocate(&g_idletcb, g_idletcb.cmn.flags)); +#ifdef CONFIG_SMP + for (cpu = 0; cpu < CONFIG_SMP_NCPUS; cpu++) +#endif + { +#ifdef HAVE_TASK_GROUP + /* Allocate the IDLE group */ + + DEBUGVERIFY(group_allocate(&g_idletcb[cpu], g_idletcb[cpu].cmn.flags)); #endif #if CONFIG_NFILE_DESCRIPTORS > 0 || CONFIG_NSOCKET_DESCRIPTORS > 0 - /* Create stdout, stderr, stdin on the IDLE task. These will be - * inherited by all of the threads created by the IDLE task. - */ +#ifdef CONFIG_SMP + if (cpu > 0) + { + /* Clone stdout, stderr, stdin from the CPU0 IDLE task. */ - DEBUGVERIFY(group_setupidlefiles(&g_idletcb)); + DEBUGVERIFY(group_setuptaskfiles(&g_idletcb[cpu])); + } + else +#endif + { + /* Create stdout, stderr, stdin on the CPU0 IDLE task. These + * will be inherited by all of the threads created by the CPU0 + * IDLE task. + */ + + DEBUGVERIFY(group_setupidlefiles(&g_idletcb[cpu])); + } #endif #ifdef HAVE_TASK_GROUP - /* Complete initialization of the IDLE group. Suppress retention - * of child status in the IDLE group. - */ + /* Complete initialization of the IDLE group. Suppress retention + * of child status in the IDLE group. + */ - DEBUGVERIFY(group_initialize(&g_idletcb)); - g_idletcb.cmn.group->tg_flags = GROUP_FLAG_NOCLDWAIT; + DEBUGVERIFY(group_initialize(&g_idletcb[cpu])); + g_idletcb[cpu].cmn.group->tg_flags = GROUP_FLAG_NOCLDWAIT; #endif +} #ifdef CONFIG_SMP /* Start all CPUs *********************************************************/