使用Rust在树莓派上编写操作系统 - 06 - UART的链式加载

概述

  • 用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

  1. 根据目标硬件,运行:make或者BSP=rpi4 make
  2. kernel8.img复制到SD卡,并将SD卡插回到树莓派。
  3. 根据目标硬件,运行make chainboot或者BSP=rpi4 make chainboot
  4. 连接USB串口线到宿主PC。
    • 按照本项目的README接线。
    • 一定确保没有连接USB串口的电源引脚。只连接了RX/TX和GND。
  5. 为树莓派连接(USB)电源。
  6. 观察加载程序通过UART获取内核:

注意make chainboot的默认串行设备名称为/dev/ttyUSB0。对于不同操作系统的宿主机,设备名称可能会有所不同。例如,在macOS上,名称可能类似于/dev/tty.usbserial-0001。这种情况下,需要明确给出名称:

1
$ DEV_SERIAL=/dev/tty.usbserial-0001 make chainboot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ 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的重定位代码处的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ make qemuasm
[...]
N:
0x00080030: 58000140 ldr x0, #0x80058
0x00080034: 9100001f mov sp, x0
0x00080038: 58000141 ldr x1, #0x80060
0x0008003c: d61f0020 br x1

----------------
IN:
0x02000070: 9400044c bl #0x20011a0

----------------
IN:
0x020011a0: 90000008 adrp x8, #0x2001000
0x020011a4: 90000009 adrp x9, #0x2001000
0x020011a8: f9446508 ldr x8, [x8, #0x8c8]
0x020011ac: f9446929 ldr x9, [x9, #0x8d0]
0x020011b0: eb08013f cmp x9, x8
0x020011b4: 54000109 b.ls #0x20011d4
[...]

与上一章代码的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
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 differ
Binary 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

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×