# Operating System Capstone - Lab0
Course Guideline: https://oscapstone.github.io/.
## Install Cross Compiler
why cross compiler?
Rpi3 uses ARM Cortex-A53 CPU. To compile our source code to 64-bit ARM machine code, we need a cross compiler if we develop on a non-ARM64 environment.
1. Install a cross compiler on your host computer.
```
sudo apt-get install gcc make binutils gcc-aarch64-linux-gnu
```
2. Install qemu-system-aarch64.
```
sudo apt install
sudo apt install qemu-system
qemu-system-aarch64 -h // see install situation
```
Reference:
[Install pre-compiled ARM cross-compiler onto Ubuntu Linux.
](https://www.youtube.com/watch?v=gm7lEJS3IuY&)
### (Cross-)compiling process

linker.ld
```
.section ".text"
_start:
wfe
b _start
```
From Object Files to ELF
An ELF file can be loaded and executed by program loaders. In bare-metal programming, ELF can be loaded by some bootloaders.
```
aarch64-linux-gnu-ld -T linker.ld -o kernel8.elf a.o
```
From ELF to Kernel Image
```
aarch64-linux-gnu-objcopy -O binary kernel8.elf kernel8.img
```
Check on QEMU
```
qemu-system-aarch64 -M raspi3b -kernel kernel8.img -display none -d in_asm
```
sample Makefile
```makefile=
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
CFLAGS = -Iinclude -fno-stack-protector -Wall -Wextra -Wpedantic -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles
CFLAGS += -I../../../opt/lib/gcc/aarch64-linux-gnu/12.2.0/include
all: kernel8.img
start.o: start.S
aarch64-linux-gnu-gcc $(CFLAGS) -c start.S -o start.o
%.o: %.c
aarch64-linux-gnu-gcc $(CFLAGS) -c $< -o $@
kernel8.img: start.o $(OBJS)
aarch64-linux-gnu-ld start.o $(OBJS) -T linker.ld -o kernel8.elf
aarch64-linux-gnu-objcopy -O binary kernel8.elf kernel8.img
clean:
rm kernel8.elf *.o >/dev/null 2>/dev/null || true
run:
qemu-system-aarch64 -M raspi3b -serial null -serial stdio -initrd initramfs.cpio -dtb bcm2710-rpi-3-b-plus.dtb -display none -kernel kernel8.img
```
# Operating System Capstone - Lab1
## Basic Exercise 1 - Basic Initialization - 20%
Goal: Initialize rpi3 after booted by bootloader.
### Task1: Allocate proper address
Linker Script
Define `text`, `rodata` and `bss` sections which are required for a C environment.

in `linker.ld`
```linker=
SECTIONS
{
. = 0x80000; //setting start point:
.text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
PROVIDE(_data = .);
.data : { *(.data .data.* .gnu.linkonce.d*) }
.bss (NOLOAD) : {
. = ALIGN(16);
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
__bss_end = .;
}
_end = .;
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}
__bss_size = (__bss_end - __bss_start)>>3;
```
### Task2: Set start point and only allow core0 CPU
in `start.S`
Initialization
1. Halt other cores by checking affinity register MPIDR_EL1.
1. If not main core, go into infinite loop to halt.
2. NOTE: according to linux convention, all other cores should be put on wfe loop until they are brought in. See:https://www.kernel.org/doc/html/latest/arm64/booting.html
```asm=
.section ".text .boot"
.global _start
_start: // = 0x80000;
// read cpu id, stop slave cores
mrs x1, mpidr_el1
and x1, x1, #3
// four-core cpu, & with 11 to preserve latest two bits
cbz x1, setting
// cbz: compare branch zero, if x1==0 jump to setting; otherwise continue
```
If x1 != zero, go to hang and wait
```asm=
halt: wfe //(Wait for event)
b halt
```
### Task3: Initialize stack pointer and Zero out bss section:

1. Set `sp` to just before the code (_start).
> our C stack should start at address 0x8000 and grow downwards. Why 0x8000? Well when the hardware loads our kernel in to memory, it does not load it into address 0, but to address 0x8000. Since our kernel runs from 0x8000 and up, our stack can safely run from 0x8000 and down without clobbering our kernel.
```asm=
setting: ldr x1, =_start
//load data from _start into register x1
mov sp, x1
//move a value x1 into a register sp
ldr x1, =__bss_start
ldr w2, =__bss_size
//__bss_start and __bss_size define in linker file
// x1 is 64bits for address, w2 is 32 bits for value
```
>由於當初 C 語言標準提到「未初始化的全域變數(un-initialized global variables)其初始值為零(zero)」,所以得到的結果便是「程式執行時,必須將未初始化的全域變數都初始化成零」。
>Linux 針對這種狀況的解決方式是「配置 zeroed pages 給 .bss section」,因此 un-initialized global variables 的值(value)便會為零。
clear bss
```asm=
clear_bss: cbz w2, kernel_main
str xzr,[x1],#8
//The line str xzr, [x1], #8 is used to clear a 64-bit word of memory
// by storing the value zero (which is always held in register xzr) at the memory location pointed to by x1
sub w2, w2, #1
cbnz w2, clear_bss
```
### Task4: Go to `main`(C program)
Go to C program
```asm=
kernel_main: bl main
b halt
```
## Basic Exercise 2 - Mini UART - 20%
### Task0: Background Knowledge
[Specifications/Important to read it](https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf)
- MMIO
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.
- GPIO
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, we should configure GPIO pin to the corresponding mode.

UART is a type of serial communication interface used to transmit and receive data between two devices.(here is RasPi & computer) UART is often used to communicate with devices that do not have a network connection, such as microcontrollers, sensors, and other embedded systems
### Task1: Uart Initialization
register
> The MMU maps physical address 0x3f000000 to bus address 0x7e000000. In our code, you should use physical addresses instead of bus addresses.
P.90 Register View
The GPIO has 41 registers. All accesses are assumed to be 32-bit.

P.8 Auxiliaries: UART1 & SPI1, SPI2


2. uart initialization
- Set AUXENB register to enable mini UART. Then mini UART register can be accessed(P.9)

```
*AUX_ENABLE |=1;
0x????????
or 0x00000001
---------------
0x???????1 -> set 0 bits to 1
```
- Set AUX_MU_CNTL_REG to 0. Disable transmitter and receiver during configuration.

```
*AUX_MU_CNTL = 0;
```
- Set AUX_MU_IER_REG to 0. Disable interrupt because currently you don’t need interrupt.

```
*AUX_MU_IER = 0;
```
- Set AUX_MU_LCR_REG to 3. Set the data size to 8 bit.(P.14)

00 : the UART works in 7-bit
11(3) : the UART works in 8-bit mode
Cause 8 bits can use in ASCII, Unicode, Char
- Set AUX_MU_MCR_REG to 0. Don’t need auto flow control.
- Set AUX_MU_BAUD to 270. Set baud rate(transmit speed) to 115200. After booting, the system clock is 250 MHz.

- Set AUX_MU_IIR_REG to 6. No FIFO.(P.13)

```
*AUX_MU_IIR = 0xc6;
0xc6 = 11000110
bit 6 bit 7 No FIFO. Sacrifice reliability(buffer) to get low latency
Writing with bit 1 set will clear the receive FIFO
Writing with bit 2 set will clear the transmit FIFO
```
- Set AUX_MU_CNTL_REG to 3(bin: 11). Enable the transmitter and receiver.

3. Set Uart1 transmit/Receive data(P.104)
typo here name: TXD1/RXD1

Every GPIO pin can carry an alternate function.
TXD1/RXD1 is in GPIO14/GPIO15 ALT5

each GPFSEL controls 10 pin, GPFSEL1 controls 10-19

```
register unsigned int r;
r=*GPFSEL1;
// gpio14, gpio15 clear to 0
r&=~((7<<12)|(7<<15));
000111000000000000 (7<<12)
or 111000000000000000 (7<<15)
----------------------------
not 111111000000000000
-----------------------------
000000111111111111
and ?????????????????? (r)
-----------------------------
000000???????????? (12-17 bits set to 0)
r|=(2<<12)|(2<<15); // set gpio14 and 15 to 010/010 which is alt5
000010000000000000 (2<<12)
or ?????????????????? r
---------------------------------
000010000000000000 r
or 010000000000000000 (2<<15)
---------------------------------
010010000000000000 r
// from here activate Trasmitter&Receiver
*GPFSEL1 = r;
```
Since We've set alt5, we want to disable basic input/output. To achieve this, we need diable pull-up and pull-down


```cpp=
*GPPUD = 0;
//Wait 150 cycles
//this provides the required set-up time for the control signal
r=150; while(r--) { asm volatile("nop"); }
// GPIO control 54 pins
// GPPUDCLK0 controls 0-31 pins
// GPPUDCLK1 controls 32-53 pins
// set 14,15 bits = 1 which means we will modify these two bits
// trigger: set pins to 1 and wait for one clock
*GPPUDCLK0 = (1<<14)|(1<<15);
r=150; while(r--) { asm volatile("nop"); }
*GPPUDCLK0 = 0; // flush GPIO setup
```
Until now, we finish Uart Initialization. Then we can start trasmitting and receiving data.
## Basic Exercise 3 - Simple Shell - 20%
### Task1: Uart functionality
1. Receive a character
```cpp=
char uart_get_char() {
char r;
/* wait until something is in the buffer */
//bit 0 is set if the receive FIFO holds at least 1 symbol.
do{asm volatile("nop");}while(!(*AUX_MU_LSR&0x01));
/* read it and return */
r=(char)(*AUX_MU_IO);
/* convert carriage return to newline */
return r=='\r'?'\n':r;
}
```
2. Send a character

```cpp=
void uart_send_char(unsigned int c) {
/* wait until we can send */
// P.15 AUX_MU_LSR register shows the data(line) status
// AUX_MU_LSR bit 5 => 0x20 = 00100000
// bit 5 is set if the transmit FIFO can accept at least one byte.
// &0x20 can preserve 5th bit, if bit 5 set 1 can get !true = false leave loop
// else FIFO can not accept at lease one byte then still wait
do{asm volatile("nop");}while(!(*AUX_MU_LSR&0x20));
/* write the character to the buffer */
//P.11 The AUX_MU_IO_REG register is primary used to write data to and read data from the
//UART FIFOs.
//communicate with(send to) the minicom and print to the screen
*AUX_MU_IO=c;
}
```
3. Display a string
```cpp=
void uart_send_string(char* s) {
while(*s) {
/* convert newline to carriage return + newline */
if(*s=='\n')
uart_send_char('\r');
uart_send_char(*s++);
}
}
```
4. Display a binary value in hexadecimal
```cpp=
void uart_binary_to_hex(unsigned int d) {
unsigned int n;
int c;
uart_send_string("0x");
for(c=28;c>=0;c-=4) {
// get highest tetrad(4 bits)
n=(d>>c)&0xF;
// 0-9 => '0'-'9', 10-15 => 'A'-'F'
n += (n>9 ? 0x37 : 0x30);
uart_send_char(n);
}
}
/*
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')
*/
```
### Task2: Shell implement
```cpp=
void shell(){
while(1) {
char buffer[BUFFER_MAX_SIZE];
uart_send_string("# ");
read_command(buffer);
parse_command(buffer);
}
}
void read_command(char* buffer) {
int index = 0;
while(1) {
buffer[index] = uart_get_char();
uart_send_char(buffer[index]);
//meet newline then we know this is the end of the string
if(buffer[index] == '\n') {
buffer[index] = '\0';
buffer[index+1] = '\n';
break;
}
index++;
}
}
void parse_command(char* buffer) {
char* input_string = buffer;
char* parameter[5]; //5 is the available parameter length
int para_idx = 0;
int input_string_len = utils_strlen(input_string);
for(int i=0; i < input_string_len; i++){
if(*(input_string+i) == ' '){
*(input_string+i) = '\0';
//point to the start of next string
parameter[para_idx++] = (input_string+i+1);
}
}
if(utils_string_compare(input_string,"help")) {
uart_send_string("help :print this help menu\n");
uart_send_string("hello :print Hello World!\n");
uart_send_string("info :Get the hardware's information\n");
} else if (utils_string_compare(input_string,"hello")) {
uart_send_string("Hello World!\n");
}
}
```
## Basic Exercise 4 - Mailbox - 20%
- Channel
A Channel is a number that tells you and the GPU what the information being sent through the mailbox means. We will only be needing channel 1, the framebuffer channel, and channel 8, the property channel.
Define the channel in `mailbox.h`
```cpp=
/* channels */
#define MBOX_CH_POWER 0
#define MBOX_CH_FB 1
#define MBOX_CH_VUART 2
#define MBOX_CH_VCHIQ 3
#define MBOX_CH_LEDS 4
#define MBOX_CH_BTNS 5
#define MBOX_CH_TOUCH 6
#define MBOX_CH_COUNT 7
#define MBOX_CH_PROP 8
```
- Tag
Mailbox property interface(channel 8, 9) contains several tags to indicate different operations. You should refer to https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface to get detail specifications.
Define the tags in `mailbox.h`
```cpp=
/* tags */
#define TAG_REQUEST_CODE 0x00000000
#define MBOX_TAG_GETSERIAL 0x00010004
#define MBOX_TAG_GETBOARD 0x00010002
#define MBOX_TAG_GETARMMEM 0x00010005
#define MBOX_TAG_LAST 0x00000000
```
Buffer contents:
* u32: buffer size in bytes (including the header values, the end tag and padding)
* u32: buffer request/response code
* Request codes:
* 0x00000000: process request
* All other values reserved
* Response codes:
* 0x80000000: request successful
* 0x80000001: error parsing request buffer (partial response)
* All other values reserved
* u8...: sequence of concatenated tags(see below tag format)
* u32: 0x0 (end tag)
* u8...: padding
Tag format:
* u32: tag identifier
* u32: value buffer size in bytes
* u32:
* Request codes:
* b31 clear: request
* b30-b0: reserved
* Response codes:
* b31 set: response
* b30-b0: value length in bytes
* u8...: value buffer
* u8...: padding to align the tag to 32 bits.

Now we are going to get board revision, which is implemented in `mailbox.c`
```cpp=
#define MBOX_TAG_GETBOARD 0x00010002
void get_board_revision(){
===u32: buffer size in bytes===
mailbox[0] = 7 * 4; // buffer size in bytes
===u32: buffer request===
mailbox[1] = MBOX_REQUEST;
===u8...: sequence of concatenated tags===
===u32: tag identifier===
// tags begin
mailbox[2] = MBOX_TAG_GETBOARD; // tag identifier
===u32: value buffer size in bytes===
mailbox[3] = 4; // maximum of request and response value buffer's length.
===u32: Request codes===
mailbox[4] = TAG_REQUEST_CODE;
===u8...: value buffer for response===
mailbox[5] = 0; // value buffer
===u32: 0x0 (end tag)===
// tags end
mailbox[6] = MBOX_TAG_LAST;
mailbox_call(); // message passing procedure call
//we should implement it following the 6 steps provided.
}
```
```cpp=
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 r = (((unsigned int)((unsigned long)&mailbox)&~0xF) | (MBOX_CH_PROP&0xF));
/* wait until we can write to the mailbox */
do{asm volatile("nop");}while(*MBOX_STATUS & MBOX_FULL);
/* write the address of our message to the mailbox with channel identifier */
*MBOX_WRITE = r;
/* now wait for the response */
while(1) {
/* is there a response? */
/*
This loop is similar to the previous one but waits until the mailbox is not empty.
This is because after writing a message to the mailbox
we expect to receive a response, so we wait until there is something in the mailbox to read.
*/
do{asm volatile("nop");}while(*MBOX_STATUS & MBOX_EMPTY);
/* is it a response to our message? */
if(r == *MBOX_READ)
/* is it a valid successful response? */
return mailbox[1]==MBOX_RESPONSE;
}
return 0;
}
```
Similarly, we implement` get_arm_mem `in the same way
Get ARM memory
* Tag: 0x00010005
* Request:
* Length: 0
* Response:
* Length: 8 ---> The response length is longer(so mailbox[0] is larger than get_board_revision)
* Value:
* u32: base address in bytes --->mailbox[5]
* u32: size in bytes --->mailbox[6]
```cpp=
void get_arm_mem(){
mailbox[0] = 8 * 4; // buffer size in bytes
mailbox[1] = MBOX_REQUEST;
// tags begin
mailbox[2] = MBOX_TAG_GETARMMEM; // 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
// tags end
mailbox[7] = MBOX_TAG_LAST;
mailbox_call(); // message passing procedure call, we should implement it following the 6 steps provided above.
}
```
After implementing both `get_board_revision` and `get_arm_mem`, then we can go back to `shell.c` to integrate these two parts.
```cpp=
if(string_compare(input_string,"help")) {
uart_send_string("help :print this help menu\n");
uart_send_string("hello :print Hello World!\n");
uart_send_string("info :Get the hardware's information\n");
uart_send_string("reboot :reboot the device\n");
} else if (string_compare(input_string,"hello")) {
uart_send_string("Hello World!\n");
} else if (string_compare(input_string,"info")) {
if (mailbox_call()) {
get_board_revision();
uart_send_string("My board revision is: ");
uart_binary_to_hex(mailbox[5]);
uart_send_string("\r\n");
get_arm_mem();
uart_send_string("My ARM memory base address is: ");
uart_binary_to_hex(mailbox[5]);
uart_send_string("\r\n");
uart_send_string("My ARM memory size is: ");
uart_binary_to_hex(mailbox[6]);
uart_send_string("\r\n");
}
}
}
```
## Advanced Exercise 1 - Reboot - 30%
We only implement the following code to reboot the system
```cpp=
#define PM_PASSWORD 0x5a000000
#define PM_RSTC 0x3F10001c
#define PM_WDOG 0x3F100024
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
}
```
and in `shell.c`
```cpp=
if (string_compare(input_string,"reboot")) {
uart_send_string("Rebooting....\n");
reset(1000);
}
```
# Other Labs
[Operating System Capstone - Lab0](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/S104l7ZS3)
[Operating System Capstone - Lab1](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/S104l7ZS3)
[Operating System Capstone - Lab2](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/Hy6j7lzrn)
[Operating System Capstone - Lab3](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/r1WP_BrX3)
[Operating System Capstone - Lab4](https://hackmd.io/@OJo2ruXGShKdpuewtwzZcQ/SJYrXgY93)