Tuesday, August 20, 2013

FreeRTOS on Beagleboard Rev C3

This post is all about running FreeRTOS on a Beagleboard Rev C3 with the OMAP 3530 processor from TI. The intention of this post is to serve as a reference for porting operating systems over various platforms and cover a few basics in the process. You might wonder - Why port FreeRTOS on a Cortex A8 based hardware? Well, its fun and a great learning process. I am assuming that the reader has at least intermediate knowledge of ARM architecture. This basic port is functional at this stage but still needs considerable polishing.

- Beagle Board Rev C3
- ARM DS-5 Suite (Evaluation)
- XLoader & U-Boot Sources
- FreeRTOS source code
- Arm-linux-gnueabi toolchain
- A SD card.
- Mkimage utility
- OMAP 3530 TRM
- Lots of patience
-12 gauge loaded shotgun.

FreeRTOS can be downloaded from the website. The latest version 7.5.2 has a port for Cortex A9 based device already. You can refer to that and its accompanying demo example. As we are using the BB here, these files cannot be used in the implementation but just as a reference. I however, chose to use the FreeRTOS version that was pretty old and released in 2010 to keep distractions away and that I could do things my way. Once this is a success i shall proceed to do the same with the latest update of FreeRTOS

The directory structure
- Source: The FreeRTOS source code.
- Source/include: The header files
- Source/portable: Target dependent files. For the ARM architecture these will be in the RVDS sub-directory of portable.

Before diving into creating a project, compilation etc it is essential to outline the various aspects the port might need and come up with basic software architecture and files required.

You must be aware of the features provided by FreeRTOS. These can be found here. It has a small footprint – which is enough for the friggin’ 256 MB of RAM on the BB. The scheduler is pre-emptive (I chose to operate it in pre-emptive mode) hence there will be a tick timer involved and a few other mainstream OS features. Hence we need one hardware tick timer from the OMAP for the OS tick and a UART for debug logs. There also has to be some kind of memory manger / allocation system. This can be found in the MemMang sub-directory of portable - three memory managers for the OS heap. It is also essential to know the memory mapping of the BB and the OMAP3530. On the BB the RAM is mapped to address 0x80000000. Refer OMAP TRM for memory mapped peripherals.

For any port there will be a bunch of files that will be target / architecture dependent (usually assembly) and a few C files that will be architecture independent. These files do not need any modification and can be used as is. In case of FreeRTOS the C files in the source directory can be used as is, so create  your project folder and copy them in that, including the headers in the include sub-directory.

As mentioned earlier the files that will require porting will be the ones in the RVDS sub-directory of portable. Now then, in my case I have used port.c and portmacro.h from the ARM7_LPC21xx directory. The portASM.s file cannot be used directly as it’s for the LPC processors from NXP. It needs modifications. Portmacro.inc contains context save and restore macros. We also have to use the startup.s file for the Beagleboard. Initially copy the two files mentioned above to your directory. The startup file for Beagleboard can be found in the fireworks demo application for the Beagleboard in DS-5. Copy this file to your workspace to be changed later. Then copy heap_2.c to your project directory, this will be the memory manager. Every demo project has a FreeRTOSConfig.h header file which contains OS options that can be enabled / disabled or modified e.g. the pre-emption, timer tick rate, heap size etc. This file needs to be copied to the project workspace.

The Beagleboard boot process
Here is a brief summary of the BB boot process. The internal ROM code transfers the control to XLoader. Internally a call to start_armboot from start.S file for omap3 starts the minimal board initialization for u-boot to run. On a BB the default boot device is the NAND flash which contains the u-boot binary. To boot off the mmc card the USER button needs to be pressed when releasing the board RESET button. At this point XLoader will load the mmc u-boot image to the address 0x80008000 defined by the macro CFG_LOADADDR. After loading, control jumps straight to u-boot.

U-boot does its necessary setup for clocks, plls, sdram initializations etc and prepares the board to load linux. Now here lies the rub, we do not want to run linux, we want to run FreeRTOS. We therefore have a few options – Create an OS image / bin file that will be directly executed by XLoader or create an image that will be loaded via u-boot. In the former case the final OS image will have to be named u-boot.bin, (because that is the name the XLoader hunts for when booting from mmc) which will be loaded at address 0x80008000. The latter case is not so trivial, because in this case we will have to provide a bin file with a proper u-boot header such that u-boot can understand and therefore load. This header will be appended to the OS image by the mkimage utility as we shall see later. I chose to load from u-boot, thereby letting u-boot initialize the entire board.

During boot, a script file is also used to execute few commands. This script file is generated from a boot.cmd (you can name it anything) file using the mkimage utility. This loads the image via the source command in u-boot. It hunts the directory structure for the script image and begins to load based on the information in the script. The details of which will be discussed later. The XLoader and U-Boot sources that I used can be found in the below GIT repositories.

Final checklist
- XLoader and U-Boot source.
- FreeRTOS sources and headers (croutine.c, list.c, queue.c, tasks.c, include folder)
- FreeRTOS ports for ARM – (port.c, portASM.S startup.S, portmacro.h, portmacro.inc, FreeRTOSConfig.h)
- FreeRTOS memory manager – (heap_2.c)

Stage 1: Modifying U-boot to load FreeRTOS
Here we shall see how we can load any OS or bare-metal application from u-boot without making considerable changes to u-boot code.

After the autoboot promt times-out u-boot will read the boot.scr file from the mmc. This file was created using the boot.cmd file whose contents were as follows

fatload mmc 0 80000000 freertosimg
bootm 80000000

The first command loads the OS image file which is in u-boot format to the RAM address 0x80000000. The second line calls the u-boot command bootm which starts execution from the specified address.

If we see the implementation of the bootm command it ultimately makes a call to the function do_bootm_linux (\arch\arm\lib\bootm.c). Here it prepares to load the linux image and finally transfer control to the kernel from the boot_jump_linux function. To avoid all this, one can comment out the original implementation of do_bootm_linux and write a new function with the same name which will call a new function – boot_jump_freertos. This function will take a bootm_headers_t pointer variable and a flag.

Prototype: static void boot_jump_freertos(bootm_headers_t *images, int flag)
The implementation is shown below. I chose not to create a new function but called my function right at the beginning thereby skipping the other stuff.

The 0x40 has been added such that the execution starts at an offset of 0x40 from the base of RAM. The initial 0x40 bytes belong to the header. Weird stuff but its tolerable. That is it. The u-boot modification has been done. The code can be built and the final u-boot.bin and img files can be written to the mmc card. Make sure the SD card is formatted according to the instructions given here.

Stage 2: startup.S
When kernel_entry will be called from u-boot, the control will be transferred to the startup.S file for the BB. This is the standard BB startup file that initializes the cortex A8 MMU, caches and sets up the vector table etc. At this point we must setup the stacks for each mode of the processor, initialize the MMU before calling __main. We must have a specific mapping of the memory regions that we will specify in the scatter file. For the BB, I chose to follow the below structure

All regions are 1 MB in size. The APP CODE region is write-protected by setting the appropriate AP bits in its translation table descriptor. Only first level translation has been used that makes up for 4096 translation table entries governing 4096 1 MB regions - simple. All other regions are RW. The entire stack region of 1 MB contains four 256 kb regions for the stacks of respective modes. There is no User/System stack, the reason for this is that FreeRTOS tasks run in System Mode and the task stacks are allocated from the heap. Whenever a context switch happens the SP is loaded with the address of the stack allocated in the heap. The scheduler runs in SVC or Supervisor mode. At this stage let’s spare the vector table. All we do is just set the VBAR register in CP15.

Stage 3: Main.c
After calling __main the code will do the necessary library initialization, scatter loading and finally jump to a user defined main() function. To make things modular, create a new file main.c and add some startup initialization into it. Here is what I chose to do in main.c

- enable_caches();
- init_uart();
- init_int_controller();
- start_freeRTOS();

Enable caches after scatter loading. Standard procedure.

Initialize the UART. I chose to re-init the UART setup by u-boot by resetting it from the System Control Register and re-configuring it for 115200, 8, n, 1. This code can be put in a new file call it uart.c. Refer to the UART section in the OMAP 3530 TRM for detailed register description.

Initialize the interrupt controller. Reset the interrupt controller. During code execution I found an interrupt was being generated from one of the peripherals as a consequence of u-boot actions, hence I chose to reset the interrupt controller to clear any interrupts setup by u-boot. Place it in a new file called interrupt_con.c

Transfer control to FreeRTOS scheduler (vTaskStartScheduler())

Stage 4: Setting up the Timer Tick
Within the scheduler initialization is a function called prvSetupTimerInterrupt that sets up the timer which will generate the tick for the OS if it’s supposed to be preemptive. For this I used general purpose timer 1 of the OMAP 3530 in auto reload mode. For the tick frequency I decided to use a period of 10msec i.e. a timer interrupt triggered every 10ms. Create a new file call it timer.c. Refer Timer section in OMAP 3530 TRM. A rough overview of the steps is detailed below.

- Reset the timer
- Stop it, for no reason. Write a 0 to TIMER1 TCLR register.
- Select the GP Timer 1 source clock as the 32 KHz clock in the CM (PRCM Module)
- Write a 1 to the CM FCLKEN bit.
- Setup the timer TCRR and TLDR registers with a count to generate a 10 ms tick. I figured a formula for that headache - TCRR Count = TLDR Count = 0xFFFFFFFF – (Delay Time / 31.25usec) where delay is the required delay #ftw. Essentially it looks like:

    *GPTIMER1_TCRR = 0xFFFFFFFF - 0x140; // Initial value to start incrementing
    *GPTIMER1_TLDR = 0xFFFFFFFF - 0x140; // Reload value after overflow 

- Clear all pending events for the timer by writing to the TISR register.
- Enable the overflow event in the TIER register.
- Clear the mask for the GP Timer 1 IRQ in the Interrupt controller. (Refer TRM)
- Set auto reload mode for the timer. Write a value of 3 to TCLR register.
- Execute a system wide DSB intrinsic (__dsb (15)) to synchronize.

The timer interrupt hander is the function vPreemptiveTick can be found in portasm.S file. The interrupt handler in the startup.S file now needs to be modified as shown below. Basically we just check and verify that it was an interrupt from timer 1 and branch off to the interrupt handler i.e. the tick function. Make sure not to use BL instruction within the IRQ handler as it will corrupt the LR of the IRQ mode and make you want to use that shot gun I mentioned earlier. Note that I have not used nested interrupts to keep things simple. (PS – I do not even have a debugger if things go wrong. #ftw)

After the interrupt has been serviced it needs to clear the timer event and acknowledge the interrupt so that subsequent interrupts can be processed. The code for this can be inserted in the portRESTORE_CONTEXT macro at the end of the macro as shown below.

Stage 5: vPortYieldProcessor
vPortYieldProcessor is used to manually force a context switch. It is initiated by a SVC (formerly SWI) instruction that switches the processor to SVC mode. The SVC exception handler needs to be setup to call vPortYieldProcessor. The below image shows the SVC handler, it also shows other exception handlers that I setup for reporting errors. Since I do not have a debugger all the errors were reported via the UART. For data aborts and prefetch aborts the contents of DFSR and DFAR, IFSR were extracted from CP15 registers. You can create a separate file called crash.c to implement the handlers for DABT and PABT and another file called coputil.s for the CP15 driver.

Stage 6: Setup the heap
The memory manager heap_2.c needs to be setup such that it utilizes the 1 MB section ARM_LIB_HEAP defined in the scatter file. This can be done by placing the heap structure in the above region using the place in section attribute as shown below

Stage 7: Setup everything in ARM mode
In the file port.c there is a function pxPortInitialiseStack. Inside this FreeRTOS will setup the stack to make it look like we returned from an IRQ handler. At the end of the function after setting up the stack entry for the SPSR, it sets up the T bit for the processor. I wanted my tasks and the whole OS to run in ARM mode. Hence I commented out this section.

Stage 8: Configure FreeRTOSconfig.h
Well the screenshot below is self explanatory. Set the tick value to 100. Heap size to 1MB. The clock setting does not matter in this case or I don’t give a damn about it.

Stage 9: Build and Deploy
ARM-DS 5 will generally build everything provided you set it up properly. I assume that you know this. Setup the CPU type, apcs value, --unaligned-access for the C compiler, scatter loading file, entry point etc.

The final output will be a freertos.axf file in the debug directory (based on your settings) that needs to be converted to a bin file by using the fromelf utility.

fromelf --binary freertos.axf --output freertos.bin

The u-boot header mentioned earlier can be applied as

mkimage -A arm -O linux -T kernel -C none -a 0x80000000 -e 0x80000000 -n "Cortex A8 FreeRTOS" -d freertos.bin freertosimg

The freertosimg can now be copied to the sdcard.

To create the script file, first create a boot.cmd file with the contents
fatload mmc 0 80000000 freertosimg
bootm 80000000

Then execute mkimage as
mkimage –A arm –O linux –T kernel –C none –a 0 –e 0 –n “Cortex A8 Image” –d boot.cmd boot.scr

boot.scr can now be copied to the sdcard.

Insert the sdcard into the board and boot from mmc. If all goes well you should see your application running. In my case I just blinked the two user led’s on the board using the flash task that comes with the demo. Here are a few more screenshots of the process.

Debugging !!!

 Running !!!

Here is a Cinemagraph of the board and the LEDs

A note – this was not meant to be a precise description but just a reference. You can use this to try deploying your FreeRTOS on any other board. This is a long write up & I could have missed something. If you get stuck trying this or have any questions please post them in the comments below. I will keep updating this post with newer items and “fixes”.

Thanks for watching.