This has been my long time wish of understanding the crt0.S code of an embedded project. This piece of assembly code does setup environment to run your C main() function of application. This is at pretty low level. I assume that reader has low level understanding of CPU.
We need to understand a few things before walking through the code.
What is Cache Line?
(Courtesy: Wikipedia) Data is transferred between memory and cache in blocks of fixed size, called cache lines. When a cache line is copied from memory into the cache, a cache entry is created. The cache entry will include the copied data as well as the requested memory location (now called a tag). When the processor needs to read or write a location in main memory, it first checks for a corresponding entry in the cache. The cache checks for the contents of the requested memory location in any cache lines that might contain that address. If the processor finds that the memory location is in the cache, a cache hit has occurred. However, if the processor does not find the memory location in the cache, a cache miss has occurred. In the case of a cache hit, the processor immediately reads or writes the data in the cache line. For a cache miss, the cache allocates a new entry and copies in data from main memory, then the request is fulfilled from the contents of the cache.
What is Shadow Register Set?
The NIOS II processor can optionally have one or more shadow register sets. A shadow register set is a
complete alternate set of Nios II general-purpose registers, which can be used to maintain a separate
run time context for an interrupt service routine (ISR). When shadow register sets are implemented, status. CRS indicates the register set currently in use. A NIOS II core can have up to 63 shadow register sets. If n is the configured number of shadow register sets, the shadow register sets are numbered from 1 to n. Register set 0 is the normal register set.
Here are the source files attached which are referred below.
These files are part of my previous post on
Code Walk through of crt0:
1. Instruction cache initialization: Skip this if it is RTL simulation (already initialized).
Use initi instruction (Initialize instruction-cache line) to initialize instruction cache if enabled.
2. Jump to entry point of .text section (_start)
_start is the start of the .text section, and also the code entry point when the code is executed by a bootloader rather than directly from reset.
1. A shadow register set is a complete alternate set of Nios II general-purpose registers, which can be used to maintain a separate run time context for an interrupt service routine (ISR). In case system supports multiple shadow Register Sets, Switch to Register Set 0 if it is not already.
2. Initialize Data Cache: Skip this if it is RTL simulation and ECC is not present – use initd instruction.
3. Set Stack Pointer and Global Pointer from Linker provided pointer.
4. Initialize all General Purpose Register if ECC is present.
5. Initialize all GPRs (from 1st – 0th one is initialized in previous step) in all Shadow Register Sets if it Shadow Register sets are supported by system and if ECC is present.
6. Initialize BSS section using __bss_start and __bss_end symbols defined by linker script.
7. Call alt_load() if there is no bootloader.
8. Call alt_main() which should in turn call main() in your c program.
9. alt_main() should not return here (since main() app will be in loop processing the app). Any way wait in a while(1) loop in case alt_main() does return.
alt_load() is called when the code is executing from flash. In this case there is no boot loader, so this application is responsible for loading to RAM any sections that are required.
1. Copy .rwdata section, exception handler and .rodata section
2. Flush Instruction cache and Data cache.
alt_main is the C entry point for the HAL. It is called by the assembler startup code in the processor specific crt0.S. It is responsible:
1. for completing the C runtime configuration;
2. configuring all the devices/filesystems/components in the system
3. call the entry point for the users application, i.e. main().
It does following in sequence:
1. Initialize the interrupt controller devices and then enable interrupts in the CPU.
2. OS Initialization place holder macro is called here, which does nothing in a simple system. “do-nothing” macro definitions for operating system hooks within the HAL. The O/S component can override these to provide it’s own implementation.
3. Semaphore initialization hook called here. It provides macro definitions (it does nothing in a basic system) that can be used to create and use semaphores. These macros can be used in both a uC/OS-II based environment and a single threaded HAL based environment.
4. Initialize the device drivers/software components – initialize non-interrupt controller devices, again a place holder macro.
5. Since devices have been initialized, it is now time to redirect stdio to corresponding devices if user has enabled (other than /dev/null).
6. Run the C++ static constructors if C++ support is necessary.
7. Set the C++ destructors to be called at system shutdown.
8. Hey! Now it is time to call main() of your app with arguments and environment variables.
9. If main is running successfully, we won’t be here 🙂