3.10. System Initialization

Before you can run a C/C++ program, you must create the C/C++ run-time environment. The C/C++ boot routine performs this task using a function called c_int00 (or _c_int00). The run-time-support source library, rts.src, contains the source to this routine in a module named boot.c (or boot.asm).

To begin running the system, the c_int00 function can be called by reset hardware. You must link the c_int00 function with the other object files. This occurs automatically when you use the --rom_model or --ram_model link option and include a standard run-time-support library as one of the linker input files.

When C/C++ programs are linked, the linker sets the entry point value in the executable output file to the symbol c_int00.

The c_int00 function performs the following tasks to initialize the environment:

  1. Switches to the appropriate mode, reserves space for the run-time stack, and sets up the initial value of the stack pointer (SP). The stack is aligned on a 64-bit boundary.

  2. Calls the function _ _TI_auto_init to perform the C/C++ autoinitialization.

    The _ _TI_auto_init function does the following tasks:

    • Processes the binit copy table, if present.

    • Performs C autoinitialization of global/static variables. For more information, see Automatic Initialization of Variables.

    • Calls C++ initialization routines for file scope construction from the global constructor table. For more information, see Global Constructors.

  3. Calls the main() function to run the C/C++ program.

You can replace or modify the boot routine to meet your system requirements. However, the boot routine must perform the operations listed above to correctly initialize the C/C++ environment.

3.10.1. Boot Hook Functions for System Pre-Initialization

Boot hooks are points at which you may insert application functions into the C/C++ boot process. Default boot hook functions are provided with the run-time support (RTS) library. However, you can implement customized versions of these boot hook functions, which override the default boot hook functions in the RTS library if they are linked before the run-time library. Such functions can perform any application-specific initialization before continuing with the C/C++ environment setup.

If customized boot hook functions are defined in a user library, then in addition to linking the library before the run-time library, you may also need to use the --priority link option to ensure that unresolved symbol references to the boot functions are resolved by the first library that contains a symbol definition. This will prevent references to the boot functions from being resolved by the default implementations defined by the compiler run-time library. See Exhaustively Read and Search Libraries (--reread_libs and --priority Options).

Note that the TI-RTOS operating system uses custom versions of the boot hook functions for system setup, so you should be careful about overriding these functions if you are using TI-RTOS.

The following boot hook functions are available:

__mpu_init(): This function provides an interface for initializing the MPU, if MPU support is included. The __mpu_init() function is called after the stack pointer is initialized but before any C/C++ environment setup is performed. This function should not return a value.

_system_pre_init(): This function provides a place to perform application-specific initialization. It is invoked after the stack pointer is initialized but before any C/C++ environment setup is performed. For targets that include MPU support, this function is called after __mpu_init().By default, _system_pre_init() should return a non-zero value. The default C/C++ environment setup is bypassed if _system_pre_init() returns 0.

_system_post_cinit(): This function is invoked during C/C++ environment setup, after C/C++ global data is initialized but before any C++ constructors are called. This function should not return a value.

The _c_int00( ) initialization routine also provides a mechanism for an application to perform the setup (set I/O registers, enable/disable timers, etc.) before the C/C++ environment is initialized.

3.10.2. Run-Time Stack

The run-time stack is allocated in a single continuous block of memory and grows down from high addresses to lower addresses. The SP points to the top of the stack.

The code does not check to see if the run-time stack overflows. Stack overflow occurs when the stack grows beyond the limits of the memory space that was allocated for it. Be sure to allocate adequate memory for the stack.

The stack size can be changed at link time by using the --stack_size link option on the linker command line and specifying the stack size as a constant directly after the option.

The C/C++ boot routine shipped with the compiler sets up the user/thread mode run-time stack. If your program uses a run-time stack when it is in other operating modes, you must also allocate space and set up the run-time stack corresponding to those modes.

EABI requires that 64-bit data (type long long and long double) be aligned at 64-bits. This requires that the stack be aligned at a 64-bit boundary at function entry so that local 64-bit variables are allocated in the stack with correct alignment. The boot routine aligns the stack at a 64-bit boundary.

3.10.3. Automatic Initialization of Variables

Any global variables declared as preinitialized must have initial values assigned to them before a C/C++ program starts running. The process of retrieving these variables’ data and initializing the variables with the data is called autoinitialization. Internally, the compiler and linker coordinate to produce compressed initialization tables. Your code should not access the initialization table.

3.10.3.1. Zero Initializing Variables

In ANSI C, global and static variables that are not explicitly initialized must be set to 0 before program execution. The C/C++ compiler supports preinitialization of uninitialized variables by default. This can be turned off by specifying the linker option --zero_init=off.

Zero initialization takes place only if the --rom_model linker option, which causes autoinitialization to occur, is used. If you use the --ram_model option for linking, the linker does not generate initialization records, and the loader must handle both data and zero initialization.

3.10.3.2. Direct Initialization

The compiler uses direct initialization to initialize global variables. For example, consider the following C code:

int i    = 23;
int a[5] = { 1, 2, 3, 4, 5 };

The compiler allocates the variables ‘i’ and ‘a[] to .data section and the initial values are placed directly.

          .global i
          .data
         .align  4
i:
          .field          23,32       ; i @ 0
          .global a
          .data
          .align  4
a:
          .field          1,32        ; a[0] @ 0
          .field          2,32        ; a[1] @ 32
          .field          3,32        ; a[2] @ 64
          .field          4,32        ; a[3] @ 96
          .field          5,32        ; a[4] @ 128

Each compiled module that defines static or global variables contains these .data sections. The linker treats the .data section like any other initialized section and creates an output section. In the load-time initialization model, the sections are loaded into memory and used by the program. See Initialization of Variables at Load Time.

In the run-time initialization model, the linker uses the data in these sections to create initialization data and an additional compressed initialization table. The boot routine processes the initialization table to copy data from load addresses to run addresses. See Autoinitialization of Variables at Run Time.

3.10.3.3. Autoinitialization of Variables at Run Time

Autoinitializing variables at run time is the most common method of autoinitialization. To use this method, invoke the linker with the --rom_model option.

Using this method, the linker creates a compressed initialization table and initialization data from the direct initialized sections in the compiled module. The table and data are used by the C/C++ boot routine to initialize variables in RAM using the table and data in ROM.

The following figure illustrates autoinitialization at run time. Use this method in any system where your application runs from code burned into ROM.

Figure: Autoinitialization at Run Time

../../_images/autoinit_run_time.png

3.10.3.4. Autoinitialization Tables

The compiled object files do not have initialization tables. The variables are initialized directly. The linker, when the --rom_model option is specified, creates C auto initialization table and the initialization data. The linker creates both the table and the initialization data in an output section named .cinit.

The autoinitialization table has the following format:

../../_images/autoinit_table.png

The linker defined symbols __TI_CINIT_Base and __TI_CINIT_Limit point to the start and end of the table, respectively. Each entry in this table corresponds to one output section that needs to be initialized. The initialization data for each output section could be encoded using different encoding.

The load address in the C auto initialization record points to initialization data with the following format:

../../_images/autoinit_load_address.png

The first 8-bits of the initialization data is the handler index. It indexes into a handler table to get the address of a handler function that knows how to decode the following data.

The handler table is a list of 32-bit function pointers.

../../_images/handler_table.png

The encoded data that follows the 8-bit index can be in one of the following format types. For clarity the 8-bit index is also depicted for each format.

3.10.3.4.1. Length Followed by Data Format

../../_images/length_followed_data_format.png

The compiler uses 24-bit padding to align the length field to a 32-bit boundary. The 32-bit length field encodes the length of the initialization data in bytes (N). N byte initialization data is not compressed and is copied to the run address as is.

The run-time support library has a function __TI_zero_init() to process this type of initialization data. The first argument to this function is the address pointing to the byte after the 8-bit index. The second argument is the run address from the C auto initialization record.

3.10.3.4.2. Zero Initialization Format

../../_images/zero_init_format.png

The compiler uses 24-bit padding to align the length field to a 32-bit boundary. The 32-bit length field encodes the number of bytes to be zero initialized.

The run-time support library has a function __TI_zero_init() to process the zero initialization. The first argument to this function is the address pointing to the byte after the 8-bit index. The second argument is the run address from the C auto initialization record.

3.10.3.4.3. Run Length Encoded (RLE) Format

../../_images/rle_format.png

The data following the 8-bit index is compressed using Run Length Encoded (RLE) format. uses a simple run length encoding that can be decompressed using the following algorithm:

  1. Read the first byte, Delimiter (D).

  2. Read the next byte (B).

  3. If B != D, copy B to the output buffer and go to step 2.

  4. Read the next byte (L).

    1. If L == 0, then length is either a 16-bit, a 24-bit value, or we’ve reached the end of the data, read next byte (L).

      1. If L == 0, length is a 24-bit value or the end of the data is reached, read next byte (L).

        1. If L == 0, the end of the data is reached, go to step 7.

        2. Else L <<= 16, read next two bytes into lower 16 bits of L to complete 24-bit value for L.

      2. Else L <<= 8, read next byte into lower 8 bits of L to complete 16-bit value for L.

    2. Else if L > 0 and L < 4, copy D to the output buffer L times. Go to step 2.

    3. Else, length is 8-bit value (L).

  5. Read the next byte (C); C is the repeat character.

  6. Write C to the output buffer L times; go to step 2.

  7. End of processing.

The run-time support library has a routine __TI_decompress_rle24() to decompress data compressed using RLE. The first argument to this function is the address pointing to the byte after the 8-bit index. The second argument is the run address from the C auto initialization record.

Note

RLE Decompression Routine The previous decompression routine, __TI_decompress_rle(), is included in the run-time-support library for decompressing RLE encodings generated by older versions of the linker.

3.10.3.4.4. Lempel-Ziv-Storer-Szymanski Compression (LZSS) Format

../../_images/lzss_format.png

The data following the 8-bit index is compressed using LZSS compression. The run-time support library has the routine __TI_decompress_lzss() to decompress the data compressed using LZSS. The first argument to this function is the address pointing to the byte after the 8-bit index. The second argument is the run address from the C auto initialization record.

3.10.3.4.5. Sample C Code to Process the C Autoinitialization Table

The run-time support boot routine has code to process the C autoinitialization table. The following C code illustrates how the autoinitialization table can be processed on the target.

Example: Processing the C Autoinitialization Table

typedef void (*handler_fptr)(const unsigned char *in,
unsigned char *out);

#define HANDLER_TABLE  __TI_Handler_Table_Base
#pragma WEAK(HANDLER_TABLE)
extern  unsigned int   HANDLER_TABLE;
extern  unsigned char *__TI_CINIT_Base;
extern  unsigned char *__TI_CINIT_Limit;

void auto_initialize()
{
    unsigned char **table_ptr;
    unsigned char **table_limit;

    /*--------------------------------------------------------------*/
    /* Check if Handler table has entries.                          */
    /*--------------------------------------------------------------*/
    if (&__TI_Handler_Table_Base >= &__TI_Handler_Table_Limit)
        return;

    /*---------------------------------------------------------------*/
    /* Get the Start and End of the CINIT Table.                     */
    /*---------------------------------------------------------------*/
    table_ptr   = (unsigned char **)&__TI_CINIT_Base;
    table_limit = (unsigned char **)&__TI_CINIT_Limit;
    while (table_ptr < table_limit)
    {

        /*-------------------------------------------------------------*/
        /* 1. Get the Load and Run address.                            */
        /* 2. Read the 8-bit index from the load address.              */
        /* 3. Get the handler function pointer using the index from    */
        /*    handler table.                                           */
        /*-------------------------------------------------------------*/
        unsigned char *load_addr   = *table_ptr++;
        unsigned char *run_addr    = *table_ptr++;
        unsigned char  handler_idx = *load_addr++;
        handler_fptr   handler     =
                             (handler_fptr)(&HANDLER_TABLE)[handler_idx];

        /*-------------------------------------------------------------*/
        /* 4. Call the handler and pass the pointer to the load data   */
        /*    after index and the run address.                         */
        /*-------------------------------------------------------------*/
        (*handler)((const unsigned char *)load_addr, run_addr);
    }
}

3.10.3.5. Initialization of Variables at Load Time

Initialization of variables at load time enhances performance by reducing boot time and by saving the memory used by the initialization tables. To use this method, invoke the linker with the --ram_model option.

When you use the --ram_model link option, the linker does not generate C autoinitialization tables and data. The direct initialized sections (.data) in the compiled object files are combined according to the linker command file to generate initialized output sections. The loader loads the initialized output sections into memory. After the load, the variables are assigned their initial values.

Since the linker does not generate the C autoinitialization tables, no boot time initialization is performed.

The following figure illustrates the initialization of variables at load time.

../../_images/init_load_time.png

3.10.3.6. Global Constructors

All global C++ variables that have constructors must have their constructor called before main(). The compiler builds a table of global constructor addresses that must be called, in order, before main() in a section called .init_array. The linker combines the .init_array section form each input file to form a single table in the .init_array section. The boot routine uses this table to execute the constructors. The linker defines two symbols to identify the combined .init_array table as shown below. This table is not null terminated by the linker.

../../_images/constructor_table.png