NuttX RTOS Porting Guide

Last Updated: June 22, 2016



Table of Contents

1.0 Introduction

Overview This document provides and overview of the NuttX build and configuration logic and provides hints for the incorporation of new processor/board architectures into the build.

See also arch/README.txt and configs/README.txt.

2.0 Directory Structure

Directory Structure. The general directly layout for NuttX is very similar to the directory structure of the Linux kernel -- at least at the most superficial layers. At the top level is the main makefile and a series of sub-directories identified below and discussed in the following paragraphs:

Configuration Files. The NuttX configuration consists of:

2.1 Documentation

General documentation for the NuttX OS resides in this directory.

2.2 nuttx/arch

2.2.1 Subdirectory Structure

This directory contains several sub-directories, each containing architecture-specific logic. The task of porting NuttX to a new processor consists of add a new subdirectory under arch/ containing logic specific to the new architecture. The complete board port in is defined by the architecture-specific code in this directory (plus the board-specific configurations in the config/ subdirectory). Each architecture must provide a subdirectory, <arch-name> under arch/ with the following characteristics:

2.2.2 Summary of Files

2.2.3 Supported Architectures

Architecture- and Chip-Specific Directories. All processor architecture-specific directories are maintained in sub-directories of the arch/ directory. Different chips or SoC's may implement the same processor core. Chip-specific logic can be found in sub-directories under the architecture directory. Current architecture/chip directories are summarized below:

Deprecated Architecture Directories. The following architecture directories are deprecated. They have been replaced by the logic in arm/arm and will deleted when arch/arm is fully verified.

Other ports for the for the TI TMS320DM270 and for MIPS are in various states of progress

2.3 nuttx/binfmt

The binfmt/ subdirectory contains logic for loading binaries in the file system into memory in a form that can be used to execute them.

2.4 nuttx/audio

The audio/ subdirectory contains the NuttX audio sub-system.

2.5 nuttx/configs

The configs/ subdirectory contains configuration data for each board. These board-specific configurations plus the architecture-specific configurations in the arch/ subdirectory complete define a customized port of NuttX.

2.5.1 Subdirectory Structure

The configs directory contains board specific configuration files. Each board must provide a subdirectory <board-name> under configs/ with the following characteristics:

2.5.2 Summary of Files

2.5.2.1 Board Specific Logic

2.5.2.2 Board Specific Configuration Sub-Directories

The configs/<board-name>/ sub-directory holds all of the files that are necessary to configure NuttX for the particular board. A board may have various different configurations using the common source files. Each board configuration is described by three files: Make.defs, defconfig, and setenv.sh. Typically, each set of configuration files is retained in a separate configuration sub-directory (<config1-dir>, <config2-dir>, .. in the above diagram). The procedure for configuring NuttX is described below, This paragraph will describe the contents of these configuration files.

2.5.3 Supported Boards

All of the specific boards supported by NuttX are identified in the README.txt file.

2.5.4 Adding a New Board Configuration

Okay, so you have created a new board configuration directory. Now, how do you hook this board into the configuration system so that you can select with make menuconfig?

You will need modify the file configs/Kconfig. Let's look at the STM32F4-Discovery configuration in the Kconfig file and see how we would add a new board directory to the configuration. For this configuration let's say that you new board resides in the directory configs/myboard; It uses an MCU selected with CONFIG_ARCH_CHIP_MYMCU; and you want the board to be selected with CONFIG_ARCH_BOARD_MYBOARD. Then here is how you can clone the STM32F4-Discovery configuration in configs/Kconfig to support your new board configuration.

In configs/Kconfig for the stm32f4-discovery, you will see a configuration definition like this:

The above selects the STM32F4-Discovery board. The select lines say that the board has both LEDs and buttons and that the board can generate interrupts from the button presses. You can just copy the above configuration definition to a new location (notice that they the configurations are in alphabetical order). Then you should edit the configuration to support your board. The final configuration definition might look something like:

Later in the configs/Kconfig file, you will see a long, long string configuration with lots of defaults like this:

This logic will assign string value to a configuration variable called CONFIG_ARCH_BOARD that will name the directory where the board-specific files reside. In our case, these files reside in configs/myboard and we add the following to the long list of defaults (again in alphabetical order):

Now the build system knows where to find your board configuration!

And finally, add something like this near the bottom of configs/myboard:

This includes additional, board-specific configuration variable definitions in configs/myboard/Kconfig.

2.6 nuttx/crypto

This sub-directory holds the NuttX cryptographic sub-system.

2.7 nuttx/drivers

This directory holds architecture-independent device drivers.

2.8 nuttx/fs

This directory contains the NuttX file system. This file system is described below.

2.9 nuttx/graphics

This directory contains files for graphics/video support under NuttX.

2.10 nuttx/include

This directory holds NuttX header files. Standard header files file retained in can be included in the normal fashion:

Directory structure:

2.11 nuttx

This is a (almost) empty directory that has a holding place for generated static libraries. The NuttX build system generates a collection of such static libraries in this directory during the compile phaase. These libraries are then in a known place for the final link phase where they are accessed to generated the final binaries.

2.12 nuttx/libc

This directory holds a collection of standard libc-like functions with custom interfaces into NuttX.

Normally the logic in this file builds to a single library (libc.a). However, if NuttX is built as a separately compiled kernel (with CONFIG_BUILD_PROTECTED=y or CONFIG_BUILD_KERNEL=y), then the contents of this directory are built as two libraries: One for use by user programs (libuc.a) and one for use only within the <kernel> space (libkc.a).

These user/kernel space libraries (along with the sycalls of nuttx/syscall) are needed to support the two differing protection domains.

Directory structure:

2.13 nuttx/libxx

This directory holds a tiny, minimal standard std C++ that can be used to build some, simple C++ applications in NuttX.

2.14 nuttx/mm

This is the NuttX memory manager.

2.15 nuttx/net

This directory contains the implementation of the NuttX networking layer including internal socket APIs.

2.16 nuttx/sched

The files forming core of the NuttX RTOS reside here.

2.17 nuttx/syscall

If NuttX is built as a separately compiled kernel (with CONFIG_BUILD_PROTECTED=y or CONFIG_BUILD_KERNEL=y), then the contents of this directory are built. This directory holds a syscall interface that can be used for communication between user-mode applications and the kernel-mode RTOS.

2.18 nuttx/tools

This directory holds a collection of tools and scripts to simplify configuring, building and maintaining NuttX.

Refer to the README file in the tools directory for more information about the individual files. Some of these tools are discussed below as well in the discussion of configuring and building NuttX.

2.19 nuttx/wireless

This directory holds support for hardware-independent wireless support.

2.20 nuttx/Makefile

The top-level Makefile in the ${TOPDIR} directory contains all of the top-level control logic to build NuttX. Use of this Makefile to build NuttX is described below.

3.0 Configuring and Building

3.1 Configuring NuttX

Manual Configuration. Configuring NuttX requires only copying the board-specific configuration files into the top level directory which appears in the make files as the make variable, ${TOPDIR}. This could be done manually as follows:

Where <board-name> is the name of one of the sub-directories of the NuttX configs/ directory. This sub-directory name corresponds to one of the supported boards identified above. <config-dir> is the optional, specific configuration directory for the board. And <app-dir> is the location of the optional application directory.

Automated Configuration. There is a script that automates these steps. The following steps will accomplish the same configuration:

There is an alternative Windows batch file, configure.bat, that can be used instead of configure.sh in the windows native environment like:

See tools/README.txt for more information about these scripts.

If your application directory is not in the standard location (../apps or ../apps-<version>), then you should also specify the location of the application directory on the command line like:

Version Files. The NuttX build expects to find a version file located in the top-level NuttX build directory. That version file is called .version. The correct version file is installed in each versioned NuttX released. However, if you are working from an GIT snapshot, then there will be no version file. If there is no version file, the top-level Makefile will create a dummy .version file on the first make. This dummy version file will contain all zeroes for version information. If that is not what you want, they you should run the version.sh script to create a better .version file.

You can get help information from the version.sh script using the -h option. For example:

As an example, the following command will generate a version file for version 6.1 using the current GIT revision number:

The .version file is also used during the build process to create a C header file at include/nuttx/version.h that contains the same version information. That version file may be used by your C applications for, as an example, reporting version information.

Additional Configuration Steps. The remainder of configuration steps will be performed by ${TOPDIR}/Makefile the first time the system is built as described below.

3.2 Building NuttX

Building NuttX. Once NuttX has been configured as described above, it may be built as follows:

The ${TOPDIR} directory holds:

That directory also holds:

The setenv.sh contains Linux/Cygwin environmental settings that are needed for the build. The specific environmental definitions are unique for each board but should include, as a minimum, updates to the PATH variable to include the full path to the architecture-specific toolchain identified in Make.defs. The setenv.sh only needs to be source'ed at the beginning of a session. The system can be re-made subsequently by just typing make.

First Time Make. Additional configuration actions will be taken the first time that system is built. These additional steps include:

4.0 Architecture APIs

The file include/nuttx/arch.h identifies by prototype all of the APIs that must be provided by the architecture specific logic. The internal OS APIs that architecture-specific logic must interface with also also identified in include/nuttx/arch.h or in other header files.

4.1 Naming and Header File Conventions

4.2 APIs Exported by Architecture-Specific Logic to NuttX

4.2.1 up_initialize()

Function Prototype: void up_initialize(void);

Description. up_initialize() will be called once during OS initialization after the basic OS services have been initialized. The architecture specific details of initializing the OS will be handled here. Such things as setting up interrupt service routines, starting the clock, and registering device drivers are some of the things that are different for each processor and hardware platform.

up_initialize() is called after the OS initialized but before the init process has been started and before the libraries have been initialized. OS services and driver services are available.

4.2.2 up_idle()

Function Prototype: void up_idle(void);

Description. up_idle() is the logic that will be executed when their is no other ready-to-run task. This is processor idle time and will continue until some interrupt occurs to cause a context switch from the idle task.

Processing in this state may be processor-specific. e.g., this is where power management operations might be performed.

4.2.3 up_initial_state()

Function Prototype: void up_initial_state(FAR struct tcb_s *tcb);

Description. A new thread is being started and a new TCB has been created. This function is called to initialize the processor specific portions of the new TCB.

This function must setup the initial architecture registers and/or stack so that execution will begin at tcb->start on the next context switch.

This function may also need to set up processor registers so that the new thread executes with the correct privileges. If CONFIG_BUILD_PROTECTED or CONFIG_BUILD_KERNEL have been selected in the NuttX configuration, then special initialization may need to be performed depending on the task type specified in the TCB's flags field: Kernel threads will require kernel-mode privileges; User tasks and pthreads should have only user-mode privileges. If neither CONFIG_BUILD_PROTECTED nor CONFIG_BUILD_KERNEL have been selected, then all threads should have kernel-mode privileges.

4.2.4 up_create_stack()

Function Prototype: STATUS up_create_stack(FAR struct tcb_s *tcb, size_t stack_size, uint8_t ttype);

Description. Allocate a stack for a new thread and setup up stack-related information in the TCB.

The following TCB fields must be initialized:

Input Parameters:

4.2.5 up_use_stack()

Function Prototype: STATUS up_use_stack(FAR struct tcb_s *tcb, FAR void *stack, size_t stack_size);

Description. Setup up stack-related information in the TCB using pre-allocated stack memory. This function is called only from task_init() when a task or kernel thread is started (never for pthreads).

The following TCB fields must be initialized:

Input Parameters:

NOTE: Unlike up_stack_create() and up_stack_release, this function does not require the task type (ttype) parameter. The TCB flags will always be set to provide the task type to up_use_stack() if the information needs that information.

4.2.6 up_stack_frame()

Function Prototype: FAR void *up_stack_frame(FAR struct tcb_s *tcb, size_t frame_size);

Description. Allocate a stack frame in the TCB's stack to hold thread-specific data. This function may be called any time after up_create_stack() or up_use_stack() have been called but before the task has been started.

Thread data may be kept in the stack (instead of in the TCB) if it is accessed by the user code directly. This includes such things as argv[]. The stack memory is guaranteed to be in the same protection domain as the thread.

The following TCB fields will be re-initialized:

Input Parameters:

Returned Value: A pointer to bottom of the allocated stack frame. NULL will be returned on any failures. The alignment of the returned value is the same as the alignment of the stack itself

4.2.7 up_release_stack()

Function Prototype: void up_release_stack(FAR struct tcb_s *dtcb);

Description. A task has been stopped. Free all stack related resources retained int the defunct TCB.

Input Parameters:

4.2.8 up_unblock_task()

Function Prototype: void up_unblock_task(FAR struct tcb_s *tcb);

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.

This function is called only from the NuttX scheduling logic. Interrupts will always be disabled when this function is called.

Input Parameters:

4.2.9 up_block_task()

Function Prototype: void up_block_task(FAR struct tcb_s *tcb, tstate_t task_state);

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.

Input Parameters:

4.2.10 up_release_pending()

Function Prototype: void up_release_pending(void);

Description. When tasks become ready-to-run but cannot run because pre-emption is disabled, they are placed into a pending task list. This function releases and makes ready-to-run all of the tasks that have collected in the pending task list. This can cause a context switch if a new task is placed at the head of the ready to run list.

This function is called only from the NuttX scheduling logic when pre-emption is re-enabled. Interrupts will always be disabled when this function is called.

4.2.11 up_reprioritize_rtr()

Function Prototype: void up_reprioritize_rtr(FAR struct tcb_s *tcb, uint8_t priority);

Description. Called when the priority of a running or ready-to-run task changes and the reprioritization will cause a context switch. Two cases:

  1. The priority of the currently running task drops and the next task in the ready to run list has priority.
  2. An idle, ready to run task's priority has been raised above the the priority of the current, running task and it now has the priority.

This function is called only from the NuttX scheduling logic. Interrupts will always be disabled when this function is called.

Input Parameters:

4.2.12 _exit()

Function Prototype: void _exit(int status) noreturn_function;

Description. This function causes the currently executing task to cease to exist. This is a special case of task_delete().

Unlike other UP APIs, this function may be called directly from user programs in various states. The implementation of this function should disable interrupts before performing scheduling operations.

4.2.13 up_assert()

Function Prototype:
void up_assert(FAR const uint8_t *filename, int linenum);

Description. Assertions may be handled in an architecture-specific way.

4.2.14 up_schedule_sigaction()

Function Prototype: void up_schedule_sigaction(FAR struct tcb_s *tcb, sig_deliver_t sigdeliver);

Description. This function is called by the OS when one or more signal handling actions have been queued for execution. The architecture specific code must configure things so that the 'sigdeliver' callback is executed on the thread specified by 'tcb' as soon as possible.

This function may be called from interrupt handling logic.

This operation should not cause the task to be unblocked nor should it cause any immediate execution of sigdeliver. Typically, a few cases need to be considered:

  1. This function may be called from an interrupt handler During interrupt processing, all xcptcontext structures should be valid for all tasks. That structure should be modified to invoke sigdeliver() either on return from (this) interrupt or on some subsequent context switch to the recipient task.
  2. If not in an interrupt handler and the tcb is NOT the currently executing task, then again just modify the saved xcptcontext structure for the recipient task so it will invoke sigdeliver when that task is later resumed.
  3. If not in an interrupt handler and the tcb IS the currently executing task -- just call the signal handler now.

This API is NOT required if CONFIG_DISABLE_SIGNALS is defined.

4.2.15 up_allocate_heap()

Function Prototype: void up_allocate_heap(FAR void **heap_start, size_t *heap_size);

Description. This function will be called to dynamically set aside the heap region.

For the kernel build (CONFIG_BUILD_PROTECTED=y or CONFIG_BUILD_KERNEL=y) with both kernel- and user-space heaps (CONFIG_MM_KERNEL_HEAP=y), this function provides the size of the unprotected, user-space heap. If a protected kernel-space heap is provided, the kernel heap must be allocated (and protected) by an analogous up_allocate_kheap().

4.2.16 up_interrupt_context()

Function Prototype: bool up_interrupt_context(void)

Description. Return true if we are currently executing in the interrupt handler context.

4.2.17 up_disable_irq()

Function Prototype:

Description. Disable the IRQ specified by 'irq' On many architectures, there are three levels of interrupt enabling: (1) at the global level, (2) at the level of the interrupt controller, and (3) at the device level. In order to receive interrupts, they must be enabled at all three levels.

This function implements enabling of the device specified by 'irq' at the interrupt controller level if supported by the architecture (up_irq_save() supports the global level, the device level is hardware specific).

If the architecture does not support up_disable_irq, CONFIG_ARCH_NOINTC should be defined in the NuttX configuration file. Since this API cannot be supported on all architectures, it should be avoided in common implementations where possible.

4.2.18 up_enable_irq()

Function Prototype:

Description. This function implements disabling of the device specified by 'irq' at the interrupt controller level if supported by the architecture (up_irq_restore() supports the global level, the device level is hardware specific).

If the architecture does not support up_disable_irq, CONFIG_ARCH_NOINTC should be defined in the NuttX configuration file. Since this API cannot be supported on all architectures, it should be avoided in common implementations where possible.

4.2.19 up_prioritize_irq()

Function Prototype:

Description. Set the priority of an IRQ.

If the architecture supports up_enable_irq, CONFIG_ARCH_IRQPRIO should be defined in the NuttX configuration file. Since this API cannot be supported on all architectures, it should be avoided in common implementations where possible.

4.2.20 up_putc()

Function Prototype: int up_putc(int ch);

Description. This is a debug interface exported by the architecture-specific logic. Output one character on the console

4.3 System Time and Clock

4.3.1 Basic System Timer

System Timer In most implementations, system time is provided by a timer interrupt. That timer interrupt runs at rate determined by CONFIG_USEC_PER_TICK (default 10000 microseconds or 100Hz. If CONFIG_SCHED_TICKLESS is selected, the default is 100 microseconds). The timer generates an interrupt each CONFIG_USEC_PER_TICK microseconds and increments a counter called g_system_timer. g_system_timer then provides a time-base for calculating up-time and elapsed time intervals in units of CONFIG_USEC_PER_TICK. The range of g_system_timer is, by default, 32-bits. However, if the MCU supports type long long and CONFIG_SYSTEM_TIME16 is selected, a 64-bit system timer will be supported instead.

System Timer Accuracy On many system, the exact timer interval specified by CONFIG_USEC_PER_TICK cannot be achieved due to limitations in frequencies or in dividers. As a result, the time interval specified by CONFIG_USEC_PER_TICK may only be approximate and there may be small errors in the apparent up-time time. These small errors, however, will accumulate over time and after a long period of time may have an unacceptably large error in the apparent up-time of the MCU.

If the timer tick period generated by the hardware is not exactly CONFIG_USEC_PER_TICK and if there you require accurate up-time for the MCU, then there are measures that you can take:

Delta-Sigma Modulation Example. Consider this case: The system timer is a count-up timer driven at 32.768KHz. There are dividers that can be used, but a divider of one yields the highest accuracy. This counter counts up until the count equals a match value, then a timer interrupt is generated. The desire frequency is 100Hz (CONFIG_USEC_PER_TICK is 10000).

This exact frequency of 100Hz cannot be obtained in this case. In order to obtain that exact frequency a match value of 327.68 would have to be provided. The closest integer value is 328 but the ideal match value is between 327 and 328. The closest value, 328, would yield an actual timer frequency of 99.9Hz! That will may cause significant timing errors in certain usages.

Use of Delta-Sigma Modulation can eliminate this error in the long run. Consider this example implementation:

  1. Initially an accumulator is zero an the match value is programmed to 328:
  2. On each timer interrupt, accumulator is updated with difference that, in this reflects, 100* the error in interval that just passed. So on the first timer interrupt, the accumulator would be updated like:
  3. And on that same timer interrupt a new match value would be programmed:

In this way, the timer interval is controlled from interrupt-to-interrupt to produce an average frequency of exactly 100Hz.

4.3.2 Hardware

To enable hardware module use the following configuration options:

which requires the following base functions to read and set time:

4.3.3 System Tick and Time

The system tick is represented by::

Running at rate of system base timer, used for time-slicing, and so forth.

If hardware RTC is present (CONFIG_RTC) and and high-resolution timing is enabled (CONFIG_RTC_HIRES), then after successful initialization variables are overridden by calls to up_rtc_gettime() which is running continuously even in power-down modes.

In the case of CONFIG_RTC_HIRES is set the g_system_timer keeps counting at rate of a system timer, which however, is disabled in power-down mode. By comparing this time and RTC (actual time) one may determine the actual system active time. To retrieve that variable use:

4.3.4 Tickless OS

4.3.4.1 Tickless Overview

Default System Timer. By default, a NuttX configuration uses a periodic timer interrupt that drives all system timing. The timer is provided by architecture-specific code that calls into NuttX at a rate controlled by CONFIG_USEC_PER_TICK. The default value of CONFIG_USEC_PER_TICK is 10000 microseconds which corresponds to a timer interrupt rate of 100 Hz.

On each timer interrupt, NuttX does these things:

What is wrong with this default system timer? Nothing really. It is reliable and uses only a small fraction of the CPU band width. But we can do better. Some limitations of default system timer are, in increasing order of importance:

Tickless OS. The so-called Tickless OS provides one solution to issue. The basic concept here is that the periodic, timer interrupt is eliminated and replaced with a one-shot, interval timer. It becomes event driven instead of polled: The default system timer is a polled design. On each interrupt, the NuttX logic checks if it needs to do anything and, if so, it does it.

Using an interval timer, one can anticipate when the next interesting OS event will occur, program the interval time and wait for it to fire. When the interval time fires, then the scheduled activity is performed.

4.3.4.2 Tickless Platform Support

In order to use the Tickless OS, one must provide special support from the platform-specific code. Just as with the default system timer, the platform-specific code must provide the timer resources to support the OS behavior. Currently these timer resources are only provided on a few platforms. An example implementation is for the simulation is at nuttx/arch/sim/src/up_tickless.c. There is another example for the Atmel SAMA5 at nuttx/arch/arm/src/sama5/sam_tickless.c. These paragraphs will explain how to provide the Tickless OS support to any platform.

4.3.4.3 Tickless Configuration Options

4.3.4.4 Tickless Imported Intefaces

The interfaces that must be provided by the platform specified code are defined in include/nuttx/arch.h, listed below, and summarized in the following paragraphs:

The tickless option can be supported either via a simple interval timer (plus elapsed time) or via an alarm. The interval timer allows programming events to occur after an interval. With the alarm, you can set a time in* the future and get an event when that alarm goes off.

If CONFIG_SCHED_TICKLESS_ALARM is defined, then the platform code must provide the following:

If CONFIG_SCHED_TICKLESS_ALARM is notdefined, then the platform code must provide the following verify similar functions:

Note that a platform-specific implementation would probably require two hardware timers: (1) A interval timer to satisfy the requirements of up_timer_start() and up_timer_cancel(), and a (2) a counter to handle the requirement of up_timer_gettime(). Ideally, both timers would run at the rate determined by CONFIG_USEC_PER_TICK (and certainly never slower than that rate).

Since timers are a limited resource, the use of two timers could be an issue on some systems. The job could be done with a single timer if, for example, the single timer were kept in a free-running at all times. Some timer/counters have the capability to generate a compare interrupt when the timer matches a comparison value but also to continue counting without stopping. If your hardware supports such counters, one might used the CONFIG_SCHED_TICKLESS_ALARM option and be able to simply set the comparison count at the value of the free running timer PLUS the desired delay. Then you could have both with a single timer: An alarm and a free-running counter with the same timer!

In addition to these imported interfaces, the RTOS will export the following interfaces for use by the platform-specific interval timer implementation:

4.3.4.4.1 up_timer_initialize()

Function Prototype:

Description:

Input Parameters:

Returned Value:

Assumptions:

4.3.4.4.2 up_timer_gettime()

Function Prototype:

Description:

Return the elapsed time since power-up (or, more correctly, since up_timer_initialize() was called). This function is functionally equivalent to clock_gettime() for the clock ID CLOCK_MONOTONIC. This function provides the basis for reporting the current time and also is used to eliminate error build-up from small errors in interval time calculations.

Input Parameters:

Returned Value:

Assumptions:

4.3.4.4.3 up_alarm_cancel()

Function Prototype:

Description:

Cancel the alarm and return the time of cancellation of the alarm. These two steps need to be as nearly atomic as possible. sched_timer_expiration() will not be called unless the alarm is restarted with up_alarm_start(). If, as a race condition, the alarm has already expired when this function is called, then time returned is the current time.

Input Parameters:

Returned Value:

Assumptions:

4.3.4.4.4 up_alarm_start()

Function Prototype:

Description:

Start the alarm. sched_timer_expiration() will be called when the alarm occurs (unless up_alaram_cancel is called to stop it).

Input Parameters:

Returned Value:

Assumptions:

4.3.4.4.5 up_timer_cancel()

Function Prototype:

Description:

Cancel the interval timer and return the time remaining on the timer. These two steps need to be as nearly atomic as possible. sched_timer_expiration() will not be called unless the timer is restarted with up_timer_start(). If, as a race condition, the timer has already expired when this function is called, then that pending interrupt must be cleared so that sched_timer_expiration() is not called spuriously and the remaining time of zero should be returned.

Input Parameters:

Returned Value:

Assumptions:

4.3.4.4.6 up_timer_start()

Function Prototype:

Description:

Start the interval timer. sched_timer_expiration() will be called at the completion of the timeout (unless up_timer_cancel() is called to stop the timing).

Input Parameters:

Returned Value:

Assumptions:

4.3.5 Watchdog Timer Interfaces

NuttX provides a general watchdog timer facility. This facility allows the NuttX user to specify a watchdog timer function that will run after a specified delay. The watchdog timer function will run in the context of the timer interrupt handler. Because of this, a limited number of NuttX interfaces are available to he watchdog timer function. However, the watchdog timer function may use mq_send(), sigqueue(), or kill() to communicate with NuttX tasks.

4.3.5.1 wd_create/wd_static

Function Prototype:

    #include <nuttx/wdog.h>
    WDOG_ID wd_create(void);
    void wd_static(FAR struct wdog_s *wdog);

Description: The wd_create() function will create a timer by allocating the appropriate resources for the watchdog. The wd_create() function returns a pointer to a fully initialized, dynamically allocated struct wdog_s instance (which is typedef'ed as WDOG_ID);

wd_static() performs the equivalent initialization of a statically allocated struct wdog_s instance. No allocation is performed in this case. The initializer definition, WDOG_INITIALIZER is also available for initialization of static instances of struct wdog_s. NOTE: wd_static() is also implemented as a macro definition.

Input Parameters: None.

Returned Value:

Assumptions/Limitations:

POSIX Compatibility: This is a NON-POSIX interface. VxWorks provides the following comparable interface:

    WDOG_ID wdCreate (void);

Differences from the VxWorks interface include:

4.3.5.2 wd_delete

Function Prototype:

    #include <nuttx/wdog.h>
    int wd_delete(WDOG_ID wdog);

Description: The wd_delete function will deallocate a watchdog timer previously allocated via wd_create(). The watchdog timer will be removed from the timer queue if has been started.

This function need not be called for statically allocated timers (but it is not harmful to do so).

Input Parameters:

Returned Value:

Assumptions/Limitations: It is the responsibility of the caller to assure that the watchdog is inactive before deleting it.

POSIX Compatibility: This is a NON-POSIX interface. VxWorks provides the following comparable interface:

    STATUS wdDelete (WDOG_ID wdog);

Differences from the VxWorks interface include:

4.3.5.3 wd_start

Function Prototype:

    #include <nuttx/wdog.h>
    int wd_start(WDOG_ID wdog, int delay, wdentry_t wdentry,
                 int argc, ....);

Description: This function adds a watchdog to the timer queue. The specified watchdog function will be called from the interrupt level after the specified number of ticks has elapsed. Watchdog timers may be started from the interrupt level.

Watchdog times execute in the context of the timer interrupt handler.

Watchdog timers execute only once.

To replace either the timeout delay or the function to be executed, call wd_start again with the same wdog; only the most recent wd_start() on a given watchdog ID has any effect.

Input Parameters:

Returned Value:

Assumptions/Limitations: The watchdog routine runs in the context of the timer interrupt handler and is subject to all ISR restrictions.

POSIX Compatibility: This is a NON-POSIX interface. VxWorks provides the following comparable interface:

    STATUS wdStart (WDOG_ID wdog, int delay, FUNCPTR wdentry, int parameter);

Differences from the VxWorks interface include:

4.3.5.4 wd_cancel

Function Prototype:

    #include <nuttx/wdog.h>
    int wd_cancel(WDOG_ID wdog);

Description: This function cancels a currently running watchdog timer. Watchdog timers may be canceled from the interrupt level.

Input Parameters:

Returned Value:

Assumptions/Limitations:

POSIX Compatibility: This is a NON-POSIX interface. VxWorks provides the following comparable interface:

    STATUS wdCancel (WDOG_ID wdog);

4.3.5.5 wd_gettime

Function Prototype:

    #include <nuttx/wdog.h>
    Sint wd_gettime(WDOG_ID wdog);

Description: This function returns the time remaining before the specified watchdog expires.

Input Parameters:

Returned Value: The time in system ticks remaining until the watchdog time expires. Zero means either that wdog is not valid or that the wdog has already expired.

4.3.5.6 Watchdog Timer Callback

When a watchdog expires, the callback function with this type is called:

    typedef void (*wdentry_t)(int argc, ...);

Where argc is the number of wdparm_t type arguments that follow.

The arguments are passed as scalar wdparm_t values. For systems where the sizeof(pointer) < sizeof(uint32_t), the following union defines the alignment of the pointer within the uint32_t. For example, the SDCC MCS51 general pointer is 24-bits, but uint32_t is 32-bits (of course).

We always have sizeof(pointer) <= sizeof(uintptr_t) by definition.

4.4 Work Queues

Work Queues. NuttX provides work queues. Work queues are threads that service a queue of work items to be performed. They are useful for off-loading work to a different threading context, for delayed processing, or for serializing activities.

4.4.1 Classes of Work Queues

Classes of Work Queues. There are three different classes of work queues, each with different properties and intended usage. These class of work queues along with the the common work queue interface are described in the following paragraphs.

4.4.1.1 High Priority Kernel Work queue

High Priority Kernel Work queue. The dedicated high-priority work queue is intended to handle delayed processing from interrupt handlers. This work queue is required for some drivers but, if there are no complaints, can be safely disabled. The high priority worker thread also performs garbage collection -- completing any delayed memory deallocations from interrupt handlers. If the high-priority worker thread is disabled, then that clean up will be performed either by (1) the low-priority worker thread, if enabled, and if not (2) the IDLE thread instead (which runs at the lowest of priority and may not be appropriate if memory reclamation is of high priority)

Device Driver Bottom Half. The higher priority worker thread is intended to serve as the bottom half for device drivers. As a consequence it must run at a very high, fixed priority rivalling the priority of the interrupt handler itself. Typically, the high priority work queue should be the highest priority thread in your system (the default priority is 224).

Compared to the Low Priority Kernel Work Queue. For less critical, lower priority, application oriented worker thread support, consider enabling the lower priority work queue. The lower priority work queue runs at a lower priority, of course, but has the added advantage that it supports priority inheritance (if <config> CONFIG_PRIORITY_INHERITANCE is also selected): The priority of the lower priority worker thread can then be adjusted to match the highest priority client.

Configuration Options.

Common Configuration Options. These options apply to all work queues:

4.4.1.2 Low Priority Kernel Work Queue

Low Priority Kernel Work Queue. This lower priority work queue is better suited for more extended, application oriented processing such as file system clean-up, memory garbage collection and asynchronous I/O operations.

Compared to the High Priority Work Queue. The lower priority work queue runs at a lower priority than the high priority work queue, of course, and so is inapproperiate to serve as a driver bottom half. The lower priority work queue has the other advantages, however, that make it better suited for some tasks:

Configuration Options.

4.4.1.3 User-Mode Work Queue

Work Queue Accessibility. The high- and low-priority worker threads are kernel-mode threads. In the normal, flat NuttX build, these work queues are are useful to application code and may be shared. However, in the NuttX protected and kernel build modes, kernel mode code is isolated and cannot be accessed from user-mode code.

User-Mode Work Queue. if either CONFIG_BUILD_PROTECTED or CONFIG_BUILD_KERNEL are selected, then the option to enable a special user-mode work queue is enable. The interface to the user-mode work queue is identical to the interface to the kernel-mode work queues and the user-mode work queue is functionally equivalent to the high priority work queue. It differs in that its implementation does not depend on internal, kernel-space facilities.

Configuration Options.

4.4.2 Common Work Queue Interfaces

4.4.2.1 Work Queue IDs

Work queue IDs. All work queues use the identical interface functions (at least identical in terms of the function signature). The first parameter passed to the work queue interface function identifies the work queue:

Kernel-Mode Work Queue IDs:

User-Mode Work Queue IDs:

4.4.2.2 Work Queue Interface Types

4.4.2.3 Work Queue Interfaces

4.4.2.3.1 work_queue()

Function Prototype:

Description. Queue work to be performed at a later time. All queued work will be performed on the worker thread of execution (not the caller's).

The work structure is allocated by caller, but completely managed by the work queue logic. The caller should never modify the contents of the work queue structure; the caller should not call work_queue() again until either (1) the previous work has been performed and removed from the queue, or (2) work_cancel() has been called to cancel the work and remove it from the work queue.

Input Parameters:

Returned Value:

4.4.2.3.2 work_cancel()

Function Prototype: #include <nuttx/wqueue.h> int work_cancel(int qid, FAR struct work_s *work);

Description. Cancel previously queued work. This removes work from the work queue. After work has been cancelled, it may be re-queue by calling work_queue() again.

Input Parameters:

Returned Value:

4.4.2.3.3 work_signal()

Function Prototype: #include <nuttx/wqueue.h> int work_signal(int qid);

Description. Signal the worker thread to process the work queue now. This function is used internally by the work logic but could also be used by the user to force an immediate re-assessment of pending work.

Input Parameters:

Returned Value:

4.4.2.3.4 work_available()

Function Prototype:

Description.

Input Parameters: Check if the work structure is available.

Returned Value:

4.4.2.3.5 work_usrstart()

Function Prototype:

Description. The function is only available as a user interface in the kernel-mode build. In the flat build, there is no user-mode work queue; in the protected mode, the user-mode work queue will automatically be started by the OS start-up code. But in the kernel mode, each user process will be required to start is own, private instance of the user-mode work thread using this interface.

Input Parameters: None

Returned Value:

4.4.2.3.6 lpwork_boostpriority()

Function Prototype:

Description. Called by the work queue client to assure that the priority of the low-priority worker thread is at least at the requested level, reqprio. This function would normally be called just before calling work_queue().

Input Parameters:

Returned Value: None

4.4.2.3.7 lpwork_restorepriority()

Function Prototype:

Description. This function is called to restore the priority after it was previously boosted. This is often done by client logic on the worker thread when the scheduled work completes. It will check if we need to drop the priority of the worker thread.

Input Parameters:

Returned Value: None

4.5 Address Environments

CPUs that support memory management units (MMUs) may provide address environments within which tasks and their child threads execute. The configuration indicates the CPUs ability to support address environments by setting the configuration variable CONFIG_ARCH_HAVE_ADDRENV=y. That will enable the selection of the actual address evironment support which is indicated by the selection of the configuration variable CONFIG_ARCH_ADDRENV=y. These address environments are created only when tasks are created via exec() or exec_module() (see include/nuttx/binfmt/binfmt.h).

When CONFIG_ARCH_ADDRENV=y is set in the board configuration, the CPU-specific logic must provide a set of interfaces as defined in the header file include/nuttx/arch.h. These interfaces are listed below and described in detail in the following paragraphs.

The CPU-specific logic must provide two categories in interfaces:

  1. Binary Loader Support. These are low-level interfaces used in binfmt/ to instantiate tasks with address environments. These interfaces all operate on type group_addrenv_t which is an abstract representation of a task group's address environment and the type must be defined inarch/arch.h if CONFIG_ARCH_ADDRENV is defined. These low-level interfaces include:

  2. Tasking Support. Other interfaces must be provided to support higher-level interfaces used by the NuttX tasking logic. These interfaces are used by the functions in sched/ and all operate on the task group which as been assigned an address environment by up_addrenv_clone().

  3. Dynamic Stack Support. CONFIG_ARCH_STACK_DYNAMIC=y indicates that the user process stack resides in its own address space. This option is also required if CONFIG_BUILD_KERNEL and CONFIG_LIBC_EXECFUNCS are selected. Why? Because the caller's stack must be preserved in its own address space when we instantiate the environment of the new process in order to initialize it.

    NOTE: The naming of the CONFIG_ARCH_STACK_DYNAMIC selection implies that dynamic stack allocation is supported. Certainly this option must be set if dynamic stack allocation is supported by a platform. But the more general meaning of this configuration environment is simply that the stack has its own address space.

    If CONFIG_ARCH_STACK_DYNAMIC=y is selected then the platform specific code must export these additional interfaces:

  4. If CONFIG_ARCH_KERNEL_STACK is selected, then each user process will have two stacks: (1) a large (and possibly dynamic) user stack and (2) a smaller kernel stack. However, this option is required if both CONFIG_BUILD_KERNEL and CONFIG_LIBC_EXECFUNCS are selected. Why? Because when we instantiate and initialize the address environment of the new user process, we will temporarily lose the address environment of the old user process, including its stack contents. The kernel C logic will crash immediately with no valid stack in place.

    If CONFIG_ARCH_KERNEL_STACK=y is selected then the platform specific code must export these additional interfaces:

4.5.1 up_addrenv_create()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.2 up_addrenv_destroy()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.3 up_addrenv_vtext()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.4 up_addrenv_vdata()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.5 up_addrenv_heapsize()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.6 up_addrenv_select()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.7 up_addrenv_restore()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.8 up_addrenv_clone()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.9 up_addrenv_attach()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.10 up_addrenv_detach()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.11 up_addrenv_ustackalloc()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.12 up_addrenv_ustackfree()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.13 up_addrenv_vustack()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.14 up_addrenv_ustackselect()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.15 up_addrenv_kstackalloc()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.5.16 up_addrenv_kstackfree()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.6 boardctl() Application Interface

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.7 Symmetric Multiprocssing (SMP) Application Interface

According to Wikipedia: "Symmetric multiprocessing (SMP) involves a symmetric multiprocessor system hardware and software architecture where two or more identical processors connect to a single, shared main memory, have full access to all I/O devices, and are controlled by a single operating system instance that treats all processors equally, reserving none for special purposes. Most multiprocessor systems today use an SMP architecture. In the case of multi-core processors, the SMP architecture applies to the cores, treating them as separate processors.

"SMP systems are tightly coupled multiprocessor systems with a pool of homogeneous processors running independently, each processor executing different programs and working on different data and with capability of sharing common resources (memory, I/O device, interrupt system and so on) and connected using a system bus or a crossbar."

For a technical description of the NuttX implementation of SMP, see the NuttX SMP Wiki Page.

None

4.7.1 up_testset()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.7.2 up_cpu_index()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.7.3 up_cpu_start()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.7.4 up_cpu_pause()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.7.5 up_cpu_resume()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.8 APIs Exported by NuttX to Architecture-Specific Logic

These are standard interfaces that are exported by the OS for use by the architecture specific logic.

4.8.1 os_start()

To be provided

4.8.2 OS List Management APIs

To be provided

4.8.3 sched_process_timer()

Function Prototype:

Description. This function handles system timer events. The timer interrupt logic itself is implemented in the architecture specific code, but must call the following OS function periodically -- the calling interval must be MSEC_PER_TICK.

4.8.4 sched_timer_expiration()

Function Prototype:

Description:

Description: if CONFIG_SCHED_TICKLESS is defined, then this function is provided by the RTOS base code and called from platform-specific code when the interval timer used to implemented the tick-less OS expires.

Input Parameters:

Returned Value:

Assumptions:

4.8.5 sched_alaram_expiration()

Function Prototype:

Description:

Description: if CONFIG_SCHED_TICKLESS is defined, then this function is provided by the RTOS base code and called from platform-specific code when the interval timer used to implemented the tick-less OS expires.

Input Parameters:

Returned Value:

Assumptions:

4.8.6 irq_dispatch()

Function Prototype: void irq_dispatch(int irq, FAR void *context);

Description. This function must be called from the architecture- specific logic in order to display an interrupt to the appropriate, registered handling logic.

4.9 Shared Memory

Shared memory interfaces are only available with the NuttX kernel build (CONFIG_BUILD_KERNEL=y). These interfaces support user memory regions that can be shared between multiple user processes. The user interfaces are provided in the standard header file include/sys/shm.h>. All logic to support shared memory is implemented within the NuttX kernel with the exception of two low-level functions that are require to configure the platform-specific MMU resources. Those interfaces are described below:

4.9.1 up_shmat()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.9.2 up_shmdt()

Function Prototype:

Description:

Input Parameters:

Returned Value:

4.10 On-Demand Paging

The NuttX On-Demand Paging feature permits embedded MCUs with some limited RAM space to execute large programs from some non-random access media. If the platform meets certain requirements, then NuttX can provide on-demand paging: It can copy .text from the large program in non-volatile media into RAM as needed to execute a huge program from the small RAM. Design and porting issues for this feature are discussed in a separate document. Please see the NuttX Demand Paging design document for further information.

4.11 LED Support

A board architecture may or may not have LEDs. If the board does have LEDs, then most architectures provide similar LED support that is enabled when CONFIG_ARCH_LEDS is selected in the NuttX configuration file. This LED support is part of architecture-specific logic and is not managed by the core NuttX logic. However, the support provided by each architecture is sufficiently similar that it can be documented here.

4.11.1 Header Files

LED-related definitions are provided in two header files:

4.11.2 LED Definitions

The implementation of LED support is very specific to a board architecture. Some boards have several LEDS, others have only one or two. Some have none. Others LED matrices and show alphanumeric data, etc. The NuttX logic does not refer to specific LEDS, rather, it refers to an event to be shown on the LEDS in whatever manner is appropriate for the board; the way that this event is presented depends upon the hardware available on the board.

The model used by NuttX is that the board can show 8 events defined as follows in <board-name>/include/board.h:

The specific value assigned to each pre-processor variable can be whatever makes the implementation easiest for the board logic. The meaning associated with each definition is as follows:

4.11.3 Common LED interfaces

The include/nuttx/board.h has declarations like:

Where:

5.0 NuttX File System

Overview. NuttX includes an optional, scalable file system. This file-system may be omitted altogether; NuttX does not depend on the presence of any file system.

Pseudo Root File System. Or, a simple in-memory, pseudo file system can be enabled. This simple file system can be enabled setting the CONFIG_NFILE_DESCRIPTORS option to a non-zero value (see Appendix A). This is an in-memory file system because it does not require any storage medium or block driver support. Rather, file system contents are generated on-the-fly as referenced via standard file system operations (open, close, read, write, etc.). In this sense, the file system is pseudo file system (in the same sense that the Linux /proc file system is also referred to as a pseudo file system).

Any user supplied data or logic can be accessed via the pseudo-file system. Built in support is provided for character and block drivers in the /dev pseudo file system directory.

Mounted File Systems The simple in-memory file system can be extended my mounting block devices that provide access to true file systems backed up via some mass storage device. NuttX supports the standard mount() command that allows a block driver to be bound to a mountpoint within the pseudo file system and to a file system. At present, NuttX supports the standard VFAT and ROMFS file systems, a special, wear-leveling NuttX FLASH File System (NXFFS), as well as a Network File System client (NFS version 3, UDP).

Comparison to Linux From a programming perspective, the NuttX file system appears very similar to a Linux file system. However, there is a fundamental difference: The NuttX root file system is a pseudo file system and true file systems may be mounted in the pseudo file system. In the typical Linux installation by comparison, the Linux root file system is a true file system and pseudo file systems may be mounted in the true, root file system. The approach selected by NuttX is intended to support greater scalability from the very tiny platform to the moderate platform.

6.0 NuttX Device Drivers

NuttX supports a variety of device drivers including:

These different device driver types are discussed in the following paragraphs. Note: device driver support requires that the in-memory, pseudo file system is enabled by setting the CONFIG_NFILE_DESCRIPTORS in the NuttX configuration file to a non-zero value.

6.1 Character Device Drivers

Character device drivers have these properties:

6.1.1 Serial Device Drivers

6.1.1 Touchscreen Device Drivers

NuttX supports a two-part touchscreen driver architecture.

  1. An "upper half", generic driver that provides the common touchscreen interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level touchscreen controls to implement the touchscreen functionality.

Files supporting the touchscreen controller (TSC) driver can be found in the following locations:

6.1.2 Analog (ADC/DAC) Drivers

The NuttX analog drivers are split into two parts:

  1. An "upper half", generic driver that provides the common analog interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level controls to implement the analog functionality.

6.1.3.1 ADC Drivers

6.1.3.2 DAC Drivers

6.1.4 PWM Drivers

For the purposes of this driver, a PWM device is any device that generates periodic output pulses of controlled frequency and pulse width. Such a device might be used, for example, to perform pulse-width modulated output or frequency/pulse-count modulated output (such as might be needed to control a stepper motor).

The NuttX PWM driver is split into two parts:

  1. An "upper half", generic driver that provides the common PWM interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the PWM functionality.

Files supporting PWM can be found in the following locations:

6.1.5 CAN Drivers

NuttX supports only a very low-level CAN driver. This driver supports only the data exchange and does not include any high-level CAN protocol. The NuttX CAN driver is split into two parts:

  1. An "upper half", generic driver that provides the common CAN interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the CAN functionality.

Files supporting CAN can be found in the following locations:

6.1.6 Quadrature Encoder Drivers

NuttX supports a low-level, two-part Quadrature Encoder driver.

  1. An "upper half", generic driver that provides the common Quadrature Encoder interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the Quadrature Encoder functionality.

Files supporting the Quadrature Encoder can be found in the following locations:

6.1.7 Timer Drivers

NuttX supports a low-level, two-part timer driver.

  1. An "upper half", generic driver that provides the common timer interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the timer functionality.

Files supporting the timer driver can be found in the following locations:

6.1.8 RTC Drivers

NuttX supports a low-level, two-part RealTime Clock (RTC) driver.

  1. An "upper half", generic driver that provides the common RTC interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the RTC functionality.

Files supporting the RTC driver can be found in the following locations:

6.1.9 Watchdog Timer Drivers

NuttX supports a low-level, two-part watchdog timer driver.

  1. An "upper half", generic driver that provides the common watchdog timer interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the watchdog timer functionality.

Files supporting the watchdog timer driver can be found in the following locations:

6.1.10 Keyboard/Keypad Drivers

Keypads vs. Keyboards Keyboards and keypads are really the same devices for NuttX. A keypad is thought of as simply a keyboard with fewer keys.

Special Commands. In NuttX, a keyboard/keypad driver is simply a character driver that may have an (optional) encoding/decoding layer on the data returned by the character driver. A keyboard may return simple text data (alphabetic, numeric, and punctuation) or control characters (enter, control-C, etc.) when a key is pressed. We can think about this the "normal" keyboard data stream. However, in addition, most keyboards support actions that cannot be represented as text or control data. Such actions include things like cursor controls (home, up arrow, page down, etc.), editing functions (insert, delete, etc.), volume controls, (mute, volume up, etc.) and other special functions. In this case, some special encoding may be required to multiplex the normal text data and special command key press data streams.

Key Press and Release Events Sometimes the time that a key is released is needed by applications as well. Thus, in addition to normal and special key press events, it may also be necessary to encode normal and special key release events.

Encoding/Decoding Layer. An optional encoding/decoding layer can be used with the basic character driver to encode the keyboard events into the text data stream. The function interfaces that comprise that encoding/decoding layer are defined in the header file include/nuttx/input/kbd_code.h. These functions provide an matched set of (a) driver encoding interfaces, and (b) application decoding interfaces.

  1. Driver Encoding Interfaces. These are interfaces used by the keyboard/keypad driver to encode keyboard events and data.

  2. Application Decoding Interfaces. These are user interfaces to decode the values returned by the keyboard/keypad driver.

I/O Streams. Notice the use of the abstract I/O streams in these interfaces. These stream interfaces are defined in include/nuttx/streams.h.

6.2 Block Device Drivers

Block device drivers have these properties:

6.3 Specialized Device Drivers

All device drivers that are accessible to application logic are either: (1) Character device drivers that can be accessed via the standard driver operations (open(), close(), read(), write(), etc.), or (2) block drivers that can be accessing only as part of mounting a file system or other special use cases as described in the preceding paragraph.

In addition to this, there are also specialized "drivers" that can be used only within the OS logic itself and are not accessible to application logic. These specialized drivers are discussed in the following paragraphs.

6.3.1 Ethernet Device Drivers

6.3.2 SPI Device Drivers

6.3.3 I2C Device Drivers

6.3.4 Frame Buffer Drivers

6.3.5 LCD Drivers

6.3.6 Memory Technology Device Drivers

6.3.7 SDIO Device Drivers

6.3.8 USB Host-Side Drivers

6.3.9 USB Device-Side Drivers

6.4 SYSLOG

6.4.1 SYSLOG Interfaces

6.4.1.1 Standard SYSLOG Interfaces

The NuttX SYSLOG is an architecture for getting debug and status information from the system. The syslogging interfaces are defined in the header file include/syslog.h. The primary interface to SYSLOG sub-system is the function syslog() and, to a lesser extent, its companion vsyslog():

6.4.1.2 syslog() and vsyslog()

Function Prototypes:

Description: syslog() generates a log message. The priority argument is formed by ORing the facility and the level values (see include/syslog.h). The remaining arguments are a format, as in printf() and any arguments to the format.

The NuttX implementation does not support any special formatting characters beyond those supported by printf().

The function vsyslog() performs the same task as syslog() with the difference that it takes a set of arguments which have been obtained using the stdarg variable argument list macros.

6.4.1.3 setlogmask()

The additional setlogmask() interface can use use to filter SYSLOG output:

Function Prototype:

Description: The setlogmask() function sets the logmask and returns the previous mask. If the mask argument is zerio, the current logmask is not modified.

The SYSLOG priorities are: LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, and LOG_DEBUG. The bit corresponding to a priority p is LOG_MASK(p); LOG_UPTO(p) provides the mask of all priorities in the above list up to and including p.

Per OpenGroup.org "If the maskpri argument is 0, the current log mask is not modified." In this implementation, the value zero is permitted in order to disable all SYSLOG levels.

REVISIT: Per POSIX the SYSLOG mask should be a per-process value but in NuttX, the scope of the mask is dependent on the nature of the build:

The above are all standard interfaces as defined at OpenGroup.org. Those interfaces are available for use by application software. The remaining interfaces discussed in this section are non-standard, OS-internal interfaces.

6.4.1.4 Debug Interfaces

In NuttX, syslog output is really synonymous to debug output and, herefore, the debugging interface macros defined in the header file include/debug.h are also syslogging interfaces. Those macros are simply wrappers around syslog(). The debugging interfaces differ from the syslog interfaces in that:

Each debug macro has a base name that represents the priority and a prefix that represents the sub-system. Each macro is individually initialized by both priority and sub-system. For example, uerr() is the macro used for error level messages from the USB subsystem and is enabled with CONFIG_DEBUG_USB_ERROR.

The base debug macro names, their priority, and configuration variable are summarized below:

6.4.2 SYSLOG Channels

6.4.2.1 SYSLOG Channel Interfaces

In the NuttX SYSLOG implementation, the underlying device logic the supports the SYSLOG output is referred to as a SYSLOG channel. Each SYSLOG channel is represented by an interface defined in include/nuttx/syslog/syslog.h:

The channel interface is instantiated by calling syslog_channel():

6.4.2.1 syslog_channel()

Function Prototype:

Description: Configure the SYSLOG function to use the provided channel to generate SYSLOG output.

syslog_channel() is a non-standard, internal OS interface and is not available to applications. It may be called numerous times as necessary to change channel interfaces. By default, all system log output goes to console (/dev/console).

Input Parmeters:

Returned Value:

6.4.2.2 SYSLOG Channel Initialization

The initial, default SYSLOG channel is established with statically initialized global variables so that some level of SYSLOG output may be available immediately upon reset. This initialized data is in the file drivers/syslog/syslog_channel.c. The initial SYSLOG capability is determined by the selected SYSLOG channel:

The syslog channel device is initialized when the bring-up logic calls syslog_initialize():

6.4.2.3 syslog_initialize()

Function Prototype:

Description: One power up, the SYSLOG facility is non-existent or limited to very low-level output. This function is called later in the initialization sequence after full driver support has been initialized. It installs the configured SYSLOG drivers and enables full SYSLOG capability.

This function performs these basic operations:

Input Parameters:

Returned Value: Zero (OK) is returned on success; a negated errno value is returned on any failure.

Different types of SYSLOG devices have different OS initialization requirements. Some are available immediately at reset, some are available after some basic OS initialization, and some only after OS is fully initialized. In order to satisfy these different initialization requirements, syslog_initialize() is called twice from the boot-up logic:

There are other types of SYSLOG channel devices that may require even further initialization. For example, the file SYSLOG channel (described below) cannot be initialized until the necessary file systems have been mounted.

6.4.2.3 Interrupt Level SYSLOG Output

As a general statement, SYSLOG output only supports //normal// output from NuttX tasks. However, for debugging purposes, it is also useful to get SYSLOG output from interrupt level logic. In an embedded system, that is often where the most critical operations are performed.

There are three conditions under which SYSLOG output generated from interrupt level processing can a included the SYSLOG output stream:

  1. Low-Level Serial Output. If you are using a SYSLOG console channel (CONFIG_SYSLOG_CONSOLE) with a serial console (CONFIG_SYSLOG_SERIAL_CONSOLE) and if the underlying architecture supports the low-level up_putc() interface(CONFIG_ARCH_LOWPUTC), then the SYLOG logic will direct the output to up_putc() which is capable of generating the serial output within the context of an interrupt handler.

    There are a few issues in doing this however:

  2. In-Memory Buffering. If the RAMLOG SYSLOG channel is supported, then all SYSLOG output is buffered in memory. Interrupt level SYSLOG output is no different than normal SYSLOG output in this case.

  3. Serialization Buffer. A final option is the use the an interrupt buffer to buffer the interrupt level SYSLOG output. In this case:

    The SYSLOG interrupt buffer is enabled with CONFIG_SYSLOG_INTBUFFER. When the interrupt buffer is enabled, you must also provide the size of the interrupt buffer with CONFIG_SYSLOG_INTBUFSIZE.

6.4.3 SYSLOG Channel Options

6.4.3.1 SYSLOG Console Device

The typical SYSLOG device is the system console. If you are using a serial console, for example, then the SYSLOG output will appear on that serial port.

This SYSLOG channel is automatically selected by syslog_initialize() in the LATE initialization phase based on configuration options. The configuration options that affect this channel selection include:

Interrupt level SYSLOG output will be lost unless: (1) the interrupt buffer is enabled to support serialization, or (2) a serial console is used and up_putc() is supported.

NOTE: The console channel uses the fixed character device at /dev/console. The console channel is not synonymous with stdout (or file descriptor 1). stdout is the current output from a task when, say, printf() if used. Initially, stdout does, indeed, use the /dev/console device. However, stdout may subsequently be redirected to some other device or file. This is always the case, for example, when a transient device is used for a console -- such as a USB console or a Telnet console. The SYSLOG channel is not redirected as stdout is; the SYSLOG channel will stayed fixed (unless it is explicitly changed via syslog_channel()).

References: drivers/syslog/syslog_consolechannel.c and drivers/syslog/syslog_device.c

6.4.3.2 SYSLOG Character Device

The system console device, /dev/console, is a character driver with some special properties. However, any character driver may be used as the SYSLOG output channel. For example, suppose you have a serial console on /dev/ttyS0 and you want SYSLOG output on /dev/ttyS1. Or suppose you support only a Telnet console but want to capture debug output /dev/ttyS0.

This SYSLOG device channel is selected with CONFIG_SYSLOG_CHAR and has no other dependencies. Differences fromthe SYSLOG console channel include:

References: drivers/syslog/syslog_devchannel.c and drivers/syslog/syslog_device.c

6.4.3.3 SYSLOG File Device

Files can also be used as the sink for SYSLOG output. There is, however, a very fundamental difference in using a file as opposed the system console, a RAM buffer, or character device: You must first mount the file system that supports the SYSLOG file. That difference means that the file SYSLOG channel cannot be supported during the boot-up phase but can be instantiated later when board level logic configures the application environment, including mounting of the file systems.

The interface syslog_file_channel() is used to configure the SYSLOG file channel:

6.4.3.4 syslog_file_channel()

Function Prototype:

Description: Configure to use a file in a mounted file system at devpath as the SYSLOG channel.

This tiny function is simply a wrapper around syslog_dev_initialize() and syslog_channel(). It calls syslog_dev_initialize() to configure the character file at devpath then calls syslog_channel() to use that device as the SYSLOG output channel.

File SYSLOG channels differ from other SYSLOG channels in that they cannot be established until after fully booting and mounting the target file system. This function would need to be called from board-specific bring-up logic AFTER mounting the file system containing devpath.

SYSLOG data generated prior to calling syslog_file_channel() will, of course, not be included in the file.

NOTE interrupt level SYSLOG output will be lost in this case unless the interrupt buffer is used.

Input Parameters:

Returned Value: Zero (OK) is returned on success; a negated errno value is returned on any failure.

References: drivers/syslog/syslog_filechannel.c, drivers/syslog/syslog_device.c, and include/nuttx/syslog/syslog.h.

6.4.3.5 SYSLOG RAMLOG Device

The RAMLOG is a standalone feature that can be used to buffer any character data in memory. There are, however, special configurations that can be used to configure the RAMLOG as a SYSLOG channel. The RAMLOG functionality is described in a more general way in the following paragraphs.

6.4.4 RAM Logging Device

The RAM logging driver is a driver that was intended to support debugging output (SYSLOG) when the normal serial output is not available. For example, if you are using a Telnet or USB serial console, the debug output will get lost -- or worse. For example, what if you want to debug the network over Telnet?

The RAM logging driver can also accept debug output data from interrupt handler with no special serialization buffering. As an added benefit, the RAM logging driver is much less invasive. Since no actual I/O is performed with the debug output is generated, the RAM logger tends to be much faster and will interfere much less when used with time critical drivers.

The RAM logging driver is similar to a pipe in that it saves the debugging output in a circular buffer in RAM. It differs from a pipe in numerous details as needed to support logging.

This driver is built when CONFIG_RAMLOG is defined in the Nuttx configuration.

6.4.4.1 dmesg

When the RAMLOG (with SYSLOG) is enabled, a new NuttShell (NSH) command will appear: dmesg. The dmesg command will dump the contents of the circular buffer to the console (and also clear the circular buffer).

6.4.4.2 RAMLOG Configuration options

If CONFIG_RAMLOG_CONSOLE or CONFIG_RAMLOG_SYSLOG is selected, then the following must also be provided:

Other miscellaneous settings

6.5 Power Management

6.5.1 Overview

Power Management (PM) Sub-System. NuttX supports a simple power management (PM) sub-system. This sub-system:

The PM sub-system integrates the MCU idle loop with a collection of device drivers to support:

Low Power Consumption States. Various "sleep" and low power consumption states have various names and are sometimes used in conflicting ways. In the NuttX PM logic, we will use the following terminology:

NORMAL
The normal, full power operating mode.
IDLE
This is still basically normal operational mode, the system is, however, IDLE and some simple simple steps to reduce power consumption provided that they do not interfere with normal Operation. Simply dimming the a backlight might be an example somethat that would be done when the system is idle.
STANDBY
Standby is a lower power consumption mode that may involve more extensive power management steps such has disabling clocking or setting the processor into reduced power consumption modes. In this state, the system should still be able to resume normal activity almost immediately.
SLEEP
The lowest power consumption mode. The most drastic power reduction measures possible should be taken in this state. It may require some time to get back to normal operation from SLEEP (some MCUs may even require going through reset).

These various states are represented with type enum pm_state_e in include/nuttx/power/pm.h.

Power Management Domains. Each PM interfaces includes a integer domain number. By default, only a single power domain is supported (CONFIG_PM_NDOMAINS=1). But that is configurable; any number of PM domains can be supported. Multiple PM domains might be useful, for example, if you would want to control power states associated with a network separately from power states associated with a user interface.

6.5.2 Interfaces

All PM interfaces are declared in the file include/nuttx/power/pm.h.

6.5.2.1 pm_initialize()

Function Prototype:

Description: This function is called by MCU-specific one-time at power on reset in order to initialize the power management capabilities. This function must be called very early in the initialization sequence before any other device drivers are initialize (since they may attempt to register with the power management subsystem).

Input Parameters: None

Returned Value: None

6.5.2.2 pm_register()

Function Prototype:

Description: This function is called by a device driver in order to register to receive power management event callbacks. Refer to the PM Callback section for more details.

Input Parameters:

callbacks
An instance of struct pm_callback_s providing the driver callback functions.

Returned Value: Zero (OK) on success; otherwise a negated errno value is returned.

6.5.2.3 pm_activity()

Function Prototype:

Description: This function is called by a device driver to indicate that it is performing meaningful activities (non-idle). This increment an activity count and/or will restart a idle timer and prevent entering reduced power states.

Input Parameters:

domain
Identifies the domain of the new PM activity
priority
Activity priority, range 0-9. Larger values correspond to higher priorities. Higher priority activity can prevent the system from entering reduced power states for a longer period of time. As an example, a button press might be higher priority activity because it means that the user is actively interacting with the device.

Returned Value: None

Assumptions: This function may be called from an interrupt handler (this is the ONLY PM function that may be called from an interrupt handler!).

6.5.2.4 pm_checkstate()

Function Prototype:

Description: This function is called from the MCU-specific IDLE loop to monitor the power management conditions. This function returns the "recommended" power management state based on the PM configuration and activity reported in the last sampling periods. The power management state is not automatically changed, however. The IDLE loop must call pm_changestate() in order to make the state change.

These two steps are separated because the platform-specific IDLE loop may have additional situational information that is not available to the PM sub-system. For example, the IDLE loop may know that the battery charge level is very low and may force lower power states even if there is activity.

NOTE: That these two steps are separated in time and, hence, the IDLE loop could be suspended for a long period of time between calling pm_checkstate() and pm_changestate(). The IDLE loop may need to make these calls atomic by either disabling interrupts until the state change is completed.

Input Parameters:

domain
Identifies the PM domain to check

Returned Value: The recommended power management state.

6.5.2.5 pm_changestate()

Function Prototype:

Description: This function is used by platform-specific power management logic. It will announce the power management power management state change to all drivers that have registered for power management event callbacks.

Input Parameters:

domain
Identifies the domain of the new PM state
newstate
Identifies the new PM state

Returned Value: 0 (OK) means that the callback function for all registered drivers returned OK (meaning that they accept the state change). Non-zero means that one of the drivers refused the state change. In this case, the system will revert to the preceding state.

Assumptions: It is assumed that interrupts are disabled when this function is called. This function is probably called from the IDLE loop... the lowest priority task in the system. Changing driver power management states may result in renewed system activity and, as a result, can suspend the IDLE thread before it completes the entire state change unless interrupts are disabled throughout the state change.

6.5.3 Callbacks

The struct pm_callback_s includes the pointers to the driver callback functions. This structure is defined include/nuttx/power/pm.h. These callback functions can be used to provide power management information to the driver.

6.5.3.1 prepare()

Function Prototype:

Description: Request the driver to prepare for a new power state. This is a warning that the system is about to enter into a new power state. The driver should begin whatever operations that may be required to enter power state. The driver may abort the state change mode by returning a non-zero value from the callback function.

Input Parameters:

cb
Returned to the driver. The driver version of the callback structure may include additional, driver-specific state data at the end of the structure.
domain
Identifies the activity domain of the state change
pmstate
Identifies the new PM state

Returned Value: Zero (OK) means the event was successfully processed and that the driver is prepared for the PM state change. Non-zero means that the driver is not prepared to perform the tasks needed achieve this power setting and will cause the state change to be aborted. NOTE: The prepare() method will also be called when reverting from lower back to higher power consumption modes (say because another driver refused a lower power state change). Drivers are not permitted to return non-zero values when reverting back to higher power consumption modes!

6.5.3.1 notify()

Function Prototype:

Description: Notify the driver of new power state. This callback is called after all drivers have had the opportunity to prepare for the new power state.

Input Parameters:

cb
Returned to the driver. The driver version of the callback structure may include additional, driver-specific state data at the end of the structure.
domain
Identifies the activity domain of the state change
pmstate
Identifies the new PM state

Returned Value: None. The driver already agreed to transition to the low power consumption state when when it returned OK to the prepare() call.

Appendix A: NuttX Configuration Settings

At one time, this section provided a list of all NuttX configuration variables. However, NuttX has since converted to use the kconfig-frontends tools. Now, the NuttX configuration is determined by a self-documenting set of Kconfig files.

The current NuttX configuration variables are also documented in separate, auto-generated configuration variable document. That configuration variable document is generated using the kconfig2html tool that can be found in the nuttx/tools directory. That tool analyzes the NuttX Kconfig files and generates excruciatingly boring HTML document.

The latest boring configuration variable documentation can be regenerated at any time using that tool or, more appropriately, the wrapper script at nuttx/tools/mkconfigvars.sh. That script will generate the file nuttx/Documentation/NuttXConfigVariables.html.

The version of NuttXConfigVariables.html for the last released version of NuttX can also be found online.

Appendix B: Trademarks

  • ARM, ARM7 ARM7TDMI, ARM9, ARM920T, ARM926EJS, Cortex-M3 are trademarks of Advanced RISC Machines, Limited.
  • Cygwin is a trademark of Red Hat, Incorporated.
  • Linux is a registered trademark of Linus Torvalds.
  • Eagle-100 is a trademark of Micromint USA, LLC.
  • LPC2148 is a trademark of NXP Semiconductors.
  • TI is a trade name of Texas Instruments Incorporated.
  • UNIX is a registered trademark of The Open Group.
  • VxWorks is a registered trademark of Wind River Systems, Incorporated.
  • ZDS, ZNEO, Z16F, Z80, and Zilog are a registered trademark of Zilog, Inc.
  • NOTE: NuttX is not licensed to use the POSIX trademark. NuttX uses the POSIX standard as a development guideline only.