
Application Note 9. Issue 1
July 2010
Copyright © 2010 Embecosm Limited
![]() | The document entitled " Howto: Porting newlib " by Jeremy Bennett of Embecosm is licensed under a Creative Commons Attribution 2.0 UK: England & Wales License. See the Legal Notice for details. |
Table of Contents
newlib within the GNU Tool Chain
newlib
newlib
libgloss
crt0.o
environ
_exit
close
execve
fork
fstat
getpid
isatty
kill
link
lseek
open
read
sbrk
stat
times
unlink
wait
write
libnosys
Newlib and Libgloss
Newlib and Libgloss
Newlib is a C library intended for use on embedded systems. It is a
conglomeration of several library parts, all under free software
licenses that make them easily usable on embedded products.
Porting newlib is not difficult, but advice for the beginner is thin
on the ground. This application note is intended for software
engineers porting newlib for the first time.
The detail of all the steps needed are covered here, and have been
tested using newlib versions 1.17.0 and 1.18.0 with the
OpenRISC 1000 .
For those who already have some experience, the entire porting process is summarized in the final chapter, with links back to the main document (see Chapter 9). It's a useful checklist when carrying out a new port.
This application note includes examples from the port of newlib to
the OpenRISC 1000 architecture, originally by Chris Bower, then of
Imperial College, London, and subsequently extensively updated by
Jeremy Bennett of Embecosm.
The examples are two Board Support Packages (BSP) for use with the OpenRISC 1000 architectural simulator Or1ksim by the same two authors.
At the time of writing the OpenRISC 1000 implementation is
not part of the main newlib distribution. It can be downloaded from
OpenCores (www.opencores.org).
The main source of information is the newlib website (sourceware.org/newlib). This
includes a FAQ, which has brief instructions on porting newlib and
documentation for libc [1] and libm, the
two libraries making up newlib. The libc documentation is
particularly useful, because it lists the system calls which must be
implemented by any new port, including minimal implementations.
The newlib README is another source of
information. Key header files within the source also contain useful
commenting, notably ieeefp.h and
reent.h.
There is also a mailing list, <newlib@sourceware.org>
where questions can be asked, or new ports submitted.
This application note does not cover the detail of testing newlib
on physical hardware. That subject is well covered by Dan Kegel's
Crosstool project [2].
This application note has drawn heavily on these sources, and the author would like to thank the providers of that original information.
Embecosm is a consultancy specializing in hardware
modeling and open source tool chains for the embedded market. If we
can ever be of help, please get in touch at
<sales@embecosm.com>.
As part of its commitment to the open source community, Embecosm publishes a series of free and open source application notes, designed to help working engineers with practical problems.
Feedback is always welcome, which should be sent to
<info@embecosm.com>.
Newlib is intended for use with the GNU tool chain. If newlib is
included within the build of the GNU tool chain, then all the
libraries will be built and installed in the correct places to be found
by GCC
The three separate packages, binutils, GCC and GDB are all taken
from a common source tree. GCC and GDB both use many libraries
from binutils. It is convenient to reassemble that source tree and
make a single build of all the tools together.
The easiest way to achieve this is to link all the top level directories in each package into a single unified directory, leaving out any duplicated files or directories.
The following bash script will take unpacked distributions of
binutils GCC and GDB and link them into a single directory,
srcw.
#!/bin/bash
component_dirs='binutils-2.18.50 gcc-4.2.2 gdb-6.8'
unified_src=srcw
cd ${unified_src}
ignore_list=". .. CVS .svn"
for srcdir in ${component_dirs}
do
echo "Component: $srcdir"
case srcdir
in
/* | [A-Za-z]:[\\/]*)
;;
*)
srcdir="../${srcdir}"
;;
esac
files=`ls -a ${srcdir}`
for f in ${files}
do
found=
for i in ${ignore_list}
do
if [ "$f" = "$i" ]
then
found=yes
fi
done
if [ -z "${found}" ]
then
echo "$f ..linked"
ln -s ${srcdir}/$f .
fi
done
ignore_list="${ignore_list} ${files}"
done
cd ..
The entire tool chain can then be configured and built in a separate
directory. The configure script understands to
pass on top level arguments to subsidiary configuration scripts. For
example to configure to build a C only tool chain for the 32-bit
OpenRISC 1000 architecture to be installed in
/opt/or32-elf, the following would be
appropriate.
mkdir build cd build ../src/configure --target=or32-elf --enable-languages=c --prefix=/opt/or32-elf cd ..
Each tool can be built with its own specific target within that build directory
cd build make all-build all-binutils all-gas all-ld all-gcc all-gdb cd ..
![]() | Note |
|---|---|
The initial make target, |
Similarly the tools can be installed using the following:
cd build
make install-build install-binutils install-gas install-ld install-gcc \
install-gdb
cd ..
Newlib can be linked into the unified source directory in the same
fashion. All that is needed is to add newlib to the component
directories in the linking script.
#!/bin/bash component_dirs='binutils-2.18.50 gcc-4.2.2 newlib-1.18.0 gdb-6.8' unified_src=srcw ...
The configuration command should also specify that this is a build
using newlib
mkdir build
cd build
../src/configure --target=or32-elf --enable-languages=c --with-newlib \
--prefix=/opt/or32-elf
cd ..
Two new targets are needed for newlib, one to build newlib itself,
and one to build any board support packages using libgloss (see
Chapter 3 for an explanation of how libgloss
is used with newlib).
cd build
make all-build all-binutils all-gas all-ld all-gcc all-target-newlib \
all-target-libgloss all-gdb
cd ..
Similarly additional targets are needed for installation.
cd build
make install-build install-binutils install-gas install-ld install-gcc \
install-target-newlib install-target-libgloss install-gdb
cd ..
Newlib is now divided into two parts. The main
newlib directory contains the bulk of the code
for the two main libraries, libc and libm, together with any
architecture specific code for particular
targets.
The libgloss directory contains code specific to
particular platforms on which the library will be used, generally
referred to as the Board Support Package
(BSP). Any particular target architecture may have multiple BSPs,
for example for different hardware platforms, for a simulator etc.
The target architecture specific code within the
newlib directory may be very modest - possibly as
little as an implementation of setjmp and a
specification of the IEEE floating point format to use.
The board support package is more complex. It requires an implementation of eighteen system calls and the definition of one global data structure, although the implementation of some of those system calls may be completely trivial.
![]() | Note |
|---|---|
The separation of BSP implementation into |
The BSP implements the system calls—functions like
close, write etc. It is
possible for the BSP to implement these directly, but these will
then be defined in the main C namespace. It is perfectly permissible
for the user to replace these functions, and the user versions take
precedence, which requires some care at link time.
Newlib allows the implementer instead to provide namespace clean
versions of these functions by prefixing them with an
underscore. Newlib will ensure that the system calls map to these
namespace clean version (i.e. a call to close
becomes a call to _close) unless the user has
reimplemented that function themselves.
A reentrant function may be safely called from a second thread, while a first thread of control is executing. In general a function that modifies no static or global state, will be reentrant.
Many system calls are trivially reentrant. However for some calls,
reentrancy is not easy to provide automatically, so reentrant versions
are provided. Thus for close, there is the
reentrant version close_r. The reentrant versions
take an extra argument, a reentrancy structure,
which can be used to ensure correct behavior, by providing per-thread
versions of global data structures.
It is worth noting that use of the global error value,
errno is a common source of non-reentrancy. The
standard reentrancy structure includes an entry for a per-thread value
of errno.
For many systems, the issue of reentrancy does not arise. If there is only ever one thread of control, or if separate threads have their own address space there is no problem.
However it's worth remembering that even a bare metal system may encounter issues with reentrancy if event handlers are allowed to use the system calls.
Newlib gives considerable flexibility, particularly where namespace
clean versions of the basic system calls are implemented. The
implementer can choose to provide implementations of the reentrant
versions of the functions. Alternatively newlib can provide
reentrancy at the library level, but mapping the calls down the system
calls, which are not themselves reentrant. This last can often prove
a practical solution to the problem.
Adding a new architecture to newlib requires the following steps.
Provide a machine specific directory within the
newlib directory for architecture specific
code, notably the setjmp implementation.
Provide a platform directory for BSP implementation(s) within
the libgloss directory. The code implementing
systems calls for each BSP is placed in this directory.
Update the configure.host file in the
newlib directory to point to the machine and
platform directories for the new target.
The configure.host file needs changes in two
places, to identify the architecture specific machine directory and
the platform directory for BSP implementations.
The machine name is specified in a case switch on
the ${host_cpu} early on in the file. Add a new
case entry defining
machine_type for the architecture. Thus for
OpenRISC 1000 32-bit architecture we have:
or32)
machine_dir=or32
;;
This specifies that the machine specific code for this architecture
will be found in the directory
newlib/libc/machine/or32.
The platform directory and details are specified in a subsequent
case switch on ${host}
(i.e. the full triplet, not just the CPU type).
For the 32-bit OpenRISC 1000 we have the following.
or32-*-*)
syscall_dir=syscalls
;;
This is the simplest option, specifying that the BSPs for all
OpenRISC 1000 32-bit targets will implement namespace clean system calls,
and rely on newlib to map reentrant calls down to them. The
directory name for the BSP implementations will match that of the
machine directory, but within the libgloss
directory. So for OpenRISC 1000 32-bit targets; the BSP implementations
are in libgloss/or32.
There are four common alternatives for specifying how the BSP will be implemented.
The implementer defines reentrant namespace clean versions of
the system calls. In this case, syscall_dir
is set to syscalls as above, but in addition,
-DREENTRANT_SYSCALLS_PROVIDED is added to
newlib_cflags in
configure.host. For the OpenRISC 1000 32-bit
target we could have done this with:
or32-*-*)
syscall_dir=syscalls
newlib_cflags="${newlib_cflags} -DREENTRANT_SYSCALLS_PROVIDED"
;;
For convenience, stub versions of the reentrant functions may
be found in the libc/reent directory. These
are in fact the functions used if the reentrant system calls are
not provided, and map to the non-reentrant versions.
The implementer defines non-reentrant, but namespace clean
versions of the system calls. This is the approach we have used
with the OpenRISC 1000 and all the implementer needs to do in this case
is to set syscall_dir to
syscalls in
configure.host. newlib will map reentrant
calls down to the non-reentrant versions.
The implementer defines non-reentrant, regular
versions of the system calls (i.e. close
rather than _close). The library will
be neither reentrant, not namespace clean, but will work. In
this case,
-DMISSING_SYSCALL_NAMES is
added to newlib_cflags in
configure.host. For the OpenRISC 1000 we could
have done this with:
or32-*-*)
newlib_cflags="${newlib_cflags} -DMISSING_SYSCALL_NAMES"
;;
Note in particular that syscall_dir is not
defined in this case.
The implementer defines non-reentrant, regular
versions of the system calls (i.e. close
rather than _close). The reentrant system
calls are mapped onto these functions. The library will
not be namespace clean, but will offer reentrancy at the library
level. In
this case,
-DMISSING_SYSCALL_NAMES and
-DREENTRANT_SYSCALLS_PROVIDED are both
added to newlib_cflags in
configure.host. For the OpenRISC 1000 we could
have done this with:
or32-*-*)
newlib_cflags="${newlib_cflags} -DMISSING_SYSCALL_NAMES"
newlib_cflags="${newlib_cflags} -DREENTRANT_SYSCALLS_PROVIDED"
;;
Note in particular that syscall_dir is not
defined in this case.
Changes that depend on the architecture, and not the particular platform
being used, are made in the newlib directory. These
comprise changes to standard headers and custom code for the
architecture.
Within the newlib directory, machine specific
code is placed in a target specific directory,
libc/machine/.
arch
The only code that has to be there is the implementation of
setjmp and longjmp, since
the implementation of these two functions invariably requires target
specific machine code. However any other target specific code may
also be placed here.
The machine directory uses GNU autoconf and automake for
configuration. There is a configuration template file
(configure.in) and Makefile template
(Makefile.am) in the main machine directory
(libc/machine within the
newlib directory).
configure.ac contains a case
statement configuring the target specific subdirectories. This must
be updated to configure the subdirectory for the new target. Thus
for the OpenRISC 1000 we have the following.
if test -n "${machine_dir}"; then
case ${machine_dir} in
a29k) AC_CONFIG_SUBDIRS(a29k) ;;
arm) AC_CONFIG_SUBDIRS(arm) ;;
<other machines not shown>
necv70) AC_CONFIG_SUBDIRS(necv70) ;;
or32) AC_CONFIG_SUBDIRS(or32) ;;
powerpc) AC_CONFIG_SUBDIRS(powerpc) ;;
<other machines not shown>
xstormy16) AC_CONFIG_SUBDIRS(xstormy16) ;;
z8k) AC_CONFIG_SUBDIRS(z8k) ;;
esac;
fi
Makefile.am is standard and will not need to be
changed. Having changed the configuration template, the
configuration file, configure, will need to be
regenerated. This only requires running autoconf
autoconf
Since Makefile.am has not been changed there is
no need to run automake
setjmp and longjmp are a
pair of C function facilitating cross-procedure transfer of
control. Typically they are used to allow resumption of execution at
a known good point after an error.
Both take as first argument a buffer, which is used to hold the
machine state at the jump destination. When
setjmp is called it populates that buffer with
the current location state (which includes stack and frame pointers
and the return address for the call to setjmp,
and returns zero.
longjmp takes a buffer previously populated by
setjmp. It also takes a (non-zero) second
argument, which will ultimately be the result of the function
call. longjmp restores the machine state from
the buffer. It then jumps to the return address it has just
restored, passing its second argument as the result. That return
address is the return address from the original call to
setjmp, so the effect will be as if
setjmp has just returned with a non-zero
argument.
setjmp and longjmp are
typically used in a top level function in the following way.
#include <setjmp.h>
...
jmp_buf buf;
if (0 == setjmp (buf))
{
normal processing passing in buf
}
else
{
error handling code
}
...
During normal processing if an error is found, the state held in
buf can be used to return control back to the top
level using longjmp.
#include <setjmp.h>
...
if (error detected)
{
longjmp (buf, 1);
}
...
The program will behave as though the original call to
setjmp had just returned with result 1.
It will be appreciated that this is behavior that cannot usually be
written in C. The OpenRISC 1000 implementation is given as an example. This
processor has 32 registers, r0 through
r31, each of 32-bits. r0 is
always tied to zero, so need not be saved. r11 is
the function result register, which is always set by
setjmp and longjmp, so
also need not be saved. In addition we should save and restore the
machine's 32-bit supervision register, which holds the branch flag.
Thus we need the buffer to be 31 32-bit words long. This is defined
in the setjmp header (see Section 4.2.2).
In the Application Binary Interface (ABI)
for the OpenRISC 1000 , function arguments are passed in registers
r3 through r8 and the function
return address is in r9.
When defining these two functions, in assembler, be aware of any
prefix conventions used by the C compiler. It is common for symbols
defined in C to have an underscore prepended (this is the case for
the OpenRISC 1000 ). Thus in this case the assembler should define
_setjmp and _longjmp.
This is the implementation of setjmp.
.global _setjmp
_setjmp:
l.sw 4(r3),r1 /* Slot 0 saved for flag in future */
l.sw 8(r3),r2
l.sw 12(r3),r3
l.sw 16(r3),r4
l.sw 20(r3),r5
l.sw 24(r3),r6
l.sw 28(r3),r7
l.sw 32(r3),r8
l.sw 36(r3),r9
l.sw 40(r3),r10 /* Skip r11 */
l.sw 44(r3),r12
l.sw 48(r3),r13
l.sw 52(r3),r14
l.sw 56(r3),r15
l.sw 60(r3),r16
l.sw 64(r3),r17
l.sw 68(r3),r18
l.sw 72(r3),r19
l.sw 76(r3),r20
l.sw 80(r3),r21
l.sw 84(r3),r22
l.sw 88(r3),r23
l.sw 92(r3),r24
l.sw 96(r3),r25
l.sw 100(r3),r26
l.sw 104(r3),r27
l.sw 108(r3),r28
l.sw 112(r3),r29
l.sw 116(r3),r30
l.sw 120(r3),r31
l.jr r9
l.addi r11,r0,0 /* Zero result */
In this simplified implementation, the status flags are not
saved—that is a potential future enhancement. All the general
registers, with the exception of r0 (always zero)
and r11 (result register) are saved in the
buffer, which, being the first argument, is pointed to by
r3.
Finally the result register, r11 is set to zero
and the function returns using r9 (the OpenRISC 1000 has
delayed branches, so the setting of r11 is placed
after the branch to return.).
The implementation of longjmp is slightly more
complex, since the second argument will be returned as the effective
result from setjmp, unless
the second argument is zero in which case 1 is used.
The result must be dealt with first and placed in the result
register, r11, because the second argument, in
r4 will be subsequently overwritten when the
machine state is restored. Similarly we must ensure that
r3, which holds the first argument pointing to
the restore buffer must itself be the last register restored.
.global _longjmp
_longjmp:
/* Sort out the return value */
l.sfne r4,r0
l.bf 1f
l.nop
l.j 2f
l.addi r11,r0,1 /* 1 as result */
1: l.addi r11,r4,0 /* val as result */
/* Restore all the other registers, leaving r3 to last. */
2: l.lwz r31,120(r3)
l.lwz r30,116(r3)
l.lwz r29,112(r3)
l.lwz r28,108(r3)
l.lwz r27,104(r3)
l.lwz r26,100(r3)
l.lwz r25,96(r3)
l.lwz r24,92(r3)
l.lwz r23,88(r3)
l.lwz r22,84(r3)
l.lwz r21,80(r3)
l.lwz r20,76(r3)
l.lwz r19,72(r3)
l.lwz r18,68(r3)
l.lwz r17,64(r3)
l.lwz r16,60(r3)
l.lwz r15,56(r3)
l.lwz r14,52(r3)
l.lwz r13,48(r3)
l.lwz r12,44(r3)
l.lwz r10,40(r3) /* Omit r11 */
l.lwz r9,36(r3)
l.lwz r8,32(r3)
l.lwz r7,28(r3)
l.lwz r6,24(r3)
l.lwz r5,20(r3)
l.lwz r4,16(r3)
l.lwz r2,8(r3) /* Skip r3 */
l.lwz r1,4(r3) /* Slot 0 saved for flag in future */
l.lwz r3,12(r3) /* Now safe */
/* Result is already in r11. Having restored r9, it will appear as
though we have returned from the earlier call to _setjmp. The
non-zero result gives it away though. */
l.jr r9
l.nop
The return address, stack pointer and frame pointer having been
restored, the return from the function, will place the execution
point immediately after the original call to
setjmp.
The following is a simple test program, which can be used to verify
that setjmp and longjmp
are working correctly.
#include <setjmp.h>
#include <stdio.h>
void
testit (jmp_buf env,
int prev_res)
{
int res = (0 == prev_res) ? prev_res : prev_res + 1;
printf ("Long jumping with result %d\n", res);
longjmp (env, res);
} /* testit () */
int
main (int argc,
char *argv[])
{
jmp_buf env;
int res = setjmp (env);
printf ("res = 0x%08x\n", res);
if (res > 1)
{
return 0;
}
testit (env, res);
return 256; /* We should never actually get here */
} /* main () */
configure.in and
Makefile.am files also be needed for the target
specific directory (i.e.
libc/machine/
within the targetnewlib directory). These are
generally quite standard, and the easiest approach is to copy the
versions used for the fr30 architecture. Modern practice is to use
the file name configure.ac rather than
configure.in, but either will be accepted by
autoconf.
Makefile.am should be modified if necessary to
specify the source files (for example setjmp.S
and longjmp.S). More complex implementations
may require modifications to configure.in as
well.
For example the OpenRISC 1000 machine directory
(libc/machine/or32 within the
newlib directory) contains the following.
AUTOMAKE_OPTIONS = cygnus INCLUDES = $(NEWLIB_CFLAGS) $(CROSS_CFLAGS) $(TARGET_CFLAGS) AM_CCASFLAGS = $(INCLUDES) noinst_LIBRARIES = lib.a lib_a_SOURCES = longjmp.S setjmp.S lib_a_CCASFLAGS=$(AM_CCASFLAGS) lib_a_CFLAGS=$(AM_CFLAGS) ACLOCAL_AMFLAGS = -I ../../.. -I ../../../.. CONFIG_STATUS_DEPENDENCIES = $(newlib_basedir)/configure.host
After any changes it will be necessary to run autoconf and/or
automake
to generate new versions of configure and
Makefile.in. autoconf requires a number of
newlib specific macros. These can be generated from the main
newlib include file (acinclude.m4) by running
aclocal. The full set of commands would be.
aclocal -I ../../.. autoconf automake --cygnus Makefile
aclocal only need to be run the first time the directory is
created, or when moving the directory to a new release of
newlib. autoconf need only be run each time
configure.in (or
configure.ac) is changed. automake need only
be run each time Makefile.am is changed.
There are two places, where header definitions must be modified for a
new target architecture: the specification of the IEEE floating
point format used, and the specification of the
setjmp buffer size.
The floating point format is specified within the
newlib directory in
libc/include/machine/ieeefp.h. Details of how
the IEEE 754 format is implemented, and variations from the
standard, are specified by defining a number of C macros.
__IEEE_BIG_ENDIAN
Define this macro if the floating point format is big endian.
![]() | Caution |
|---|---|
One, and only one of |
__IEEE_LITTLE_ENDIAN
Define this macro if the floating point format is little endian.
![]() | Caution |
|---|---|
One, and only one of |
__IEEE_BYTES_LITTLE_ENDIAN
Define this macro in addition to
__IEEE_BIG_ENDIAN, where the words of a
multi-word IEEE floating point number are in big endian
order, but the bytes within each word are in little endian
order.
_DOUBLE_IS_32BITS
Define this if double precision floating point is represented using the 32-bit IEEE representation.
_FLOAT_ARG
Floating point arguments are usually promoted to double when passed as arguments. If this is not the case, then this macro should be defined to the type actually used to pass floating point arguments.
_FLT_LARGEST_EXPONENT_IS_NORMAL
Define this if the floating point format uses the largest
exponent for finite numbers rather than NaN and
infinities. Such a format cannot represent NaNs or infinities,
but it's FLT_MAX is twice the standard IEEE
value.
_FLT_NO_DENORMALS
Define this if the floating point format does not support IEEE denormalized numbers. In this case, every floating point number with a zero exponent is treated as a zero representation.
![]() | Caution |
|---|---|
Two of these macros
( |
For most targets it is sufficient to define just one of
__IEEE_BIG_ENDIAN or
__IEEE_LITTLE_ENDIAN. The definitions should
always be surrounded by a conditional, so they are only used when
the target architecture is selected. For example the OpenRISC 1000 is
big-endian, so we add the following to the header file.
#if defined(__or32__) #define __IEEE_BIG_ENDIAN #endif
The implementation of setjmp and
longjmp made use of a buffer to hold the
machine state. The size of that buffer is architecture dependent and
specified within the newlib directory in
libc/include/machine/setjmp.h.
The header specifies the number of entries in the buffer and the size of each entry (as a C type). So for the OpenRISC 1000 we use the following.
#if defined(__or32__) /* Enough space for all regs except r0 and r11 and the status register */ #define _JBLEN 31 #define _JBTYPE unsigned long #endif
As before, the definition is within a conditional, so it is only used when the target is the OpenRISC 1000 32-bit architecture.
The type jmp_buf used with
setjmp and longjmp is then
defined as:
typedef _JBTYPE jmp_buf[_JBLEN];
Various system wide constants are specified within the
newlib directory in
libc/include/sys/config.h.
Very often the system default values are quite sufficient (this is the case for the OpenRISC 1000 ). However target specific overrides of these values can be provided at the end of this file. This file is included in all other source files, so can be used to redefine any of the constants used in the system. The existing file gives numerous examples from different machines.
![]() | Caution |
|---|---|
A number of the constants defined here mirror those in GCC's
|
If other headers must be overridden (not usually necessary with a
simple port), then the new versions can be placed in
libc/machine/
within the arch/machinenewlib directory. These header files
will be used in preference to those in the standard distribution's
machine header directory.
Any target architecture may need multiple implementations, suited to
different platforms on which the code may run. The connection between
the library and a specific platform is known as a Board
Support Package (BSP). In recent versions of newlib,
BSPs are separated out into their own library, libgloss, the source
for which is in the top level libgloss directory.
For newlib the BSP within libgloss comprises an implementation of
the C runtime initialization, crt0.o, a definition
of one global data structure, and implementation of eighteen system
calls for each platform.
![]() | Note |
|---|---|
|
A directory is created in the libgloss directory
corresponding to the machine directory created in the
newlib/libc/machine directory (see Chapter 4).
This directory will hold the source code for the C runtime
initialization (crt0.o) and for the system calls
for each BSP
configure.in within the
libgloss directory includes a
case statement configuring the target for each
target platform. This should be extended to add the new platform
directory. The OpenRISC 1000 32-bit target requires the following change.
case "${target}" in
i[[3456]]86-*-elf* | i[[3456]]86-*-coff*)
AC_CONFIG_SUBDIRS([i386])
;;
m32r-*-*)
AC_CONFIG_SUBDIRS([m32r])
;;
<Other targets not shown>
spu-*-elf)
AC_CONFIG_SUBDIRS([spu])
config_testsuite=false
config_libnosys=false
;;
or32-*-*)
AC_CONFIG_SUBDIRS(or32)
;;
iq2000-*-*)
AC_CONFIG_SUBDIRS([iq2000])
;;
esac
After making this change the configure file
should be regenerated by running autoconf.
The C Runtime system must carry out the following tasks.
Set up the target platform in a consistent state. For example setting up appropriate exception vectors.
Initialize the stack and frame pointers
Invoke the C constructor initialization and ensure destructors are called on exit.
Carry out any further platform specific initialization.
Call the C main function.
Exit with the return code supplied if the C
main function ever terminates.
The code is invariably assembler, although it may call out to C
functions, and is best illustrated by example from the OpenRISC 1000 . This
is a BSP designed for use with a fast architectural simulator. It
comes in two variants, one providing just standard output to the
console, the other implementing a simulated UART with both
standard input and standard output. The crt0.0
is common to both BSPs and found in crt0.S.
The first requirement is to populate the exception vectors. The
OpenRISC 1000 uses memory from 0x0 to
0x1fff for exception vectors, with vectors
placed 0x100 bytes apart. Thus a reset
exception will jump to 0x100, a bus error
exception to 0x200 and so on.
In this simple BSP, the vast majority of exceptions are not
supported. If they are received, they print out (using
printf) identification of the exception and
the address which caused it to the simulator console, and then
exit. We provide a macro for that assembly code, since it will be
reused many times.
#define UNHANDLED_EXCEPTION(str) \
l.addi r1,r1,-20 /* Standard prologue */ ;\
l.sw 16(r1),r2 ;\
l.addi r2,r1,20 ;\
l.sw 12(r1),r9 ;\
;\
l.movhi r3,hi(.Lfmt) /* printf format string */ ;\
l.ori r3,r3,lo(.Lfmt) ;\
l.sw 0(r1),r3 ;\
l.movhi r4,hi(str) /* Name of exception */ ;\
l.ori r4,r4,lo(str) ;\
l.sw 4(r1),r4 ;\
l.mfspr r5,r0,SPR_EPCR_BASE /* Source of the interrupt */ ;\
l.jal _printf ;\
l.sw 8(r1),r5 ;\
;\
l.ori r3,r0,0xffff /* Failure RC */ ;\
l.jal _exit ;\
l.nop ;\
;\
l.rfe /* Never executed we hope */
The call to printf is expected to use a
standard format string (at the label .Lfmt)
which requires two other arguments, an identification string
(labeled by the parameter st to the macro) and
the program counter where the exception occurred (loaded from
Special Purpose Register SPR_EPCR_BASE). Return
from exception is provided as a formality, although the call to
exit means that we should never execute it.
Note that compiled C functions have their names prepended by underscore on the OpenRISC 1000 . It is these names that must be used from the assembler code.
The format and identification strings are read only data.
.section .rodata
.Lfmt: .string "Unhandled %s exception at address %08p\n"
.L200: .string "bus error"
.L300: .string "data page fault"
.L400: .string "instruction page fault"
.L500: .string "timer"
.L600: .string "alignment"
.L700: .string "illegal instruction"
.L800: .string "external interrupt"
.L900: .string "data TLB"
.La00: .string "instruction TLB"
.Lb00: .string "range"
.Lc00: .string "syscall"
.Ld00: .string "floating point"
.Le00: .string "trap"
.Lf00: .string "undefined 0xf00"
.L1000: .string "undefined 0x1000"
.L1100: .string "undefined 0x1100"
.L1200: .string "undefined 0x1200"
.L1300: .string "undefined 0x1300"
.L1400: .string "undefined 0x1400"
.L1500: .string "undefined 0x1500"
.L1600: .string "undefined 0x1600"
.L1700: .string "undefined 0x1700"
.L1800: .string "undefined 0x1800"
.L1900: .string "undefined 0x1900"
.L1a00: .string "undefined 0x1a00"
.L1b00: .string "undefined 0x1b00"
.L1c00: .string "undefined 0x1c00"
.L1d00: .string "undefined 0x1d00"
.L1e00: .string "undefined 0x1e00"
.L1f00: .string "undefined 0x1f00"
The first executable code is for the exception vectors. These must
go first in memory, so are placed in their own section,
.vectors. The linker/loader will ensure this
this code is placed first in memory (see Section 7.3).
The reset vector just jumps to the start code. The code is too
large to sit within the 0x100 bytes of an
exception vector entry, and is placed in the main text space, in
function _start.
.section .vectors,"ax"
/* 0x100: RESET exception */
.org 0x100
_reset:
/* Jump to program initialisation code */
l.movhi r2,hi(_start)
l.ori r2,r2,lo(_start)
l.jr r2
l.nop
The second vector, at address 0x200 is the bus
error exception vector. In normal use, like all other exceptions
it it causes exit and uses the
UNHANDLED_EXCEPTION macro.
However during start up, the code tries deliberately to write out of memory, to determine the end of memory, which will trigger this bus exception. For this a simple exception handler, which just skips the offending instruction is required.
The solution is to place this code first, followed by the
unhandled exception code. Once the end of memory has been located,
the initial code can be overwritten by l.nop
opcodes, so the exception will drop through to the
UNHANDLED_EXCEPTOON code.
.org 0x200
_buserr:
l.mfspr r24,r0,SPR_EPCR_BASE
l.addi r24,r24,4 /* Return one instruction on */
l.mtspr r0,r24,SPR_EPCR_BASE
l.rfe
_buserr_std:
UNHANDLED_EXCEPTION (.L200)
No effort is made to save the register (r24) that
is used in the handler. The start up code testing for end of memory
must not use this register.
The next exception, data page fault, at location 0x300, like all other exceptions is unhandled.
.org 0x300
UNHANDLED_EXCEPTION (.L300)
The OpenRISC 1000 ABI uses a falling stack. The linker will place code and static data at the bottom of memory (starting with the exception vectors). The heap then starts immediately after this, while the stack grows down from the end of memory.
The linker will supply the address for the start of heap (it is in
the global variable end). However we must find
the stack location by trying to write to memory above the heap to
determine the end of memory. Rather than write to every location,
the code assumes memory is a multiple of 64KB, and tries writing
to the last word of each 64KB block above end
until the value read back fails.
This failure will trigger a bus error exception, which must be
handled (see Section 5.2.1). The address used for
the start of the stack (which is also the last word of memory) is
stored in a global location, _stack (which C
will recognize as stack).
.section .data
.global _stack
_stack: .space 4,0
_start is declared so it looks like a C
function. GDB knows that _start is special,
and this will ensure that backtraces do not wind back further than
main. It is located in ordinary text space,
so will be placed with other code by the linker/loader.
.section .text
.global _start
.type _start,@function
_start:
The first memory location to test is found by rounding the
end location down to a multiple of 64KB, then
taking the last word of the 64KB above
that. 0xaaaaaaaa is used as the test word to
write to memory and read back.
l.movhi r30,hi(end)
l.ori r30,r30,lo(end)
l.srli r30,r30,16 /* Round down to 64KB boundary */
l.slli r30,r30,16
l.addi r28,r0,1 /* Constant 64KB in register */
l.slli r28,r28,16
l.add r30,r30,r28
l.addi r30,r30,-4 /* SP one word inside next 64KB? */
l.movhi r26,0xaaaa /* Test pattern to store in memory */
l.ori r26,r26,0xaaaa
Each 64KB block is tested by writing the test value and reading back to see if it matches.
.L3:
l.sw 0(r30),r26
l.lwz r24,0(r30)
l.sfeq r24,r26
l.bnf .L4
l.nop
l.j .L3
l.add r30,r30,r28 /* Try 64KB higher */
.L4:
The previous value is then the location to use for end of stack,
and should be stored in the _stack location.
l.sub r30,r30,r28 /* Previous value was wanted */
l.movhi r26,hi(_stack)
l.ori r26,r26,lo(_stack)
l.sw 0(r26),r30
The stack pointer (r1) and frame pointer
(r2) can be initialized with this value.
l.add r1,r30,r0
l.add r2,r30,r0
Having determined the end of memory, there is no need to handle
bus errors silently. The words of code between
_buserr and _buserr_std can
be replaced by l.nop.
l.movhi r30,hi(_buserr)
l.ori r30,r30,lo(_buserr)
l.movhi r28,hi(_buserr_std)
l.ori r28,r28,lo(_buserr_std)
l.movhi r26,0x1500 /* l.nop 0 */
l.ori r26,r26,0x0000
.L5:
l.sfeq r28,r30
l.bf .L6
l.nop
l.sw 0(r30),r26 /* Patch the instruction */
l.j .L5
l.addi r30,r30,4 /* Next instruction */
.L6:
![]() | Note |
|---|---|
It is essential that this code is before any data or instruction cache is initialized. Otherwise more complex steps would be required to enforce data write back and invalidate any instruction cache entry. |
The OpenRISC 1000 has optional instruction and data caches. If
these are declared (in the or1ksim-board.h
header), then they must be enabled by setting the appropriate bit
in the supervision register.
This is an example of machine specific initialization.
/* Cache initialisation. Enable IC and/or DC */
.if IC_ENABLE || DC_ENABLE
l.mfspr r10,r0,SPR_SR
.if IC_ENABLE
l.ori r10,r10,SPR_SR_ICE
.endif
.if DC_ENABLE
l.ori r10,r10,SPR_SR_DCE
.endif
l.mtspr r0,r10,SPR_SR
l.nop /* Flush the pipeline. */
l.nop
l.nop
l.nop
l.nop
.endif
BSS is the area of
memory used to hold static variables which must be initialized to
zero. Its start and end are defined by two variables from the
linker/loader, __bss_start and
end respectively.
l.movhi r28,hi(__bss_start)
l.ori r28,r28,lo(__bss_start)
l.movhi r30,hi(end)
l.ori r30,r30,lo(end)
.L1:
l.sw (0)(r28),r0
l.sfltu r28,r30
l.bf .L1
l.addi r28,r28,4
GCC may require constructors to be initialized at start up and
destructors to be called on exit. This behavior is captured in the
GCC functions __do_global_ctors and
__do_global_dtors. There is some complexity
associated with this functionality, since there may be separate
lists for the main code and shared libraries that are dynamically
loaded.
It is usual to wrap this functionality in two functions,
init and fini, which are
placed in their own sections, .init and
.fini. The .init section is
loaded before all other text sections and the
.fini section after all other text sections.
The start up code should call init to handle
any constructors.
l.jal init
l.nop
The fini function is passed to the library
function _atexit to ensure it is called on a
normal exit.
l.movhi r3,hi(fini)
l.jal _atexit
l.ori r3,r3,lo(fini) /* Delay slot */
Now that the C infrastructure is set up, it is appropriate to call any C functions that are used during initialization. In the OpenRISC 1000 case this is a function to initialize a UART. Only one version of the library actually has a UART. However it is easiest to substitute a dummy version of the initialization function in the version of the library without a UART, rather than making this function conditional.
l.jal __uart_init
l.nop
The final stage is to call the main program. In this simple
implementation there is no mechanism to pass arguments or
environments to main, so the arguments
argc, argv and
env (in r3,
r4 and r5) are set to
0, NULL and
NULL respectively.
l.or r3,r0,r0
l.or r4,r0,r0
l.jal _main
l.or r5,r0,r0 /* Delay slot */
If the main program returns, its result (held in
r11 on the OpenRISC 1000 ) will be a return code from
the program, which we pass to the exit.
l.jal _exit
l.addi r3,r11,0 /* Delay slot */
exit should not return, but just in case, we
can put the processor in a tight loop at this stage, in order to
ensure consistent behavior.
.L2:
l.j .L2
l.nop
The simplest way to provide a board support package is to implement the 18 system calls in non-reentrant fashion. For many bare metal implementations this is sufficient.
The simplest possible BSP supports just output to standard output and non input. We give the minimal implementation for such a system.
Where appropriate, we also show the OpenRISC 1000 implementation as a practical example.
This section duplicates much of the information found in the
newlib libc documentation [1]. It is
included here for completeness.
Many functions set an error code on failure in the global
variable, errno.
There is a slight complication with newlib, because
errno is not implemented as a variable, but a
macro (this make life easier for reentrant functions).
The solution for standard system call implementations, which must return an error code is to undefine the macro and use the external variable instead. At the head of such functions use the following.
#include <errno.h> #undef errno extern int errno;
![]() | Note |
|---|---|
|
The global variable, environ must point to a
null terminated list of environment variable name-value pairs.
For a minimal implementation it is sufficient to use an empty list as follows.
char *__env[1] = { 0 };
char **environ = __env;
Exit a program without any cleanup.
The OpenRISC 1000 s implementation makes use of the
l.nop opcode. This opcode takes a 16-bit
immediate operand. Functionally the operand has no effect on the
processor itself. However a simulator can inspect the operand to
provide additional behavior external to the machine.
When executing on Or1ksim, l.nop 1 causes a
tidy exit of the simulator, using the value in
r3 as the return code.
void
_exit (int rc)
{
register int t1 asm ("r3") = rc;
asm volatile ("\tl.nop\t%0" : : "K" (NOP_EXIT), "r" (t1));
while (1)
{
}
} /* _exit () */
Note the use of volatile. Otherwise there is a
strong possibility of an optimizing compiler recognizing that this
opcode does nothing (we are relying on a simulation side-effect)
and removing it.
![]() | Caution |
|---|---|
The name of this function is already namespace clean. If a
namespace clean implementation of the system calls has been
specified in |
For a namespace clean function, implement
_close, otherwise implement
close. The detailed implementation will
depend on the file handling functionality available.
In the minimal implementation, this function always fails, since there is only standard output, which is not a valid file to close. This implementation is sufficient for the OpenRISC 1000 .
#include <errno.h>
#undef errno
extern int errno;
int
_close (int file)
{
errno = EBADF;
return -1; /* Always fails */
} /* _close () */
For a namespace clean function, implement
_execve, otherwise implement
execve. The implementation of this
functionality will be tightly bound to any operating
infrastructure for handling multiple processes.
A minimal implementation, such as that for bare metal coding, only offers a single user thread of control. It is thus impossible to start a new process, so this function always fails.
#include <errno.h>
#undef errno;
extern int errno;
int
_execve (char *name,
char **argv,
char **env)
{
errno = ENOMEM;
return -1; /* Always fails */
} /* _execve () */
The choice of errno is somewhat
arbitrary. However no value for "no processes available" is
provided, and ENOMEM is the closest in meaning
to this.
For a namespace clean function, implement
_fork, otherwise implement
fork. The implementation of this
functionality will be tightly bound to any operating
infrastructure for handling multiple processes.
A minimal implementation, such as that for bare metal coding, only offers a single user thread of control. It is thus impossible to start a new process, so this function always fails.
#include <errno.h>
#undef errno
extern int errno;
int
_fork ()
{
errno = EAGAIN;
return -1; /* Always fails */
} /* _fork () */
The choice of errno is again somewhat
arbitrary. However no value for "no processes available" is
provided, and EAGAIN is the closest in meaning
to this.
For a namespace clean function, implement
_fstat, otherwise implement
fstat. The detailed implementation will
depend on the file handling functionality available.
A minimal implementation should assume that all files are character special devices and populate the status data structure accordingly.
#include <sys/stat.h>
int
_fstat (int file,
struct stat *st)
{
st->st_mode = S_IFCHR;
return 0;
} /* _fstat () */
The OpenRISC 1000 implementation requires two versions of this, one for the BSP using the console for output and one for the BSP using a UART and supporting both standard input and standard output.
Without a UART, the implementation still checks that the file descriptor is one of the two that are supported, and otherwise returns an error.
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#undef errno
extern int errno;
int
_fstat (int file,
struct stat *st)
{
if ((STDOUT_FILENO == file) || (STDERR_FILENO == file))
{
st->st_mode = S_IFCHR;
return 0;
}
else
{
errno = EBADF;
return -1;
}
} /* _fstat () */
The implementation when a UART is available is almost identical,
except that STDIN_FILENO is also an acceptable
file for which status can be provided.
For a namespace clean function, implement
_getpid, otherwise implement
getpid. The implementation of this
functionality will be tightly bound to any operating
infrastructure for handling multiple processes.
For a minimal implementation, with no processes, this can just return a constant. It is perhaps safer to return one rather than zero, to avoid issue with software that believes process zero is something special.
int
_getpid ()
{
return 1; /* Success */
} /* _getpid () */
For a namespace clean function, implement
_isatty, otherwise implement
isatty. The detailed implementation will
depend on the file handling functionality available.
This specifically checks whether a stream is a terminal. The minimal implementation only has the single output stream, which is to the console, so always returns 1.
int
_isatty (int file)
{
return 1;
} /* _isatty () */
![]() | Caution |
|---|---|
Contrary to the standard |
The OpenRISC 1000 version gives a little more detail, setting
errno if the stream is not standard output,
standard error or (for the UART version of the BSP) standard
input.
#include <errno.h>
#include <unistd.h>
#undef ERRNO
extern int errno;
int
_isatty (int file)
{
if ((file == STDOUT_FILENO) || (file == STDERR_FILENO))
{
return 1;
}
else
{
errno = EBADF;
return -1;
}
} /* _isatty () */
The UART version is almost identical, but also succeeds for standard input.
For a namespace clean function, implement
_kill, otherwise implement
kill. The implementation of this
functionality will be tightly bound to any operating
infrastructure for handling multiple processes.
A minimal implementation has no concept of either signals, nor of
processes to receive those signals. So this function should always
fail with an appropriate value in errno.
#include <errno.h>
#undef errno
extern int errno;
int
_kill (int pid,
int sig)
{
errno = EINVAL;
return -1; /* Always fails */
} /* _kill () */
For a namespace clean function, implement
_link, otherwise implement
link. The detailed implementation will
depend on the file handling functionality available.
A minimal implementation has no file system, so this function must
always fail, with an appropriate value set in
errno.
#include <errno.h>
#undef errno
extern int errno;
int
_link (char *old,
char *new)
{
errno = EMLINK;
return -1; /* Always fails */
} /* _link () */
For a namespace clean function, implement
_lseek, otherwise implement
lseek. The detailed implementation will
depend on the file handling functionality available.
A minimal implementation has no file system, so this function can return 0, indicating that the only stream (standard output) is positioned at the start of file.
#include <errno.h>
#undef errno
extern int errno;
int
_lseek (int file,
int offset,
int whence)
{
return 0;
} /* _lseek () */
The OpenRISC 1000 version is a little more detailed, returning zero only
if the stream is standard output, standard error or (for the
UART version of the BSP) standard input. Otherwise -1 is
returned and an appropriate error code set in
errno.
#include <errno.h>
#include <unistd.h>
#undef errno
extern int errno;
int
_lseek (int file,
int offset,
int whence)
{
if ((STDOUT_FILENO == file) || (STDERR_FILENO == file))
{
return 0;
}
else
{
errno = EBADF;
return (long) -1;
}
} /* _lseek () */
The UART version is almost identical, but also succeeds for standard input.
For a namespace clean function, implement
_open, otherwise implement
open. The detailed implementation will
depend on the file handling functionality available.
A minimal implementation has no file system, so this function must
always fail, with an appropriate error code set in
errno.
#include <errno.h>
#undef errno
extern int errno;
int
_open (const char *name,
int flags,
int mode)
{
errno = ENOSYS;
return -1; /* Always fails */
} /* _open () */
For a namespace clean function, implement
_read, otherwise implement
read. The detailed implementation will depend
on the file handling functionality available.
A minimal implementation has no file system. Rather than failing, this function returns 0, indicating end-of-file.
#include <errno.h>
#undef errno
extern int errno;
int
_read (int file,
char *ptr,
int len)
{
return 0; /* EOF */
} /* _read () */
The OpenRISC 1000 BSP without a UART is very similar to the minimal implementation, but checks that the stream is standard input before returning 0. For all other streams it returns an error.
#include <errno.h>
#include <unistd.h>
#undef errno
extern int errno;
int
_read (int file,
char *ptr,
int len)
{
if (STDIN_FILENO == file)
{
return 0; /* EOF */
}
else
{
errno = EBADF;
return -1;
}
} /* _read () */
The OpenRISC 1000 BSP with a UART is more complex. In this case, if the stream is standard input, a character is read (and optionally echoed) from the UART.
#include <errno.h>
#include <unistd.h>
#undef errno
extern int errno;
int
_read (int file,
char *buf,
int len)
{
if (STDIN_FILENO == file)
{
int i;
for (i = 0; i < len; i++)
{
buf[i] = _uart_getc ();
#ifdef UART_AUTO_ECHO
_uart_putc (buf[i]);
#endif
/* Return partial buffer if we get EOL */
if ('\n' == buf[i])
{
return i;
}
}
return i; /* Filled the buffer */
}
else
{
errno = EBADF;
return -1;
}
} /* _read () */
![]() | Caution |
|---|---|
The Or1ksim UART implementation only returns data when carriage return is hit, rather than as each character becomes available, which can lead to some unexpected behavior. |
For a namespace clean function, implement
_sbrk, otherwise implement
sbrk. This is one function for which there is
no default minimal implementation. It is important that it is
implemented wherever possible, since malloc
depends on it, and in turn many other functions depend on
malloc. In this application note, the OpenRISC 1000
implementation is used as an example.
As noted earlier (Section 5.2.2), the heap on
the OpenRISC 1000 grows up from the end of loaded program space, and the
stack grows down from the top of memory. The linker defines the
symbol _end, which will be the start of the
heap, whilst the C runtime initialization places the address of
the last work in memory in the global variable
_stack.
![]() | Caution |
|---|---|
|
Within a C program these two variables are referred to without their leading underscore—the C compiler prepends all variable names with underscore.
#include <errno.h>
#undef errno
extern int errno;
#define STACK_BUFFER 65536 /* Reserved stack space in bytes. */
void *
_sbrk (int nbytes)
{
/* Symbol defined by linker map */
extern int end; /* start of free memory (as symbol) */
/* Value set by crt0.S */
extern void *stack; /* end of free memory */
/* The statically held previous end of the heap, with its initialization. */
static void *heap_ptr = (void *)&end; /* Previous end */
if ((stack - (heap_ptr + nbytes)) > STACK_BUFFER )
{
void *base = heap_ptr;
heap_ptr += nbytes;
return base;
}
else
{
errno = ENOMEM;
return (void *) -1;
}
} /* _sbrk () */
The program always tries to keep a minimum of 65,536 (216) bytes spare for the stack.
![]() | Note |
|---|---|
This implementation defines |
![]() | Important |
|---|---|
The problem is that this now makes the function
non-reentrant. If the function were interrupted after the
assignment to
For simple systems, it would be sufficient to avoid using this
function in interrupt service routines. However the problem then
knowing which functions might call The problem cannot even be completely avoided by using reentrant functions (see Section 5.4), since just providing a per thread data structure does not help. The end of heap is a single global value. The only full solution is to surround the update of the global variable by a semaphore, and failing the allocation if the region is blocked (we cannot wait, or deadlock would result). |
For a namespace clean function, implement
_stat, otherwise implement
stat. The detailed implementation will
depend on the file handling functionality available.
A minimal implementation should assume that all files are character special devices and populate the status data structure accordingly.
#include <sys/stat.h>
int
_stat (char *file,
struct stat *st)
{
st->st_mode = S_IFCHR;
return 0;
} /* _stat () */
The OpenRISC 1000 implementation takes a stricter view of this. Since no named files are supported, this function always fails.
#include <errno.h>
#include <sys/stat.h>
#undef errno
extern int errno;
int
_stat (char *file,
struct stat *st)
{
errno = EACCES;
return -1;
} /* _stat () */
For a namespace clean function, implement
_times, otherwise implement
times. The implementation of this
functionality will be tightly bound to any operating
infrastructure for handling multiple processes.
A minimal implementation need not offer any timing information, so
should always fail with an appropriate value in
errno.
#include <errno.h>
#include <sys/times.h>
#undef errno
extern int errno;
int
_times (struct tms *buf)
{
errno = EACCES;
return -1;
} /* _times () */
For a namespace clean function, implement
_unlink, otherwise implement
unlink. The detailed implementation will
depend on the file handling functionality available.
A minimal implementation has no file system, so this function
should always fail, setting an appropriate value in
errno.
#include <errno.h>
#undef errno
extern int errno;
int
_unlink (char *name)
{
errno = ENOENT;
return -1; /* Always fails */
} /* _unlink () */
For a namespace clean function, implement
_wait, otherwise implement
wait. The implementation of this
functionality will be tightly bound to any operating
infrastructure for handling multiple processes.
A minimal implementation has only one process, so can wait for no
other process and should always fail with an appropriate value in
errno.
#include <errno.h>
#undef errno
extern int errno;
int
_wait (int *status)
{
errno = ECHILD;
return -1; /* Always fails */
} /* _wait () */
For a namespace clean function, implement
_write, otherwise implement
write. The detailed implementation will
depend on the file handling functionality available.
A minimal implementation only supports writing to standard output. The core of the implementation is:
int
_write (int file,
char *buf,
int nbytes)
{
int i;
/* Output character at at time */
for (i = 0; i < nbytes; i++)
{
outbyte (buf[i]);
}
return nbytes;
} /* _write () */
The function outbyte must use the
functionality of the target platform to write a single character
to standard output. For example copying the character to a serial
line for display. There can be no standard implementation of this
function.
For the OpenRISC 1000 two versions are needed one for the BSP without a UART one for the BSP with a UART.
Without a UART the implementation uses the
l.nop opcode with a parameter, as with the
implementation of _exit (Section 5.3.3). In this case the parameter 4 will cause the
simulator to print out the value in register r3
as an ASCII character.
#include "or1ksim-board.h"
static void
outbyte (char c)
{
register char t1 asm ("r3") = c;
asm volatile ("\tl.nop\t%0" : : "K" (NOP_PUTC), "r" (t1));
} /* outbyte () */
We also use a stricter implementation of the main
write function, only permitting a write if
the standard output or standard error stream is specified.
#include <errno.h>
#include <unistd.h>
#undef errno
extern int errno;
int
_write (int file,
char *buf,
int nbytes)
{
int i;
/* We only handle stdout and stderr */
if ((file != STDOUT_FILENO) && (file != STDERR_FILENO))
{
errno = EBADF;
return -1;
}
/* Output character at at time */
for (i = 0; i < nbytes; i++)
{
outbyte (buf[i]);
}
return nbytes;
} /* _write () */
For the BSP supporting a UART, all that is needed is to change
the outbyte function to use the routines to
drive the UART
static void
outbyte (char c)
{
_uart_putc (c);
} /* outbyte () */
The UART support routines are provided separately, driving the interface via its memory mapped registers.
Reentrancy is achieved by providing a global reentrancy structure,
struct _reent for each thread of control, which
holds thread specific versions of global data structures, such as
errno.
For a fully reentrant system, the BSP should implement the
reentrant versions of the system calls, having defined
syscall_dir=syscalls and added
-DREENTRANT_SYSCALLS_PROVIDED" to
newlib_cflags in
configure.host (see Section 3.3.1).
16 of the system calls have reentrant versions, which take the
suffix _r and are passed an additional first
argument, which is a pointer to the reentrancy structure,
struct reent for the thread of control. Thus
_close is replaced by
_close_r. The reentrant functions are
_close_r, _execve_r,
_fcntl_r, _fork_r,
_fstat_r, _getpid_r,
_link_r, _lseek_r,
_open_r, _read_r,
_sbrk_r, _stat_r,
_times_r, _unlink_r,
_wait_r and _write_r.
Two system calls do not need reentrant versions,
_kill and _exit, which are
provided as with non-reentrant versions.
For many of the reentrant functions, the behavior is almost
identical to that of the non-reentrant versions, beyond ensuring the
thread specific version of errno in the
reentrancy structure is used. Template versions can be found in the
libc/reent directory under the
newlib directory.
There are two ways in which the end user can be supported with these reentrancy functions. In the first it is up to the user to manage per thread reentrancy data structures and to call the reentrant functions explicitly.
However the more powerful solution is for the system to manage the reentrancy structure itself. The end user can call the standard functions, and they will be mapped to reentrant calls, passing in a reentrancy structure for the thread.
For this approach to be used, -D__DYNAMIC_REENT__
must be added to newlib_cflags and the BSP must
define the function __getreent, to return the
reentrancy structure for the current thread.
There is little documentation for the configuration and make files
for the BSPs. The general guideline is to copy the baseline
versions of these files in the default platform library,
libnosys, which is based on the minimal
implementations described in Section 5.3.
This application note uses the configuration and make files for the OpenRISC 1000 to illustrate the key principles.
Building the BSP only uses autoconf and autoheader, but not
automake. So there is a configure.in (or
configure.ac) and
Makefile.in, but no
Makefile.am. After making any changes it is
important to run autoconf and autoheader to regenerate the
configure script and header files. It will also
need a aclocal.m4 to give the local macro
definitions, which can be regenerated from the main libgloss
acinclude.m4 using aclocal. The command
needed are:
aclocal -I .. autoheader autoconf
aclocal need only be run the first time the directory is
created. autoheader is only needed if the BSP needs configuration
parameters from the system in a local config.h
file.
The configure.in for the OpenRISC 1000 is closely
based on the version in libnosys.
The initial declarations just need modifying to change the name of the package.
AC_PREREQ(2.59) AC_INIT(libor32.a,0.2.0) AC_CONFIG_HEADER(config.h)
There is then code to print a warning if the user has asked for shared library support (not available) and to locate the auxiliary tools for autoconf.
The script makes use of AC_CANONICAL_SYSTEM to
determine the system type and set appropriate variables. This is
now obsolete, and is replaced by
AC_CANONICAL_TARGET in the OpenRISC 1000 version. The
installed program names may be changed (for example by
--prefix), so we need
AC_ARG_PROGRAM and we locate the install
program.
AC_CANONICAL_TARGET AC_ARG_PROGRAM AC_PROG_INSTALL
The assumption is made that we are using GNU ld, so we define
HAVE_GNU_LD. The script in
libnosys does this in an obsolete way, which
is fixed in the OpenRISC 1000 script.
AC_DEFINE(HAVE_GNU_LD, 1, [Using GNU ld])
The standard script tests the canonical target name to determine if this is an ELF target. For OpenRISC 1000 this is always the case, so the test can be replaced by a simple declaration.
AC_DEFINE(HAVE_ELF, 1, [Using ELF format])
The script in libnosys then tests for the
presence of various features. Most of those are not relevant to
OpenRISC 1000 so can be left out. However we do need to determine what
the symbol prefix is. We could just define this as being '_', but
instead we let the script work it out, using the standard script's
code.
AC_CACHE_CHECK([for symbol prefix], libc_symbol_prefix, [dnl
cat > conftest.c <<\EOF
foo () { }
EOF
libc_symbol_prefix=none
if AC_TRY_COMMAND([${CC-cc} -S conftest.c -o - | fgrep "\$foo" > /dev/null]);
then
libc_symbol_prefix='$'
else
if AC_TRY_COMMAND([${CC-cc} -S conftest.c -o - | fgrep "_foo" > /dev/null]);
then
libc_symbol_prefix=_
fi
fi
rm -f conftest* ])
if test $libc_symbol_prefix != none; then
AC_DEFINE_UNQUOTED(__SYMBOL_PREFIX, "$libc_symbol_prefix", [symbol prefix])
else
AC_DEFINE(__SYMBOL_PREFIX, "", [symbol prefix])
fi
The code to define the various host tools used is
standard. However it will expect to find an
aclocal.m4 file in the directory. This can be
regenerated, or simply copied from the
libnosys directory. The variable
host_makefile_frag refers to standard make
script defining how compilation is carried out for the various
source files.
Finally the new Makefile can be generated in
a suitably initialized environment.
AC_CONFIG_FILES(Makefile,
ac_file=Makefile . ${libgloss_topdir}/config-ml.in,
srcdir=${srcdir}
target=${target}
with_multisubdir=${with_multisubdir}
ac_configure_args="${ac_configure_args} --enable-multilib"
CONFIG_SHELL=${CONFIG_SHELL-/bin/sh}
libgloss_topdir=${libgloss_topdir}
)
AC_OUTPUT
The first part of Makefile.in is just
transferring values from configure and is
used unchanged. The first potential variation is in multilib
handling. If your GCC implements multilibs, then that may need
to be mirrored in the BSP implementation. If not, then there is
no need to set MULTIDO and
MULTICLEAN to true and these
lines can be removed.
The Makefile.in in
libnosys includes an option to use
new versions of the loader and
assembler. However for most implementations, the plain tool is all
that is needed, so simple transfer of the configured values is
sufficient.
CC = @CC@ AS = @AS@ AR = @AR@ LD = @LD@ RANLIB = @RANLIB@
The main tools will already have been transformed to take account of any prefix (for example using or32-elf-gcc rather than gcc). However this has not been done for objdump and objcopy, so these are transformed here.
This is the point at which we define the BSPs to be built. Any
custom flags for the compilation can be added to
CFLAGS here.
CFLAGS = -g
We specify the C start up file(s) and BSP(s) to be built.
CRT0 = crt0.o BSP = libor32.a BSP_UART = libor32uart.a OUTPUTS = $(CRT0) $(BSP) $(BSP_UART)
![]() | Important |
|---|---|
It is important to define |
For each BSP we specify the object files from which it is built. For the plain OpenRISC 1000 BSP we have:
OBJS = _exit.o \
close.o \
environ.o \
execve.o \
fork.o \
fstat.o \
getpid.o \
isatty.o \
kill.o \
link.o \
lseek.o \
open.o \
read.o \
sbrk.o \
stat.o \
times.o \
uart-dummy.o \
unlink.o \
wait.o \
write.o
For the BSP with UART support we use many of the same files, but also have some different files.
UART_OBJS = _exit.o \
close.o \
environ.o \
execve.o \
fork.o \
fstat-uart.o \
getpid.o \
isatty-uart.o \
kill.o \
link.o \
lseek-uart.o \
open.o \
read-uart.o \
sbrk.o \
stat.o \
times.o \
uart.o \
unlink.o \
wait.o \
write-uart.o
At this point, the version of Makefile.in in
libnosys specifies explicitly the rules for
compiling object files from C and assembler source. However it is
better to incorporate a standard set of rules, using the
host_makefile_frag reference from the
configuration.
@host_makefile_frag@
This is the point at which to specify the first make rule to create the C runtime start up files and BSPs.
all: ${CRT0} ${BSP} ${BSP_UART}
The object files (including crt0.o) will be
built automatically, but we need rules to build the libraries from
them.
$(BSP): $(OBJS)
${AR} ${ARFLAGS} $@ $(OBJS)
${RANLIB} $@
$(BSP_UART): $(UART_OBJS)
${AR} ${ARFLAGS} $@ $(UART_OBJS)
${RANLIB} $@
The remainder of Makefile.in is standard. It
provides rules to clean the build directory, to install the
generated BSP(s) and C start up file(s), and rules to ensure
configure and Makefile
are regenerated when necessary.
There also hooks to create, clean and install any documentation (as info files), which are empty by default.
Very often these rules are sufficient, so long as all the entities
created have been listed in OUTPUTS. They
should be modified if necessary.
Newlib also builds a default BSP
libnosys.a. This can be used with the
-lnosys flag, and provides a convenient way of
testing that code will link correctly in the absence of a full BSP
The code can be found in the libnosys
sub-directory of the main libgloss directory.
For completeness, the configuration template file,
configure.in, in this directory should be updated
for any new target that is defining namespace clean versions of the
functions. Each such system is selected using a
case statement. The new entry for the OpenRISC 1000 is as
follows.
or32-*-*) ;;
Having updated the configuration template, run autoconf to
regenerate the configure script file.
Having made all the changes it is not time to configure, build and
install the system. The examples in this chapter for the OpenRISC 1000 assume a
unified source tree in srcw and a build directory,
bld-or32, with the installation directory prefix
/opt/or32-elf-new.
Newlib is configured as follows.
cd bld-or32 ../srcw/configure --target=or32-elf --with-newlib --prefix=/opt/or32-elf-new
![]() | Note |
|---|---|
Other options may be needed on the command line if other GNU tools
are being built. However these are the options relevant to |
The system is built using make from within the
bld-or32 directory.
make all-target-newlib make all-target-libgloss
Testing newlib and libgloss requires further configuration. The
details are discussed later in this application note (see Chapter 8). For now this step can be skipped.
Normally newlib will be installed in a standard place with the rest
of the tool chain. Its headers will go in the
include directory within the target specific
installation directory. The C runtime start up file, the newlib
libraries themselves and BSP libraries will go in the
lib directory within the target specific
installation directory.
This arrangement ensures that GCC will pick up the headers and libraries automatically and in the correct sequence.
However if newlib is not the only C library, then this may be
inconvenient. For example the OpenRISC 1000 usually uses uClibc, and
only uses newlib when regression testing the GNU tool chain.
The solution is to move the newlib headers and libraries to a custom
location and modify GCC to search there when newlib is being used
(see Section 7.2).
This is achieved with a simple script at the end of build and
install. For example with the OpenRISC 1000 the following command will
suffice, where the prefix used for the entire tool chain build is in
${install_dir}.
mkdir -p ${install_dir}/or32-elf/newlib
rm -rf ${install_dir}/or32-elf/newlib-include
mv ${install_dir}/or32-elf/include ${install_dir}/or32-elf/newlib-include
mv ${install_dir}/or32-elf/lib/*.a ${install_dir}/or32-elf/newlib
mv ${install_dir}/or32-elf/lib/crt0.o ${install_dir}/or32-elf/newlib
In general GCC will work with newlib with no change. All that is
needed is to include the BSP library on the command line.
However it is convenient to modify GCC so that it picks up the BSP
automatically. This is particularly useful when newlib has been
installed in a custom location (see Section 7.1).
This is achieved by adding machine specific options to GCC, and
modifying the Spec definitions to pick up the newlib libraries when
the relevant option is in effect.
All the relevant files are found in the
gcc/config/
directory of GCC. For the 32-bit OpenRISC 1000 this is
targetgcc/config/or32.
Machine specific options are described in the
file. By
convention machine specific options begin with 'm'.
target.opt
For the OpenRISC 1000 we define two options,
-mor32-newlib and
-mor32-newlib-uart for the plain and UART
enabled versions of the BSP respectively.
For each option we provide its name on one line, any parameters on
subsequent lines and a final line of description. In this case the
only parameter is to say that the parameter can only appear in its
positive form (i.e. --mno-or32-newlib is not
permitted).
mor32-newlib Target RejectNegative Link with the OR32 newlib library mor32-newlib-uart Target RejectNegative Link with the OR32 newlib UART library
These parameters can then be used elsewhere.
GCC calls a number of subsidiary programs (the compiler itself, the assembler, the linker etc). The arguments to these are built up from the parametrized strings, known as Spec strings.
This application note cannot describe the huge range of possible
parameters. However we will use one example to show what is
possible. The changes are all made to the definitions of the strings
in . In the
case of the OpenRISC 1000 this is target.hor32.h.
We need to make four changes.
We need to tell the C preprocessor to look for headers in the relocated newlib library directory.
We need to tell the linker to pick up the newlib C runtime start up file.
We need to tell the linker where to find the newlib libraries.
We need to tell the linker to include the BSP library in the right place.
All of these changes will require knowing the location of the
target specific installation directory. Unfortunately there is no
Spec parameter giving this. However we can construct it from two
definitions available when compiling
GCC. STANDARD_EXEC_PREFIX is the directory
where the GCC executables will be found. Two directories up from
that will be the main prefix directory. The target machine is
specified in DEFAULT_TARGET_MACHINE. So
concatenating the three strings yields the target specific
directory.
STANDARD_EXEC_PREFIX "/../../" DEFAULT_TARGET_MACHINE
The newlib headers are in the subdirectory
newlib-include and the C runtime start up and
libraries in newlib.
We define a new string, TARGET_PREFIX based on
the concatenation.
#define CONC_DIR(dir1, dir2) dir1 "/../../" dir2 #define TARGET_PREFIX CONC_DIR (STANDARD_EXEC_PREFIX, DEFAULT_TARGET_MACHINE)
Defined constants cannot be used directly in Spec strings, but we
can make them available by defining the macro
EXTRA_SPECS.
#define EXTRA_SPECS \
{ "target_prefix", TARGET_PREFIX }
The Spec string target_prefix is now available
to be used in other Spec strings.
Additional arguments to the C preprocessor are defined in
CPP_SPEC. The newlib header directory should
we searched after any user specified header directories (from
-I arguments) and after the GCC system
headers. So it is specified using the
-idirafter option.
#undef CPP_SPEC
#define CPP_SPEC "%{mor32-newlib*:-idirafter %(target_prefix)/newlib-include}"
This specifies that any option beginning
-mor32-newlib should be replaced by the string
-idirafter followed by the
newlib-incldue subdirectory of the
target_prefix directory.
So so for example, if we build the OpenRISC 1000 GCC with
--prefix=/opt/or32-elf-new, we would have
STANDARD_EXEC_PREFIX set to
/opt/or32-elf-new/lib/gcc and
DEFAULT_TARGET_MACHINE set to
or32-elf. The Spec variable
target_prefix would therefore be
/opt/or32-elf-new/lib/gcc/../../or32-elf and
thus the C preprocessor would have the following added to its
option list.
-idirafter /opt/or32-elf-new/lib/gcc/../../or32-elf/newlib-include"
This substitution only occurs when
-mor32-newlib or
-mor32-newlib-uart is specified, which is
exactly the behavior desired.
![]() | Note |
|---|---|
If |
crt0.o should be the first object file or
library specified to the linker. This is covered by
STARTFILE_SPEC.
This string already has a partial definition, to look for
crt0.o in a standard place, and to include
the crtinit.o file from a standard place.
#undef STARTFILE_SPEC
#define STARTFILE_SPEC "%{!shared:crt0%s crtinit.o%s}"
So long as -shared is not specified as an
option, this looks for crt0.o and
crtinit.o in standard directories and
substitutes them on the command line (the suffix
%s indicates that the preceding file should be
searched for in standard directories, and its name expanded to
include the directory name).
This needs changing to indicate that if
-mor32-newlib or
-mor32-newlib-uart is specified, then
crt0.o should be taken from the newlib
directory.
#define STARTFILE_SPEC \
"%{!shared:%{mor32-newlib*:%(target_prefix)/newlib/crt0.o} \
%{!mor32-newlib*:crt0.o%s} crtinit.o%s}"
Note that we must also include the case that when neither of the
newlib options is specified, then crt0.o
will be searched for in standard directories.
![]() | Note |
|---|---|
If |
We need to tell the linker where to look for newlib
libraries. This is achieved in a similar manner to the search for
the headers, but using the -L option and
LINK_SPEC.
#undef LINK_SPEC
#define LINK_SPEC "%{mor32-newlib*:-L%(target_prefix)/newlib}"
![]() | Note |
|---|---|
If |
The libraries searched by GCC are by default specified to be
-lgcc -lc -lgcc, with variants if
profiling is being used. When a BSP is used, it must be searched
after libc, but that can leave references unresolved, so libc
must be searched again afterward.
The sequence of libraries to be searched between the two searches
of libgcc is given in LIB_SPEC. It already
has a definition.
#define LIB_SPEC "%{!p:%{!pg:-lc}}%{p:-lc_p}%{pg:-lc_p}
This specifies a variant library when profiling is in
place. newlib does not offer profiling support, but it does have
a debugging version of the library (libg).
#undef LIB_SPEC
#define LIB_SPEC "%{!mor32-newlib*:%{!p:%{!pg:-lc}}%{p:-lc_p}%{pg:-lc_p}} \
%{mor32-newlib:%{!g:-lc -lor32 -lc} \
%{g:-lg -lor32 -lg}} \
%{mor32-newlib-uart:%{!g:-lc -lor32uart -lc} \
%{g:-lg -lor32uart -lg}}"
This ensures that the correct BSP library will be used,
according the the option selected, and that if
-g is specified on the command line, the
debugging version of the C library (libg) will be used instead.
Even if the newlib is not relocated as described in Section 7.1, then this Spec change is
required in order to ensure the correct libraries are picked up.
In general changes to the linker are not needed. Instead the BSP
should make use of information provided by the standard linker. For
example in the definition of sbrk (see Section 5.3.15) the code uses the _end symbol
defined by the linker at the end of the loaded image to be the start
of the heap.
Newlib and libgloss both come with DejaGnu test infrastructures,
although as noted in Section 8.2, the
libgloss infrastructure is non-functional.
The total number of tests is modest (24 tests in release 1.18.0). In practice much of the testing is achieved through the GCC test suite (40,000+ tests) and the GDB test suite (5,000+ tests).
Like all tools, newlib can be tested with a DejaGnu
test suite. DejaGnu must be installed on the test machine.
If you already have testing set up for other tools in the GNU tool
chain on your target, then you can skip the remainder of this section,
and just test newlib from the build directory with the following.
cd bld-or32 make check-target-newlib
If this is the first time you have tried testing, then you'll need to set up your system appropriately. Once this is done, you will be able to test all the GNU tool chain components.
The tests require a target on which to run the tests. This can be a physical machine, or it can be a simulator for the target architecture.
The details of the target are provided in an expect board
configuration file. This is referenced from the DejaGnu global
configuration file. The environment variable
DEJAGNU should point to the global configuration
file.
For the OpenRISC 1000 , the global configuration file is in
site.exp and a subdirectory,
boards contains
or32-sim.exp, which is the board configuration
file for the OpenRISC simulator target.
The site.exp file has two functions. First, it
must add the boards directory to the list of
board directories to search. Secondly, it must ensure that the target
triplet name is mapped to the name of the board configuration file.
This site.exp file can be reused for checking
other targets in the GNU tool chain, which may have a different
test suite hierarchy. We cannot therefore just reference the
boards directory relative to the test
directory. All we know is that it will be in one of the directories
above, and there is no other boards directory in the hierarchy, so we
add all the possible directories. Not elegant, but effective.
#Make sure we look in the right place for the board description files
if ![info exists boards_dir] {
set boards_dir {}
}
# Crude way of finding the boards directory
lappend boards_dir "${tool_root_dir}/../boards"
lappend boards_dir "${tool_root_dir}/../../boards"
lappend boards_dir "${tool_root_dir}/../../../boards"
lappend boards_dir "${tool_root_dir}/../../../../boards"
global target_list
case "$target_triplet" in {
{ "or32-*-elf" } {
set target_list { "or32-sim" }
}
}
Within the boards directory, the board
configuration file, or32-sim.cfg gives all the
details required for the configuration.
The tool chains supported by this board are specified first. In the case of the OpenRISC 1000 , only one is supported.
set_board_info target_install {or32-elf}
We then need to load some generic routines, and the generic board configuration.
load_generic_config "sim" load_base_board_description "basic-sim"
The default settings assume that a program is executed on the target
by a command named run, built in a target specific
subdirectory of the top level sim directory. In
the case of the OpenRISC 1000 this directory would be
sim/or32.
At a minimum, run takes as argument an executable to run, and returns the exit code from that executable as its result.
The sim directory is usually distributed as part
of GDB. Simulators may be derived from CGEN specifications of the
architecture, or by integrating third party simulators. The latter is
the case for the OpenRISC 1000 .
The default settings for a target are obtained using the
setup_sim procedure.
setup_sim or32
The remainder of the file is used to configure variations on the
default settings. This is done using the
set_board_info procedure.
The OpenRISC 1000 simulator needs an additional argument, which is a
configuration file for the simulator. We know that file will be in the
libgloss target directory and named sim.cfg. We
can use the lookfor_file procedure to search up
from the current source directory to locate the file.
set cfg_file [lookfor_file ${srcdir} libgloss/or32/sim.cfg]
set_board_info sim,options "-a \"-f ${cfg_file}\""
A number of helpful procedures make it easy to locate parts of the
tool chain and their default arguments. For the OpenRISC 1000 we make one
change, which is to specify -mor32-newlib for the
linker flags, so that the newlib BSP will be used.
process_multilib_options "" set_board_info compiler "[find_gcc]" set_board_info cflags "[libgloss_include_flags] [newlib_include_flags]" set_board_info ldflags "[libgloss_link_flags] -mor32-newlib [newlib_link_flags]" set_board_info ldscript ""
Not all targets have the same functionality, and the remaining options
specify those limitations. This is a generic board specification, so
some of these apply to testing components other than newlib. The
limitations specified will mean that some tests, which are
inappropriate do not run.
For the OpenRISC 1000 we specify that the simulator is fast, that programs it runs cannot be passed arguments, that it does not support signals (for testing GDB) and that the maximum stack size is 64KB (for testing GCC).
set_board_info slow_simulator 0 set_board_info noargs 1 set_board_info gdb,nosignals 1 set_board_info gcc,stack_size 65536
We can now set DEJAGNU to point to the global
configuration directory, change to the build directory and run the
make command to check newlib.
export DEJAGNU=`pwd`/site.exp cd bld-or32 make check-target-newlib
The good thing is that this set up is generic across all the GNU tool chain, so all the other tools can be checked in the same way.
The same technique can be used to run the tests against physical hardware rather than a simulator. The setup of the board configuration is rather more complicated, with considerable variation for different arrangements.
The detail is beyond the scope of this application note, but is well described in Dan Kegel's Crosstool project [2].
In principle, having set up newlib testing, testing libgloss
should be as simple as:
cd bld-or32 make check-target-libgloss
Unfortunately, the current newlib release (at the time of writing
1.18.0) does not implement testing for libgloss. The
testsuite subdirectory exists, but the code to
configure it is currently commented out in
configure.in.
It should not be difficult to build the infrastructure. However
as noted at the start of this chapter, testing of newlib and
libgloss is as much achieved through GCC and GDB testing as
through the modest number of tests within newlib
This summary can be used as a checklist when creating a new port of
newlib. The configuration and build steps are typically encapsulated
in a simple shell script, which builds, tests and installs the entire
GNU tool chain as well as newlib and libgloss
Throughout this checklist, the new target architecture is referred to as
target. It is recommended newlib and
libgloss are built as part of a unified source tree including the
newlib distribution (see Section 2.1).
Edit newlib/configure.host adding entries for
the new target (Section 3.3).
Decide whether to implement reentrant or non-reentrant system calls and whether to use namespace clean system call names (Section 3.2).
Add a newlib machine subdirectory for the new target,
newlib/libc/machine/
(Section 4.1).
target
Modify configure.in in
newlib/libc/machine to configure the new
target subdirectory and run autoconf in
newlib/libc/machine to regenerate the
configure script (Section 4.1.1).
Implement setjmp and
longjmp in the target specific machine
directory,
newlib/libc/machine/
(Section 4.1.2).
target
Copy and modify Makefile.am and
configure.in from the fr30 directory and
run aclocal, autoconf and automake in
newlib/libc/machine/
to regenerate the targetconfigure script and
Makefile template. (Section 4.1.3).
Modify newlib header files (Section 4.2).
Add entries in newlib/libc/include/ieeefp.c
(Section 4.2.1)
Add entry in in
newlib/libc/include/setjmp.c (Section 4.2.2)
Add entries in
newlib/libc/include/sys/config.h (Section 4.2.3).
Optionally add other custom headers in
newlib/libc/machine/target/machine (Section 4.2.4).
Add a libgloss platform directory,
libgloss/
(Section 5.1).
target
Modify libgloss/configure.in to configure the
platform subdirectory and run autoconf in the
libgloss directory to regenerate the
configure script (Section 5.1.1).
Implement the Board Support Package(s) for the target (Chapter 5).
Implement the C Runtime start up,
crt0.o for each BSP (Section 5.2).
Implement the environment global variable and 18 system call
functions for each BSP following the convention namespace and
reentrancy conventions specified in
newlib/configure.host (Section 5.3 and Section 5.4).
Create
libgloss/
and
target/Makefile.inlibgloss/,
based on the versions in the
target/configure.aclibgloss/libnosys directory and run
aclocal and autoconf in
libgloss/
(Section 5.5).
target
If necessary update
libgloss/libnosys/configure.in to indicate the
target is using namespace clean system calls and run autoconf in
libgloss/libnosys (Section 5.6).
Modify GCC for newlib (Section 7.2).
Optionally add target specific option(s) to specify newlib
BSP(s) (Section 7.2.1).
Optionally specify the location of newlib headers, the BSP C
runtime start up file and the newlib libraries, if they have been
moved from their standard locations and/or names (Section 7.2.2)
Specify the libgloss BSP library to be linked, ensuring
malloc and free are
linked in if required (the section called “
Adding a BSP to the link line.
”).
Ensure the linker scripts are suitable for use with newlib (Section 7.3).
Configure and build newlib and libgloss (Chapter 6).
Optionally move the newlib header directory, libraries, C
start-up and BSP(s) to a custom location (Section 7.1).
Rebuild GCC
Rebuild ld if any linker scripts have been changed.
Test newlib (Section 8.1).
Install newlib and libgloss (Chapter 6).
Reinstall GCC
Reinstall ld if any linker scripts have been changed.
You should now have a working newlib implementation integrated within
your GNU tool chain.
The definition of how registers are used during function call and return for a particular architecture.
A multi-byte number representation, in which the most significant byte is placed first (i.e. at the lowest address) in memory.
See also little endian
The low level interface between an operating system or library and the underlying physical platform.
Universally known by its acronym (the full name is a historical relic), this refers to an area of storage used for holding static variables and initialized to zero.
A multi-byte number representation, in which the least significant byte is placed first (i.e. at the lowest address) in memory.
See also big endian
A function which is reentrant may be safely called from another thread of control while an initial thread's flow of control is still within the function.
In general a function will be reentrant if it changes no static state.
A set of up to 216 32-bit registers used to hold additional information controlling the operation of the OpenRISC 1000
An OpenRISC 1000 special purpose register holding information about the most recent test result, whether the processor is in supervisor mode, and whether certain functions (cache etc) are enabled.
See also special purpose register
[1]
The Red Hat Newlib C Library
Available at sourceware.org/newlib/libc.html.
[2] The Crosstool Project, Available at www.kegel.com/crosstool.