Custom FreeRTOS port for QEMU RTX
This is the second part that follows the porting QEMU for the Real-Time Experiment (RTX) ARM R5F core. This part addresses the porting of FreeRTOS to run on the ported QEMU that I blogged earlier. For porting, I picked the latest FreeRTOS 10.4.1 at that time. It appears that the versioning of this RTOS has since changed since the later part of 2020; however this released version is still available for downloading from its hosting site.
Regardless of version of FreeRTOS used in the porting, you might still find some information to be useful to carry on with your port into the latest version. The porting that I did was heavily inspired by the knowledge I learned while using Xilinx ZynqMP and information that I gathered from its Wiki pages. While Xilinx work was far more in complexity because of their hardware, mine was to make it simple for simple hardware, a stand-alone ARM R5F single core.
After I finished my porting of QEMU then I realized, now what ? Nothing to run and nothing to prove whether or not my porting of QEMU is working or not ! I just opened a can of worm that I needed to find the way to close it. This led me to finding the simplest, smallest RTOS to port. FreeRTOS appears to be very popular for being free and open source and several chip vendors are supporting this RTOS thus it became my primary candidate for porting so I downloaded and went through its code and see what I needed to do to get it going.
The essential FreeRTOS files
If you look at FreeRTOS code you will find a lot of things all over the place to the point of not knowing of what to do with it. It happened to me. First I began examining its example of port for some platforms here and there to get some picture primarily the ports of the ARM based platforms. I built some Demo examples on how they are built and what components they used for those ports. Since I used GCC toolchain that I already have installed from Xilinx SDK, I focused on the demo that used this toolchain.
Populating FreeRTOS files
I started by creating an empty directory freertos-rtx and populated this directory with a set of header files, and from the demo examples, it appeared that all of the header files in the Source/include directory are needed. I populated my freertos-rtx/include/freertos by copying them over before making any modification. It looked like this,
freertos-rtx/ ├── include │ ├── freertos │ │ ├── FreeRTOS.h │ │ ├── FreeRTOSConfig.h │ │ ├── StackMacros.h │ │ ├── atomic.h │ │ ├── croutine.h │ │ ├── deprecated_definitions.h │ │ ├── event_groups.h │ │ ├── list.h │ │ ├── message_buffer.h │ │ ├── mpu_prototypes.h │ │ ├── mpu_wrappers.h │ │ ├── portable.h │ │ ├── portmacro.h │ │ ├── projdefs.h │ │ ├── queue.h │ │ ├── semphr.h │ │ ├── stack_macros.h │ │ ├── stdint.readme │ │ ├── stream_buffer.h │ │ ├── task.h │ │ └── timers.h
The only file that I modified to suit my virtual hardware is FreeRTOSConfig.h. This modification was commonly done to all of the OS port. It is the FreeRTOS configuration file where you could add, subtract, modify specific features that suit your hardware, for example, CPU clock frequency, tick rate, heap size, task features etc..
So at this point, any headers that are FreeRTOS's are in freertos-rtx/include/freertos where I eventually populated with more header files specific to my device drivers or task applications. All of the header files to be added can be at the top include directory so they wouldn't mix up with FreeRTOS's native files. This would help future porting to the later version easier by knowing where all the files came from.
Next was to bring in C source files of FreeRTOS into my freertos-rtx. I created src directory and copied all of C files from FreeRTOSv10.4.1/FreeRTOS/Source into this directory. These are timers.c, tasks.c etc.. which are common files to most of the ported platforms in the demo. There are three essential files to bring over as well. These are port.c, portASM.S, and portmacro.h. This set of files is in its Source/portable and I picked to best fit my need. For this port I picked the set in portable/GCC/ARM_CA9. Being portable implies that they may be modified as suitable for my need, but I did not modify them at all ! I put portmacro.h into my freertos-rtx/include/freertos header files directory as you may have noticed.
I also picked one file heap_3.c from its Source/portable/MemMang. You might as well pick any heap_x.c file that would fit well to your model regarding memory heap management. Since my model is a simple model so it could just use a simple heap management code.
In summary, so far I had
Basic C header files (*.h) from Source/include
Basic C source files (*.c) from Source
Basic C source files from Source/portable
My freertos-rtx/src source directory looked like this,
freertos-rtx/ ├── src │ ├── ParTest.c │ ├── croutine.c │ ├── event_groups.c │ ├── heap_3.c │ ├── list.c │ ├── port.c │ ├── portASM.S │ ├── queue.c │ ├── stream_buffer.c │ ├── tasks.c │ └── timers.c
Populating platform files
From this point on it was getting more specific to platform so I created freertos-rtx/platform directory to house the specific components. When a processor goes to reset or power-on, it has to fetch and execute code from its boot vector table. A boot vector code from Xilinx Demo of its R5 core (RTOSDemo_R5) is the perfect code to use so I copied its FreeRTOS_asm_vectors.S to my freertos-rtx/platform directory where I modified the interrupt handlers for debugging. You could pretty much use this file as-is. This is the only file I made use from Xilinx R5 demo since my hardware model is different. The only similarity is the R5 core so its CPU boot vectors are identical. This should be true for most implementation of R5 core.
Since boot vector jump to _boot at reset/power-cycle, I need to have this function some where in the code so I cloned one of Xilinx R5 demo boot.S and simplified it to my simpler model to become my Init.qemu.S. The _boot (line 4 below) in this file was to set up stacks, initialized CPU mode, interrupt masks, invalidate caches, enable instruction cache, enable FPU (if present). Mostly it is done by call to arm_cpu_lowlevel_init (line 10) followed by enabling cycle counter, setting up data MMU and data cache, initializing GIC done by calling _sysinit (line 30) and finally jump to to start of c-runtime, _os_start (line 32) that eventuall jump to main task. A snippet of this file,
The _sys_init set up two memory banks according to my h/w model starting at physical offset zero(0). Each bank is 2MB of on-chip embedded memory eDRAM/eSRAM type for cachable data memory. It also initialized timer (sysctl_init) needed for clock tick to support FreeRTOS and initialed (routed) the interrupt to the GIC (gic_init). Routing the timer tick to GIC is important because it is used by task switching of FreeRTOS. For my h/w platform model, I have the integrated timer and UART (PL010) so I need the interrupts for both of these devices. FreeRTOS is counting on having proper timer tick interrupt for task switching among other things so it is essential that the timer works correctly. A snippet of code,
I put the set of files that are specific to my platform in freertos-rtx/platform. Their are for initialization of the h/w platform components such as timer, MMU, GIC etc.. Here is what this tree looks like,
freertos-rtx/platform/ ├── FreeRTOS_asm_vectors.S ├── FreeRTOS_tick_config.c ├── Init.qemu.S ├── cache-armv7.S ├── cache.c ├── gic.c ├── lowlevel.S ├── mmu.c ├── reent_init.c ├── sysinit.c └── vectors.c
The _os_start was to set up C-runtime code/data sections,stacks and heaps. It also initialized memory array for the data variables of the program, for example, "int one23 = 123;" in your c-code ? It set up this initialized memory location before it jump to main() so when your program read variable one23 it actually is 123. Again, you can find and reuse the CRT code in the demo example and make the adjustment as needed to match the code and data sections of your port. Typically you would define them in your linker script. I will leave the linker script later. A snippet of CRT code,
At the bottom (line 42), it is where it branches to FreeRTOS's main task where it spawns more tasks. From this point forward anything you want to do can be done in FreeRTOS.
For my port, I created freertos-rtx/lib and put the CRT code here along with C-runtime support functions such as read.c, write.c, exit.c, errno.c, open.c, sbrk.c, isatty.c etc... They are typical set of files needed in most OS port. My lib files looks like this,
freertos-rtx/lib/ ├── CMakeLists.txt ├── _exit.c ├── _sbrk.c ├── close.c ├── crt0.S ├── errno.c ├── fstat.c ├── isatty.c ├── lseek.c ├── open.c ├── read.c ├── usleep.c ├── util.c └── write.c
These files are mostly 10-20 lines of code, for example, isatty(..) is simply returned true since the UART port is a TTY device and the open(..) is simply returned EOI since I do not have FS support. They are functions to make the linking process happy. They are just the filler set of files and most of the functions are defined with weak attribute in such as a way that they can be overridden.
The application tasks
At this point it is almost done. The remaining would be application specific so I added app and driver as directories to house files for RTOS main task and UART device driver. UART device driver is the only device driver I have for this port. I needed it to support TTY console so I can used for debugging and command line interfacing with the RTOS. The main() is copied from demo example and modified for this port.
The main function is to install and set up UART driver, start RTOS timer, TX/RX UART tasks and console task and run RTOS scheduler. app_main is a TTY console task for command line interface as shown here, for example, when I typed help I would get the list of commands supported. If I typed tasks I would get the information about tasks registered.
xxx@xxx:~/freertos-rtx$ ./run.sh machine cpu_type cortex-r5f-arm-cpu UART base 0x58000000 created for serial0. main: Entering main(265) init_console, line 222 current state: standby, last_state initialize Entering app_main(89), 0.000000 rtx> tasks Task Name Status Prio HWM Task Number app_main X 1 333 3 IDLE R 0 478 6 Tmr Svc B 4 451 7 TX B 2 472 2 uart_rx_poll B 1 471 4 Rx B 1 468 1 regi_state_mon B 2 339 5 Timer ulCount : 35 rtx> help help Print the list of registered commands tasks Get information about running tasks state Get the current monitored state free Get the total size of heap memory available read read memory address next read next memory address reboot System reboot QEMU: Terminated
What I had in app and drivers are specific to the platform and OS usage. You would have what you need to get what you want it to do for your application.
freertos-rtx/ ├── app │ ├── app_main.c │ ├── command.c │ ├── main.c │ ├── partest.h │ └── regi-state.c ├── drivers │ ├── serial_pl010.c │ └── serial_pl010.h
Don't forget about one important file, the linker script. Make sure you have the correct definition of code and data sections in it. You could as well use the linker script in some of the demo example and modify it to fit your port. Look for file ending with *.ld in the demo, for example, the Xilinx's demo example linker script. Modify it as needed to fit the sections of your code. It is what I did. Instead of writing Makefile to build, I used cmake so I wrote CMakeLists.txt from top directory to all of subdirectories that need to be compiled or assembled to create the bootable code. Here is the sample of my CMakeLists.txt file,
There are other CMakeLists.txt files in nearly all of the subdirectories that contained C or S source files. cmake will create Makefile to build the ARM executable code.
mkdir build cd build cmake .. && make
is cmake building process. The binary executable, freertos-rtx and the compiled object files will be in freertos-rtx/build directory that's built using armr5-none-eabi Xilinx toolchain.
Debugging
At the earliest stage of your port you are most likely need to debug some of your code so in terms of debugging with QEMU, it was pretty much the way I blogged in my previous post on QEMU porting. You just start up QEMU in single step mode and single step it from reset vector all the way to main function. Start QEMU in debugging mode in one shell. This will be FreeRTOS shell,
qemu-system-aarch64 -M rtx-r5 -m 2m -no-reboot -nographic -s -S -kernel build/freertos-rtx
and open another shell to debug with gdb built for ARM version. I also used Xilinx's built version of ARM R5 GDB.
xx@xx:~/freertos-rtx/build$ armr5-none-eabi-gdb freertos-rtx GNU gdb (GDB) 8.3.1 Copyright (C) 2019 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "--host=x86_64-oesdk-linux --target=arm-xilinx-eabi". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from freertos-rtx... (gdb) target remote :1234 Remote debugging using :1234 _freertos_vector_table () at /home/xxxxx/freertos-rtx/platform/FreeRTOS_asm_vectors.S:82 82 B _boot (gdb) b _boot Breakpoint 1 at 0x2f848: file /home/xxxx/freertos-rtx/platform/Init.qemu.S, line 75. (gdb) b _sysinit Breakpoint 2 at 0xecbc: file /home/xxxxx/freertos-rtx/platform/sysinit.c, line 311. (gdb) c Continuing. Breakpoint 1, _boot () at /home/xxxx/freertos-rtx/platform/Init.qemu.S:75 75 cpsid aif (gdb) c Continuing. Breakpoint 2, _sysinit () at /home/xxxxx/freertos-rtx/platform/sysinit.c:311 311 setup_mem_banks(); (gdb)
Here I set two break point, one at _boot reset vector and one at _sysinit. You can single step your code this way during your debugging. There are many GUI GDB front end debuggers that you could use as well if you prefer.
Conclusion
Most work in this porting is primarily getting the code execution from reset vector to main program ie.. boot loader code. Little of it involved modifying FreeRTOS code other than its configuration file(s) that is specifically platform dependent. Find what you need in the demo examples and make use of it. The best place to look for the very low-level assembly code is to look in some boot code such as U-Boot or Barebox among others. It will save you a lot of time by using the proven code, modify as needed. Try to understand what it does and why it does so you will know if you could make use of it for your port. The most time I spent on porting this FreeRTOS is on debugging the interrupt routing to GIC because of what I thought I knew, but I didn't. If there is no timer tick, the tasks would never get scheduled and switched. It would stuck in main and going no where. I eventually got it right.
The majority of code I had for this port came from various sources and I modified to fit my need. I do not like to reinvent the wheel and you shouldn't too. Although I tested my port on QEMU, I have 99% confident that it too would run on real h/w. QEMU is quite powerful and very useful for this type of prototyping work.