## Main ### Linker script (.ld) The primary purpose of the linker script is to describe how the sections in the input object files (`c_program.o` and `assembly_program.o`) should be mapped into the output file (`.elf`). ```c= SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(8); bss_begin = .; .bss (NOLOAD) : { *(.bss) } bss_end = .; } ``` After startup, the Raspberry Pi loads `kernel8.img` into physical memory at `0x80000` and starts execution from there. That's why the `.text.boot` section must be first; It is intended for the OS startup code. The `.text`, `.rodata`, and `.data` sections contain kernel-compiled instructions, read-only data, and initialized data, respectively. And align the section so that it starts at an address that is a multiple of 8. If the section is not aligned, it would be more difficult to use the `str` instruction to store 0 at the beginning of the `bss` section because the `str` instruction can be used only with 8-byte-aligned addresses. The `.bss` section is designated for uninitialized data. This allows the ELF binary to only store the size of the section without its contents by utilizing the `NOLOAD`. The linker understands not to load this section from the ELF file into memory during startup. That's why we need to record the start and end of the section (hence the `bss_begin` and `bss_end` symbols). ### Booting kernel (.S) This file contains the kernel startup code: ```c= .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl clear_bss clear_bss: cbz x1, prepare_kernel str xzr, [x0], #8 subs x1, x1, #8 b clear_bss prepare_kernel: mov sp, #0x3F000000 bl kernel_main ``` Let's review this file in detail: ```c= .section ".text.boot" ``` First, we specify that everything defined in `boot.S` should go in the `.text.boot` section. Previously, we saw that this section is placed at the beginning of the kernel image by the linker script. So when the kernel is started, execution begins at the `start` function: ```c= .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang ``` The first thing this function does is check the processor ID. The Raspberry Pi 3 has four core processors, and after the device is powered on, each core begins to execute the same code. However, we don't want to work with four cores; we want to work only with the first one and put all of the other cores in an endless loop. This is exactly what the `_start` function is responsible for. It gets the processor ID from the `mpidr_el1` system register. If the current process ID is 0, then execution is transferred to the master function: ```c= master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl clear_bss ``` Here, we clean the `.bss` section by calling `clear_bss`. The `clear_bss` function need two arguments: the start address (bss_begin) and the size of the section needed to be cleaned (bss_end - bss_begin): ```c= clear_bss: cbz x1, prepare_kernel // If BSS cleared, jump to prepare_kernel str xzr, [x1], #8 // Store 0 into x1. Then updates x1 to the next 8 bytes sub x1, x1, #8 // Subtract 8 bytes from the remaining size to clear b clear_bss // Loop back to check if there's more BSS should clear ``` In the `clear_bss` section, we are zeroing out the uninitialized data area (`.bss` section) which must be zeroed before the kernel can properly start. This is a standard procedure in booting systems to ensure that all uninitialized variables start with a zero value, avoiding unpredictable behaviors. The clearing loop continues until the entire `.bss` section is zeroed out. This loop uses the ARMv8 instructions to efficiently clear memory in 8byte chunks. ```c= prepare_kernel: mov sp, #0x3F000000 bl kernel_main ``` After cleaning the `.bss` section, we initialize the stack pointer (`sp`) to the `0x3F000000` and pass execution to the kernel_main function. This approach ensures that the stack will not override the kernel image as it grows. Kernel's stack won't grow very large and the image itself is tiny。 The Raspberry Pi 3 reserves the memory above address `0x3F000000` for devices. To activate or configure a particular device. ``` +-----------------------------------------+ <- 0x40000000 (1GB) | Reserved for Peripherals | | (mini uart) | +-----------------------------------------+ <- 0x3F000000 | User Stack | | (Starts here, grows downward) | +-----------------------------------------+ | Free Memory | | (for dynamic allocation, user heap) | +-----------------------------------------+ | User Heap | | (Starts here, grows upward) | +-----------------------------------------+ <- bss_end | .bss | | (Uninitialized data, zeroed at start) | +-----------------------------------------+ <- bss_begin | .data | | (Initialized data, from ELF) | +-----------------------------------------+ | .text | | (Executable instructions) | +-----------------------------------------+ <- 0x80000 (Kernel Load Address) ``` ### main (.c) We have seen that the boot code eventually passes control to the `kernel_main` function. Let's take a look at it: ```c= #include "../header/mini_uart.h" #include "../header/shell.h" void kernel_main(void) { uart_init(); uart_send_string("Type in `help` to get instruction menu!\r\n"); shell(); } ``` This function is one of the simplest in the kernel. It works with the `Mini UART` device to print to screen and read user input. The kernel just prints some `string` and then enters to shell run infinite loop that reads characters from the user and sends them back to the screen. ## Mini UART uart_init is one of the most complex and important functions in this lesson, and we will continue to examine it in the next three sections. #### GPIO alternative function selection Rpi3 has several GPIO lines for basic input-output devices such as LED or button. Besides, some GPIO lines provide alternate functions such as UART and SPI. Before using UART, you should configure GPIO pin to the corresponding mode. You can see the list of all available GPIO alternative functions in the image below (the image is taken from page 102 of `BCM2837 ARM Peripherals` manual): ![image](https://hackmd.io/_uploads/HJg-cVN0a.png) GPIO 14, 15 can be both used for mini UART and PL011 UART. However, mini UART should set ALT5 and PL011 UART should set ALT0. You need to configure `GPFSEL1` register to change alternate function. The `GPFSEL1` register is used to control alternative functions for pins 10-19. The meaning of all the bits in those registers is shown in the following table (page 92 of `BCM2837 ARM Peripherals` manual): ![image](https://hackmd.io/_uploads/rJzkTINA6.png) ``` - GPFSEL1 control alternative functions for pins 10-19 bit 12 = GPIO 14 bit 15 = GPIO 15 - Clear GPIO 14 and 15 to (000) by using NOT (~) on '111' (7) selector &= ~(7 << 12); selector &= ~(7 << 15); - Set GPIO 14 and 15 to ALT5 (010) by using '010' (2) selector |= 2 << 12; selector |= 2 << 15; ``` So now you know everything you need to understand the following lines of code that are used to configure GPIO pins 14 and 15 to work with the Mini UART device: ```c= unsigned int selector; selector = *GPFSEL1; selector &= ~(7 << 12); // clean gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 12; // set alt5 for gpio14 selector |= 2 << 15; // set alt5 for gpio 15 *GPFSEL1 = selector; ``` #### GPIO pull-up/down Next, you need to configure pull up/down register to disable GPIO pull up/down. It’s because these GPIO pins use alternate functions, not basic input-output. Please refer to the description of `GPPUD` and `GPPUDCLKn` registers for a detailed setup. If you use a particular pin as input and don't connect anything to this pin, you will not be able to identify whether the value of the pin is 1 or 0. In fact, the device will report random values. The pull-up/pull-down mechanism allows you to overcome this issue. If you set the pin to the pull-up state and nothing is connected to it, it will report `1` all the time (for the pull-down state, the value will always be 0). In our case, we need neither the pull-up nor the pull-down state, because both the 14 and 15 pins are going to be connected all the time. The pin state is preserved even after a reboot, so before using any pin, we always have to initialize its state. There are three available states: pull-up, pull-down, and neither (to remove the current pull-up or pull-down state), and we need the third one. Switching between pin states is not a very simple procedure because it requires physically toggling a switch on the electric circuit. This process involves the `GPPUD` and `GPPUDCLK` registers and is described on page 101 of the `BCM2837 ARM Peripherals` manual. ![image](https://hackmd.io/_uploads/rypKbH4R6.png) This procedure describes how we can remove both the pull-up and pull-down states from a pin, which is what we are doing for pins 14 and 15 in the following code: ```c= *GPPUD = 0; delay(150); *GPPUDCLK0 = (1 << 14) | (1 << 15); delay(150); *GPPUDCLK0 = 0; ``` #### Initializing the Mini UART Now our Mini UART is connected to the GPIO pins, and the pins are configured. The rest of the `uart_init` function is dedicated to Mini UART initialization. ```c= *AUX_ENABLE = 1; // Enable mini uart (this also enables access to its registers) *AUX_MU_CNTL_REG = 0; // Disable auto flow control and disable receiver and transmitter (for now) *AUX_MU_IER_REG = 0; // Disable receive and transmit interrupts *AUX_MU_LCR_REG = 3; // Enable 8 bit mode *AUX_MU_MCR_REG = 0; // Set RTS line to be always high *AUX_MU_BAUD_REG = 270; // Set baud rate to 115200 *AUX_MU_CNTL_REG = 3; // Finally, enable transmitter and receiver ``` After this is executed, the Mini UART is ready for work! #### Sending data using the Mini UART After the Mini UART is ready, we can try to use it to send and receive some data. To do this, we can use the following two functions: ```c= void uart_send(char c) { while (!(*(AUX_MU_LSR_REG)&0x20)){} *AUX_MU_IO_REG = c; } char uart_recv() { while (!(*(AUX_MU_LSR_REG)&0x01)){} char temp = *(AUX_MU_IO_REG)&0xFF; return temp == '\r' ? '\n' : temp; } ``` Both of the functions start with an infinite loop, the purpose of which is to verify whether the device is ready to transmit or receive data. We are using the `AUX_MU_LSR_REG` register to do this. Bit zero, if set to 1, indicates that the data is ready; this means that we can read from the UART. Bit five, if set to 1, tells us that the transmitter is empty, meaning that we can write to the UART. Next, we use `AUX_MU_IO_REG` to either store the value of the transmitted character or read the value of the received character. We also have a very simple function that is capable of sending strings instead of characters. This function just iterates over all characters in a string and sends them one by one. ```c= void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } ``` This is a fuction to display a binary value in hexadecimal: ```c= void uart_hex(unsigned int d) { unsigned int n; int c; uart_send_string("0x"); for (c = 28; c >= 0; c -= 4) { n = (d >> c) & 0xF; n += n > 9 ? 0x37 : 0x30; uart_send(n); } } /* Example: 10 + 0x37 = 0x41 (which is the ASCII code for the character 'A') 11 + 0x37 = 0x42 (which is the ASCII code for the character 'B') 12 + 0x37 = 0x43 (which is the ASCII code for the character 'C') 13 + 0x37 = 0x44 (which is the ASCII code for the character 'D') 14 + 0x37 = 0x45 (which is the ASCII code for the character 'E') 15 + 0x37 = 0x46 (which is the ASCII code for the character 'F') */ ``` ### mini_uart.c ```c= #include "../header/mini_uart.h" #include "../header/utils.h" void delay(unsigned int clock) { while (clock--) { asm volatile("nop"); } } void uart_init() { unsigned int selector; selector = *GPFSEL1; selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio 15 *GPFSEL1 = selector; *GPPUD = 0; delay(150); *GPPUDCLK0 = (1 << 14) | (1 << 15); delay(150); *GPPUDCLK0 = 0; *AUX_ENABLE = 1; // Enable mini uart (this also enables access to its registers) *AUX_MU_CNTL_REG = 0; // Disable auto flow control and disable receiver and transmitter (for now) *AUX_MU_IER_REG = 0; // Disable receive and transmit interrupts *AUX_MU_LCR_REG = 3; // Enable 8 bit mode *AUX_MU_MCR_REG = 0; // Set RTS line to be always high *AUX_MU_IIR_REG = 6; *AUX_MU_BAUD_REG = 270; // Set baud rate to 115200 *AUX_MU_CNTL_REG = 3; // Finally, enable transmitter and receiver } void uart_send(char c) { while (!(*(AUX_MU_LSR_REG)&0x20)){} *AUX_MU_IO_REG = c; } char uart_recv() { while (!(*(AUX_MU_LSR_REG)&0x01)){} char temp = *(AUX_MU_IO_REG)&0xFF; return temp == '\r' ? '\n' : temp; } void uart_send_string(const char *str) { while (*str) { uart_send(*str++); } } void uart_hex(unsigned int d) { unsigned int n; int c; uart_send_string("0x"); for (c = 28; c >= 0; c -= 4) { n = (d >> c) & 0xF; n += n > 9 ? 0x57 : 0x30; uart_send(n); } } ``` ### mini_uart.h Rpi3 accesses its peripherals through memory mapped input output(MMIO). When a CPU loads/stores value at a specific physical address, it gets/sets a peripheral’s register. Therefore, you can access different peripherals from different memory addresses. There is a VideoCore/ARM MMU translating physical addresses to bus addresses. The MMU maps physical address `0x3f000000` to bus address `0x7e000000`. In your code, you should **use physical addresses instead of bus addresses**. However, the reference uses bus addresses. You should translate them into physical one. The GPIO address refer to page 90 of the `BCM2837 ARM Peripherals` manual, Auxiliary peripherals address refer to page 8 of the `BCM2837 ARM Peripherals` manual. ```c= #define MMIO_BASE 0x3F000000 // Base address for memory-mapped I/O // GPIO Function Select Registers. #define GPFSEL1 ((volatile unsigned int*)(MMIO_BASE+0x00200004)) #define GPPUD ((volatile unsigned int*)(MMIO_BASE+0x00200094)) #define GPPUDCLK0 ((volatile unsigned int*)(MMIO_BASE+0x00200098)) // Mini UART (AUX) Peripheral Registers. #define AUX_ENABLE ((volatile unsigned int *)(MMIO_BASE + 0x00215004)) #define AUX_MU_IO_REG ((volatile unsigned int *)(MMIO_BASE + 0x00215040)) #define AUX_MU_IER_REG ((volatile unsigned int *)(MMIO_BASE + 0x00215044)) #define AUX_MU_LCR_REG ((volatile unsigned int *)(MMIO_BASE + 0x0021504C)) #define AUX_MU_MCR_REG ((volatile unsigned int *)(MMIO_BASE + 0x00215050)) #define AUX_MU_LSR_REG ((volatile unsigned int *)(MMIO_BASE + 0x00215054)) #define AUX_MU_CNTL_REG ((volatile unsigned int *)(MMIO_BASE + 0x00215060)) #define AUX_MU_BAUD_REG ((volatile unsigned int *)(MMIO_BASE + 0x00215068)) #define AUX_MU_IIR_REG ((volatile unsigned int *)(MMIO_BASE + 0x00215048)) // Function declarations for UART operations. void uart_init(); void uart_send(char c); char uart_recv(); void uart_send_string(char* str); void uart_hex(unsigned int d); ``` ## Shell ### shell.c ```c= #include "header/shell.h" #include "../header/utils.h" #include "../header/mini_uart.h" #include "../header/mailbox.h" #include "../header/reboot.h" #include <stddef.h> #define BUFFER_MAX_SIZE 256 #define COMMNAD_LENGTH_MAX 20 void help() { uart_send_string("help : print this help menu\n"); uart_send_string("hello : print Hello World!\n"); uart_send_string("reboot : reboot the device\n"); uart_send_string("info : the mailbox hardware info\n"); } void hello() { uart_send_string("Hello World!\r\n"); } void reboot() { uart_send_string("rebooting...\r\n"); reset(1000); } void info() { get_board_revision(); get_arm_memory(); } void read_command(char *buffer) { size_t index = 0; while (1) { buffer[index] = uart_recv(); uart_send(buffer[index]); if (buffer[index] == '\n') break; index++; } buffer[index + 1] = '\0'; } void parse_command(char *buffer) { utils_newline2end(buffer); uart_send('\r'); if (buffer[0] == '\0') return; else if (utils_str_compare(buffer, "help") == 0) help(); else if (utils_str_compare(buffer, "hello") == 0) hello(); else if (utils_str_compare(buffer, "reboot") == 0) reboot(); else if (utils_str_compare(buffer, "info") == 0) info(); else uart_send_string("commnad not found\r\n"); } void shell() { while (1) { char buffer[BUFFER_MAX_SIZE]; uart_send_string("# "); read_command(buffer); parse_command(buffer); } } ``` ### shell.h ```c= void shell(); ``` ## Mailbox Mailbox is a communication mechanism between ARM and VideoCoreIV GPU. You can use it to set framebuffer or configure some peripherals. Because only upper 28 bits of message address could be passed, the message array should be correctly aligned. #### Mailbox Message Structure for "Get Board Revision" ![image](https://hackmd.io/_uploads/rJk-GNB0T.png) - **Buffer Size** (`mailbox[0]`): - Total size of the buffer in bytes. Here, `7 * 4` accounts for the header (2 u32s), one tag (including its own header of 3 u32s and value buffer), and the end tag. Total entries = 7. - **Buffer Request/Response Code** (`mailbox[1]`): - Indicates whether the message is a request or a response. - For requests, this is `0x00000000`. - **Tag Section**: - **Tag Identifier** (`mailbox[2]`): Identifies the request type, `0x00010002` for "Get Board Revision". - **Value Buffer Size** (`mailbox[3]`): Indicates the size of the request's value buffer, which is `4` bytes for "Get Board Revision". - **Request/Response Code** (`mailbox[4]`): Set to `0` to indicate a request. - **Response Value** (`mailbox[5]`): Reserved for the response, initially set to `0`. - **End Tag** (`mailbox[6]`): - Marks the end of the message, set to `0x0`. ```c= void get_board_revision() { mailbox[0] = 7 * 4; // buffer size in bytes mailbox[1] = REQUEST_CODE; // tags begin mailbox[2] = GET_BOARD_REVISION; mailbox[3] = 4; mailbox[4] = TAG_REQUEST_CODE; mailbox[5] = 0; // value buffer // tags end mailbox[6] = END_TAG; mailbox_call(); uart_send_string("In get_board_revision: "); uart_hex(mailbox[5]); // it should be 0xa020d3 for rpi3 b+ uart_send_string("\r\n"); } ``` #### Mailbox Message Structure for "Get ARM memory" ![image](https://hackmd.io/_uploads/S1c-zEHCp.png) - **Buffer Size** (`mailbox[0]`): - The total size of the buffer in bytes. Here, `8 * 4` accounts for the header (2 u32s), one tag (including its own header of 3 u32s and value buffer of 2 u32s), and the end tag. Total entries = 8. - **Buffer Request/Response Code** (`mailbox[1]`): - Indicates whether the message is a request or a response. - For requests, this is `0x00000000`. - **Tag Section**: - **Tag Identifier** (`mailbox[2]`): Identifies the request type, `0x00010005` for "Get ARM Memory". - **Value Buffer Size** (`mailbox[3]`): Indicates the size of the request's value buffer, which is `8` bytes for "Get ARM Memory", accounting for both base address and size. - **Request/Response Code** (`mailbox[4]`): Set to `0` to indicate a request. - **Response Values** (`mailbox[5]` and `mailbox[6]`): Reserved for the response, initially set to `0`. The first for base address, the second for size. - **End Tag** (`mailbox[7]`): - Marks the end of the message, set to `0x0`. ```c= void get_arm_memory() { mailbox[0] = 8 * 4; // buffer size in bytes mailbox[1] = REQUEST_CODE; // tags begin mailbox[2] = ARM_MEMORY; // tag identifier mailbox[3] = 8; mailbox[4] = TAG_REQUEST_CODE; mailbox[5] = 0; // value buffer mailbox[6] = 0; // value buffer // tags end mailbox[7] = END_TAG; mailbox_call(); uart_send_string("Arm base address: "); uart_hex(mailbox[5]); uart_send_string("\r\n"); uart_send_string("Arm memory size: "); uart_hex(mailbox[6]); uart_send_string("\r\n"); } ``` Now we need to implement the `mailbox_call()` function: ```c= volatile unsigned int __attribute__((aligned(16))) mailbox[8]; int mailbox_call() { //The message consists of the address of the mailbox array in the upper bits //And the channel number in the lower 4 bits. unsigned int readChannel = (((unsigned int)((unsigned long)&mailbox) & ~0xF) | (0x8 & 0xF)); while (*MAILBOX_STATUS & MAILBOX_FULL){} /* wait until we can write to the mailbox */ *MAILBOX_WRITE = readChannel; /* write our message to the mailbox with channel identifier */ while (1) /* now wait for the response */ { while (*MAILBOX_STATUS & MAILBOX_EMPTY){} /* waits until the mailbox is not empty */ if (readChannel == *MAILBOX_READ) /* is it a valid successful response */ return mailbox[1] == MAILBOX_RESPONSE; } return 0; } ``` `volatile` the compiler does not apply certain optimizations—like caching variable values or reordering accesses—because their values can change at any time without the compiler knowing. `unsigned int` Specifies that each element in the mailbox array will be an unsigned integer. This is appropriate because the mailbox interface operates with 32-bit values (u32). `__attribute__((aligned(16)))` This is a GCC-specific extension that instructs the compiler to align the mailbox array in memory on a 16-byte boundary. The alignment is crucial for two reasons: 1. The Raspberry Pi's mailbox interface requires the message buffer (our mailbox array) to be 16-byte aligned. This is because the mailbox protocol uses the lower 4 bits of the buffer address for channel identification, and these bits must be zeroes in the address. A 16-byte alignment ensures that the address' lower 4 bits are indeed zero. 2. It can also improve performance on many architectures by ensuring that the data structure aligns well with the cache lines. `& ~0xF` Applies a bitwise AND with the bitwise negation of 0xF (which is 1111 in binary). This operation clears the lower 4 bits of the address. It's done because the mailbox interface uses the upper 28 bits for the address and reserves the lower 4 bits for specifying the mailbox channel. Clearing these bits ensures they're ready to set the channel number. `| (0x8 & 0xF)` This sets the channel number. First, 0x8 specifies channel 8 (the property channel used for communicating with the GPU). The & 0xF is technically not necessary here since 0x8 is already within the lower 4 bits range, but it's a form of ensuring that only the lower 4 bits are considered. Finally, the bitwise OR | operation combines the cleared address with the channel number, resulting in a value that includes both the memory address (in the upper 28 bits) and the channel number (in the lower 4 bits). ### mailbox.c ```c= #include "../header/utils.h" #include "../header/mini_uart.h" #include "header/mailbox.h" volatile unsigned int __attribute__((aligned(16))) mailbox[8]; int mailbox_call() { unsigned int readChannel = (((unsigned int)((unsigned long)&mailbox) & ~0xF) | (0x8 & 0xF)); while (*MAILBOX_STATUS & MAILBOX_FULL){} *MAILBOX_WRITE = readChannel; while (1) { while (*MAILBOX_STATUS & MAILBOX_EMPTY){} if (readChannel == *MAILBOX_READ) return mailbox[1] == MAILBOX_RESPONSE; } return 0; } void get_board_revision() { mailbox[0] = 7 * 4; // buffer size in bytes mailbox[1] = REQUEST_CODE; mailbox[2] = GET_BOARD_REVISION; // tag identifier mailbox[3] = 4; // maximum of request and response value buffer's length. mailbox[4] = TAG_REQUEST_CODE; mailbox[5] = 0; // value buffer mailbox[6] = END_TAG; mailbox_call(); uart_send_string("Get board revision : "); uart_hex(mailbox[5]); uart_send_string("\r\n"); } void get_arm_memory() { mailbox[0] = 8 * 4; // buffer size in bytes mailbox[1] = REQUEST_CODE; mailbox[2] = ARM_MEMORY; // tag identifier mailbox[3] = 8; // maximum of request and response value buffer's length. mailbox[4] = TAG_REQUEST_CODE; mailbox[5] = 0; // value buffer mailbox[6] = 0; // value buffer mailbox[7] = END_TAG; mailbox_call(); uart_send_string("Arm base address : "); uart_hex(mailbox[5]); uart_send_string("\r\n"); uart_send_string("Arm memory size : "); uart_hex(mailbox[6]); uart_send_string("\r\n"); } ``` ### mailbox.h ```c= #define MAILBOX_BASE MMIO_BASE + 0xb880 // Base address for the mailbox interface // Mailbox registers #define MAILBOX_READ ((volatile unsigned int *)(MAILBOX_BASE)) #define MAILBOX_STATUS ((volatile unsigned int *)(MAILBOX_BASE + 0x18)) #define MAILBOX_WRITE ((volatile unsigned int *)(MAILBOX_BASE + 0x20)) // Buffer and tag request code #define REQUEST_CODE 0x00000000 #define TAG_REQUEST_CODE 0x00000000 // Buffer responde code #define REQUEST_SUCCEED 0x80000000 #define REQUEST_FAILED 0x80000001 // Tags for identifier #define GET_BOARD_REVISION 0x00010002 #define ARM_MEMORY 0x00010005 // Tag for end tag #define END_TAG 0x00000000 // unknown #define MAILBOX_RESPONSE 0x80000000 #define MAILBOX_EMPTY 0x40000000 #define MAILBOX_FULL 0x80000000 // Function declarations for mailbox operations. int mailbox_call(); void get_board_revision(); void get_arm_memory(); ``` ## Reboot Rpi3 doesn’t originally provide an on board reset button. You can follow this example code to reset your rpi3. But, this snippet of code only works on real rpi3, not on QEMU. ### reboot.c ```c= #include "header/reboot.h" void set(long addr, unsigned int value) { volatile unsigned int* point = (unsigned int*)addr; *point = value; } void reset(int tick) { // reboot after watchdog timer expire set(PM_RSTC, PM_PASSWORD | 0x20); // full reset set(PM_WDOG, PM_PASSWORD | tick); // number of watchdog tick } void cancel_reset() { set(PM_RSTC, PM_PASSWORD | 0); // full reset set(PM_WDOG, PM_PASSWORD | 0); // number of watchdog tick } ``` ### reboot.h ```c= #define PM_PASSWORD 0x5a000000 #define PM_RSTC 0x3F10001c #define PM_WDOG 0x3F100024 void set(long addr, unsigned int value); void reset(int tick); void cancel_reset(); ``` ## Utils ### utils.c ```c= #include "header/utils.h" #include <stddef.h> int utils_str_compare(char *a, char *b) { char aa, bb; do { aa = (char)*a++; bb = (char)*b++; if (aa == '\0' || bb == '\0') { return aa - bb; } } while (aa == bb); return aa - bb; } void utils_newline2end(char *str) { while (*str != '\0') { if (*str == '\n') { *str = '\0'; return; } ++str; } } ``` ### utils.h ```c= int utils_str_compare(char* a, char* b); void utils_newline2end(char *str); ``` ## Makefile ```c= CC := aarch64-linux-gnu-gcc LD := aarch64-linux-gnu-ld OBJCOPY := aarch64-linux-gnu-objcopy CFLAGS := -Wall -Wextra -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles CFLAGS += -IMain -IUART/header -IShell/header -IMailbox/header -IUtils/header -IReboot/header CFLAGS += -I/usr/lib/gcc-cross/aarch64-linux-gnu/11/include K8 := kernel8 LINKER := Main/linker.ld SRC_S := $(wildcard Main/*.s) SRC_C := $(wildcard Main/*.c UART/*.c Shell/*.c Mailbox/*.c Reboot/*.c Utils/*.c) OBJS := $(SRC_C:.c=.o) $(SRC_S:.s=.o) Main/main.o all: $(K8).img $(K8).img: $(OBJS) $(LD) -T $(LINKER) -o $(K8).elf $^ $(OBJCOPY) -O binary $(K8).elf $@ %.o: %.s $(CC) $(CFLAGS) -c $< -o $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ sd: $(K8).img cp $(K8).img /media/ben/4DFF-0A36 sync qemu: qemu-system-aarch64 -M raspi3b -serial null -serial stdio -display none -kernel $(K8).img rpi3: screen /dev/ttyUSB0 115200 clean: rm -f $(wildcard */*.o) $(wildcard *.o) $(K8).elf $(K8).img ``` ## Complete Result ![image](https://hackmd.io/_uploads/SJU9yCsRT.png) Right terminal is rpi3b result, left terminal is qemu result