Booting Your Own Kernel on Raspberry Pi via Uart
Tags: c, kernel, raspberrypi, python
Published: 2019-09-03 05:17:45

---
title: Booting Your Own Kernel on Raspberry Pi via Uart
published: true
tags: ["c", "kernel", "raspberry pi", "python"]
canonical_url: https://blog.nicolasmesa.co/posts/2019/08/booting-your-own-kernel-on-raspberry-pi-via-uart/
---

Cross-posted from my personal blog [https://blog.nicolasmesa.co](https://blog.nicolasmesa.co).

For the last few weeks, I’ve been reading a great [book](https://github.com/s-matyukevich/raspberry-pi-os/) about building your own operating system for a Raspberry Pi from scratch. It has been a great experience, and I’ve learned a lot. One thing I didn’t like was my development workflow, which looked like this:

1. Make a code change.
2. Build the change.
3. Unplug the Raspberry Pi.
4. Remove the SD card.
5. Plug the SD card to my laptop.
6. Copy the newly built kernel over to the SD card.
7. Eject the SD card.
8. Place the SD card back on the Raspberry Pi.
9. Connect the Raspberry Pi to my computer.
10. Close previous terminal since the `screen` command made it pretty useless.
11. Connect to the Raspberry Pi using the `screen` command.
12. Test my change.

All of this was pretty time consuming making the feedback loop very slow (especially, when I forgot a crucial step such as placing the SD card back in the Raspberry Pi). I searched to see if there was a way to improve my workflow and found an issue in the github repo called [UART boot](https://github.com/s-matyukevich/raspberry-pi-os/issues/115). I read through it and decided to build the UART boot myself as a learning exercise. In this blog post, I go through the process that I took to make UART boot work, including my wrong assumptions/failures and how I overcame them.

## Requirements

To make this project worth my time, I decided that it needed to be able to do the following:

1. The kernel must act as both the UART bootloader and the kernel itself (a single executable).
2. I must be able to send a newly compiled kernel over UART, and the Raspberry Pi must boot.
3. I must be able to have an interactive session over UART using my program (basically, replace `screen`).
4. The `screen` replacement must leave my terminal in a working state (I assume there’s a way to make this work in `screen` but, I wanted to do this as a learning exercise).

## Prerequisites

If you want to follow along, you’ll need:

1. To meet all the [prerequisites from the book](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/Prerequisites.md).
2. To have completed the [first lesson](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson01/rpi-os.md).
3. To have completed the [second exercise](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson01/exercises.md) to use UART instead of Mini UART (this might still work with Mini UART, but I haven’t tried).
4. Python 3.7 (This is the version I used).

**Note** If you haven’t completed the exercise and would still like to follow along, follow the setup instructions below. I also tagged each commit to make it easier to follow along/jump ahead.

### Setup

If you already have your project, make sure that you’ve built your kernel and have it in the SD card. Otherwise, you can clone my [repository](https://github.com/nicolasmesa/PiOS) and can follow along:

```console
$ git clone https://github.com/nicolasmesa/PiOS
$ git checkout tags/uart-boot-initial
```

#### Git tags

I’ve created commit tags for each section in case you want to experiment with a specific part of the project. You can checkout the tags by running the command:

```console
$ git checkout tags/<tag-name>
```

I’ll reference the tag name at the end of each section.

**Git tag:** `uart-boot-initial` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-initial)

## `screen` replacement

To be able to send the kernel over UART, we need to establish a serial connection with the Raspberry Pi and send the kernel bytes. The `screen` replacement will do the same thing with the difference that it will also read from the serial connection and `stdin` and will write to `stdout`. If we’re able to make this work, sending the kernel should be easy.

Let’s get started by installing `pyserial` (I suggest you do this in a [virtual environment](https://docs.python-guide.org/dev/virtualenvs/)):

```console
(rpi_os) $ pip install pyserial==3.4
```

Let’s create a file called `boot_send.py` and add the following code:

```python
import os
import select
import serial
import sys
import time
import tty

class UartConnection:

    def __init__ (self, file_path, baud_rate):
        self.serial = serial.Serial(file_path, baud_rate)

    def send_string(self, string):
        return self.send_bytes(bytes(string, "ascii"))

    def send_bytes(self, bytes_to_send):
        return self.serial.write(bytes_to_send)

    def read(self, max_len):
        return self.serial.read(max_len)

    def read_buffer(self):
        return self.read(self.serial.in_waiting)

    def read_buffer_string(self):
        return self._decode_bytes(self.read_buffer())

    def start_interactive(self, input_file, output_file):
        try:
            tty.setcbreak(input_file.fileno())
            while True:
                rfd, _, _ = select.select([self.serial, input_file], [], [])

                if self.serial in rfd:
                    r = self.read_buffer_string()
                    output_file.write(r)
                    output_file.flush()

                if input_file in rfd:
                    r = input_file.read(1)
                    self.send_string(r)
        except KeyboardInterrupt:
            print("Got keyboard interrupt. Terminating...")
        except OSError as e:
            print("Got OSError. Terminating...")
        finally:
            os.system("stty sane")

    def _decode_bytes(self, bytes_to_decode):
        return bytes_to_decode.decode("ascii")
```

Wow, that’s a lot of code! Let’s go through it.

##### Constructor

The constructor (` __init__ `) receives two arguments which we use to create a serial connection:

- `file_path`: The path to the file where the UART device is (`/dev/cu.SLAB_USBtoUART` in my computer).
- `baud_rate`: The baud rate to use in the serial connection.

##### Utility methods

We have a bunch of utility methods with varying levels of abstraction, but they all end up calling either `send_bytes` (which calls `serial.write`) or `read` (which calls `serial.read`).

##### start\_interactive

The `start_interactive` method receives two arguments (`input_file` and `output_file`). We will use these arguments as `stdin` and `stdout`, respectively. The first thing this method does is call `tty.setcbreak(input_file.fileno())` on the `stdin` file. `tty.setcbreak` turns [the `tty` into “raw” mode](https://www.oreilly.com/library/view/python-standard-library/0596000960/ch12s08.html) and allows us to read the characters as they are typed instead of buffering them until the user presses enter.

The `select` function receives a set of file descriptors to monitor. When called, it blocks until one of those files has data that we can read. When the `select` function returns, the `rfd` variable has a list of file descriptors that have data available. Next, we verify if the `serial` file descriptor has data available, and, if it does, we read it all and send the output to our terminal (`output_file`). We also check if our `input_file` has data, and, if it does, we send it over the serial connection to the Raspberry Pi. Note that in this case, we don’t write anything to the `output_file` and we rely on the Raspberry Pi echoing back the character that we sent.

The `os.system("stty sane")` call makes our tty not be in raw mode anymore. This function allows us to keep using the same terminal (instead of the problem I had with `screen`).

```python
def main():
    import sys
    uart_connection = UartConnection(
        # Change these to match your setup
        file_path='/dev/cu.SLAB_USBtoUART',
        baud_rate=115200
    )
    time.sleep(1)
    uart_connection.start_interactive(sys.stdin, sys.stdout)

if __name__ == ' __main__':
    main()
```

Now, let’s execute the script. Assuming everything worked, you should see something like this:

```console
(rpi_os) $ python boot_send.py
Hello world!
```

You should be able to type anything you want, and the Raspberry Pi should echo it back. Press `ctrl-c` to exit, and your terminal should still work normally.

**Note:** If you’re using the git branch I provided, you’ll need to hit `enter` to see the “Hello world!” message. I did this to make sure that the kernel only sends the `Hello world!` message after `boot_send` has connected successfully.

**Git tag:** `uart-boot-screen-replacement` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-screen-replacement)

## Sending the kernel over UART

Sending the kernel should be no different than sending characters over serial; everything gets translated to bytes, after all.

### Protocol

We’ll use the following protocol to send the kernel:

1. The Raspberry Pi boots into the kernel and blocks while waiting to read a single line.
2. If the line it reads matches the word `kernel` then we go into UART booting mode, if not, we skip the UART boot.
3. When in UART booting mode, the `boot_send` script sends an integer (4 bytes) containing the size of the kernel that we’re about to send.
4. The Raspberry Pi takes note of this size and sends it back.
5. The `boot_send` script verifies that the Raspberry Pi got the correct number.
6. The `boot_send` script starts reading the kernel and sends it byte by byte over the serial connection.
7. The Raspberry Pi receives each byte and…
  1. Keeps a running sum of all the bytes (a checksum).
  2. Places that byte in memory (overwriting the current kernel).
8. When the Raspberry Pi receives the number of bytes equal to the size of the kernel (sent in a previous step), it sends the checksum over UART.
9. The `boot_send` script verifies that the checksums match (for error detection purposes).
10. The Raspberry Pi responds with the string `"Done"` to denote that it will now jump to the new kernel.
11. The Raspberry Pi jumps back to address `0x00` to start executing in the new kernel.
12. The `boot_send` script starts an interactive session.

One thing that we need to keep in mind is [endianness](https://en.wikipedia.org/wiki/Endianness) when sending the size. I decided to use big-endian for all communication.

### boot\_send modifications

We need to add some functionality to our `boot_send` script to implement the protocol:

##### UartConnection

Let’s add these functions to our `UartConnection` class:

```python
class UartConnection:
    # ...
    def send_line(self, line):
        if not line.endswith("\n"):
            line += "\n"
        return self.send_string(line)

    def send_int(self, number):
        if number > 2 ** 32 - 1:
            raise 'Number can only be 4 bytes long'
        number_in_bytes = number.to_bytes(4, byteorder='big')
        return self.send_bytes(number_in_bytes)

    def read_int(self):
        bytes_to_read = 4
        number_bytes = self.read(bytes_to_read)
        return int.from_bytes(number_bytes, byteorder='big')

    def read_line(self):
        return self._decode_bytes(self.serial.readline())
```

The most important thing to call out here is the `byteorder` which we decided to use (`big` stands for `big-endian`).

##### Kernel checksum

We compute a checksum to validate against some errors. I don’t think this is the best way to check for errors since something as simple as changing the order of two bytes would still make this checksum pass. I use it because it gives me a bit more confidence that things are working as expected.

```python
def compute_kernel_checksum(kernel_bytes):
    num = 0
    for b in kernel_bytes:
        num = (num + b) % (2 ** 32)
    return num
```

We wrap around at `2^32` because that’s the max size of an integer.

##### send\_kernel

Let’s add a function to send the kernel to the Raspberry Pi following our protocol:

```python
def send_kernel(path, uart_connection):
    with open(path, mode='rb') as f:
        uart_connection.send_line("kernel")
        kernel = f.read()
        size = len(kernel)
        checksum = compute_kernel_checksum(kernel)

        print("Sending kernel with size", size, "and checksum", checksum)
        uart_connection.send_int(size)
        time.sleep(1)
        size_confirmation = uart_connection.read_int()
        if size_confirmation != size:
            print("Expected size to be", size, "but got", size_confirmation)
            return False

        print("Kernel size confirmed. Sending kernel")
        uart_connection.send_bytes(kernel)
        time.sleep(1)

        print("Validating checksum...")
        checksum_confirmation = uart_connection.read_int()
        if checksum_confirmation != checksum:
            print("Expected checksum to be", checksum,
                  "but was", checksum_confirmation)
            return False

        line = uart_connection.read_line()
        if not line.startswith("Done"):
            print("Didn't get confirmation for the kernel. Got", line)
            return False

        return True
```

Our function receives a `path` to the kernel file and a `UartConnection` object. We use these to follow the protocol described above to send the kernel to the Raspberry Pi.

##### Calling the `send_kernel` function

Let’s make a small tweak to our `main` function.

```python
def main():
    # ...
    uart_connection = UartConnection(...)
    time.sleep(1)
    result = send_kernel(
        path="kernel8.img",
        uart_connection=uart_connection
    )
    if result:
        print("Done!")
        uart_connection.start_interactive(sys.stdin, sys.stdout)
    else:
        print("Error sending kernel :(")
```

Our script sends the kernel first and then starts an interactive session.

**Git tag:** `uart-boot-boot-send-client-side` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-boot-send-client-side)

### Kernel side modifications

Let’s dive into the modifications needed for the kernel.

##### branch\_to\_address

The last part of our boot protocol involves jumping back to address `0x00` and starting the execution from that memory location. Let’s start by adding a function that can do that.

Open the `utils.h` file and add the function declaration:

```c
extern void branch_to_address( void * );
```

Now, go to the `utils.S` file and add the definition:

```armasm
global branch_to_address
branch_to_address:
    br x0
```

##### uart\_send\_int / uart\_read\_int

We’ll need to send and read integers via UART (for the kernel size and the checksum). Let’s open the `uart.h` file and add the following declarations:

```c
void uart_send_int(int number);
int uart_read_int();
```

Let’s implement the functions in the `uart.c` file:

```c
int uart_read_int() {
    int num = 0;
    for (int i = 0; i < 4; i++) {
        char c = uart_recv();
        num = num << 8;
        num += (int)c;
    }
    return num;
}

void uart_send_int(int number) {
    uart_send((char)((number >> 24) & 0xFF));
    uart_send((char)((number >> 16) & 0xFF));
    uart_send((char)((number >> 8) & 0xFF));
    uart_send((char)(number & 0xFF));
}
```

In the `uart_read_int`, we read one byte at a time (most significant byte first) and keep shifting those bytes to the left until we get the 4 bytes that represent the integer. In the `uart_send_int`, we send one byte at a time (most significant byte first). Note that using pointers here is not a good idea since we shouldn’t make assumptions on how the Raspberry Pi stores `int`s in memory ([big-endian vs. little-endian](https://en.wikipedia.org/wiki/Endianness)).

##### include utils.h

Open the `kernel.c` file and add the following `#include` statement:

```c
#include "utils.h"
```

##### readline

Here we write a pretty simple `readline` function:

```c
int readline(char *buf, int maxlen) {
    int num = 0;
    while (num < maxlen - 1) {
        char c = uart_recv();
        if (c == '\n' || c == '\0' || c == '\r') {
            break;
        }
        buf[num] = c;
        num++;
    }
    buf[num] = '\0';
    return num;
}
```

##### strcmp

Our protocol needs to read a line of text and compare it against the word `kernel`. To do that, we need a string comparison function:

```c
int strcmp(char *str1, char *str2) {
    while (1) {
        if (*str1 != *str2) {
            return *str1 - *str2;
        }

        if (*str1 == '\0') {
            return 0;
        }

        str1++;
        str2++;
    }
}
```

##### copy\_and\_jump\_to\_kernel

Now we have all the pieces to implement the protocol we brought up before:

```c
void copy_and_jump_to_kernel() {
    int kernel_size = uart_read_int();
    uart_send_int(kernel_size);

    char *kernel = (char *)0;
    int checksum = 0;
    for (int i = 0; i < kernel_size; i++) {
        char c = uart_recv();
        checksum += c;
        kernel[i] = c;
    }
    uart_send_int(checksum);

    uart_send_string("Done copying kernel\r\n");
    branch_to_address((void *)0x00);
}
```

This function receives the kernel size and sends it back. Then, receives each byte from the new kernel and places it in memory starting on address `0x00`. Next, it sends the calculated checksum and a string saying `"Done copying the kernel"`. Finally, it branches to start executing at address `0x00` (where the new kernel resides).

##### kernel\_main

Let’s add the logic to read a line on boot, and compare it against `kernel` to decide if we want to boot over UART or not:

```c
void kernel_main(void) {
    int buff_size = 100;
    uart_init();

    char buffer[buff_size];
    readline(buffer, buff_size);
    if (strcmp(buffer, "kernel") == 0) {
        copy_and_jump_to_kernel();
    }

    uart_send_string("Hello world!\r\n");
    while (1) {
        uart_send(uart_recv());
    }
}
```

Note that the call to `copy_and_jump_to_kernel` never returns.

**Git tag:** `uart-boot-boot-send-kernel-side` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-boot-send-kernel-side)

### Testing our UART boot

#### Build the kernel and copy it to the SD card

Here are the steps I took to build the kernel and copy it to the SD card on my Mac:

```console
$ ./build.sh
$ cp kernel8.img /Volumes/boot/
```

#### Booting test: sending the same kernel

Now eject the SD card and put in your Raspberry Pi.

Let’s run our `boot_send` script:

```console
(rpi_os) $ python boot_send.py 
Sending kernel with size 1991 and checksum 201267
Kernel size confirmed. Sending kernel
Validating checksum...
Done!
```

If you see this, it means that you just sent a new kernel (the same kernel that you installed in the SD card) over UART! At this point, the `kernel` is stuck in the `readline` function to check whether you want to send a new kernel or not. Let’s press enter:

```console
Hello world!
Got keyboard interrupt. Terminating...
```

Press `ctrl-c` to exit.

#### Booting test: making a small change

Sending the same kernel is not why we did all this work! Let’s make a small change to our kernel code, compile it and send it over UART (instead of putting it in the SD card). Go to the `kernel_main` function and change the string that we send:

```c
int kernel_main(void) {
    // ...
   uart_send_string("Hello from a new kernel!!!\r\n"); 
   // ...
}
```

Let’s compile it and execute our python script (no need to copy anything to the SD card anymore!):

```console
(rpi_os) $ ./build.sh
(rpi_os) $ python boot_send.py 
Sending kernel with size 2005 and checksum 202127
Kernel size confirmed. Sending kernel
Validating checksum...
Done!
```

Great! We sent the kernel! Now, the moment of truth! Let’s press enter:

```console
Hello from a new kernel!!!
Got keyboard interrupt. Terminating...
```

**Git tag:** `uart-boot-string-change-test-uart-boot` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-string-change-test-uart-boot)

#### Booting test: adding a function

We did it! We’re done, right? Well, not so fast! Let’s try to make a different change. Let’s add a function and call it from `kernel_main`. Open the `kernel.c` file and add the following code:

```c
// ...
void my_test_function(void) {
    uart_send_string("Sending a test message!\r\n"); 
}

void kernel_main(void) {
    // ...
   uart_send_string("Hello from a new kernel!!!\r\n");  
   my_test_function();
   // ...
}
```

We added a new function called `my_test_function`, and we call it from `kernel_main`. Let’s compile and send this over UART:

```console
(rpi_os) $ ./build.sh
(rpi_os) $ python boot_send.py Sending kernel with size 2077 and checksum 208704
Kernel size confirmed. Sending kernel
Validating checksum...
```

It hangs in the _Validating checksum…_ part! So, what’s going on? Let’s think for a minute about what we’re doing. The Raspberry Pi loads our kernel from the SD card onto address `0x00` and starts executing code in that address. When we copy the kernel over (over UART), we overwrite the kernel from the SD card with the new kernel. If the kernels are the same, it won’t make a difference since we’re effectively leaving the code as-is. Our string change didn’t affect us either because the strings go in the `.rodata` (read-only data) section of the executable which goes after the `.text` section (where the executable code lives), so changing the string doesn’t modify the addresses of the executable code. Adding a function, however, changes the `.text` layout causing the kernel to “corrupt” itself while it copies the new kernel.

**Git tag:** `uart-boot-fail-by-adding-function` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-fail-by-adding-function)

### The fix

Before implementing a fix, let’s remove `my_test_function` to get back into a “good” state.

**Git tag:** `uart-boot-remove-test-function` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-remove-test-function)

#### Options

So, how do we fix this problem? Let’s consider these two options:

##### Option 1: Copy the kernel to a different location and jump there

Using this option would not overwrite the kernel that is currently running, and we would jump to the new kernel in a different address. There’s a few drawbacks to this approach:

1. The kernel code will not start in address `0x00`. The [course](https://github.com/s-matyukevich/raspberry-pi-os/) might make some assumptions about the kernel being in address `0x00`, and we would be breaking those assumptions.
2. The course could use the address range that we choose to put our new kernel.
3. We wouldn’t be able to send a kernel multiple times over UART in the same session (once we copy over the kernel and we’re running in the new address, we wouldn’t have a new place to copy another new kernel).

##### Option 2: Copy the current running kernel to a new address range, jump to it, copy the new kernel to the original address range, jump back to it

To implement this option, we need to:

1. Copy the currently running kernel to a new memory location (let’s say `0x8000`).
2. Jump to a function in the new address range.
3. The function in the new memory location implements the protocol described above (our `copy_and_jump_to_kernel` function).
  - It copies the kernel over UART starting at memory location `0x00`.
4. Jump back to address `0x00`.
5. Start running the new kernel.

We treat the address range starting at `0x8000` as temporary storage that we can use for other purposes once we jump back to address `0x00`.

With this implementation, we’re able to keep all the assumptions that the course might be making and we’re able to copy the kernel multiple times over the same session! This approach is called chain loading. You can read more about [chain loading here](https://github.com/rust-embedded/rust-raspi3-OS-tutorials/tree/76b86a4997db57c37bfe12e7f2fb18e292069183/06_raspbootin64#chain-loading).

#### Fix implementation

##### Position Independent Code

Currently, the compiler compiles our kernel with the assumption that its code starts at address `0x00`. The compiler can then hardcode the absolute addresses of every function using this. Absolute addresses become a problem once we start executing instructions in the kernel that starts at location `0x8000` because it would still be referencing the old absolute memory locations.

We can solve this issue by using [Position Independent Code](https://en.wikipedia.org/wiki/Position-independent_code), where the addresses are relative to the address held by the program counter.

To enable this, we need to tweak our `Makefile` to add the `-fPIC` flag. You can read more about this flag in [this Stack Overflow post](https://stackoverflow.com/a/5311538).

```makefile
# -fPIC makes the addresses relative instead of absolute allowing
# us to place the kernel anywhere in memory.
COPS = -fPIC -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only
ASMOPS = -fPIC -Iinclude

```

This change is all that is needed to make our code position independent!

##### Determining current kernel size

The kernel needs to know its size to be able to copy itself. We use the `bss_end` section of the `linker.ld` file to determine the size of the kernel. Let’s add a reference to that section in our `kernel.c` file:

```c
#include "utils.h"
// ...
extern char bss_end[];
```

We define this as an array, even though it technically isn’t (you can read more about why in [this post](https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html)). Now we can use `bss_end` as a pointer to the address where the kernel ends (this includes the [.bss](https://en.wikipedia.org/wiki/.bss)).

##### copy\_current\_kernel\_and\_jump

Let’s implement a function that:

1. Copies the current kernel (starting in address `0x00`) to the new address range (starting at `0x8000`).
2. Jumps to the `copy_and_jump_to_kernel` function, that we wrote before, in the new address range.

**Note:** Make sure to define this function below the `copy_and_jump_to_kernel`.

```c
void copy_current_kernel_and_jump(char *new_address) {
    char *kernel = (char *)0x00;
    char *end = bss_end;

    char *copy = new_address;

    while (kernel <= end) {
        *copy = *kernel;
        kernel++;
        copy++;
    }

    // Cast the function pointer to char* to deal with bytes.
    char *original_function_address = (char *)&copy_and_jump_to_kernel;

    // Add the new address (we're assuming that the original kernel resides in
    // address 0). copied_function_address should now contain the address of the
    // original function but in the new location.
    char *copied_function_address =
        original_function_address + (long)new_address;

    // Cast the address back to a function and call it.
    void (*call_function)() = (void (*)())copied_function_address;
    call_function();
}
```

This function copies the currently running kernel to the address range starting at `new_address`. Then, it does some pointer math to determine the address of the `copy_and_jump_to_kernel` function in the new address range. Finally, it calls that function in the new address range.

##### Call `copy_current_kernel_and_jump` from `kernel_main`

All that remains is to call the `copy_current_kernel_and_jump` from `kernel_main` and, we should be ready to test:

```c
void kernel_main(void) {
    // ...
    if (strcmp(buffer, "kernel") == 0) {
        copy_current_kernel_and_jump((char *)0x8000);
    }
    // ...
}
```

**Git tag:** `uart-boot-fix-implementation` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-fix-implementation)

### Testing the fix

First, we need to build our kernel and copy it to the SD card. Refer to the Build the kernel and copy it to the SD card section.

Let’s perform the same tests we ran before to make sure that we can still send the kernel over UART. Refer back to the following sections:

- Booting test: sending the same kernel: This will help us make sure that the boot process works end-to-end.
- Booting test: making a small change: This test will help us make sure that everything is working at least as well as it was working before the fix.

##### Testing the previous error

Now, the moment of truth! Let’s make a change that adds a function just like in the Adding a function section. Let’s test it out!

```console
(rpi_os) $ ./build.sh
(rpi_os) $ python boot_send.py 
Sending kernel with size 2280 and checksum 223784
Kernel size confirmed. Sending kernel
Validating checksum...
Done!
```

This output is promising! At least our `boot_send` script didn’t hang in the _Validating checksum…_ step like it did last time. The new kernel probably booted up and is stuck in the `readline` function. Let’s press enter to send an empty line:

```console
Hello from a new kernel!!!
Sending a test message!
Got keyboard interrupt. Terminating...

```

We did it! We were able to add a new function to the kernel, and it was still able to boot up!

##### Sending the same kernel multiple times

Now, what if we try to send the same kernel over and over again? We would be able to send a new kernel that handles the next UART boot differently without having to put it in the SD card!

```console
(rpi_os) $ python boot_send.py 
Sending kernel with size 2440 and checksum 234324
Kernel size confirmed. Sending kernel
Validating checksum...
Done!
```

Nothing surprising here, our kernel is stuck in the `readline` function again. This time, instead of pressing enter, let’s press `ctrl-c` to exit out of our `boot_send` program and rerun it.

```console
Got keyboard interrupt. Terminating...
(rpi_os) $ python test.py 
Sending kernel with size 2440 and checksum 234324
Kernel size confirmed. Sending kernel
Validating checksum...
Done!
```

We sent the kernel again in the same session! Let’s press enter to make sure that our new kernel booted:

```console
Hello from a new kernel!!!
Sending a test message!
Got keyboard interrupt. Terminating...
```

**Git tag:** `uart-boot-succeeds-when-adding-function` - [link](https://github.com/nicolasmesa/PiOS/tree/uart-boot-succeeds-when-adding-function)

## Conclusion

In this post, we built a kernel that can be booted via UART to test, or from the SD card (for more permanent solutions). To do this, we came up with a protocol for the UART communication and implemented it. After a few tests, we noticed there was a problem when we tried to boot a kernel with an extra function. The problem was that the kernel was overwriting/corrupting its instructions. To overcome this problem, we made the kernel first copy itself to a new address range and perform the UART copy while executing from that address range. After the kernel finishes copying the new kernel over UART, it can jump back to the original start address (`0x00`) and execute the new kernel.

## Improvements

Here is a small list of improvements that you can make to the project:

- Add arguments to the `boot_send` script. I did this in [my project](https://github.com/nicolasmesa/PiOS/blob/e567525eddccc22f55f4575052bddbacd3199e4d/boot_client/boot_send.py), but this post is already long as it is.
- Clean the `kernel.c` file and extract the functions to a helper file.
- Use the C preprocessor to exclude the UART boot section if you don’t pass a `UARTBoot` flag.
- Make UART boot work with multiple CPUs. [I implemented this](https://github.com/nicolasmesa/PiOS#multiprocessor) as well in a somewhat hacky way (I might write a post about it later).
- Add a better checksum ([CRC](https://en.wikipedia.org/wiki/Cyclic_redundancy_check), for example).

## Links / References

- [Learning operating system development using Linux kernel and Raspberry Pi](https://github.com/s-matyukevich/raspberry-pi-os/) (very recommended course!)
- [Chain loading](https://github.com/rust-embedded/rust-raspi3-OS-tutorials/tree/76b86a4997db57c37bfe12e7f2fb18e292069183/06_raspbootin64#chain-loading)
- [BSS](https://en.wikipedia.org/wiki/.bss)
- [Endianness](https://en.wikipedia.org/wiki/Endianness)
- [Position Independent Code](https://en.wikipedia.org/wiki/Position-independent_code)
- [Position Independent Code Stack Overflow explanation](https://stackoverflow.com/a/5311538)
- [Python virtual environments](https://docs.python-guide.org/dev/virtualenvs/)
- [Referencing linker script defined variable](https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html)
- [Setting terminal to “raw” mode](https://www.oreilly.com/library/view/python-standard-library/0596000960/ch12s08.html)
- [UART Boot Github Issue](https://github.com/s-matyukevich/raspberry-pi-os/issues/115)
- [CRC](https://en.wikipedia.org/wiki/Cyclic_redundancy_check)