WARNING本文所有内容均为翻译,原文:Tutorial 06 - UART Chainloader;原项目:Operating System development tutorials in Rust on the Raspberry Pi
概述
- 用SD卡上的镜像启动树莓派感觉很棒,但如果对每个新的二进制文件,都需要手动将其放在SD卡上就会非常麻烦。因此,本章我们将编写一个链式加载器。
- 这是我们最后一次手动拷贝镜像到SD卡上。后面的每章教程都会在
Makefile中提供一个chainboot目标,以便通过UART加载内核。
注意
请注意,若仅查看源码差异,则很难掌握本章教程中出现的一些新功能。
关键就在boot.s中,我们正在编写一段位置无关代码,代码能够自动确定固件从什么位置(0x8_0000)加载二进制文件,以及应当链接到什么位置(0x200_0000,请参阅link.ld)。然后二进制文件将自身从加载地址复制到链接地址(即“重定位”自身),再跳转到重定位版本的_start_rust()中。
由于链式加载器自己已经“让出位置”,它现在就可以从UART接收另一个内核二进制文件,并将其复制到树莓派固件的标准加载地址0x8_0000。最后,它跳转到0x8_0000再透明的执行新加载的二进制文件,仿佛该文件是从SD卡中加载似的。
请耐心等待,等我一有时间,就会将这个过程写成详尽的文档。不过目前,请参阅本章教程看做一个方便的启动器,该驱动器允许我们快速启动后续教程章节中新的内核二进制文件。
安装并测试
我们的链式加载器叫做MiniLoad,其灵感来自raspbootin。
在本章教程中试用MiniLoad:
- 根据目标硬件,运行:
make或者BSP=rpi4 make。 - 将
kernel8.img复制到SD卡,并将SD卡插回到树莓派。 - 根据目标硬件,运行
make chainboot或者BSP=rpi4 make chainboot。 - 连接USB串口线到宿主PC。
- 按照本项目的根
README接线。 - 一定确保没有连接USB串口的电源引脚。只连接了RX/TX和GND。
- 按照本项目的根
- 为树莓派连接(USB)电源。
- 观察加载程序通过UART获取内核:
❗注意:
make chainboot的默认串行设备名称为/dev/ttyUSB0。对于不同操作系统的宿主机,设备名称可能会有所不同。例如,在macOS上,名称可能类似于/dev/tty.usbserial-0001。这种情况下,需要明确给出名称:
$ DEV_SERIAL=/dev/tty.usbserial-0001 make chainboot$ make chainboot[...]Minipush 1.0
[MP] ⏳ Waiting for /dev/ttyUSB0[MP] ✅ Serial connected[MP] 🔌 Please power the target now __ __ _ _ _ _| \/ (_)_ _ (_) | ___ __ _ __| || |\/| | | ' \| | |__/ _ \/ _` / _` ||_| |_|_|_||_|_|____\___/\__,_\__,_|
Raspberry Pi 3
[ML] Requesting binary[MP] ⏩ Pushing 6 KiB ==========================================🦀 100% 0 KiB/s Time: 00:00:00[ML] Loaded! Executing the payload now
[0] mingo version 0.5.0[1] Booting on: Raspberry Pi 3[2] Drivers loaded: 1. BCM GPIO 2. BCM PL011 UART[3] Chars written: 117[4] Echoing input now实验
本章教程中的Makefile有一个额外的目标——qemuasm——可以让您更好地观察内核内核在重定位后,是如何从加载地址0x80_XXX跳转到位于0x0200_0XXX的重定位代码处的:
$ make qemuasm[...]N:0x00080030: 58000140 ldr x0, #0x800580x00080034: 9100001f mov sp, x00x00080038: 58000141 ldr x1, #0x800600x0008003c: d61f0020 br x1
----------------IN:0x02000070: 9400044c bl #0x20011a0
----------------IN:0x020011a0: 90000008 adrp x8, #0x20010000x020011a4: 90000009 adrp x9, #0x20010000x020011a8: f9446508 ldr x8, [x8, #0x8c8]0x020011ac: f9446929 ldr x9, [x9, #0x8d0]0x020011b0: eb08013f cmp x9, x80x020011b4: 54000109 b.ls #0x20011d4[...]与上一章代码的区别
diff -uNr 05_drivers_gpio_uart/Cargo.toml 06_uart_chainloader/Cargo.toml--- 05_drivers_gpio_uart/Cargo.toml+++ 06_uart_chainloader/Cargo.toml@@ -1,6 +1,6 @@ [package] name = "mingo"-version = "0.5.0"+version = "0.6.0" authors = ["Andre Richter <andre.o.richter@gmail.com>"] edition = "2021"
Binary files 05_drivers_gpio_uart/demo_payload_rpi3.img and 06_uart_chainloader/demo_payload_rpi3.img differBinary files 05_drivers_gpio_uart/demo_payload_rpi4.img and 06_uart_chainloader/demo_payload_rpi4.img differ
diff -uNr 05_drivers_gpio_uart/Makefile 06_uart_chainloader/Makefile--- 05_drivers_gpio_uart/Makefile+++ 06_uart_chainloader/Makefile@@ -23,27 +23,29 @@
# BSP-specific arguments. ifeq ($(BSP),rpi3)- TARGET = aarch64-unknown-none-softfloat- KERNEL_BIN = kernel8.img- QEMU_BINARY = qemu-system-aarch64- QEMU_MACHINE_TYPE = raspi3- QEMU_RELEASE_ARGS = -serial stdio -display none- OBJDUMP_BINARY = aarch64-none-elf-objdump- NM_BINARY = aarch64-none-elf-nm- READELF_BINARY = aarch64-none-elf-readelf- LINKER_FILE = src/bsp/raspberrypi/link.ld- RUSTC_MISC_ARGS = -C target-cpu=cortex-a53+ TARGET = aarch64-unknown-none-softfloat+ KERNEL_BIN = kernel8.img+ QEMU_BINARY = qemu-system-aarch64+ QEMU_MACHINE_TYPE = raspi3+ QEMU_RELEASE_ARGS = -serial stdio -display none+ OBJDUMP_BINARY = aarch64-none-elf-objdump+ NM_BINARY = aarch64-none-elf-nm+ READELF_BINARY = aarch64-none-elf-readelf+ LINKER_FILE = src/bsp/raspberrypi/link.ld+ RUSTC_MISC_ARGS = -C target-cpu=cortex-a53+ CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi3.img else ifeq ($(BSP),rpi4)- TARGET = aarch64-unknown-none-softfloat- KERNEL_BIN = kernel8.img- QEMU_BINARY = qemu-system-aarch64- QEMU_MACHINE_TYPE =- QEMU_RELEASE_ARGS = -serial stdio -display none- OBJDUMP_BINARY = aarch64-none-elf-objdump- NM_BINARY = aarch64-none-elf-nm- READELF_BINARY = aarch64-none-elf-readelf- LINKER_FILE = src/bsp/raspberrypi/link.ld- RUSTC_MISC_ARGS = -C target-cpu=cortex-a72+ TARGET = aarch64-unknown-none-softfloat+ KERNEL_BIN = kernel8.img+ QEMU_BINARY = qemu-system-aarch64+ QEMU_MACHINE_TYPE =+ QEMU_RELEASE_ARGS = -serial stdio -display none+ OBJDUMP_BINARY = aarch64-none-elf-objdump+ NM_BINARY = aarch64-none-elf-nm+ READELF_BINARY = aarch64-none-elf-readelf+ LINKER_FILE = src/bsp/raspberrypi/link.ld+ RUSTC_MISC_ARGS = -C target-cpu=cortex-a72+ CHAINBOOT_DEMO_PAYLOAD = demo_payload_rpi4.img endif
QEMU_MISSING_STRING = "This board is not yet supported for QEMU."@@ -75,8 +77,8 @@ -O binary
EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)-EXEC_TEST_DISPATCH = ruby ../common/tests/dispatch.rb-EXEC_MINITERM = ruby ../common/serial/miniterm.rb+EXEC_TEST_MINIPUSH = ruby tests/chainboot_test.rb+EXEC_MINIPUSH = ruby ../common/serial/minipush.rb
##------------------------------------------------------------------------------ ## Dockerization@@ -95,7 +97,7 @@ ifeq ($(shell uname -s),Linux) DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV)
- DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE)+ DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) endif
@@ -103,7 +105,7 @@ ##-------------------------------------------------------------------------------------------------- ## Targets ##---------------------------------------------------------------------------------------------------.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu miniterm clippy clean readelf objdump nm check+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check
all: $(KERNEL_BIN)
@@ -132,7 +134,7 @@ ##------------------------------------------------------------------------------ ifeq ($(QEMU_MACHINE_TYPE),) # QEMU is not supported for the board.
-qemu:+qemu qemuasm: $(call colorecho, "\n$(QEMU_MISSING_STRING)")
else # QEMU is supported.@@ -140,13 +142,18 @@ qemu: $(KERNEL_BIN) $(call colorecho, "\nLaunching QEMU") @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)++qemuasm: $(KERNEL_BIN)+ $(call colorecho, "\nLaunching QEMU with ASM output")+ @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm+ endif
##-------------------------------------------------------------------------------## Connect to the target's serial+## Push the kernel to the real HW target ##-------------------------------------------------------------------------------miniterm:- @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL)+chainboot: $(KERNEL_BIN)+ @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD)
##------------------------------------------------------------------------------ ## Run clippy@@ -210,7 +217,8 @@ ##------------------------------------------------------------------------------ test_boot: $(KERNEL_BIN) $(call colorecho, "\nBoot test - $(BSP)")- @$(DOCKER_TEST) $(EXEC_TEST_DISPATCH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)+ @$(DOCKER_TEST) $(EXEC_TEST_MINIPUSH) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) \+ -kernel $(KERNEL_BIN) $(CHAINBOOT_DEMO_PAYLOAD)
test: test_boot
diff -uNr 05_drivers_gpio_uart/src/_arch/aarch64/cpu/boot.s 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s--- 05_drivers_gpio_uart/src/_arch/aarch64/cpu/boot.s+++ 06_uart_chainloader/src/_arch/aarch64/cpu/boot.s@@ -18,6 +18,17 @@ add \register, \register, #:lo12:\symbol .endm
+// Load the address of a symbol into a register, absolute.+//+// # Resources+//+// - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html+.macro ADR_ABS register, symbol+ movz \register, #:abs_g2:\symbol+ movk \register, #:abs_g1_nc:\symbol+ movk \register, #:abs_g0_nc:\symbol+.endm+ .equ _core_id_mask, 0b11
//--------------------------------------------------------------------------------------------------@@ -39,23 +50,35 @@ // If execution reaches here, it is the boot core.
// Initialize DRAM.- ADR_REL x0, __bss_start- ADR_REL x1, __bss_end_exclusive+ ADR_ABS x0, __bss_start+ ADR_ABS x1, __bss_end_exclusive
.L_bss_init_loop: cmp x0, x1- b.eq .L_prepare_rust+ b.eq .L_relocate_binary stp xzr, xzr, [x0], #16 b .L_bss_init_loop
+ // Next, relocate the binary.+.L_relocate_binary:+ ADR_REL x0, __binary_nonzero_start // The address the binary got loaded to.+ ADR_ABS x1, __binary_nonzero_start // The address the binary was linked to.+ ADR_ABS x2, __binary_nonzero_end_exclusive++.L_copy_loop:+ ldr x3, [x0], #8+ str x3, [x1], #8+ cmp x1, x2+ b.lo .L_copy_loop+ // Prepare the jump to Rust code.-.L_prepare_rust: // Set the stack pointer.- ADR_REL x0, __boot_core_stack_end_exclusive+ ADR_ABS x0, __boot_core_stack_end_exclusive mov sp, x0
- // Jump to Rust code.- b _start_rust+ // Jump to the relocated Rust code.+ ADR_ABS x1, _start_rust+ br x1
// Infinitely wait for events (aka "park the core"). .L_parking_loop:
diff -uNr 05_drivers_gpio_uart/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs--- 05_drivers_gpio_uart/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs+++ 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs@@ -148,7 +148,7 @@ // Make an educated guess for a good delay value (Sequence described in the BCM2837 // peripherals PDF). //- // - According to Wikipedia, the fastest Pi3 clocks around 1.4 GHz.+ // - According to Wikipedia, the fastest RPi4 clocks around 1.5 GHz. // - The Linux 2837 GPIO driver waits 1 µs between the steps. // // So lets try to be on the safe side and default to 2000 cycles, which would equal 1 µs
diff -uNr 05_drivers_gpio_uart/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs--- 05_drivers_gpio_uart/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs+++ 06_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs@@ -278,7 +278,7 @@ }
/// Retrieve a character.- fn read_char_converting(&mut self, blocking_mode: BlockingMode) -> Option<char> {+ fn read_char(&mut self, blocking_mode: BlockingMode) -> Option<char> { // If RX FIFO is empty, if self.registers.FR.matches_all(FR::RXFE::SET) { // immediately return in non-blocking mode.@@ -293,12 +293,7 @@ }
// Read one character.- let mut ret = self.registers.DR.get() as u8 as char;-- // Convert carrige return to newline.- if ret == '\r' {- ret = '\n'- }+ let ret = self.registers.DR.get() as u8 as char;
// Update statistics. self.chars_read += 1;@@ -378,14 +373,14 @@ impl console::interface::Read for PL011Uart { fn read_char(&self) -> char { self.inner- .lock(|inner| inner.read_char_converting(BlockingMode::Blocking).unwrap())+ .lock(|inner| inner.read_char(BlockingMode::Blocking).unwrap()) }
fn clear_rx(&self) { // Read from the RX FIFO until it is indicating empty. while self .inner- .lock(|inner| inner.read_char_converting(BlockingMode::NonBlocking))+ .lock(|inner| inner.read_char(BlockingMode::NonBlocking)) .is_some() {} }
diff -uNr 05_drivers_gpio_uart/src/bsp/raspberrypi/link.ld 06_uart_chainloader/src/bsp/raspberrypi/link.ld--- 05_drivers_gpio_uart/src/bsp/raspberrypi/link.ld+++ 06_uart_chainloader/src/bsp/raspberrypi/link.ld@@ -3,8 +3,6 @@ * Copyright (c) 2018-2021 Andre Richter <andre.o.richter@gmail.com> */
-__rpi_phys_dram_start_addr = 0;- /* The physical address at which the the kernel binary will be loaded by the Raspberry's firmware */ __rpi_phys_binary_load_addr = 0x80000;
@@ -28,7 +26,8 @@
SECTIONS {- . = __rpi_phys_dram_start_addr;+ /* Set the link address to 32 MiB */+ . = 0x2000000;
/*********************************************************************************************** * Boot Core Stack@@ -45,6 +44,7 @@ /*********************************************************************************************** * Code + RO Data + Global Offset Table ***********************************************************************************************/+ __binary_nonzero_start = .; .text : { KEEP(*(.text._start))@@ -61,6 +61,10 @@ ***********************************************************************************************/ .data : { *(.data*) } :segment_data
+ /* Fill up to 8 byte, b/c relocating the binary is done in u64 chunks */+ . = ALIGN(8);+ __binary_nonzero_end_exclusive = .;+ /* Section is zeroed in pairs of u64. Align start and end to 16 bytes */ .bss (NOLOAD) : ALIGN(16) {
diff -uNr 05_drivers_gpio_uart/src/bsp/raspberrypi/memory.rs 06_uart_chainloader/src/bsp/raspberrypi/memory.rs--- 05_drivers_gpio_uart/src/bsp/raspberrypi/memory.rs+++ 06_uart_chainloader/src/bsp/raspberrypi/memory.rs@@ -11,9 +11,10 @@ /// The board's physical memory map. #[rustfmt::skip] pub(super) mod map {+ pub const BOARD_DEFAULT_LOAD_ADDRESS: usize = 0x8_0000;
- pub const GPIO_OFFSET: usize = 0x0020_0000;- pub const UART_OFFSET: usize = 0x0020_1000;+ pub const GPIO_OFFSET: usize = 0x0020_0000;+ pub const UART_OFFSET: usize = 0x0020_1000;
/// Physical devices. #[cfg(feature = "bsp_rpi3")]@@ -35,3 +36,13 @@ pub const PL011_UART_START: usize = START + UART_OFFSET; } }++//--------------------------------------------------------------------------------------------------+// Public Code+//--------------------------------------------------------------------------------------------------++/// The address on which the Raspberry firmware loads every binary by default.+#[inline(always)]+pub fn board_default_load_addr() -> *const u64 {+ map::BOARD_DEFAULT_LOAD_ADDRESS as _+}
diff -uNr 05_drivers_gpio_uart/src/main.rs 06_uart_chainloader/src/main.rs--- 05_drivers_gpio_uart/src/main.rs+++ 06_uart_chainloader/src/main.rs@@ -141,38 +141,56 @@ kernel_main() }
+const MINILOAD_LOGO: &str = r#"+ __ __ _ _ _ _+| \/ (_)_ _ (_) | ___ __ _ __| |+| |\/| | | ' \| | |__/ _ \/ _` / _` |+|_| |_|_|_||_|_|____\___/\__,_\__,_|+"#;+ /// The main function running after the early init. fn kernel_main() -> ! { use bsp::console::console; use console::interface::All;- use driver::interface::DriverManager;
- println!(- "[0] {} version {}",- env!("CARGO_PKG_NAME"),- env!("CARGO_PKG_VERSION")- );- println!("[1] Booting on: {}", bsp::board_name());-- println!("[2] Drivers loaded:");- for (i, driver) in bsp::driver::driver_manager()- .all_device_drivers()- .iter()- .enumerate()- {- println!(" {}. {}", i + 1, driver.compatible());+ println!("{}", MINILOAD_LOGO);+ println!("{:^37}", bsp::board_name());+ println!();+ println!("[ML] Requesting binary");+ console().flush();++ // Discard any spurious received characters before starting with the loader protocol.+ console().clear_rx();++ // Notify `Minipush` to send the binary.+ for _ in 0..3 {+ console().write_char(3 as char); }
- println!(- "[3] Chars written: {}",- bsp::console::console().chars_written()- );- println!("[4] Echoing input now");+ // Read the binary's size.+ let mut size: u32 = u32::from(console().read_char() as u8);+ size |= u32::from(console().read_char() as u8) << 8;+ size |= u32::from(console().read_char() as u8) << 16;+ size |= u32::from(console().read_char() as u8) << 24;
- // Discard any spurious received characters before going into echo mode.- console().clear_rx();- loop {- let c = bsp::console::console().read_char();- bsp::console::console().write_char(c);+ // Trust it's not too big.+ console().write_char('O');+ console().write_char('K');++ let kernel_addr: *mut u8 = bsp::memory::board_default_load_addr() as *mut u8;+ unsafe {+ // Read the kernel byte by byte.+ for i in 0..size {+ core::ptr::write_volatile(kernel_addr.offset(i as isize), console().read_char() as u8)+ } }++ println!("[ML] Loaded! Executing the payload now\n");+ console().flush();++ // Use black magic to create a function pointer.+ let kernel: fn() -> ! = unsafe { core::mem::transmute(kernel_addr) };++ // Jump to loaded kernel!+ kernel() }
diff -uNr 05_drivers_gpio_uart/tests/boot_test_string.rb 06_uart_chainloader/tests/boot_test_string.rb--- 05_drivers_gpio_uart/tests/boot_test_string.rb+++ 06_uart_chainloader/tests/boot_test_string.rb@@ -1,3 +0,0 @@-# frozen_string_literal: true--EXPECTED_PRINT = 'Echoing input now'
diff -uNr 05_drivers_gpio_uart/tests/chainboot_test.rb 06_uart_chainloader/tests/chainboot_test.rb--- 05_drivers_gpio_uart/tests/chainboot_test.rb+++ 06_uart_chainloader/tests/chainboot_test.rb@@ -0,0 +1,80 @@+# frozen_string_literal: true++# SPDX-License-Identifier: MIT OR Apache-2.0+#+# Copyright (c) 2020-2021 Andre Richter <andre.o.richter@gmail.com>++require_relative '../../common/serial/minipush'+require_relative '../../common/tests/boot_test'+require 'pty'++# Match for the last print that 'demo_payload_rpiX.img' produces.+EXPECTED_PRINT = 'Echoing input now'++# Extend BootTest so that it listens on the output of a MiniPush instance, which is itself connected+# to a QEMU instance instead of a real HW.+class ChainbootTest < BootTest+ MINIPUSH = '../common/serial/minipush.rb'+ MINIPUSH_POWER_TARGET_REQUEST = 'Please power the target now'++ def initialize(qemu_cmd, payload_path)+ super(qemu_cmd, EXPECTED_PRINT)++ @test_name = 'Boot test using Minipush'++ @payload_path = payload_path+ end++ private++ # override+ def post_process_and_add_output(output)+ temp = output.join.split("\r\n")++ # Should a line have solo carriage returns, remove any overridden parts of the string.+ temp.map! { |x| x.gsub(/.*\r/, '') }++ @test_output += temp+ end++ def wait_for_minipush_power_request(mp_out)+ output = []+ Timeout.timeout(MAX_WAIT_SECS) do+ loop do+ output << mp_out.gets+ break if output.last.include?(MINIPUSH_POWER_TARGET_REQUEST)+ end+ end+ rescue Timeout::Error+ @test_error = 'Timed out waiting for power request'+ rescue StandardError => e+ @test_error = e.message+ ensure+ post_process_and_add_output(output)+ end++ # override+ def setup+ pty_main, pty_secondary = PTY.open+ mp_out, _mp_in = PTY.spawn("ruby #{MINIPUSH} #{pty_secondary.path} #{@payload_path}")++ # Wait until MiniPush asks for powering the target.+ wait_for_minipush_power_request(mp_out)++ # Now is the time to start QEMU with the chainloader binary. QEMU's virtual tty is connected+ # to the MiniPush instance spawned above, so that the two processes talk to each other.+ Process.spawn(@qemu_cmd, in: pty_main, out: pty_main)++ # The remainder of the test is done by the parent class' run_concrete_test, which listens on+ # @qemu_serial. Hence, point it to MiniPush's output.+ @qemu_serial = mp_out+ end+end++##--------------------------------------------------------------------------------------------------+## Execution starts here+##--------------------------------------------------------------------------------------------------+payload_path = ARGV.pop+qemu_cmd = ARGV.join(' ')++ChainbootTest.new(qemu_cmd, payload_path).run
diff -uNr 05_drivers_gpio_uart/update.sh 06_uart_chainloader/update.sh--- 05_drivers_gpio_uart/update.sh+++ 06_uart_chainloader/update.sh@@ -0,0 +1,8 @@+#!/usr/bin/env bash++cd ../05_drivers_gpio_uart+BSP=rpi4 make+cp kernel8.img ../06_uart_chainloader/demo_payload_rpi4.img+make+cp kernel8.img ../06_uart_chainloader/demo_payload_rpi3.img+rm kernel8.img