From 817e62974359f50288380fe3f53881906de97e62 Mon Sep 17 00:00:00 2001 From: Mathieu Maret Date: Mon, 21 Mar 2022 23:21:12 +0100 Subject: [PATCH] Add way to upload kernel by serial link For this code before __ld_kernel_begin should not be modifier (e.g. crt0.S and static_tools.c). Inspired/Taken by/from https://github.com/nicolasmesa/PiOS --- Makefile | 8 +- boot_client/boot_send.py | 193 +++++++++++++++++++++++++++++++++++ boot_client/requirements.txt | 2 + hello.c | 12 +++ rpi3.ld | 2 + static_tools.c | 44 ++++++++ static_tools.h | 3 + 7 files changed, 263 insertions(+), 1 deletion(-) create mode 100755 boot_client/boot_send.py create mode 100644 boot_client/requirements.txt create mode 100644 static_tools.c create mode 100644 static_tools.h diff --git a/Makefile b/Makefile index 29ddb77..191fc9a 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ LD=$(CROSS)ld CFLAGS=-Wall -Wextra -ffreestanding -march=armv8-a+crc -mcpu=cortex-a53 DEBUG_FLAGS += -g -Og -DDEBUG -fno-omit-frame-pointer -fno-inline LDSCRIPT=rpi3.ld +TTY?=/dev/ttyUSB0 gasmsrc=$(wildcard *.S) gasmobj=$(gasmsrc:%.S=%.o) @@ -28,11 +29,16 @@ font_psf.o: font.psf $(LD) -r -b binary -o font_psf.o font.psf clean: - rm -rf $(cobj) $(gasmobj) $(deps) *.bin *.elf *.map + rm -rf $(cobj) $(gasmobj) $(deps) font_psf.o *.sym *.bin *.elf *.map run: $(KERNEL) qemu-system-aarch64 -machine raspi3b -kernel $< -serial stdio +update_serial: + $(eval skip := $(shell $(CROSS)objdump -t kernel.elf | grep __ld_kernel_begin | cut -d " " -f 1)) + $(eval skip := $(shell printf "%d" $$((0x$(skip)- 0x80000)))) + ./boot_client/boot_send.py -d $(TTY) -b 115200 -k kernel.bin -i -s $(skip) + debug: CFLAGS += $(DEBUG_FLAGS) debug: CXXFLAGS += $(DEBUG_FLAGS) debug:$(KERNEL) diff --git a/boot_client/boot_send.py b/boot_client/boot_send.py new file mode 100755 index 0000000..c7c3a94 --- /dev/null +++ b/boot_client/boot_send.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +import argparse +import os +import select +import serial +import sys +import time +import tty + +# TODO: Make ti work with contexts (with UartConnection() as u) + + +class UartConnection: + + def __init__(self, file_path, baud_rate): + self.serial = serial.Serial(file_path, baud_rate) + + def send_line(self, line): + if not line.endswith("\n"): + # Intentionally not adding the \r for now + line += "\n" + return self.send_string(line) + + 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 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(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 read_line(self): + return self._decode_bytes(self.serial.readline()) + + def read_int(self): + bytes_to_read = 4 + number_bytes = self.read(bytes_to_read) + return int.from_bytes(number_bytes, byteorder='big') + + def start_interactive(self, input_file, output_file): + try: + # Make the tty cbreak + # https://www.oreilly.com/library/view/python-standard-library/0596000960/ch12s08.html + 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...") + return + except OSError as e: + print("Got OSError. Terminating...") + return + finally: + os.system("stty sane") + + def close(self): + self.serial.close() + + def _decode_bytes(self, bytes_to_decode): + return bytes_to_decode.decode("ascii") + + +def compute_kernel_checksum(kernel_bytes): + num = 0 + for b in kernel_bytes: + num = (num + b) % (2 ** 32) + return num + +def send_kernel_debug(uart_connection, kernel): + for i, b in enumerate(kernel): + print(i, 'Sending byte', b) + uart_connection.send_bytes(bytes([b])) + read_byte = uart_connection.read(1)[0] + print(i, 'Received byte', read_byte) + + if b != read_byte: + print(i, 'Sent', b, 'but got', read_byte) + return False + + return True + +def send_kernel(path, uart_connection, skip=0, debug=False): + with open(path, mode='rb') as f: + uart_connection.send_line("kernel") + time.sleep(1) + + print("Skipping", skip) + f.seek(skip) + 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) + uart_connection.send_int(checksum) + time.sleep(1) + + if debug: + print("Starting debug workflow") + uart_connection.send_int(1) + send_kernel_debug(uart_connection, kernel) + else: + uart_connection.send_int(0) + 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() + #Print("Received: ", line) + #If not line.startswith("Done"): + # print("Didn't get confirmation for the kernel. Got", line) + # return False + + return True + + +def main(argv): + ap = argparse.ArgumentParser( + description=""" + Utility program to send the kernel to the RPI over UART and to start an interactive session over uart. + Sample usage: + The following command will setup a serial connection and will send the kernel over it. Then, it + will start an interactive session over the serial connection. + python boot_send.py -d /dev/cu.SLAB_USBtoUART -b 115200 -k ../kernel8.img -i + """ + ) + ap.add_argument('-d', '--device', help='path to RPI UART device', required=True, + default='/dev/cu.SLAB_USBtoUART') + ap.add_argument('-b', '--baud-rate', + help='baud rate to use for the UART communication', required=True, type=int, + default=115200) + ap.add_argument('-k', '--kernel', help='file path to the kernel', required=False, + type=str) + ap.add_argument('-i', '--interactive', help='start interactive session', + action='store_const', const=True, default=False) + ap.add_argument('-dd', '--debug', const=True, default=False, action='store_const') + ap.add_argument('-s', '--skip', + help='Skip first n bytes of the kernel (i.e. offset of symbole __ld_kernel_begin)', required=False, type=lambda x: int(x, 0), + default=0x100) + + args = ap.parse_args(argv[1:]) + + if not args.kernel and not args.interactive: + print("At least one of '--kernel' or '--interactive' are required") + sys.exit(1) + + uart_connection = UartConnection(args.device, args.baud_rate) + time.sleep(1) + + if args.kernel: + success = send_kernel(args.kernel, uart_connection, args.skip, args.debug) + if not success: + sys.exit(1) + time.sleep(1) + + if args.interactive: + print("Making it interactive. Press ctrl-c to exit. You may need to press enter...") + uart_connection.start_interactive(sys.stdin, sys.stdout) + + uart_connection.close() + + +if __name__ == '__main__': + main(sys.argv) diff --git a/boot_client/requirements.txt b/boot_client/requirements.txt new file mode 100644 index 0000000..4d343ab --- /dev/null +++ b/boot_client/requirements.txt @@ -0,0 +1,2 @@ +certifi==2019.6.16 +pyserial==3.4 diff --git a/hello.c b/hello.c index 335027d..dad755f 100644 --- a/hello.c +++ b/hello.c @@ -1,17 +1,29 @@ #include "fb.h" #include "mbox.h" +#include "static_tools.h" #include "uart.h" #include "utils.h" #define FB_WIDTH 640 #define FB_HEIGHT 480 +#define BUFF_SIZE 100 +extern unsigned long int __ld_kernel_begin; int kernelmain(void) { struct fbst *fb; unsigned char *ptr; uart_init(); + puts("starting kernel\n"); + char buffer[BUFF_SIZE]; + readline(buffer, BUFF_SIZE); + + if (strcmp(buffer, "kernel") == 0) { + if(copy_kernel()){ + puts("Checksum error"); + } + } if (fb_init(FB_WIDTH, FB_HEIGHT) == 0) { puts("Fail to init framebuffer"); } diff --git a/rpi3.ld b/rpi3.ld index 893688c..4dff06d 100644 --- a/rpi3.ld +++ b/rpi3.ld @@ -8,6 +8,8 @@ MEMORY SECTIONS { .text : { *(.text.boot) + *(static_tools) + __ld_kernel_begin = .; *(.text) } .rodata : { *(.rodata .rodata.*) } diff --git a/static_tools.c b/static_tools.c new file mode 100644 index 0000000..3321d53 --- /dev/null +++ b/static_tools.c @@ -0,0 +1,44 @@ +#define IO_BASE 0x3f000000 +#define UART0_BASE (IO_BASE + 0x201000) +#define UART0_DR (*(volatile unsigned *)(UART0_BASE + 0x0)) +#define UART0_FR (*(volatile unsigned *)(UART0_BASE + 0x18)) + +extern unsigned long int __ld_kernel_begin; + +static __attribute__((section("static_tools"))) char uart_recv(void) +{ + while (UART0_FR & (1 << 4)) { + } + return UART0_DR & 0xFF; +} + +static int __attribute__((section("static_tools"))) 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; +} + +int __attribute__((section("static_tools"))) copy_kernel() +{ + unsigned int kernel_size = uart_read_int(); + int expected_checksum = uart_read_int(); + int debug = uart_read_int(); + char *kernel = (char *)&__ld_kernel_begin; + int checksum = 0; + (void)debug; + + for (unsigned int i = 0; i < kernel_size; i++) { + char c = uart_recv(); + checksum += c; + kernel[i] = c; + } + + if(checksum != expected_checksum) + return -1; + + return 0; +} diff --git a/static_tools.h b/static_tools.h new file mode 100644 index 0000000..cb52bd9 --- /dev/null +++ b/static_tools.h @@ -0,0 +1,3 @@ +#pragma once + +int __attribute__((section("static_tools"))) copy_kernel();