# 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 ![](https://i.imgur.com/v3eUk85.png) 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. ![](https://i.imgur.com/kVnZsbU.png) 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: ![](https://i.imgur.com/HgWFYYP.png) 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. ![](https://i.imgur.com/x8TvhPp.png) 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. ![](https://i.imgur.com/DSjU5Pi.png) P.8 Auxiliaries: UART1 & SPI1, SPI2 ![](https://i.imgur.com/MhfuOZW.png) ![](https://i.imgur.com/mPYCjrk.png) 2. uart initialization - Set AUXENB register to enable mini UART. Then mini UART register can be accessed(P.9) ![](https://i.imgur.com/PjlQ3nS.png) ``` *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. ![](https://i.imgur.com/fSbMlUH.png) ``` *AUX_MU_CNTL = 0; ``` - Set AUX_MU_IER_REG to 0. Disable interrupt because currently you don’t need interrupt. ![](https://i.imgur.com/ftWp4Ds.png) ``` *AUX_MU_IER = 0; ``` - Set AUX_MU_LCR_REG to 3. Set the data size to 8 bit.(P.14) ![](https://i.imgur.com/TpEBkUZ.png) 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. ![](https://i.imgur.com/fMQEhdO.png) - Set AUX_MU_IIR_REG to 6. No FIFO.(P.13) ![](https://i.imgur.com/4gy6XPV.png) ``` *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. ![](https://i.imgur.com/fSbMlUH.png) 3. Set Uart1 transmit/Receive data(P.104) typo here name: TXD1/RXD1 ![](https://i.imgur.com/RvuQwzW.png) Every GPIO pin can carry an alternate function. TXD1/RXD1 is in GPIO14/GPIO15 ALT5 ![](https://i.imgur.com/TNsmj0v.png) each GPFSEL controls 10 pin, GPFSEL1 controls 10-19 ![](https://i.imgur.com/WHIZoOE.png) ``` 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 ![](https://i.imgur.com/M7Ie5CT.png) ![](https://i.imgur.com/cxnqyBk.png) ```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 ![](https://i.imgur.com/xx5LF1f.png) ```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. ![](https://hackmd.io/_uploads/HJP9j1zS2.png) 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)