Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Overview

At the highest level, the NuttX initialization sequence can be represented in three phases:

  1. The hardware-specific power-on reset initialization,
  2. NuttX RTOS initialization, and
  3. Application Initialization.

This initialization sequence is really quite simple because the system runs in single-thread mode up until the point the that is starts the application. That means that the initialization sequence is just a simple, straight-line of function calls. Just It is until just before starting the application , the that system goes to multi-threaded mode and things can get more complex.

Each of these will be discussed in more detail in the following paragraphs.

Power-On Reset Initialization.

Overview

The software begins execution when the processor is reset. This usually at power-on, but all resets are basically the same whether they occur because of power-on, pressing the reset button, or on a watchdog timer expiration. The software that executes when the processor is reset is unique to the particular CPU architecture and is not a common part of NuttX. The kinds of things that must be done by the architecture-specific reset handling includes:

  1. Putting the processor in its operational state. This may include things like setting CPU modes; initializing co-processors, etc.
  2. Setting up clocking so that the software and peripherals operate as expected,
  3. Setting up the C stack pointer (and other processor registers)
  4. Initializing memory, and
  5. Starting NuttX.

Memory Initialization

In C implementations, there are two general classes of variable storage. First there are the initialized variables. For example, consider the global variable x:

Code Block

  int x = 5;

The C code must be assured that after reset, the variable x has the value 5. Initialized variable of this kind are retained in a special memory section called data (or .data).

Other variables are not initialized. Like the global variable y:

Code Block

  int y;

But the C code will still expect y to have an initial value. That initial value will be zero. All uninitialized variables of this this type have have the value zero. These uninitialized variables are retained in a section called bss (or .bss).

When we say that the reset handling logic initializes memory, we mean two things:

  1. It provides the (initial) values of the initialized variables by copying the values from FLASH into the .data section, and
  2. It resets all of the uninitialized variables to zero. It clears the .bss section.

STM32 F4 Reset

Lets walk through reset sequence of one particular processor. Let's look at the NuttX initialization for the STM32 F4 MCU. This reset logic can be found in two files:

  1. nuttx/arch/arm/src/stm32_vectors.S
  2. nuttx/arch/arm/src/stm32_start.c
nuttx/arch/arm/src/stm32_vectors.S

The roll of stm32_vectors.S in this reset sequence is very small. This file provides all of the STM32 exception vectors and power-on reset is simply another exception vector. Some important things to note about this file:

...

The first two 32-bit entries in the vector table represent the power-up exception vector (which we know will be positioned at address 0x0000 0000 when the reset occurs). Those two entries are:

Code Block

.word IDLE_STACK /* Vector  0: Reset stack pointer */
.word __start    /* Vector  1: Reset vector */

At the highest level, the NuttX initialization sequence can be represented in three phases:

Phase A - The hardware-specific power-on reset initialization,

Phase B - NuttX RTOS initialization, and

Phase C - Application Initialization.

Each of these will be discussed in more detail in the following sections.

Case example: STM32 F4

In this discussion, we'll use the STM32 F4 MCU and its popular evaluation board STM32F4Discovery as example but the explanation can be applied to any supported architecture.

Here is the map of initialization function calls

Code Block
titleFunction Map
collapsetrue
__start()-arch/arm/src/stm32/stm32_start.c
    |
    +--*Set stack limit
    +--stm32_clockconfig()
    +--stm32_fpuconfig()
    +--stm32_lowsetup()
    +--stm32_gpioinit()
    +--showprogress('A')
    +--
    +--
    +--stm32_boardinitialize()-boards/arm/stm32/stm32f4discovery/src/stm32_boot.c
    |    |
    |    +--stm32_spidev_initialize()-stm32_spi.c:ONLY CHIP SELECTS
    |    +--stm32_usbinitialize()-
    |    +--stm32_netinitialize()-
    |    +--board_autoled_initialize()-
    |                  
nx_start()-sched/init/nx_start.c
    |                  
    +--*Initialize global data structures
    +--*Initialize OS facilities
    +--net_initialize()-net/net_initialize.c
    |    |   
    |    +--net_lockinitialize()
    |    +--mld_initialize()
    |    +--can_initialize()
    |    +--netlink_initialize()
    |    +--tcp_initialize()
    |    +--udp_initialize()
    |    +--usrsock_initialize()
    |
    +--up_initialize()-arch/arm/src/common/up_initialize.c
    |    |                  
    |    +--arm_dmainitialize()
    |    +--Config basic /dev nodes
    |    +--arm_serialinit()
    |    +--Console Init
    |    +--Crypto Config
    |    +--arm_netinitialize()
    |    |    |
    |    |    +--stm32_spibus_initialize()
    |    |
    |    |
    |    +--arm_usbinitialize()
    |    +--L2 Cache Init
    |
    +--board_early_initialize()
    +--g_nx_initstate = OSINIT_HARDWARE
    +--shm_initialize()
    +--lib_initialize()
    +--binfmt_initialize()
    +--Start SMP
    +--syslog_initialize()
    +--g_nx_initstate = OSINIT_OSREADY
    +--DEBUGVERIFY(nx_bringup())-sched/init/nx_bringup.c
    |    |
    |    +--nx_pgworker()
    |    +--nx_workqueues()
    |    +--nx_create_initthread()-sched/init/nx_bringup.c
    |         |              
    |         +----+different thread
    |         :    :
    |         :  nx_start_task()-sched/init/nx_bringup.c
    |         :    :        
    |     same+----+--nx_start_application()-sched/init/nx_bringup.c
    |     thread        |
    |                   +--board_late_initialize()-stm32_boot.c:BOARD_DEPENDANT
    |                   |    |
    |                   |    +--stm32_bringup()
    |                   |         |
    |                   |         +--stm32_i2ctool()
    |                   |         +--board_bmp180_initialize()
    |                   |         +--stm32_sdio_initialize()
    |                   |         +--stm32_usbhost_initialize()
    |                   |         +--stm32_pwm_setup()
    |                   |         +--stm32_can_setup()
    |                   |         +--etc
    |                   |
    |                   +--
    |
    |
    |
    |
    |
    +--kmm_givesemaphore()
    +--up_idle()

Phase A - Power-On Reset Initialization.

The system begins execution when the processor is reset. This usually at power-on, but all resets are basically the same whether they occur because of power-on, pressing the reset button, or on a watchdog timer expiration. The code that executes when the processor is reset is unique to the particular CPU architecture and is not a common part of NuttX. The kind of things that must be done by the architecture-specific reset handling includes:

  1. Putting the processor in its operational state. This may include things like setting CPU modes; initializing co-processors, etc.
  2. Setting up clocking so that the software and peripherals operate as expected,
  3. Setting up the C stack pointer (and other processor registers).
  4. Initializing memory, and
  5. Starting NuttX.

Memory Initialization

In C implementations, there are two general classes of variable storage. First there are the initialized variables. For example, consider the global variable x:

Code Block
  int x = 5;

The C code must be assured that after reset, the variable x has the value 5. Initialized variable of this kind are retained in a special memory section called data (or .data).

Other variables are not initialized. Like the global variable y:

Code Block
  int y;

But the C code will still expect y to have an initial value. That initial value will be zero. All uninitialized variables of this type need to have the value zero. These uninitialized variables are retained in a section called bss (or .bss).

When we say that the reset handling logic initializes memory, we mean two things:

  1. It provides the (initial) values of the initialized variables by copying the values from FLASH into the .data section, and
  2. It resets all of the uninitialized variables to zero. It clears the .bss section.

Lets walk through reset sequence. This reset logic can be found in two files:

  1. nuttx/arch/arm/src/stm32_vectors.S
  2. nuttx/arch/arm/src/stm32_start.c
nuttx/arch/arm/src/stm32_vectors.S

This file provides all of the STM32 exception vectors and power-on reset is simply another exception vector. Some important things to note about this file:

.section .vectors, "ax". This pseudo operation will place all of the vectors into a special section call .vectors. On of the STM32 F4 linker scripts is located at nuttx/boards/arm/stm32/stm32f4discovery/scripts/ld.script. In that file, you can see that section .vectors is forced to lie at the very beginning of FLASH memory. The STM32 F4 can be configured to boot in different ways via strapping. If it is strapped to boot from FLASH, then the STM32 FLASH memory will be aliased to address 0x0000 0000 when the reset occurs. That is the address of the power-up reset interrupt vector.

The first two 32-bit entries in the vector table represent the power-up exception vector (which we know will be positioned at address 0x0000 0000 when the reset occurs). Those two entries are:

Code Block
.word IDLE_STACK /* Vector  0: Reset stack pointer */
.word __start    /* Vector  1: Reset vector */

The Cortex-M family is unique in the way that is handles the reset vector. Notice that there are two values: the stack pointer for the start-up thread (the IDLE thread), and the entry point in the IDLE thread. When the reset occurs, the the stack pointer is automatically set to the first value and then the processor jumps to reset entry point __start specified in the second entry. This means that the reset exception handling code can be implemented in C rather than assembly language.

nuttx/arch/arm/src/stm32_start.c

The reset vector __start lies in the file nuttx/arch/arm/src/stm32/stm32_start.c and does the real, low-level architecture-specific initialization. This initialization includes:

  1. stm32_clockconfig() - Initialize the PLLs and peripheral clocking needed by the board.
  2. stm32_fpuconfig() - If the STM32 F4's hardware floating point is initialized, then configure the FPU and enable access to the FPU co-processors.
  3. stm32_lowsetup() - Enable the low-level UART. This is done very early in initialization so that we can get serial debug output to the console as soon as possible. If you are doing a board bring-up this is very important.
  4. stm32_gpioinit() - Perform any GPIO remapping that is needed (this is a stub for the F4, but the F1 family requires this step).
  5. ''showprogress('A');'' This simply outputs the character 'A' on the serial console (only if CONFIG_DEBUG is enabled). If debug is enabled, you will always see the letters ABDE output on the console. That output all comes from this file.
  6. Next the memory is initialized:
    1. The .bss section is set to zero (Letter 'B' is then output if CONFIG_DEBUG is enabled), then
    2. The .data section is set to its initial values (The letter 'C' is output if debug is enabled),
  7. stm32_boardinitialize() - Board-specific logic is initialized by calling this function. For the case of the STM32F4Discovery board, this logic can be found at nuttx/boards/arm/stm32/stm32f4discovery/src/stm32_boot.c and does the following operations:
    1. stm32_spidev_initialize() - Initialize SPI chip selects if SPI is enabled.
    2. stm32_usbinitialize() - Initialize hardware USB devices if enabled.
    3. stm32_netinitialize() - Initialize hardware network devices if enabled.
    4. board_autoled_initialize() - Configure on-board LEDs if LED support has been selected.
  8. When stm32_boardinitialize() returns to __start(), the low-level, architecture-specific initialization is complete.

Phase B - NuttX RTOS Initialization

nx_start()

This function resides in the file nuttx/sched/init/nx_start.c and is the NuttX entry point.

It is called by __start() and performs the next phase of RTOS-specific initialization before bringing up the application.

The operations performed by nx_start() are summarized below. Note that many of these features can be disabled from the NuttX configuration file and in that case those operations are not performed:

1- Initializes some NuttX global data structures,

2- Initializes the TCB for the IDLE (i.e, the thread that the initialization is performed on),

3- nxsem_initialize() - Initialize the POSIX semaphore facilities. This needs to be done first because almost all other OS features depend on POSIX counting semaphores.

4- Memory organization - This includes heap configuration, memory manager, paging, I/O buffers, etc.

5- task_initialize() - Initialize task data structures.

6- fs_initialize() - Initialize the file system (needed to support device drivers).

7- irq_initialize() - Initialize the interrupt handler subsystem. This initializes only data structures; CPU interrupts are still disabled.

8- wd_initialize() - Initialize the NuttX watchdog timer facility,

9- clock_initialize() - Initialize the system clock,

10- timer_initialize() - Initialize the POSIX timer facilities,

11- nxsig_initialize() - Initialize the POSIX signal facilities,

12- nxmq_initialize() - Initialize the POSIX message queue facilities,

13- pthread_initialize() - Initialize the POSIX pthread facilities,

14- net_initialize() - Initialize networking facilities,

Up to this point, all of the initialization steps have only been software initializations. Nothing has interacted with the hardware. Rather, all of these steps simply prepared the environment so that things like interrupts and threads can function properly. The next phases depend upon that setup.

15- up_initialize() - The processor specific details of running the operating system will be handled here. Such things as setting up interrupt service routines and starting the clock are some of the things that are different for each processor and hardware platform.

All ARM-based MCUs share a common up_initialize() implementation provided at nuttx/arch/arm/src/common/up_initialize.c. The operations perform by this common ARM initialization will, however, call into facilities provided by the particular ARM chip. For the STM32 F4, those facilities would be provided by logic in files as nuttx/arch/arm/src/stm32. The common ARM initialization sequence is:

up_color_intstack() - Colorize the interrupt stack.

arm_addregion() - The basic heap was set up during processing by nx_start(). However, if the board supports multiple, discontiguous memory regions, any addition memory regions can be added to the heap by this function. For the STM32 F4, up_addregion() is implemented in nuttx/arch/arm/src/stm32/stm32_allocateheap.c.

arm_pminitialize() - If CONFIG_PM is defined, the function must initialize the power management subsystem. This MCU-specific function must be called very early in the intialization sequence before any other device drivers are initialized (since they may attempt to register with the power management subsystem). There is no implementation of up_pminitialize() for any STM32 platform.

arm_dmainitialize() - Initialize the DMA subsystem. For the STM32 F4, this DMA initialization can be found in nuttx/arch/arm/src/stm32/stm32_dma.c (which includes nuttx/arch/arm/src/stm32f4xxx_dma.c).

devnull_register() - Registers the standard /dev/null.

devrandom_register() - Registers the standard /dev/random.

devurandom_register() - Registers the standard /dev/urandom.

devzero_register() - Registers the standard /dev/zero.

loop_register() - Registers the standard /dev/loop.

note_register() - Registers the standard /dev/note.

arm_serialinit() - Initialize the standard serial driver (found at nuttx/arch/arm/src/stm32/stm32_serial.c STM32 F4).

arm_netinitialize(); Initialize the network. For the STM32 F4, this function is in nuttx/arch/arm/src/stm32/stm32_eth.c.

arm_usbinitialize(); Initialize USB (host or device). For the STM32 F4, this function is in nuttx/arch/arm/src/stm32/stm32_otgfsdev.c.

arm_l2ccinitialize() - Initialize the L2 cache if present and selected .

up_ledon(LED_IRQSENABLED); Finally, up_initialize() illuminates board-specific LEDs to indicate the IRQs are now enabled.


16- board_early_initialize() - If CONFIG_BOARD_EARLY_INITIALIZE is selected, then an additional initialization call will be performed in the boot-up sequence to a function called board_early_initialize(). It will be called immediately after up_initialize() (and may be thought of as a board-specific, extension of up_initialize()) and well before board_late_initialize() is called and the initial application is started.

17- g_nx_initstate = OSINIT_HARDWARE - This signals that basic hardware setup is complete

18- shm_initialize() - Initialize shared memory support

19- lib_initialize() - Initialize the C libraries. This is done last because the libraries may depend on the above.

20- binfmt_initialize() - Initialize binary loader subsystem.

21- Start SMP support in multi-core MCUs. This is not the case in STM32F4Discovery but relevant because here are created stdin, stdout and stderr for each CPU's (even if there is only one) IDLE task. All tasks subsequently created by the IDLE thread will inherit these file descriptors.

22- syslog_initialize() - Late initialization of the system logging device. Some SYSLOG channel must be initialized late in the initialization sequence because it may depend on having IDLE task file structures setup.

23- nx_bringup() - Create the initial tasks. This will be described in more detail below

The Cortex-M family is unique in the way that is handles the reset vector. Notice that there are two values: the stack pointer for the start-up thread (the IDLE thread), and the entry point in the IDLE thread. When the reset occurs, the the stack pointer is automatically set to the first value and then the processor jumps to reset entry point

No Format
__start

 specified in the second entry. This means that the reset exception handling code can be implemented in C rather than assembly language.

nuttx/arch/arm/src/stm32_start.c

The reset vector

No Format
__start

lies in the file stm32_start.c and does the real, low-level architecture-specific initialization. This initialization includes:

  1. stm32_clockconfig(); Initialize the PLLs and peripheral clocking needed by the board.
  2. stm32_fpuconfig(); If the STM32 F4's hardware floating point is initialized, then configure the FPU and enable access to the FPU co-processors.
  3. stm32_lowsetup(); Enable the low-level UART. This is done very early in initialization so that we can get serial debug output to the console as soon as possible. If you are doing a board bring-up this is very important.
  4. stm32_gpioinit(); Perform any GPIO remapping that is needed (this is a stub for the F4, but the F1 family requires this step).
  5. ''showprogress('A');'' This simply outputs the character 'A' on the serial console (only if CONFIG_DEBUG is enabled). If debug is enabled, you will always see the letters ABDE output on the console. That output all comes from this file.
  6. Next the memory is initialized:
    1. The .bss section is set to zero (Letter 'B' is then output if CONFIG_DEBUG is enabled), then
    2. The .data section is set to its initial values (The letter 'C' is output if debug is enabled),
  7. Then board-specific logic is initialized:
    1. stm32_boardinitialize(); This function resides with the board-specific logic. For the case of the STM3240G-EVAL board, this board initialization logic can be found at boards/arm/stm32/stm3240g-eval/src/stm32_boot.c.
    2. For the case of the STM3240G-EVAL board, the stm32_boardinitialize() does the following operations:
      1. stm32_spiinitialize(); Initialize SPI chip selects if SPI is enabled.
      2. stm32_selectsram(); Configure the STM32 FSMC to support external SRAM if external SRAM support is enabled.
      3. stm32_autoled_initialize(); Initialize the on-board LEDs if they are used.
  8. When stm32_boardinitialize() returns to

    No Format
    __start()

    , the low-level, architecture-specific initialization is complete and NuttX is started:

    1. nx_start(); This is the NuttX entry point. It performs the next phase of RTOS-specific initialization and then brings up the application.

The operations performed by nx_start() are discussed in the next paragraph.

NuttX RTOS Initialization

nx_start()

When the low-level, architecture-specific initialization is complete and NuttX is started by calling the function nx_start(). This function resides in the file nuttx/sched/init/nx_start.c. The operations performed by nx_start() are summarized below. Note that many of these features can be disabled from the NuttX configuration file and in that case those operations are not performed:

  1. Initializes some NuttX global data structures,
  2. Initializes the TCB for the IDLE (i.e, the thread that the initialization is performed on),
  3. sem_initialize(); Initialize the POSIX semaphore facilities. This needs to be done first because almost all other OS features depend on POSIX counting semaphores.
  4. kmm_initialize(); Initialize the memory manager (in most configurations, kmm_initialize() is an alias for the common mm_initialize()).
  5. irq_initialize(); Initialize the interrupt handler subsystem. This initializes only data structures; CPU interrupts are still disabled.
  6. wd_initialize(); Initialize the NuttX watchdog timer facility,
  7. clock_initialize(); Initialize the system clock,
  8. timer_initialize(); Initialize the POSIX timer facilities,
  9. sig_initialize(); Initialize the POSIX signal facilities,
  10. mq_initialize(); Initialize the POSIX message queue facilities,
  11. pthread_initialize(); Initialize the POSIX pthread facilities,
  12. fs_initialize(); Initialize file system facilities (currently an empty function),
  13. net_initialize(); Initialize networking facilities,

Up to this point, all of the initialization steps have only been software initializations. Nothing has interacted with the hardware. Rather, all of these steps simply prepared the environment so that things like interrupts and threads can function properly. The next phases depend upon that setup.

  1. up_initialize(); The processor specific details of running the operating system will be handled here. Such things as setting up interrupt service routines and starting the clock are some of the things that are different for each processor and hardware platform. See below for a specific example of the initialization steps performed by the ARM version of this function.
  2. board_early_initialize(); If CONFIG_BOARD_EARLY_INITIALIZE is selected, then an additional initialization call will be performed in the boot-up sequence to a function called board_early_initialize(). board_early_initialize() will be called immediately after up_initialize() (and may be thought of as a board-specific, extension of up_initialize()) and well before board_early_initialize() is called and the initial application is started. The context in which board_early_initialize() executes is suitable for early initialization of most, simple device drivers. This would be place where low-level hardware configuration would need to be performed such as configuration of GPIO pins and for initialization of simple device drivers. Some initialization operations cannot be performed board_early_initialize(), however, because they require that more of the operating system has been initialized. For this reason, some driver initialize must be deferred to board_late_initialize().
  3. board_late_initialize(); If CONFIG_BOARD_LATE_INITIALIZE is defined in the NuttX configuration, then an additional initialization call is made to a user-provided board_late_initialize() function. CONFIG_BOARD_LATE_INITIALIZE should be defined if there any any board-specific initialization actions that that need to be performed. Note above that there was an earlier, board-specific initialization calls (that one to stm32_board_initialize() and to board_early_initialize()). The difference here is these first, low-level initialization calls were made before the OS was started. board_late_initialize(), on the other hand, is called late in the initialization sequence; after the OS has been initialized but before any application tasks have been started. board_late_initialize() would be an ideal place to do board-specific initialization steps that depend on having a fully initialized OS such as memory allocations, initialization of complex device drivers, mounting of file systems, etc.
  4. lib_initialize(); Initialize the C libraries. This is done last because the libraries may depend on the above.
  5. sched_setupidlefiles(); This is the logic that opens /dev/console and creates stdin, stdout, and stderr for the IDLE thread. All tasks subsequently created by the IDLE thread will inherit these file descriptors.
  6. nx_bringup(); Create the initial tasks. This will be described more below.
  7. And finally enter the IDLE loop. After completing the initialization, the roll of the IDLE thread changes. It is now becomes the thread that executes only when there is nothing else to do in the system (hence, the name IDLE thread).

IDLE Thread Activities

As mention, the IDLE thread is the thread that executes only when there is nothing else to do in the system. It has the lowest priority in the system. It always has the priority 0. It is the only thread that is permitted to have the priority 0. And it can never be blocked (otherwise, what would run then?).

As a result, the IDLE thread is always in the g_readytorun list and, in fact, since that list is prioritized, can guaranteed to always be the final entry at the tail of the g_readytorun list.

The IDLE is an an infinite loop. But this does not make it a “CPU hog.” Since it is the lowest priority, the it can be suspended whenever anything else needs to run.

The IDLE thread does two things in this infinite loop:

...

.

nx_bringup()

This function is called at the very end of the initialization sequence in nx_start(), just before entering the IDLE loop. This function It is located in nuttx/sched/init/nx_bringup.c. This function and it starts all of the required threads and tasks needed to bring up the system. This function performed the following specific operations:

...

  • nx_pgworker() - Start the page fill worker kernel thread that will resolve page faults. This should always be the first thread started because it may have to resolve page faults in other threads. This is the task that runs in order to satisfy page faults in processors that have an MMU and in configurations where on-demand paging is enabled.

...

  • nx_workqueues() - Start the worker thread. The worker thread may be used to execute any processing deferred to the worker thread via APIs provided in include/nuttx/wqueue.h. The worker thread's primary function is as the “bottom half” for extended device driver processing but can be used for a variety of purposes

...

  • like misc garbage clean-up.
  • nx_create_initthread() - Once the operating system has been initialized,this funcions either directly calls nx_start_application() or creates a thread for running it
  • nx_start_application() - If set in the NuttX configuration, this function calls board_late_initialize().
    • board_late_initialize() is a last-minute, board-specific initialization. Note that there was earlier, board-specific initialization calls (to stm32_board_initialize() and to board_early_initialize()). The difference here is these first, low-level initialization calls were made before the OS was completely launched. board_late_initialize(), on the other hand, is called at the end after the OS has been initialized but before any application tasks have been started. board_late_initialize() would be an ideal place to do board-specific initialization steps that depend on having a fully initialized OS such as memory allocations, initialization of complex device drivers, mounting of file systems, etc.
  • After that, nx_start_application() launches the application either by creating a task for it or executing a program from a filesystem after mounting it.
  • In the case of creating a task for the application, its entry has the name user_start(). user_start() is provided by application code and when it runs, it begins the application-specific phase of the initialization sequence as described below.

NOTE: The default user_start() entry point can be changed to use one of the named applications used by NSH. This is a start-up option that is not often used and will not be discussed further here.

And finally enter the IDLE loop. After completing the initialization, the role of the IDLE thread changes. It becomes the thread that executes only when there is nothing else to do in the system (hence, the name IDLE thread).

IDLE Thread Activities

As mention, the IDLE thread is the thread that executes only when there is nothing else to do in the system. It has the lowest priority in the system. It always has the priority 0. It is the only thread that is permitted to have the priority 0. And it can never be blocked (otherwise, what would run then?).

As a result, the IDLE thread is always in the g_readytorun list and, in fact, since that list is prioritized, can guaranteed to always be the final entry at the tail of the g_readytorun list.

The IDLE is an an infinite loop. But this does not make it a “CPU hog.” Since it is the lowest priority, the it can be suspended whenever anything else needs to run.

The IDLE thread does two things in this infinite loop:

  1. If the worker was not started (see nx_bringup() below), then the IDLE thread will perform memory clean-up. Memory clean is required to handle deferred memory deallocation. Memory allocations must be deferred when the memory is freed in a context where the software does not have access to the heap and, hence, cannot truly free the memory (such as in an interrupt handler). In this case, the memory is simply put into a list of freed memory and, eventually, cleaned up by the IDLE thread. NOTE: The worker thread's primary function is as the “bottom half” for extended device driver processing. If the worker thread was started, then it will run at a higher priority than the IDLE thread. In this case, the worker thread will take over responsibility for cleaning up these deferred allocations.
  2. up_idle(); Then the loop calls up_idle(). The operations performed by up_idle() are architecture- and board-specific. In general, this is the location where CPU-specific reduced power operations may be performed

NOTE: The default user_start() entry point can be changed to use one of the named applications used by NSH. This is a start-up option that is not often used and will not be discussed further here.

STM32 F4 up_initialize()

All ARM-based MCUs share a common up_initialize() implementation provided at nuttx/arch/arm/common/up_initialize.c. The operations perform by this common ARM initialization will, however, call into facilities provided by the particular ARM chip. For the STM32 F4, those facilities would be provided by logic in files as nuttx/arch/arm/src/stm32. The common ARM initialization sequence is:

  1. up_calibratedelay(); One operation that must be performed during a CPU port is the calibration of timing delay loops. If CONFIG_ARCH_CALIBRATION is defined, then up_initialize() will perform some specific operations for the calibration of the delay loop. This, however, is not part of the normal initialization sequence. up_calibratedelay() is implemented within up_initialize.c.
  2. up_addregion(); The basic heap was set up during processing by nx_start(). However, if the board supports multiple, discontiguous memory regions, any addition memory regions can be added to the heap by this function. For the STM32 F4, up_addregion() is implemented in nuttx/arch/arm/src/stm32/stm32_allocateheap.c.
  3. up_irqinitialize(); This function initialize the interrupt subsystem. For the STM32 F4, up_irqinitialize() is implemented in nuttx/arch/arm/src/stm32/stm32_irq.c.
  4. up_pminitialize(); If CONFIG_PM is defined, the function must initialize the power management subsystem. This MCU-specific function must be called very early in the intialization sequence before any other device drivers are initialized (since they may attempt to register with the power management subsystem). There is no implementation of up_pminitialize() for any STM32 platform.
  5. up_dmainitialize(); Initialize the DMA subsystem. For the STM32 F4, this DMA initialization can be found in nuttx/arch/arm/src/stm32/stm32_dma.c (which includes nuttx/arch/arm/src/stm32f4xxx_dma.c).
  6. up_timerinit(); Initialize the system timer interrupt. For the STM32 F4, this function initializes the ARM Cortex-M SYSTICK timer and can be found at nuttx/arch/arm/src/stm32/stm32_timerisr.c.
  7. devnull_register(); Registers the standard /dev/null.
  8. Then this function initializes the console device (if any). This means calling one of (1) up_serialinit(); for the standard serial driver (found at nuttx/arch/arm/src/stm32/stm32_serial.c for the STM32 F4), (2) lowconsole_init(); for the low-level, write-only serial console (found at nuttx/drivers/serial/lowconsole_init.c), or (2) ramlog_sysloginit() for the RAM console (found at nuttx/drivers/ramlog.c).
  9. up_netinitialize(); Initialize the network. For the STM32 F4, this function is in nuttx/arch/arm/src/stm32/stm32_eth.c.
  10. up_usbinitialize(); Initialize USB (host or device). For the STM32 F4, this function is in nuttx/arch/arm/src/stm32/stm32_otgfsdev.c.
  11. up_ledon(LED_IRQSENABLED); Finally, up_initialize() illuminates board-specific LEDs to indicate the IRQs are now enabled.

STM32 F4 IDLE thread

The default STM32 F4 IDLE thread is located at nuttx/arch/arm/src/stm32_idle.c. This default version does very little:

...

  1. If you have C++ static initializers, it will call your implementation of up_cxxinitialize() which will, in turn, call those static initializers. For the case of the STM3240G-EVAL board, the implementation of up_cxxinitialize() can be found at nuttx/boards/arm/stm32/stm3240g-eval/src/up_cxxinitialize.c.
  2. This function then calls nsh_initialize() which initializes the NSH library. nsh_initialize() is described in more detail below.
  3. If the Telnet console is enabled, it calls nsh_telnetstart() which resides in the NSH library. nsh_telnetstart() will start the Telnet daemon that will listen for Telnet connections and start remote NSH sessions.
  4. If a local console is enabled (probably on a serial port), then nsh_consolemain() is called. nsh_consolemain() also resides in the NSH library. nsh_consolemain() does not return so that finished the entire NSH initialization sequence.

...

The NSH initialization function, nsh_initialize(), be found in apps/nshlib/nsh_init.c. It does only three things:

nsh_romfsetc(); If so configured, it executes an NSH start-up script that can be found at /etc/init.d/rcS in the target file system. /etc is the location where a read-only, ROMFS file system is mounted by nsh_romfsetc(). The ROMFS image is, itself, just built into the firmware. By default, this rcS startup script contains the following logic:

Code Block

# Create a RAMDISK and mount it at XXXRDMOUNTPOUNTXXX

mkrd -m XXXMKRDMINORXXX -s XXMKRDSECTORSIZEXXX XXMKRDBLOCKSXXX
mkfatfs /dev/ramXXXMKRDMINORXXX
mount -t vfat /dev/ramXXXMKRDMINORXXX XXXRDMOUNTPOUNTXXX

Where the XXXX*XXXX strings get replaced in the template when the ROMFS image is created:

...

  • board_app_initialize(): For the STM3240G-EVALSTM32F4Discovery, this architecture specific initialization can be found at boards/arm/stm32/stm3240g-evalstm32f4discovery/src/stmstm32_nshappinit.c. This it does things like: (1) Initialize SPI devices, (2) Initialize SDIO, and (3) mount any SD cards that may be inserted.

...