====== IPU Breakdown ======
This section describes how ViewZone's standalone IPU works by describing the code, logic behind it and with code snippets for a practical approach.
===== Bootstrap and Assembly =====
There is no assembly code related to any IPU functionality in ViewZone, but in order to fully document this example we should start by taking a look at what is necessary in order to boot this microkernel.
==== Linker ====
If you don't know the absolute fundamentals of Linker Script semantics check [[concepts:linker-files|page]] for a small introduction.
The linker needs to know where the code starts in order for it to boot in the board. This is done by the kernel.ld file:
ENTRY(_start)
SECTIONS
{
. = 0x97800000;
.text : { *(.text) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss*) }
__bss_end = .;
. = 0xa0000000;
.stack :
{
STACK_ADDR = . ;
}
free_memory_start = .;
}
This is a simple Linker Script. The first line tells the linker that the entry point for our microkernel is in the _start assembly function (we will get to that later). The second line starts the SECTIONS command which specifies the memory sections of the output file. The (.) is the location counter and it is used to specify where a memory section should be. By starting with
. = 0x97800000;
we are telling the linker to load the system at this location in memory (this is platform dependent, for instance for the versatilepb board the value would be 0x10000).
Next we tell the linker to load the text section (where the code is stored) of all input files after address 0x97800000. The same is done for the data and bss section respectively. Afterwards, we set the stack to address 0xa0000000 (remember the stack grows upwards) and finally we set a variable to know where the remaining free memory starts (this is later used by the IPU to load the pictures).
==== Assembly ====
The Linker Script is expecting the entry point of our microkernel to be the _start function in assembly:
.globl _start
_start:
b reset
ldr pc, UndefAddr
ldr pc, SWIAddr
ldr pc, PAbortAddr
ldr pc, DAbortAddr
ldr pc, ReservedAddr
ldr pc, IRQAddr
ldr pc, FIQAddr
# reset handler
.global reset
.align 4
reset:
bl lowlevel_init
ldr sp, =STACK_ADDR @works even disabled
ldr r0, =STACK_ADDR
cps #Mode_MON
ldr sp, =STACK_ADDR
msr cpsr_c, #Mode_FIQ | I_Bit | F_Bit /* Change Mode and Disable interrupts*/
sub sp, r0, #Offset_FIQ_Stack
msr cpsr_c, #Mode_IRQ | I_Bit | F_Bit /* Change Mode and Disable interrupts */
sub sp, r0, #Offset_IRQ_Stack
msr cpsr_c, #Mode_ABT | I_Bit | F_Bit /* Change Mode and Disable interrupts */
sub sp, r0, #Offset_ABT_Stack
msr cpsr_c, #Mode_UND | I_Bit | F_Bit
sub sp, r0, #Offset_UND_Stack
msr cpsr_c, #Mode_SVC | I_Bit | F_Bit /* Change Mode and Disable interrupts */
sub sp, r0, #Offset_SVC_Stack
bl bootmain
b .
The _start function just calls the reset function which is responsible for initializing the board (by setting the clocks), set the stack pointer to the stack address defined in the Linker Script and disabling all interrupts in all CPU modes (we may set the ones we need later). Lastly this function calls the main function (bootmain) written in C.
===== Main Execution Flow =====
Now we are in C domain. This is where we initialize the remaining kernel features such as the serial I/O and IPU.
void bootmain(void) {
// enable_hwfirewall allows cprintf in Normal World
enable_hwfirewall();
iomuxc_init(); // set the iomuxc policy to enable serial port I/O
mxc_serial_init(); // initialize registers and set frequency for serial
/* IPU Specific */
// set frequency for IPU clocks
// set frequency for board clock
// set display type (VGA)
platform_init();
/* IPU Specific */
// set the ipu display channel path, including IDMAC->DC->DI->LCD
// Enable the display panel, basicly reset and provide the backlight
display_tz_enable();
// (...)
}
Because this document is meant to document the IPU execution we are not focused on explaining the first three functions in bootmain. Because there is so much happening in both platform_init and display_tz_enable we created specific documents which describe each one. You can check those documents here:
* [[ipu-specifics:platform_init|platform_init()]]
* [[ipu-specifics:display_tz_enable()]]
Afterwards, we have to tell the monitor what is the normal world.
// (...)
init_secure_monitor(normal_world);
int i;
for(i=0; i<10; i++) {
secure_world();
};
// End of bootmain
return;
}
For specifics about init_secure_monitor check this page:
* [[ipu-specifics:init_secure_monitor|init_secure_monitor()]]
The secure_world function is shown above and displays the image of a lock (a symbol of it's secure world context), waits for 5 seconds and then triggers an SMC (secure monitor call) which is responsible for the world switch. This means that after the secure world displays its image for 5 seconds it switches to the normal world. The normal world, also shown bellow, does the same thing but with a different image. This is done for a total of 10 times.
// code in TrustZone Normal World
void normal_world(void) {
while(1) {
display_logo_image();
hal_delay_us(5000000);
asm volatile("smc #0\n\t");
}
}
void secure_world(void) {
display_lock_image();
hal_delay_us(5000000);
asm volatile("smc #0\n\t");
}