rust-raspberrypi-OS-tutorials链式加载器:UART传输内核实现

【免费下载链接】rust-raspberrypi-OS-tutorials :books: Learn to write an embedded OS in Rust :crab: 【免费下载链接】rust-raspberrypi-OS-tutorials 项目地址: https://gitcode.com/gh_mirrors/ru/rust-raspberrypi-OS-tutorials

为什么需要链式加载器?

每次开发都需要将内核镜像写入SD卡是一件繁琐的事情。链式加载器(Chainloader)作为最后一个需要烧录到SD卡的程序,能够通过UART接口接收新的内核镜像并执行,大幅提升开发效率。本文将详细介绍如何通过UART实现Raspberry Pi内核的链式加载功能。

链式加载器工作原理

链式加载器的核心实现分为两个关键阶段:自重定位和内核接收执行。

1. 自重定位过程

链式加载器启动时首先需要将自身从固件加载地址(0x80000)重定位到更高的内存区域(0x2000000),为后续接收新内核腾出空间。这一过程通过位置无关代码(Position Independent Code)实现:

// src/_arch/aarch64/cpu/boot.s
.L_relocate_binary:
    ADR_REL x0, __binary_nonzero_start         // 加载地址
    ADR_ABS x1, __binary_nonzero_start         // 链接地址
    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

链接脚本src/bsp/raspberrypi/kernel.ld中定义了重定位相关的符号:

/* 设置链接地址为32MB */
. = 0x2000000;

__binary_nonzero_start = .;
.text : { *(.text*) }
.data : { *(.data*) }
. = ALIGN(8);
__binary_nonzero_end_exclusive = .;

2. UART传输协议实现

重定位完成后,加载器通过UART与主机建立通信,接收新内核镜像:

// src/main.rs
const MINILOAD_LOGO: &str = r#"
 __  __ _      _ _                 _
|  \/  (_)_ _ (_) |   ___  __ _ __| |
| |\/| | | ' \| | |__/ _ \/ _` / _` |
|_|  |_|_|_||_|_|____\___/\__,_\__,_|
"#;

fn kernel_main() -> ! {
    println!("{}", MINILOAD_LOGO);
    println!("{:^37}", bsp::board_name());
    println!("[ML] Requesting binary");
    
    // 发送启动信号
    for _ in 0..3 {
        console().write_char(3 as char); // ASCII ETX字符
    }
    
    // 读取内核大小(4字节)
    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;
    
    // 确认接收
    console().write_char('O');
    console().write_char('K');
    
    // 接收内核数据
    let kernel_addr: *mut u8 = bsp::memory::board_default_load_addr() as *mut u8;
    unsafe {
        for i in 0..size {
            core::ptr::write_volatile(kernel_addr.offset(i as isize), console().read_char() as u8)
        }
    }
    
    // 跳转到新内核
    let kernel: fn() -> ! = unsafe { core::mem::transmute(kernel_addr) };
    kernel()
}

硬件连接与测试

1. 串口连接

使用USB转TTL适配器连接Raspberry Pi的UART接口:

UART接线图

2. 编译与烧录

# 编译链式加载器
cd 06_uart_chainloader
make BSP=rpi3

# 将生成的kernel8.img烧录到SD卡

3. 加载内核镜像

通过minipush工具发送内核镜像:

# 发送演示内核
make chainboot

成功执行后将显示:

 __  __ _      _ _                 _
|  \/  (_)_ _ (_) |   ___  __ _ __| |
| |\/| | | ' \| | |__/ _ \/ _` / _` |
|_|  |_|_|_||_|_|____\___/\__,_\__,_|
           Raspberry Pi 3

[ML] Requesting binary
[MP] ⏩ Pushing 7 KiB ==========================================🦀 100% 0 KiB/s Time: 00:00:00
[ML] Loaded! Executing the payload now

关键代码模块解析

1. UART驱动

UART驱动src/bsp/device_driver/bcm/bcm2xxx_pl011_uart.rs实现了基础的字符读写功能:

fn read_char(&mut self, blocking_mode: BlockingMode) -> Option<char> {
    // 检查接收缓冲区状态
    if self.registers.FR.matches_all(FR::RXFE::SET) {
        if let BlockingMode::NonBlocking = blocking_mode {
            return None;
        }
        while self.registers.FR.matches_all(FR::RXFE::SET) {}
    }
    
    // 读取字符
    let ret = self.registers.DR.get() as u8 as char;
    self.chars_read += 1;
    Some(ret)
}

2. 内存地址定义

src/bsp/raspberrypi/memory.rs定义了Raspberry Pi的内存布局:

/// 固件默认加载地址
pub const BOARD_DEFAULT_LOAD_ADDRESS: usize = 0x8_0000;

/// 获取默认加载地址
#[inline(always)]
pub fn board_default_load_addr() -> *const u64 {
    BOARD_DEFAULT_LOAD_ADDRESS as _
}

调试与扩展

1. QEMU调试

使用QEMU模拟链式加载过程:

# 带汇编输出的QEMU模拟
make qemuasm

将显示重定位过程的指令执行序列:

0x00080030:  58000140  ldr      x0, #0x80058
0x00080034:  9100001f  mov      sp, x0
0x00080038:  58000141  ldr      x1, #0x80060
0x0008003c:  d61f0020  br       x1
...
0x02000070:  9400044c  bl       #0x20011a0  ; 重定位后的代码

2. 自动化测试

测试脚本tests/chainboot_test.rb验证了链式加载功能:

# 测试预期输出
EXPECTED_PRINT = 'Echoing input now'

# 启动QEMU并验证加载过程
def run
  setup
  @console_subtests.prepend(PowerTargetRequestTest.new(@qemu_cmd, pty_main))
  execute_subtests
  finish
end

总结与展望

链式加载器通过自重定位和UART通信,解决了嵌入式开发中频繁烧录SD卡的痛点。这一机制为后续教程中的内核开发提供了高效的测试环境。下一阶段可以扩展实现:

  1. 内核镜像校验功能
  2. 传输进度指示
  3. 多内核版本管理

完整实现可参考06_uart_chainloader目录下的源代码,官方文档请参见README.md

点赞收藏本文,关注后续Rust嵌入式开发教程!

【免费下载链接】rust-raspberrypi-OS-tutorials :books: Learn to write an embedded OS in Rust :crab: 【免费下载链接】rust-raspberrypi-OS-tutorials 项目地址: https://gitcode.com/gh_mirrors/ru/rust-raspberrypi-OS-tutorials

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐