Supervisor call happens, when the svc (previously called swi) instruction get executed. Exception is then entered. Supervisor call is the standard way for user process to ask the kernel for something. As user code might request many different things, the kernel must somehow know which one was requested. The svc instruction takes one immediate operand. The supervisor call exception handler can check at what address the execution was, read svc instruction from there and inspect it's bytes. This way, by executing svc with different immediate values, the used mode code can request different things from the kernel - the value in svc shall encode the request's type.
To save time and for the sake of simplicity, we don't make use of immediades in svc and instead we encode call's type in r0. In our implementation we decided, that supervisor call will preserve and clobber the same registers as function call and it will return values through r0, just as function call. This enables us to use actually perform the supervisor call as call to function defined in src/arm/PL0/svc.S. Calls from C are performed in src/arm/PL0/PL0_utils.c and request type encodings are defined in src/arm/common/svc_interface.h (they must be known to both user mode code and handler code).
We've compiled useful utilities (i.e. memcpy(), strlen(), etc.) in src/arm/common/strings.c. Those Do not depend on the environment and can be used by both user mode code, kernel code, even bootloader code.
Functions used for io (like puts()) are also defined in common way for privileged and unprivileged code. They do, however, rely on the existence of putchar() and getchar(). In PL0 code (src/arm/PL0/PL0_utils.c), putchar() and getchar() are defined to perform a supervisor call, that does that operation. In the PL1 code, they are defined as operations on UART.
src/arm/PL1/PL1_common/uart.c implements putchar() and getchar() in terms of UART. Those implementations are blocking - they poll UART peripheral registers in a loop, checking, if the device is ready to perform the operation. They are, however, accompanied by functions getchar_non_blocking() and putchar_non_blocking(), that check **once** if the device is ready and only perform the operation if it is. Otherwise, they return an error value, Their purpose is to use them with interrupts. In interrupt-driven UART we avoid waiting in a loop - instead, an IRQ comes when desired UART's operation completes. The code that wants to write/read from UART, does, however, need to tie it's operation with IRQ handler and scheduler. Blocking versions should not be used once UART interrupts are enabled or in exception handlers, that should always run quickly. However, doing this does not break UART and might be justified for debugging purposes (like error() function defined in src/arm/common/io.c and used throughout the kernel code).
There are 2 UARTs in RapsberryPi. One mini UART (also called UART 1) and one PL011 UART (also called UART 0). The PL011 UART is used exclusively in this project. The hardware allows some degree of configuration of which pins which UART is routed to (via so-called alternative functions). In our project it is assumed, that UART 0's TX and RX are routed to GPIO pins 14 & 15 by the firmware, which is true for rpi-open-firmware. With stock Broadcom firmware, either changing the default configuration (config.txt) or selection of alternative fuctions as part of uart initialization (present in TODOs list) might be required.
Before UART can be used, GPIO pins 14 and 15 should have pull up/down disabled. This is done as part of UART initialization in uart_init() in src/arm/PL1/PL1_common/uart.c. There is a requirement that UART is disabled when being configured, which is also fulfilled by uart_init(). The PL011 is toroughly described in BCM2837 ARM Peripherals as well as PrimeCell UART (PL011) Techincal Reference Manual .
TODOs
Contains what the name suggests, in plain text. It lists things that can be implemented or improvemed, as well as tasks, that were once listed and have since been completed (in which case they're marked as done).
Several timers are available on the RaspberryPi:
1. System Timer (with 4 interrupt lines, regarded as the most reliable, as it is not derived from the system clock and hence is not affecter by processor power mode changes)
2. ARM side Timer (based on a ARM AP804)
3. ARM Generic Timer (optional extension to ARMv7-A and ARMv7-R, configured through coprocessor registers)
At first, we attempted to use the System Timer, some code for which is still present in src/arm/PL1/kernel/bcmclock.h. The interrupts from that timer are not, however, routed to any ARM core under rpi-open-firmware, but rather to the GPU. Because of that, we ended using the ARM side Timer (programmed in src/arm/PL1/kernel/armclock.h).
The ARM side Timer based on ARM AP804 is currently only available on real hardware and not in qemu. Programming the ARM Generic Timer (listed in TODOs) could enable the use of timer interrupts in qemu.
This project has been done as part of the Embedded Systems course on AGH University of Science and Technology . The goal of the project was to investigate and program the MMU (Memory Management Unit) of the RaspberryPi, but ended up to form a basis of a small operating system.
RaspberyPi 3 model B was the hardware platform used, with stock firmware replaced with rpi-open-firmware . An emulator, qemu (version 2.9.1) capable of emulating an older RaspberryPi 2 was also used extensively.
The project was written in C programming language and ARM assembly. Knowlegde of C is required to understand the code. Knowledge of ARM assembly is useful, but it should be considered a thing, that can be learned **while** working with it. Still, the reader should at least have an idea of what assembly language is and how it is used.
This documentation is intended to provide information on bare-metal programming on the RapsberryPi and ARM in general, as well as description of our solutions and implementations. There is a lot of information available on the topic in online sources , yet it is not always in an easy-to-understand form and the amount of different options described in manuals might me overwhelming for people new to the topic. That's why we attempted to describe our work in a way the audience of bare-metal programming newcomers will find useful. External resources we used are linked throughout the documentation as well as listed here .
It is planned, for future years students of the Embedded Systems course, to have an option to continue or reuse previous projects, such as this one. We hope this documentation will prove useful to our younger colleagues who happen to be work with the codebase.
In case on any bugs or questions, the authors can be contacted at <>.