TMS320C28x Optimizing C/C++ Compiler v15.9.0.STS User's Guide
SPRU514 - REVISED SEPTEMBER, 2015

8 Using Run-Time-Support Functions and Building Libraries

Some of the features of C/C++ (such as I/O, dynamic memory allocation, string operations, and trigonometric functions) are provided as an ANSI/ISO C/C++ standard library, rather than as part of the compiler itself. The TI implementation of this library is the run-time-support library (RTS). The C/C++ compiler implements the ISO standard library except for those facilities that handle exception conditions, signal, and locale issues (properties that depend on local language, nationality, or culture). Using the ANSI/ISO standard library ensures a consistent set of functions that provide for greater portability.

In addition to the ANSI/ISO-specified functions, the run-time-support library includes routines that give you processor-specific commands and direct C language I/O requests. These are detailed in Section 8.1 and Section 8.2.

A library-build utility is provided with the code generation tools that lets you create customized run-time-support libraries. This process is described in Section 8.4 .

8.1 C and C++ Run-Time Support Libraries

TMS320C28x compiler releases include pre-built run-time support (RTS) libraries that provide all the standard capabilities. Separate libraries are provided for FPU support and C++ exception support. See Section 8.1.7 for information on the library-naming conventions.

The run-time-support library contains the following:

  • ANSI/ISO C/C++ standard library
  • C I/O library
  • Low-level support functions that provide I/O to the host operating system
  • Fundamental arithmetic routines
  • System startup routine, _c_int00
  • Compiler helper functions (to support language features that are not directly efficiently expressible in C/C++)

The run-time-support libraries do not contain functions involving signals and locale issues.

The C++ library supports wide chars, in that template functions and classes that are defined for char are also available for wide char. For example, wide char stream classes wios, wiostream, wstreambuf and so on (corresponding to char classes ios, iostream, streambuf) are implemented. However, there is no low-level file I/O for wide chars. Also, the C library interface to wide char support (through the C++ headers <cwchar> and <cwctype>) is limited as described in Section 6.1.

TI does not provide documentation that covers the functionality of the C++ library. TI suggests referring to one of the following sources:

  • The Standard C++ Library: A Tutorial and Reference,Nicolai M. Josuttis, Addison-Wesley, ISBN 0-201-37926-0
  • The C++ Programming Language (Third or Special Editions), Bjarne Stroustrup, Addison-Wesley, ISBN 0-201-88954-4 or 0-201-70073-5

8.1.1 Linking Code With the Object Library

When you link your program, you must specify the object library as one of the linker input files so that references to the I/O and run-time-support functions can be resolved. You can either specify the library or allow the compiler to select one for you. See Section 4.3.1 for further information.

When a library is linked, the linker includes only those library members required to resolve undefined references. For more information about linking, see the TMS320C28x Assembly Language Tools User's Guide.

C, C++, and mixed C and C++ programs can use the same run-time-support library. Run-time-support functions and variables that can be called and referenced from both C and C++ will have the same linkage.

8.1.2 Header Files

You must use the header files provided with the compiler run-time support when using functions from C/C++ standard library. Set the C2000_C_DIR environment variable to the specific include directory: "include\lib".

8.1.3 Modifying a Library Function

You can inspect or modify library functions by examining the source code in the lib/src subdirectory of the compiler installation. For example, C:\ti\ccsv6\tools\compiler\c2000_#.#.#\lib\src.

One you have located the relevant source code, change the specific function file and rebuild the library.

You can use this source tree to rebuild the rts2800_ml.lib library or to build a new library. See Section 8.4 for details on building.

8.1.4 Support for String Handling

The library includes the header files <string.h> and <strings.h>, which provide the following functions for string handling beyond those required.

  • string.h
    • strdup(), which duplicates a string
  • strings.h
    • bcmp(), which is equivalent to memcmp()
    • bcopy(), which is equivalent to memmove()
    • bzero(), which replaces memory locations with zero-valued bytes
    • ffs(), which finds the first bit set and returns the index of that bit
    • index(), which is equivalent to strchr()
    • rindex(), which is equivalent to strrchr()
    • strcasecmp() and strncasecmp(), which perform case-insensitive string comparisons

8.1.5 Minimal Support for Internationalization

The library includes the header files <locale.h>, <wchar.h>, and <wctype.h>, which provide APIs to support non-ASCII character sets and conventions. Our implementation of these APIs is limited in the following ways:

  • The library has minimal support for wide and multibyte characters. The type wchar_t is implemented as int. The wide character set is equivalent to the set of values of type char. The library includes the header files <wchar.h> and <wctype.h> but does not include all the functions specified in the standard. So-called multibyte characters are limited to single characters. There are no shift states. The mapping between multibyte characters and wide characters is simple equivalence; that is, each wide character maps to and from exactly a single multibyte character having the same value.
  • The C library includes the header file <locale.h> but with a minimal implementation. The only supported locale is the C locale. That is, library behavior that is specified to vary by locale is hard-coded to the behavior of the C locale, and attempting to install a different locale via a call to setlocale() will return NULL.

8.1.6 Allowable Number of Open Files

In the <stdio.h> header file, the value for the macro FOPEN_MAX has the value of the macro _NFILE, which is set to 10. The impact is that you can only have 10 files simultaneously open at one time (including the pre-defined streams - stdin, stdout, stderr).

The C standard requires that the minimum value for the FOPEN_MAX macro is 8. The macro determines the maximum number of files that can be opened at one time. The macro is defined in the stdio.h header file and can be modified by changing the value of the _NFILE macro and recompiling the library.

8.1.7 Library Naming Conventions

By default, the linker uses automatic library selection to select the correct run-time-support library (see Section 4.3.1.1) for your application. If you select the library manually, you must select the matching library according to the following naming scheme:

rts2800_ml.libor rts2800_fpu32.lib

rts2800_ml The default C28x support library
rts2800_fpu32 Indicates support for FPU targets

8.2 The C I/O Functions

The C I/O functions make it possible to access the host's operating system to perform I/O. The capability to perform I/O on the host gives you more options when debugging and testing code.

The I/O functions are logically divided into layers: high level, low level, and device-driver level.

With properly written device drivers, the C-standard high-level I/O functions can be used to perform I/O on custom user-defined devices. This provides an easy way to use the sophisticated buffering of the high-level I/O functions on an arbitrary device.

NOTE

Debugger Required for Default HOST

For the default HOST device to work, there must be a debugger to handle the C I/O requests; the default HOST device cannot work by itself in an embedded system. To work in an embedded system, you will need to provide an appropriate driver for your system.

NOTE

C I/O Mysteriously Fails

If there is not enough space on the heap for a C I/O buffer, operations on the file will silently fail. If a call to printf() mysteriously fails, this may be the reason. The heap needs to be at least large enough to allocate a block of size BUFSIZ (defined in stdio.h) for every file on which I/O is performed, including stdout, stdin, and stderr, plus allocations performed by the user's code, plus allocation bookkeeping overhead. Alternately, declare a char array of size BUFSIZ and pass it to setvbuf to avoid dynamic allocation. To set the heap size, use the --heap_size option when linking (refer to the Linker Description chapter in the TMS320C28x Assembly Language Tools User's Guide).

NOTE

Open Mysteriously Fails

The run-time support limits the total number of open files to a small number relative to general-purpose processors. If you attempt to open more files than the maximum, you may find that the open will mysteriously fail. You can increase the number of open files by extracting the source code from rts.src and editing the constants controlling the size of some of the C I/O data structures. The macro _NFILE controls how many FILE (fopen) objects can be open at one time (stdin, stdout, and stderr count against this total). (See also FOPEN_MAX.) The macro _NSTREAM controls how many low-level file descriptors can be open at one time (the low-level files underlying stdin, stdout, and stderr count against this total). The macro _NDEVICE controls how many device drivers are installed at one time (the HOST device counts against this total).

8.2.1 High-Level I/O Functions

The high-level functions are the standard C library of stream I/O routines (printf, scanf, fopen, getchar, and so on). These functions call one or more low-level I/O functions to carry out the high-level I/O request. The high-level I/O routines operate on FILE pointers, also called streams.

Portable applications should use only the high-level I/O functions.

To use the high-level I/O functions, include the header file stdio.h, or cstdio for C++ code, for each module that references a C I/O function.

For example, given the following C program in a file named main.c:

#include <stdio.h>void main() { FILE *fid; fid = fopen("myfile","w"); fprintf(fid,"Hello, world\n"); fclose(fid); printf("Hello again, world\n"); }

Issuing the following compiler command compiles, links, and creates the file main.out from the run-time-support library:

cl2000 main.c --run_linker --heap_size=400 --library=rts2800_ml.lib --output_file=main.out

Executing main.out results in

Hello, world

being output to a file and

Hello again, world

being output to your host's stdout window.

8.2.2 Overview of Low-Level I/O Implementation

The low-level functions are comprised of seven basic I/O functions: open, read, write, close, lseek, rename, and unlink. These low-level routines provide the interface between the high-level functions and the device-level drivers that actually perform the I/O command on the specified device.

The low-level functions are designed to be appropriate for all I/O methods, even those which are not actually disk files. Abstractly, all I/O channels can be treated as files, although some operations (such as lseek) may not be appropriate. See Section 8.2.3 for more details.

The low-level functions are inspired by, but not identical to, the POSIX functions of the same names.

The low-level functions operate on file descriptors. A file descriptor is an integer returned by open, representing an opened file. Multiple file descriptors may be associated with a file; each has its own independent file position indicator.

open
Open File for I/O
Syntax

#include <file.h>

int open (const char * path , unsigned flags , int file_descriptor );

Description

The open function opens the file specified by path and prepares it for I/O.

  • The path is the filename of the file to be opened, including an optional directory path and an optional device specifier (see Section 8.2.5).
  • The flags are attributes that specify how the file is manipulated. The flags are specified using the following symbols:
  • O_RDONLY (0x0000) /* open for reading */ O_WRONLY (0x0001) /* open for writing */ O_RDWR (0x0002) /* open for read & write */ O_APPEND (0x0008) /* append on each write */ O_CREAT (0x0200) /* open with file create */ O_TRUNC (0x0400) /* open with truncation */ O_BINARY (0x8000) /* open in binary mode */

    Low-level I/O routines allow or disallow some operations depending on the flags used when the file was opened. Some flags may not be meaningful for some devices, depending on how the device implements files.

  • The file_descriptor is assigned by open to an opened file.
  • The next available file descriptor is assigned to each new file opened.

Return Value

The function returns one of the following values:

non-negative file descriptor if successful
-1 on failure
close
Close File for I/O
Syntax

#include <file.h>

int close (int file_descriptor );

Description

The close function closes the file associated with file_descriptor.

The file_descriptor is the number assigned by open to an opened file.

Return Value

The return value is one of the following:

0 if successful
-1 on failure
read
Read Characters from a File
Syntax

#include <file.h>

int read (int file_descriptor , char * buffer , unsigned count );

Description

The read function reads count characters into the buffer from the file associated with file_descriptor.

  • The file_descriptor is the number assigned by open to an opened file.
  • The buffer is where the read characters are placed.
  • The count is the number of characters to read from the file.
Return Value

The function returns one of the following values:

0 if EOF was encountered before any characters were read
# number of characters read (may be less than count)
-1 on failure
write
Write Characters to a File
Syntax

#include <file.h>

int write (int file_descriptor , const char * buffer , unsigned count );

Description

The write function writes the number of characters specified by count from the buffer to the file associated with file_descriptor.

  • The file_descriptor is the number assigned by open to an opened file.
  • The buffer is where the characters to be written are located.
  • The count is the number of characters to write to the file.
Return Value

The function returns one of the following values:

# number of characters written if successful (may be less than count)
-1 on failure
lseek
Set File Position Indicator
Syntax for C

#include <file.h>

off_t lseek (int file_descriptor , off_t offset , int origin );

Description

The lseek function sets the file position indicator for the given file to a location relative to the specified origin. The file position indicator measures the position in characters from the beginning of the file.

  • The file_descriptor is the number assigned by open to an opened file.
  • The offset indicates the relative offset from the origin in characters.
  • The origin is used to indicate which of the base locations the offset is measured from. The origin must be one of the following macros:
  • SEEK_SET (0x0000) Beginning of file

    SEEK_CUR (0x0001) Current value of the file position indicator

    SEEK_END (0x0002) End of file

Return Value

The return value is one of the following:

# new value of the file position indicator if successful
(off_t)-1 on failure
Delete File
Syntax

#include <file.h>

int unlink (const char * path );

Description

The unlink function deletes the file specified by path. Depending on the device, a deleted file may still remain until all file descriptors which have been opened for that file have been closed. See Section 8.2.3.

The path is the filename of the file, including path information and optional device prefix. (See Section 8.2.5.)

Return Value

The function returns one of the following values:

0 if successful
-1 on failure
rename
Rename File
Syntax for C

#include {<stdio.h> | <file.h>}

int rename (const char * old_name , const char * new_name );

Syntax for C++

#include {<cstdio> | <file.h>}

int std::rename (const char * old_name , const char * new_name );

Description

The rename function changes the name of a file.

  • The old_name is the current name of the file.
  • The new_name is the new name for the file.
  • NOTE

    The optional device specified in the new name must match the device of the old name. If they do not match, a file copy would be required to perform the rename, and rename is not capable of this action.

Return Value

The function returns one of the following values:

0 if successful
-1 on failure

NOTE

Although rename is a low-level function, it is defined by the C standard and can be used by portable applications.

8.2.3 Device-Driver Level I/O Functions

At the next level are the device-level drivers. They map directly to the low-level I/O functions. The default device driver is the HOST device driver, which uses the debugger to perform file operations. The HOST device driver is automatically used for the default C streams stdin, stdout, and stderr.

The HOST device driver shares a special protocol with the debugger running on a host system so that the host can perform the C I/O requested by the program. Instructions for C I/O operations that the program wants to perform are encoded in a special buffer named _CIOBUF_ in the .cio section. The debugger halts the program at a special breakpoint (C$$IO$$), reads and decodes the target memory, and performs the requested operation. The result is encoded into _CIOBUF_, the program is resumed, and the target decodes the result.

The HOST device is implemented with seven functions, HOSTopen, HOSTclose, HOSTread, HOSTwrite, HOSTlseek, HOSTunlink, and HOSTrename, which perform the encoding. Each function is called from the low-level I/O function with a similar name.

A device driver is composed of seven required functions. Not all function need to be meaningful for all devices, but all seven must be defined. Here we show the names of all seven functions as starting with DEV, but you may choose any name except for HOST.

DEV_open
Open File for I/O
Syntax

int DEV_open (const char * path , unsigned flags , int llv_fd );

Description

This function finds a file matching path and opens it for I/O as requested by flags.

  • The path is the filename of the file to be opened. If the name of a file passed to open has a device prefix, the device prefix will be stripped by open, so DEV_open will not see it. (See Section 8.2.5 for details on the device prefix.)
  • The flags are attributes that specify how the file is manipulated. The flags are specified using the following symbols:
  • O_RDONLY (0x0000) /* open for reading */ O_WRONLY (0x0001) /* open for writing */ O_RDWR (0x0002) /* open for read & write */ O_APPEND (0x0008) /* append on each write */ O_CREAT (0x0200) /* open with file create */ O_TRUNC (0x0400) /* open with truncation */ O_BINARY (0x8000) /* open in binary mode */

    See POSIX for further explanation of the flags.

  • The llv_fd is treated as a suggested low-level file descriptor. This is a historical artifact; newly-defined device drivers should ignore this argument. This differs from the low-level I/O open function.

This function must arrange for information to be saved for each file descriptor, typically including a file position indicator and any significant flags. For the HOST version, all the bookkeeping is handled by the debugger running on the host machine. If the device uses an internal buffer, the buffer can be created when a file is opened, or the buffer can be created during a read or write.

Return Value

This function must return -1 to indicate an error if for some reason the file could not be opened; such as the file does not exist, could not be created, or there are too many files open. The value of errno may optionally be set to indicate the exact error (the HOST device does not set errno). Some devices might have special failure conditions; for instance, if a device is read-only, a file cannot be opened O_WRONLY.

On success, this function must return a non-negative file descriptor unique among all open files handled by the specific device. The file descriptor need not be unique across devices. The device file descriptor is used only by low-level functions when calling the device-driver-level functions. The low-level function open allocates its own unique file descriptor for the high-level functions to call the low-level functions. Code that uses only high-level I/O functions need not be aware of these file descriptors.

DEV_close
Close File for I/O
Syntax

int DEV_close (int dev_fd );

Description

This function closes a valid open file descriptor.

On some devices, DEV_close may need to be responsible for checking if this is the last file descriptor pointing to a file that was unlinked. If so, it is responsible for ensuring that the file is actually removed from the device and the resources reclaimed, if appropriate.

Return Value

This function should return -1 to indicate an error if the file descriptor is invalid in some way, such as being out of range or already closed, but this is not required. The user should not call close() with an invalid file descriptor.

DEV_read
Read Characters from a File
Syntax

int DEV_read (int dev_fd , char * buf , unsigned count );

Description

The read function reads count bytes from the input file associated with dev_fd.

  • The dev_fd is the number assigned by open to an opened file.
  • The buf is where the read characters are placed.
  • The count is the number of characters to read from the file.
Return Value

This function must return -1 to indicate an error if for some reason no bytes could be read from the file. This could be because of an attempt to read from a O_WRONLY file, or for device-specific reasons.

If count is 0, no bytes are read and this function returns 0.

This function returns the number of bytes read, from 0 to count. 0 indicates that EOF was reached before any bytes were read. It is not an error to read less than count bytes; this is common if the are not enough bytes left in the file or the request was larger than an internal device buffer size.

DEV_write
Write Characters to a File
Syntax

int DEV_write (int dev_fd , const char * buf , unsigned count );

Description

This function writes count bytes to the output file.

  • The dev_fd is the number assigned by open to an opened file.
  • The buffer is where the write characters are placed.
  • The count is the number of characters to write to the file.
Return Value

This function must return -1 to indicate an error if for some reason no bytes could be written to the file. This could be because of an attempt to read from a O_RDONLY file, or for device-specific reasons.

DEV_lseek
Set File Position Indicator
Syntax

off_t lseek (int dev_fd , off_t offset , int origin );

Description

This function sets the file's position indicator for this file descriptor as lseek.

If lseek is supported, it should not allow a seek to before the beginning of the file, but it should support seeking past the end of the file. Such seeks do not change the size of the file, but if it is followed by a write, the file size will increase.

Return Value

If successful, this function returns the new value of the file position indicator.

This function must return -1 to indicate an error if for some reason no bytes could be written to the file. For many devices, the lseek operation is nonsensical (e.g. a computer monitor).

Delete File
Syntax

int DEV_unlink (const char * path );

Description

Remove the association of the pathname with the file. This means that the file may no longer be opened using this name, but the file may not actually be immediately removed.

Depending on the device, the file may be immediately removed, but for a device which allows open file descriptors to point to unlinked files, the file will not actually be deleted until the last file descriptor is closed. See Section 8.2.3.

Return Value

This function must return -1 to indicate an error if for some reason the file could not be unlinked (delayed removal does not count as a failure to unlink.)

If successful, this function returns 0.

DEV_rename
Rename File
Syntax

int DEV_rename (const char * old_name , const char * new_name );

Description

This function changes the name associated with the file.

  • The old_name is the current name of the file.
  • The new_name is the new name for the file.
Return Value

This function must return -1 to indicate an error if for some reason the file could not be renamed, such as the file doesn't exist, or the new name already exists.

NOTE

It is inadvisable to allow renaming a file so that it is on a different device. In general this would require a whole file copy, which may be more expensive than you expect.

If successful, this function returns 0.

8.2.4 Adding a User-Defined Device Driver for C I/O

The function add_device allows you to add and use a device. When a device is registered with add_device, the high-level I/O routines can be used for I/O on that device.

You can use a different protocol to communicate with any desired device and install that protocol using add_device; however, the HOST functions should not be modified. The default streams stdin, stdout, and stderr can be remapped to a file on a user-defined device instead of HOST by using freopen() as in Example 8-1. If the default streams are reopened in this way, the buffering mode will change to _IOFBF (fully buffered). To restore the default buffering behavior, call setvbuf on each reopened file with the appropriate value (_IOLBF for stdin and stdout, _IONBF for stderr).

The default streams stdin, stdout, and stderr can be mapped to a file on a user-defined device instead of HOST by using freopen() as shown in Example 8-1. Each function must set up and maintain its own data structures as needed. Some function definitions perform no action and should just return.

Example 8-1 Mapping Default Streams to Device

#include <stdio.h>#include <file.h>#include "mydevice.h"void main() { add_device("mydevice", _MSA, MYDEVICE_open, MYDEVICE_close, MYDEVICE_read, MYDEVICE_write, MYDEVICE_lseek, MYDEVICE_unlink, MYDEVICE_rename); /*-----------------------------------------------------------------------*/ /* Re-open stderr as a MYDEVICE file */ /*-----------------------------------------------------------------------*/ if (!freopen("mydevice:stderrfile", "w", stderr)) { puts("Failed to freopen stderr"); exit(EXIT_FAILURE); } /*-----------------------------------------------------------------------*/ /* stderr should not be fully buffered; we want errors to be seen as */ /* soon as possible. Normally stderr is line-buffered, but this example */ /* doesn't buffer stderr at all. This means that there will be one call */ /* to write() for each character in the message. */ /*-----------------------------------------------------------------------*/ if (setvbuf(stderr, NULL, _IONBF, 0)) { puts("Failed to setvbuf stderr"); exit(EXIT_FAILURE); } /*-----------------------------------------------------------------------*/ /* Try it out! */ /*-----------------------------------------------------------------------*/ printf("This goes to stdout\n"); fprintf(stderr, "This goes to stderr\n"); }

NOTE

Use Unique Function Names

The function names open, read, write, close, lseek, rename, and unlink are used by the low-level routines. Use other names for the device-level functions that you write.

Use the low-level function add_device() to add your device to the device_table. The device table is a statically defined array that supports n devices, where n is defined by the macro _NDEVICE found in stdio.h/cstdio.

The first entry in the device table is predefined to be the host device on which the debugger is running. The low-level routine add_device() finds the first empty position in the device table and initializes the device fields with the passed-in arguments. For a complete description, see the add_device function.

8.2.5 The device Prefix

A file can be opened to a user-defined device driver by using a device prefix in the pathname. The device prefix is the device name used in the call to add_device followed by a colon. For example:

FILE *fptr = fopen("mydevice:file1", "r"); int fd = open("mydevice:file2, O_RDONLY, 0);

If no device prefix is used, the HOST device will be used to open the file.

add_device
Add Device to Device Table
Syntax for C

#include <file.h>

int add_device(char * name,
     unsigned flags,
     int (*dopen)(const char *path, unsigned flags, int llv_fd),
     int (*dclose)( int dev_fd),
     int (*dread)(intdev_fd, char *buf, unsigned count),
     int (*dwrite)(intdev_fd, const char *buf, unsigned count),
     off_t (*dlseek)(int dev_fd, off_t ioffset, int origin),
     int (*dunlink)(const char *path),
     int (*drename)(const char *old_name, const char *new_name));

Defined in

lowlev.c (in the lib/src subdirectory of the compiler installation)

Description

The add_device function adds a device record to the device table allowing that device to be used for I/O from C. The first entry in the device table is predefined to be the HOST device on which the debugger is running. The function add_device() finds the first empty position in the device table and initializes the fields of the structure that represent a device.

To open a stream on a newly added device use fopen( ) with a string of the format devicename:filename as the first argument.

  • The name is a character string denoting the device name. The name is limited to 8 characters.
  • The flags are device characteristics. The flags are as follows:
  • _SSA Denotes that the device supports only one open stream at a time

    _MSA Denotes that the device supports multiple open streams

    More flags can be added by defining them in file.h.

  • The dopen, dclose, dread, dwrite, dlseek, dunlink, and drename specifiers are function pointers to the functions in the device driver that are called by the low-level functions to perform I/O on the specified device. You must declare these functions with the interface specified in Section 8.2.2. The device driver for the HOST that the TMS320C28x debugger is run on are included in the C I/O library.
Return Value

The function returns one of the following values:

0 if successful
-1 on failure
Example

Example 8-2 does the following:

  • Adds the device mydevice to the device table
  • Opens a file named test on that device and associates it with the FILE pointer fid
  • Writes the string Hello, world into the file
  • Closes the file

Example 8-2 illustrates adding and using a device for C I/O:

Example 8-2 Program for C I/O Device

#include <file.h>#include <stdio.h>/****************************************************************************/ /* Declarations of the user-defined device drivers */ /****************************************************************************/ extern int MYDEVICE_open(const char *path, unsigned flags, int fno); extern int MYDEVICE_close(int fno); extern int MYDEVICE_read(int fno, char *buffer, unsigned count); extern int MYDEVICE_write(int fno, const char *buffer, unsigned count); extern off_t MYDEVICE_lseek(int fno, off_t offset, int origin); extern int MYDEVICE_unlink(const char *path); extern int MYDEVICE_rename(const char *old_name, char *new_name); main() { FILE *fid; add_device("mydevice", _MSA, MYDEVICE_open, MYDEVICE_close, MYDEVICE_read, MYDEVICE_write, MYDEVICE_lseek, MYDEVICE_unlink, MYDEVICE_rename); fid = fopen("mydevice:test","w"); fprintf(fid,"Hello, world\n"); fclose(fid); }

8.3 Handling Reentrancy (_register_lock() and _register_unlock() Functions)

The C standard assumes only one thread of execution, with the only exception being extremely narrow support for signal handlers. The issue of reentrancy is avoided by not allowing you to do much of anything in a signal handler. However, SYS/BIOS applications have multiple threads which need to modify the same global program state, such as the CIO buffer, so reentrancy is a concern.

Part of the problem of reentrancy remains your responsibility, but the run-time-support environment does provide rudimentary support for multi-threaded reentrancy by providing support for critical sections. This implementation does not protect you from reentrancy issues such as calling run-time-support functions from inside interrupts; this remains your responsibility.

The run-time-support environment provides hooks to install critical section primitives. By default, a single-threaded model is assumed, and the critical section primitives are not employed. In a multi-threaded system such as SYS/BIOS, the kernel arranges to install semaphore lock primitive functions in these hooks, which are then called when the run-time-support enters code that needs to be protected by a critical section.

Throughout the run-time-support environment where a global state is accessed, and thus needs to be protected with a critical section, there are calls to the function _lock(). This calls the provided primitive, if installed, and acquires the semaphore before proceeding. Once the critical section is finished, _unlock() is called to release the semaphore.

Usually SYS/BIOS is responsible for creating and installing the primitives, so you do not need to take any action. However, this mechanism can be used in multi-threaded applications that do not use the SYS/BIOS locking mechanism.

You should not define the functions _lock() and _unlock() functions directly; instead, the installation functions are called to instruct the run-time-support environment to use these new primitives:

void _register_lock (void ( *lock)()); void _register_unlock(void (*unlock)());

The arguments to _register_lock() and _register_unlock() should be functions which take no arguments and return no values, and which implement some sort of global semaphore locking:

extern volatile sig_atomic_t *sema = SHARED_SEMAPHORE_LOCATION; static int sema_depth = 0; static void my_lock(void) { while (ATOMIC_TEST_AND_SET(sema, MY_UNIQUE_ID) != MY_UNIQUE_ID); sema_depth++; } static void my_unlock(void) { if (!--sema_depth) ATOMIC_CLEAR(sema); }

The run-time-support nests calls to _lock(), so the primitives must keep track of the nesting level.

8.4 Library-Build Process

When using the C/C++ compiler, you can compile your code under a large number of different configurations and options that are not necessarily compatible with one another. Because it would be infeasible to include all possible run-time-support library variants, compiler releases pre-build only a small number of very commonly-used libraries such as rts2800_ml.lib.

To provide maximum flexibility, the run-time-support source code is provided as part of each compiler release. You can build the missing libraries as desired. The linker can also automatically build missing libraries. This is accomplished with a new library build process, the core of which is the executable mklib, which is available beginning with CCS 5.1

8.4.1 Required Non-Texas Instruments Software

To use the self-contained run-time-support build process to rebuild a library with custom options, the following are required:

  • sh (Bourne shell)
  • gmake (GNU make 3.81 or later)
  • More information is available from GNU at https://www.gnu.org/software/make. GNU make (gmake) is also available in earlier versions of Code Composer Studio. GNU make is also included in some UNIX support packages for Windows, such as the MKS Toolkit, Cygwin, and Interix. The GNU make used on Windows platforms should explicitly report "This program build for Windows32" when the following is executed from the Command Prompt window:

    gmake -h

All three of these programs are provided as a non-optional feature of CCS 5.1. They are also available as part of the optional XDC Tools feature if you are using an earlier version of CCS.

The mklib program looks for these executables in the following order:

  1. in your PATH
  2. in the directory getenv("CCS_UTILS_DIR")/cygwin
  3. in the directory getenv("CCS_UTILS_DIR")/bin
  4. in the directory getenv("XDCROOT")
  5. in the directory getenv("XDCROOT")/bin

If you are invoking mklib from the command line, and these executables are not in your path, you must set the environment variable CCS_UTILS_DIR such that getenv("CCS_UTILS_DIR")/bin contains the correct programs.

8.4.2 Using the Library-Build Process

You should normally let the linker automatically rebuild libraries as needed. If necessary, you can run mklib directly to populate libraries. See Section 8.4.2.2 for situations when you might want to do this.

8.4.2.1 Automatic Standard Library Rebuilding by the Linker

The linker looks for run-time-support libraries primarily through the C2000_C_DIR environment variable. Typically, one of the pathnames in C2000_C_DIR is your install directory/lib, which contains all of the pre-built libraries, as well as the index library libc.a. The linker looks in C2000_C_DIR to find a library that is the best match for the build attributes of the application. The build attributes are set indirectly according to the command-line options used to build the application. Build attributes include things like CPU revision. If the library is explicitly named (e.g. rts2800_ml.lib), run-time support looks for that library exactly; otherwise, it uses the index library libc.a to pick an appropriate library.

The index library describes a set of libraries with different build attributes. The linker will compare the build attributes for each potential library with the build attributes of the application and will pick the best fit. For details on the index library, see the archiver chapter in the TMS320C28x Assembly Language Tools User's Guide.

Now that the linker has decided which library to use, it checks whether the run-time-support library is present in C2000_C_DIR . The library must be in exactly the same directory as the index library libc.a. If the library is not present, the linker invokes mklib to build it. This happens when the library is missing, regardless of whether the user specified the name of the library directly or allowed the linker to pick the best library from the index library.

The mklib program builds the requested library and places it in 'lib' directory part of C2000_C_DIR in the same directory as the index library, so it is available for subsequent compilations.

Things to watch out for:

  • The linker invokes mklib and waits for it to finish before finishing the link, so you will experience a one-time delay when an uncommonly-used library is built for the first time. Build times of 1-5 minutes have been observed. This depends on the power of the host (number of CPUs, etc).
  • In a shared installation, where an installation of the compiler is shared among more than one user, it is possible that two users might cause the linker to rebuild the same library at the same time. The mklib program tries to minimize the race condition, but it is possible one build will corrupt the other. In a shared environment, all libraries which might be needed should be built at install time; see Section 8.4.2.2 for instructions on invoking mklib directly to avoid this problem.
  • The index library must exist, or the linker is unable to rebuild libraries automatically.
  • The index library must be in a user-writable directory, or the library is not built. If the compiler installation must be installed read-only (a good practice for shared installation), any missing libraries must be built at installation time by invoking mklib directly.
  • The mklib program is specific to a certain version of a certain library; you cannot use one compiler version's run-time support's mklib to build a different compiler version's run-time support library.
8.4.2.2 Invoking mklib Manually

You may need to invoke mklib directly in special circumstances:

  • The compiler installation directory is read-only or shared.
  • You want to build a variant of the run-time-support library that is not pre-configured in the index library libc.a or known to mklib. (e.g. a variant with source-level debugging turned on.)
8.4.2.2.1 Building Standard Libraries

You can invoke mklib directly to build any or all of the libraries indexed in the index library libc.a. The libraries are built with the standard options for that library; the library names and the appropriate standard option sets are known to mklib.

This is most easily done by changing the working directory to be the compiler run-time-support library directory 'lib' and invoking the mklib executable there:

mklib --pattern=rts2800_ml.lib

For C28x these libraries can be built:

  • rts2800_ml.lib (C/C++ run-time object library)
  • rts2800_fpu32.lib (C/C++ run-time object library for FPU targets)
8.4.2.2.2 Shared or Read-Only Library Directory

If the compiler tools are to be installed in shared or read-only directory, mklib cannot build the standard libraries at link time; the libraries must be built before the library directory is made shared or read-only.

At installation time, the installing user must build all of the libraries which will be used by any user. To build all possible libraries, change the working directory to be the compiler RTS library directory 'lib' and invoke the mklib executable there:

mklib --all

Some targets have many libraries, so this step can take a long time. To build a subset of the libraries, invoke mklib individually for each desired library.

8.4.2.2.3 Building Libraries With Custom Options

You can build a library with any extra custom options desired. This is useful for building a debugging version of the library, or with silicon exception workarounds enabled. The generated library is not a standard library, and must not be placed in the 'lib' directory. It should be placed in a directory local to the project which needs it. To build a debugging version of the library rts2800_ml.lib, change the working directory to the 'lib' directory and run the command:

mklib --pattern=rts2800_ml.lib --name=rts2800_debug.lib --install_to=$Project/Debug --extra_options="-g"
8.4.2.2.4 The mklib Program Option Summary

Run the following command to see the full list of options. These are described in Table 8-1.

mklib --help

Table 8-1 The mklib Program Options

Option Effect
--index= filename The index library (libc.a) for this release. Used to find a template library for custom builds, and to find the source files (in the lib/src subdirectory of the compiler installation). REQUIRED.
--pattern= filename Pattern for building a library. If neither --extra_options nor --options are specified, the library will be the standard library with the standard options for that library. If either --extra_options or --options are specified, the library is a custom library with custom options. REQUIRED unless --all is used.
--all Build all standard libraries at once.
--install_to= directory The directory into which to write the library. For a standard library, this defaults to the same directory as the index library (libc.a). For a custom library, this option is REQUIRED.
--compiler_bin_dir=
     directory
The directory where the compiler executables are. When invoking mklib directly, the executables should be in the path, but if they are not, this option must be used to tell mklib where they are. This option is primarily for use when mklib is invoked by the linker.
--name= filename File name for the library with no directory part. Only useful for custom libraries.
--options=' str ' Options to use when building the library. The default options (see below) are replaced by this string. If this option is used, the library will be a custom library.
--extra_options=' str ' Options to use when building the library. The default options (see below) are also used. If this option is used, the library will be a custom library.
--list_libraries List the libraries this script is capable of building and exit. ordinary system-specific directory.
--log= filename Save the build log as filename.
--tmpdir= directory Use directory for scratch space instead of the ordinary system-specific directory.
--gmake= filename Gmake-compatible program to invoke instead of "gmake"
--parallel= N Compile N files at once ("gmake -j N").
--query= filename Does this script know how to build FILENAME?
--help or --h Display this help.
--quiet or --q Operate silently.
--verbose or --v Extra information to debug this executable.

Examples:

To build all standard libraries and place them in the compiler's library directory:

mklib --all --index=$C_DIR/lib

To build one standard library and place it in the compiler's library directory:

mklib --pattern=rts2800_ml.lib --index=$C_DIR/lib

To build a custom library that is just like rts2800_ml.lib, but has symbolic debugging support enabled:

mklib --pattern=rts2800_ml.lib --extra_options="-g" --index=$C_DIR/lib --install_to=$Project/Debug --name=rts2800_debug.lib

8.4.3 Extending mklib

The mklib API is a uniform interface that allows Code Composer Studio to build libraries without needing to know exactly what underlying mechanism is used to build it. Each library vendor (e.g. the TI compiler) provides a library-specific copy of 'mklib' in the library directory that can be invoked, which understands a standardized set of options, and understands how to build the library. This allows the linker to automatically build application-compatible versions of any vendor's library without needing to register the library in advance, as long as the vendor supports mklib.

8.4.3.1 Underlying Mechanism

The underlying mechanism can be anything the vendor desires. For the compiler run-time-support libraries, mklib is just a wrapper that knows how to use the files in the lib/src subdirectory of the compiler installation and invoke gmake with the appropriate options to build each library. If necessary, mklib can be bypassed and the Makefile used directly, but this mode of operation is not supported by TI, and you are responsible for any changes to the Makefile. The format of the Makefile and the interface between mklib and the Makefile is subject to change without notice. The mklib program is the forward-compatible path.

8.4.3.2 Libraries From Other Vendors

Any vendor who wishes to distribute a library that can be rebuilt automatically by the linker must provide:

  • An index library (like 'libc.a', but with a different name)
  • A copy of mklib specific to that library
  • A copy of the library source code (in whatever format is convenient)

These things must be placed together in one directory that is part of the linker's library search path (specified either in C2000_C_DIR or with the linker --search_path option).

If mklib needs extra information that is not possible to pass as command-line options to the compiler, the vendor will need to provide some other means of discovering the information (such as a configuration file written by a wizard run from inside CCS).

The vendor-supplied mklib must at least accept all of the options listed in Table 8-1 without error, even if they do not do anything.

Submit Documentation Feedback

Copyright© 2015, Texas Instruments Incorporated. An IMPORTANT NOTICE for this document addresses availability, warranty, changes, use in safety-critical applications, intellectual property matters and other important disclaimers.