BSPACM
20150113
Board Support Package for ARM Cortex-M Microcontrollers
|
Newlib is an open-source implementation of the standard C library, and is used as the libc implementation in BSPACM's primary toolchain, GNU Tools for ARM Embedded Processors. It was designed for use in embedded systems, using a small set of externally-supplied functions to interface with the system.
The classic reference (in my experience) for porting newlib to a new platform is: http://neptune.billgatliff.com/newlib.html
Although newlib was designed for embedded systems, it has historically been unusable on microcontrollers due to its relatively large memory requirements, e.g. 1 kiB SRAM to support malloc
, 25 kiB FLASH to support printf
, etc.
With the addition of a variant called newlib-nano, this is no longer the case. newlib-nano was implemented specifically for ARM within the GNU Tools for ARM Embedded Processors, and some of its enhancements have been merged upstream.
By adding -specs=nano.specs
to the gcc link command, a reduced-size libc is linked in. The effect of this is significant. The size of the classic "hello" program compiled for the EFM32GG-STK3700 kit with traditional newlib under 4_8-2013q4 is:
llc[56]$ rm app.axf ; make WITH_NANO=0 app.axf arm-none-eabi-gcc -o app.axf -L/prj/arm/bspacm//board/efm32gg-stk3700 -ggdb -Wl,-gc-sections -Wl,-T,/prj/arm/bspacm//toolchain/GCC/gcc_arm.ld -L/prj/arm/bspacm//device/efm32/efm32gg/efm32gg990f1024 -mthumb -mcpu=cortex-m3 system_efm32gg.o main.o startup_ARMCM3.o periph_config.o -Wl,--start-group -lc -lemlib -lbspacm-fdops -lbspacm -Wl,--end-group arm-none-eabi-size app.axf text data bss dec hex filename 27832 2340 116 30288 7650 app.axf
The same program linked with newlib-nano is one third the FLASH, and one tenth the SRAM:
llc[57]$ rm app.axf ; make WITH_NANO=1 app.axf arm-none-eabi-gcc -o app.axf -L/prj/arm/bspacm//board/efm32gg-stk3700 -ggdb -Wl,-gc-sections -Wl,-T,/prj/arm/bspacm//toolchain/GCC/gcc_arm.ld -L/prj/arm/bspacm//device/efm32/efm32gg/efm32gg990f1024 -specs=nano.specs -mthumb -mcpu=cortex-m3 system_efm32gg.o main.o startup_ARMCM3.o periph_config.o -Wl,--start-group -lc_s -lemlib -lbspacm-fdops -lbspacm -Wl,--end-group arm-none-eabi-size app.axf text data bss dec hex filename 8720 232 72 9024 2340 app.axf
BSPACM defaults to use WITH_NANO=1
but this can be overridden on the command line if necessary.
If text formatting functions alone are required alternative libraries such as embtextf may be used. Initial experimentation suggests that embtextf saves only about 1 kiB flash and 50 bytes SRAM relative to newlib-nano. At this time, there is no built-in support to incorporate embtextf support in BSPACM.
The following behavioral differences/limitations are known to exist with newlib-nano:
uint64_t
format specifiers (e.g. PRIx64 from <inttypes.h>newlib maps standard C functions to a specific implementation environment through a chain of functions, for example:
write()
invokes _write_r()
with the current reentrancy context (e.g. thread/task-unique errno
);_write_r()
invokes _write()
and copies errno appropriately;_write()
must be provided by somethingIf nothing provides an implementation of _write()
but the application requires one, the application will fail to link.
The standard solution for newlib is to add -specs=nosys.specs
to the gcc linker command line. This links in a separate library with implementations for all required system functions. Most of these simple return an error; some (like _sbrk()
) provide a usable definition.
To simplify customization, BSPACM provides weak definitions of all the system functions in its core library libbspacm.a
, eliminating the need to add -specs=nosys.specs
. The following function and data object definitions may be overridden by the application:
File Descriptor Operations provides an framework for the following functions, which may instead be supplied by the application:
Heap Management describes how to override this function:
The standard ARM SRAM layout comprises initialized data objects followed by heap space and stack space. The stack grows downward as material is pushed and popped; the heap grows upward only when dynamic memory is allocated through the _sbrk() system call. BSPACM allows significant customization of the policies that control heap allocation.
The standard ARM startup file supports two preprocessor macros that specify memory to be reserved for the initial stack and heap. These can be controlled within BSPACM:
STARTUP_STACK_SIZE
make
variable defines the amount of space reserved for the stack. The default value is 1 kiB; whether this actually limits stack size depends on the _sbrk() policy.STARTUP_HEAP_SIZE
make
variable defines the amount of space reserved for the heap. The default value is 0 kiB; whether this actually limits heap size depends on the _sbrk() policy.These macro values propagate to symbols that specify SRAM boundaries for heap and stack, following all application-declared data.
BSPACM provides a make
variable NEWLIB_SBRK
which selects among the following policies for dynamic memory management. Explicit selection of an allocation policy by assigning one of the following values to NEWLIB_SBRK
will link the corresponding policy into the application even if dynamic memory allocation is not done. Each policy invokes _bspacm_sbrk_error() if allocation fails, allowing diagnosis and recovery to be customized.
Leaving NEWLIB_SBRK
unset causes a weak reference to the unlimitedstack
implementation, providing good default behavior without increasing application size if no dynamic memory allocation is performed. A user application may supply its own non-weak definition of _sbrk() if none of the following policies is acceptable.
unlimitedstack
An _sbrk() implementation that allows heap (growing up) and stack (growing down) to share a region of memory, with a minimum size reserved for the stack but allowing for the stack to grow below that point. An error is indicated if the new break point would encroach into the reserved stack space or the currently used stack space. This is the default policy for BSPACM, and is closely related to dynstack
which is the policy implemented by newlib's -specs=nosys.specs
option.
heap
An _sbrk() implementation that allocates within the reserved heap. An error is indicated if the reserved heap size would be exceeded. There is no check against the current stack pointer.
fatal
An _sbrk() implementation that rejects any attempt to allocate memory dynamically. The behavior is equivalent to the heap
policy with a zero-sized heap.
fixedstack
An _sbrk() implementation that allows heap (growing up) to grow to the bottom of reserved stack region. An error is indicated if the new program break would encroach into the reserved stack space. There is no check against the current stack pointer. This policy is preferred to unlimitedstack
when code may be executing in tasks where the stack frame is in previously allocated memory, making comparison with the stack pointer invalid.
dynstack
An _sbrk() implementation that allows heap (growing up) and stack (growing down) to share a region of memory. An error is indicated if the new break point would encroach into the current stack space. (This eliminates the minimum reserved stack that is checked by unlimitedstack
.)
In <bspacm/newlib/fdops.h> BSPACM provides an infrastructure that implements various functions as well as the function below, so that standard operations on file descriptors are supported:
The libbspacm-fdops.a
library implements the bulk of this, with the assistance of application-provided drivers. This feature is disabled by default, and is enabled by setting WITH_FDOPS=1
in make
.
xBSPACMnewlibFDOPSdriver is a list of drivers that are supported by the application. Entries in this list are pointers to functions with the signature fBSPACMnewlibFDOPSdriver, taking a path string and some flags, and returning a handle to a file object. The list terminates with a null pointer. The newlib _open() system call invokes each driver function in turn until it finds one that recognizes the path and returns a file object.
Use of WITH_FDOPS=1
will provide a weak definition of this array that contains a single driver, which maps the path "/dev/console"
to the default UART on the board. Applications override this by providing a strong definition in the application code.
The C API for file descriptors represents each file by an integer, numbered consecutively from zero. As drivers return file handles, they are stored in the array xBSPACMnewlibFDOPSfile_, with their index used as the file descriptor. The number of entries allowed in this array is provided by nBSPACMnewlibFDOPSfile.
Use of WITH_FDOPS=1
will provide a weak definition of the array and its length, containing three entries which is sufficient for the standard C I/O descriptors. Use of additional file descriptors requires that the application provide an alternative definition of the array and its length.
Application code itself should never need to access the xBSPACMnewlibFDOPSfile_ array. open() will fail if too many devices are opened.
Each file handle references an instance of sBSPACMnewlibFDOPSfile, which provides a pointer to device-specific data as well as a pointer to a sBSPACMnewlibFDOPSfileOps table that provides implementation for each file descriptor operation supported by the device. System calls that would attempt to invoke an unsupported operation return an error indicator and set errno
to ENOSYS
.
For an example of implementing devices, see src/newlib/uart.c, which provides hBSPACMnewlibFDOPSdriverUARTbind() which binds a generic UART handle to a file handle. This function in turn is used by fBSPACMnewlibFDOPSdriverCONSOLE(), a full driver implementation that provides access to the board-specific default UART as a console device.
The C library assumes that descriptors for stdin (0), stdout (1), and stderr(2) are provided by the system. BSPACM supports this by ensuring that vBSPACMnewlibFDOPSinitializeStdio_() is invoked prior to executing any library function that manipulates file descriptors.
Use of WITH_FDOPS=1
provides a weak definition of vBSPACMnewlibFDOPSinitializeStdio_() which opens "/dev/console"
three times with appropriate flags to initialize the first three entries of the descriptor table.
Makefile
so that hBSPACMdefaultUART is provided.Copyright 2014-2015, Peter A. Bigot