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 target
newlib
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
target
gcc/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 target
configure
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.