diff --git a/part1/README.md b/part1-bootstrapping/README.md similarity index 100% rename from part1/README.md rename to part1-bootstrapping/README.md diff --git a/part1/boot.S b/part1-bootstrapping/boot.S similarity index 100% rename from part1/boot.S rename to part1-bootstrapping/boot.S diff --git a/part1/kernel.c b/part1-bootstrapping/kernel.c similarity index 100% rename from part1/kernel.c rename to part1-bootstrapping/kernel.c diff --git a/part1/link.ld b/part1-bootstrapping/link.ld similarity index 100% rename from part1/link.ld rename to part1-bootstrapping/link.ld diff --git a/part2/Makefile b/part2-building/Makefile similarity index 100% rename from part2/Makefile rename to part2-building/Makefile diff --git a/part2/README.md b/part2-building/README.md similarity index 100% rename from part2/README.md rename to part2-building/README.md diff --git a/part2/boot.S b/part2-building/boot.S similarity index 100% rename from part2/boot.S rename to part2-building/boot.S diff --git a/part2/kernel.c b/part2-building/kernel.c similarity index 100% rename from part2/kernel.c rename to part2-building/kernel.c diff --git a/part2/link.ld b/part2-building/link.ld similarity index 100% rename from part2/link.ld rename to part2-building/link.ld diff --git a/part3/Makefile b/part3-helloworld/Makefile similarity index 100% rename from part3/Makefile rename to part3-helloworld/Makefile diff --git a/part3/README.md b/part3-helloworld/README.md similarity index 99% rename from part3/README.md rename to part3-helloworld/README.md index 12bda97..0dce663 100644 --- a/part3/README.md +++ b/part3-helloworld/README.md @@ -201,7 +201,7 @@ void uart_writeText(char *buffer) { } ``` -You'll see that the two functions we defined in our _io.h_ header file now have some actual code, along with some other supporting functions. I'll explain what's going on in this code in the next lesson, but let's skip straight to the action now! +You'll see that the two functions we defined in our _io.h_ header file now have some actual code, along with some other supporting functions. I'll explain what's going on in this code in the next tutorial, but let's skip straight to the action now! With your new _io.c_ and _io.h_ files in place, as well as the changes to _kernel.c_ made, run `make` to build your new OS. diff --git a/part3/boot.S b/part3-helloworld/boot.S similarity index 100% rename from part3/boot.S rename to part3-helloworld/boot.S diff --git a/part3/images/3-helloworld-cable.jpg b/part3-helloworld/images/3-helloworld-cable.jpg similarity index 100% rename from part3/images/3-helloworld-cable.jpg rename to part3-helloworld/images/3-helloworld-cable.jpg diff --git a/part3/images/3-helloworld-ctlpanel.png b/part3-helloworld/images/3-helloworld-ctlpanel.png similarity index 100% rename from part3/images/3-helloworld-ctlpanel.png rename to part3-helloworld/images/3-helloworld-ctlpanel.png diff --git a/part3/images/3-helloworld-pinloc.png b/part3-helloworld/images/3-helloworld-pinloc.png similarity index 100% rename from part3/images/3-helloworld-pinloc.png rename to part3-helloworld/images/3-helloworld-pinloc.png diff --git a/part3/io.c b/part3-helloworld/io.c similarity index 100% rename from part3/io.c rename to part3-helloworld/io.c diff --git a/part3/io.h b/part3-helloworld/io.h similarity index 100% rename from part3/io.h rename to part3-helloworld/io.h diff --git a/part3/kernel.c b/part3-helloworld/kernel.c similarity index 100% rename from part3/kernel.c rename to part3-helloworld/kernel.c diff --git a/part3/link.ld b/part3-helloworld/link.ld similarity index 100% rename from part3/link.ld rename to part3-helloworld/link.ld diff --git a/part4-miniuart/Makefile b/part4-miniuart/Makefile new file mode 100644 index 0000000..89235e9 --- /dev/null +++ b/part4-miniuart/Makefile @@ -0,0 +1,19 @@ +CFILES = $(wildcard *.c) +OFILES = $(CFILES:.c=.o) +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-9.2-2019.12-x86_64-aarch64-none-elf/bin + +all: clean kernel8.img + +boot.o: boot.S + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o + +%.o: %.c + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ + +kernel8.img: boot.o $(OFILES) + $(GCCPATH)/aarch64-none-elf-ld -nostdlib -nostartfiles boot.o $(OFILES) -T link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img + +clean: + /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part4-miniuart/README.md b/part4-miniuart/README.md new file mode 100644 index 0000000..021b063 --- /dev/null +++ b/part4-miniuart/README.md @@ -0,0 +1,67 @@ +Writing a "bare metal" operating system for Raspberry Pi 4 (Part 4) +=================================================================== + +Memory-Mapped I/O +----------------- + +We have our "Hello world!" example up and running. Let's just take a little time to explain the concepts that _io.c_ is using to send this message over the UART to our dev machine. + +We started with the UART for a reason - it's a (relatively) simple piece of hardware to talk to because it uses **memory-mapped I/O** (MMIO). That means we can talk directly to the hardware by reading from and writing to a set of predetermined memory addresses on the RPi4. We can write to different addresses to influence the hardware's behaviour in different ways. + +These memory addresses start at `0xFE000000` (our `PERIPHERAL_BASE`). + +Configuring the GPIO (General Purpose Input/Output) pins +-------------------------------------------------------- + +The GPIO pins (remember - we connected our USB to serial TTL cable to these) use MMIO. The top section of _io.c_ (marked with `// GPIO`) implements a few functions to configure these pins. + +At this point, I recommend digging into the [BCM2711 ARM Peripherals document](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/rpi_DATA_2711_1p0.pdf). It has a very detailed section on GPIO. Just don't believe everything you read as there are plenty of mistakes in this document at the moment. + +It will, however, tell you what our memory-mapped GPIO **registers** like `GPFSEL0`, `GPSET0`, `GPCLR0` and `GPPUPPDN0` do. These are all at known offsets from the `PERIPHERAL_BASE` and are defined by our first `enum`. + +The two functions `mmio_read` and `mmio_write` can be used to read a value from and write a value to these registers. + +About the GPIO pins +------------------- + +Remember how we said that computers communicate in 1's and 0's? One thing we might want to do is to **set a pin** high (binary 1) or **clear a pin** low (binary 0). The two functions `gpio_set` and `gpio_clear` do just this. The corresponding hardware pin will receive a voltage when it is set high, and not when it cleared low. + +That said, however, pins can also have one of three **pull states**. This tells the RPi4 what the default state of a pin is. If a pin is set to "Pull Up", then its resting state is high (receiving voltage) unless it's told otherwise. If a pin is set to "Pull Down", then its resting state is low. This can be useful for connecting different types of devices. If a pin is set to "Pull None" then it is said to be "floating", and this is what our UART needs. The `gpio_pull` function sets the pull state of a given pin for us. + +You need to know just a few more things about the GPIO pins: + + * The RPi4 is capable of more functions than there are hardware pins available for + * To solve this, our code can dynamically map a pin to a function + * In our case, we want GPIO 14 and GPIO 15 to take alternate function 5 (TXD1 and RXD1 respectively) + * This maps the RPi4's mini UART (UART1) to the pins we connected our cable to! + * We use the `gpio_function` call to set this up + +Now the GPIO section of _io.c_ should be clear. Let's move on. + +Configuring the UART +-------------------- + +The second section of _io.c_ (marked with `// UART`) implements a few functions to help us talk to the UART. Thankfully, this device also uses MMIO, and you'll see the registers set up in the first `enum` just like you saw before. Look in the [BCM2711 ARM Peripherals document](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/rpi_DATA_2711_1p0.pdf) for a more detailed explanation of these registers. + +I do just want to call out the `AUX_UART_CLOCK` parameter, which we set to `500000000`. Remember how I said that UART communication is all about timing? Well, this is exactly the same clock speed (500Mhz) that we set in _config.txt_ when we added the `core_freq_min=500` line. This is no coincidence! + +You'll also note some other familiar numbers in the `uart_init()` function, which we call directly from our `main()` routine in _kernel.c_. We set the baud rate to `115200`, and the number of bits to `8`. + +Finally we add some useful functions: + + * `uart_isWriteByteReady` - checks the UART line status to ensure we are "ready to send" + * `uart_writeByteBlockingActual` - waits until we are "ready to send" and then sends a single character + * `uart_writeText` - sends a whole string using `uart_writeByteBlockingActual` + +You'll remember that `uart_writeText` is what we call from `main()` to print "Hello world!". + +Some extra code +--------------- + +I don't want this tutorial to just be an explanation so, in the code, you'll see I've added some more functionality to _io.c_ and made use of it in our kernel. Have a read through and see if you can understand what's going on. Refer to the documentation again if you need to. + +We can now read from our UART too! If you build the kernel and power on the RPi4 just like before, it'll say hello to the world again. But, after that, you can type into the PuTTY window and the RPi4 sends the characters right back to you. + +_Now we're communicating in two directions!_ + +We also implemented a software [FIFO buffer](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)) for our UART communication. The RPi4 has limited buffer space for data arriving on the UART, and incorporating our own is likely to make it easier to manage incoming data in future. diff --git a/part4-miniuart/boot.S b/part4-miniuart/boot.S new file mode 100644 index 0000000..e09b7d4 --- /dev/null +++ b/part4-miniuart/boot.S @@ -0,0 +1,30 @@ +.section ".text.boot" // Make sure the linker puts this at the start of the kernel image + +.global _start // Execution starts here + +_start: + // Check processor ID is zero (executing on main core), else hang + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, 2f + // We're not on the main core, so hang in an infinite wait loop +1: wfe + b 1b +2: // We're on the main core! + + // Set stack to start below our code + ldr x1, =_start + mov sp, x1 + + // Clean the BSS section + ldr x1, =__bss_start // Start address + ldr w2, =__bss_size // Size of the section +3: cbz w2, 4f // Quit loop if zero + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, 3b // Loop if non-zero + + // Jump to our main() routine in C (make sure it doesn't return) +4: bl main + // In case it does return, halt the master core too + b 1b diff --git a/part4-miniuart/io.c b/part4-miniuart/io.c new file mode 100644 index 0000000..9561780 --- /dev/null +++ b/part4-miniuart/io.c @@ -0,0 +1,165 @@ +// GPIO + +enum { + PERIPHERAL_BASE = 0xFE000000, + GPFSEL0 = PERIPHERAL_BASE + 0x200000, + GPSET0 = PERIPHERAL_BASE + 0x20001C, + GPCLR0 = PERIPHERAL_BASE + 0x200028, + GPPUPPDN0 = PERIPHERAL_BASE + 0x2000E4 +}; + +enum { + GPIO_MAX_PIN = 53, + GPIO_FUNCTION_OUT = 1, + GPIO_FUNCTION_ALT5 = 2, + GPIO_FUNCTION_ALT3 = 7 +}; + +enum { + Pull_None = 0, + Pull_Down = 1, // Are down and up the right way around? + Pull_Up = 2 +}; + +void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } +unsigned int mmio_read(long reg) { return *(volatile unsigned int *)reg; } + +unsigned int gpio_call(unsigned int pin_number, unsigned int value, unsigned int base, unsigned int field_size, unsigned int field_max) { + unsigned int field_mask = (1 << field_size) - 1; + + if (pin_number > field_max) return 0; + if (value > field_mask) return 0; + + unsigned int num_fields = 32 / field_size; + unsigned int reg = base + ((pin_number / num_fields) * 4); + unsigned int shift = (pin_number % num_fields) * field_size; + + unsigned int curval = mmio_read(reg); + curval &= ~(field_mask << shift); + curval |= value << shift; + mmio_write(reg, curval); + + return 1; +} + +unsigned int gpio_set (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPSET0, 1, GPIO_MAX_PIN); } +unsigned int gpio_clear (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPCLR0, 1, GPIO_MAX_PIN); } +unsigned int gpio_pull (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPPUPPDN0, 2, GPIO_MAX_PIN); } +unsigned int gpio_function(unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPFSEL0, 3, GPIO_MAX_PIN); } + +void gpio_useAsAlt3(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT3); +} + +void gpio_useAsAlt5(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT5); +} + +void gpio_initOutputPinWithPullNone(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_OUT); +} + +void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff) { + if (onOrOff) { + gpio_set(pin_number, 1); + } else { + gpio_clear(pin_number, 1); + } +} + +// UART + +enum { + AUX_BASE = PERIPHERAL_BASE + 0x215000, + AUX_IRQ = AUX_BASE, + AUX_ENABLES = AUX_BASE + 4, + AUX_MU_IO_REG = AUX_BASE + 64, + AUX_MU_IER_REG = AUX_BASE + 68, + AUX_MU_IIR_REG = AUX_BASE + 72, + AUX_MU_LCR_REG = AUX_BASE + 76, + AUX_MU_MCR_REG = AUX_BASE + 80, + AUX_MU_LSR_REG = AUX_BASE + 84, + AUX_MU_MSR_REG = AUX_BASE + 88, + AUX_MU_SCRATCH = AUX_BASE + 92, + AUX_MU_CNTL_REG = AUX_BASE + 96, + AUX_MU_STAT_REG = AUX_BASE + 100, + AUX_MU_BAUD_REG = AUX_BASE + 104, + AUX_UART_CLOCK = 500000000, + UART_MAX_QUEUE = 16 * 1024 +}; + +#define AUX_MU_BAUD(baud) ((AUX_UART_CLOCK/(baud*8))-1) + +unsigned char uart_output_queue[UART_MAX_QUEUE]; +unsigned int uart_output_queue_write = 0; +unsigned int uart_output_queue_read = 0; + +void uart_init() { + mmio_write(AUX_ENABLES, 1); //enable UART1 + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_CNTL_REG, 0); + mmio_write(AUX_MU_LCR_REG, 3); //8 bits + mmio_write(AUX_MU_MCR_REG, 0); + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_IIR_REG, 0xC6); //disable interrupts + mmio_write(AUX_MU_BAUD_REG, AUX_MU_BAUD(115200)); + gpio_useAsAlt5(14); + gpio_useAsAlt5(15); + mmio_write(AUX_MU_CNTL_REG, 3); //enable RX/TX +} + +unsigned int uart_isOutputQueueEmpty() { + return uart_output_queue_read == uart_output_queue_write; +} + +unsigned int uart_isReadByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x01; } +unsigned int uart_isWriteByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x20; } + +unsigned char uart_readByte() { + while (!uart_isReadByteReady()); + return (unsigned char)mmio_read(AUX_MU_IO_REG); +} + +void uart_writeByteBlockingActual(unsigned char ch) { + while (!uart_isWriteByteReady()); + mmio_write(AUX_MU_IO_REG, (unsigned int)ch); +} + +void uart_loadOutputFifo() { + while (!uart_isOutputQueueEmpty() && uart_isWriteByteReady()) { + uart_writeByteBlockingActual(uart_output_queue[uart_output_queue_read]); + uart_output_queue_read = (uart_output_queue_read + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + } +} + +void uart_writeByteBlocking(unsigned char ch) { + unsigned int next = (uart_output_queue_write + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + + while (next == uart_output_queue_read) uart_loadOutputFifo(); + + uart_output_queue[uart_output_queue_write] = ch; + uart_output_queue_write = next; +} + +void uart_writeText(char *buffer) { + while (*buffer) { + if (*buffer == '\n') uart_writeByteBlocking('\r'); + uart_writeByteBlocking(*buffer++); + } +} + +void uart_drainOutputQueue() { + while (!uart_isOutputQueueEmpty()) uart_loadOutputFifo(); +} + +void uart_update() { + uart_loadOutputFifo(); + + if (uart_isReadByteReady()) { + unsigned char ch = uart_readByte(); + if (ch == '\r') uart_writeText("\n"); else uart_writeByteBlocking(ch); + } +} diff --git a/part4-miniuart/io.h b/part4-miniuart/io.h new file mode 100644 index 0000000..47038a1 --- /dev/null +++ b/part4-miniuart/io.h @@ -0,0 +1,7 @@ +void uart_init(); +void uart_writeText(char *buffer); +void uart_loadOutputFifo(); +unsigned char uart_readByte(); +unsigned int uart_isReadByteReady(); +void uart_writeByteBlocking(unsigned char ch); +void uart_update(); diff --git a/part4-miniuart/kernel.c b/part4-miniuart/kernel.c new file mode 100644 index 0000000..e7057c3 --- /dev/null +++ b/part4-miniuart/kernel.c @@ -0,0 +1,9 @@ +#include "io.h" + +void main() +{ + uart_init(); + uart_writeText("Hello world!\n"); + + while (1) uart_update(); +} diff --git a/part4-miniuart/link.ld b/part4-miniuart/link.ld new file mode 100644 index 0000000..dfbf022 --- /dev/null +++ b/part4-miniuart/link.ld @@ -0,0 +1,19 @@ +SECTIONS +{ + . = 0x80000; /* Kernel load address for AArch64 */ + .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;