Author: Zhang Handong

Learning online reading notes series address: zhanghandong. Making. IO/raspberrypi…

This is my study note on the official Operating System development tutorials in Rust on the Raspberry Pi. To learn Rust (bare-matel) programming.

Learning notes code repository: github.com/ZhangHanDon…

Note: If hands-on implementation is required, please use the official tutorial source repository to accompany this study note.

Currently updated:

  • ARM Assembly Basics
  • 00: Preparation
  • 01: Waiting in a loop
  • 02: Initializing execution Environment (Runtime)
  • 03: Core output Hello World
  • 04: Secure access to global data structures
  • 05: Drivers: GPIO and UART

This article extracts the content of chapter 5.

Drivers: GPIO and UART

Note: this chapter does not support BSP=rpi4 make qemu.

Some pre-knowledge

Arm Physical memory vs Arm virtual address space

Arm is uniformly addressed with peripherals such as physical memory in a 4GB(32-bit) address space. X86 addresses memory separately in one address space, and peripheral I/O ports in another address space. Accessing the IO address space requires special instructions.

Each Linux process has 4GB of virtual address space, of which 13GB is the user space exclusively owned by each process and 3GB and 4GB is the kernel space shared by all processes (0xC0000000 to 0xFFFFFFFF). Therefore, virtual address space includes both kernel space and user space. The conversion of real and virtual addresses in Linux is accomplished by MMU in the form of page tables.

The Arm virtual address space layout is a standard Linux Kernel implementation. The address space is divided into 1G kernel space and 3G user space. The kernel space address range is 0xC0000000-0xeffffffF, and the user space address range is 0x00000000-0xBfffff.

Accessing peripherals by manipulating memory is called memory-mapped IO.

Note: The physical address space of the Peripherals is 0x20000000+16MB, which is normally mapped to 0x7E000000+16MB in the kernel virtual address.

The mapping between addresses seen by ARM is described in the raspberry PI 4B BCM 2711 documentation:

rpi4: 0xFE200000 – 0xFE2000b3 : gpio@ 0x7E200000

Simply subtract the GPIO-BaseADDR (0x00200000) from fe200000 to obtain the raspberry PI’s PeripheralBaseAddr (PBase) : 0xFE000000.

GPIO and UART

Before we get to this chapter, we need some prior knowledge. If you don’t know PI, it’s hard to understand what its code is doing. This part, if you don’t want to read it for the moment, or if you already know it, you can skip it. Look back when you need to.

GPIO (General Purpose I/O Ports) is a generic input/output port. In General terms, it is a set of pins through which you can either output high or low levels or read the state of the pin – either high or low levels. GPIO is an important concept. Users can use GPIO port to interact with hardware data (such as UART), control hardware work (such as LED, buzzer, etc.), and read hardware working status signals (such as interrupt signals). GPIO ports are widely used. Mastering GPIO is almost equivalent to mastering the ability to manipulate hardware.

Take raspberry PI 4B as an example, its 40 stitches are shown in the picture below:

Universal Asynchronous Receiver/Transmitter (UART) is a serial communication protocol in which data is transmitted in serial, one byte at a time, that is, bit by bit transmission. As a chip that converts a parallel input signal into a serial output signal, the UART is usually integrated into the links of other communication interfaces.

In order to communicate with raspberry PI serial port, we connected the RASPberry PI UART pin to a PERSONAL computer (hereinafter referred to as PC).

The UART port must have at least three pins: RX, TX, and ground cable. RX reads, TX outputs. If there are two UART ports, they are connected as follows:

Usb2ttl (RXD) <-> GPIO (TXD) usb2TTL (TXD) <-> gPIO (RXD) Also, do not connect the power cord on the USB2TT to the raspberry PI. Be careful to burn the raspberry PI.

Uart protocol layer

The protocol layer defines the content of the packet, which consists of the start bit, the main data bit, the check bit and the stop bit. The communication parties must agree on the same format of the packet to send and receive data normally.

  • Baud rate: In asynchronous communication, because there is no clock signal, the baud rate must be set for the two communication devices. Common baud rates are 4800, 9600, and 115200.
  • Communication start and stop signals: A packet of serial communication starts with a start signal and ends with a stop signal. The start signal of a packet is represented by a data bit of a logical 0, while the stop signal of a packet can be represented by a data bit of 0.5, 1, 1.5, or 2 logical 1s, as long as the two parties agree.
  • Valid data: The main data content to be transmitted immediately after the start bit of a packet, also known as valid data. Valid data is usually agreed to be 8 or 9 bits long.
  • Data check: After the valid data, there is an optional data check bit. Because data communication is relatively easy to be affected by external interference, data transmission deviation can be solved by adding check bits in the transmission process. The parity methods are odd, even, space, mark, and Noparity.
  • Parity check requires that the number of valid data and parity bit 1s must be odd. For example, an 8-bit long valid data is 01101001. In this case, there are four 1s in total. The parity check and parity check requirements are just the opposite. The number of “1s” in frame data and parity bit is required to be even. For example, data frame: 11001010, the number of “1s” in data frame is 4, so the parity bit is 0. Zero check is no matter what’s in the valid data, the check bit is always zero, and one check is always one.

Uart baud rate calculation

Formula:

Where, FCK is USART clock, USARTDIV is an unsigned fixed-point number stored in the baud rate register (USART_BRR). Where the DIV_Mantissa[11:0] bit defines the integer part of USARTDIV, and the DIV_Fraction[3:0] bit defines the decimal part of USARTDIV.

For example, DIV_Mantissa=24(0x18), DIV_Fraction=10(0x0A), USART_BRR is 0x18A. So the decimal place of USARTDIV 10/16=0.625; The integer bit is 24, and the final USARTDIV value is 24.625.

Common baud rate values are 2400, 9600, 19200, and 115200.

How to set the value of baud rate worth register?

Assume that the serial port is set to 9612008N1, that is, the baud rate is 961200. there are 8 data bits (N indicates no parity bit and 1 stop bit).

In the kernel config.txt, set the clock to 48MHZ (init_uart_clock=48000000).

Then baud rate (BAUD) is calculated as: (48_000_000/16) / 921_600 = 3.2552083. This means that the integer part (DIV_Mantissa) is 3 and the decimal part is 0.2552083.

DIV_Fraction calculation according to PL011 technical Reference manual: INTEGER((0.2552083 * 64) + 0.5) = 16. INTEGER means round.

Therefore, the generated baud rate divider is 3 + 16/64 = 3.25, and the generated baud rate is 48_000_000 / (16 x 3.25) = 923_077. The error rate is (923_077-921_600) / 921_600) * 100 = 0.16%.

If the baud rate is 115200 8N1, then:

  • Integer part:(48000000/16) / 115200 = 26.0416666667.
  • Decimal part:INTEGER((0.0416666667 * 64) + 0.5) = 3

Setting the baud rate correctly is important.

Raspberry PI startup process

The Raspberry PI was designed to save money by not using non-volatile storage media with power failures, so the relevant flash cannot be found on the board. So the program that the chip starts can only be placed on the SD card. Originally, usb boot is also a way, but the design of raspberry PI 4 was not very good, so this way of boot can not be used. There have been some attempts at raspberry PI 4, but the most popular way to launch PI 4 is to use an SD card.

Raspberry Pi 4 has an EEPROM (4MBits / 512KB) that has an SPI connection. This contains the code to boot the system and replaces the bootcode.bin previously found in the SD card’s boot partition. If your Raspberry PI 4 is powered up but can’t start the system, and the green light is on and not blinking, check for two things:

  1. An SD card is inserted.
  2. If there is an SD card, then the EEPROM is corrupted. You need to reformat the SD card and download the Recovery firmware from the official website.

In the raspberry PI bare-metal experiment, booting the system from an SD card must include the necessary files:

  1. bootcode.bin(Raspberry PI 4 is not required, previous required).
  2. config.txt
  3. kernel8.img, kernel image
  4. start4.elf
  5. bcm2711-rip-4.dtb

Startup process:

  1. Power on the chip and perform the curing insidefirst-stage bootloaderTo load the SD cardbootcode.binFile. But on raspberry PI 4, you don’t need this file because you have an EEPROM with SPI.
  2. Start the GPU. The ARM Cortex-A72 Core is standby and the VideoCore IV GPU Core is responsible for booting the system.
  3. willbootcode.bin128 KB Cache (L2 Cache) is read. Start to performbootcode.binThe code. Elf is used to initialize RAM and load start4.elf into memory and read the configuration information in config.txt to set the configuration information. Of course, raspberry PI 4 doesn’tbootcode.binWhat’s up?
  4. bcm2711-rpi-4-b.dtbThe file is also needed. If it does not exist, it will affect the output of the serial port. So presumably the start4.elf file will also read the device tree file and set some basic parameters.

Config. TXT Configuration information

Enable_uart =1 // miniUART arm_64bit=0 // Tell ARM to start the program 32-bit core_freq=250 // Set the arm frequency kernel=kernel7.img // Kernel_address =0x8000 // Indicates the memory address that needs to be executed. This address is the entry address of the bare-metal program when linkingCopy the code

Not all of these configurations are necessary, depending on the situation. Early understanding of the chip startup process is helpful for the subsequent analysis of writing bare machine code.

important

The above concepts need to be verified with the code in this chapter and a real raspberry PI machine to have a deeper understanding.

Interpretation of code

Chapter 5 is a milestone.

The first four chapters complete the process from raspberry PI bare machine to the establishment of Rust execution environment, but all based on QEMU. Support for real Raspberry PI kernel code execution begins in Chapter 5.

So, in order to communicate with the real Raspberry PI, we need to implement two drivers.

bootstrap

The bootstrap is basically the same as the code in the previous chapter. SRC /_arch/ aARCH64 /cpu.rs adds the code for raspberry PI 3:

pub use asm::nop;

/// Spin for `n` cycles.
#[cfg(feature = "bsp_rpi3")]
#[inline(always)]
pub fn spin_for_cycles(n: usize) {
    for _ in 0..n {
        asm::nop();
    }
}
Copy the code

This is going to come in later. Conditional compilation is used here to specify the bSP_rpi3 feature. CPU empty wait using noP operations of assembly.

Kernel initialization

Open SRC /main.rs and see that the kernel_init function has changed a lot.

// Since only a single core (core0) is activated to execute the initialization code, the correct order of execution is guaranteed. // # Safety /// /// - Only a single core must be active and running this function. /// - The init calls in this function must appear in the correct order. unsafe fn kernel_init() -> ! {/ / here increased the driver management use driver: : interface: : DriverManager; // Initializes the iteration driver instance, Panic for I in BSP ::driver:: Driver_manager ().all_device_drivers().iter() {if let Err(x) = i.init() {panic! ("Error loading driver: {}: {}", i.compatible(), x); } } // bsp::driver::driver_manager().post_device_driver_init(); // println! Usable from here on. // Transition from unsafe to safe. kernel_main()} // The main function is 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()); // Print the driver loading process println! ("[2] Drivers loaded:"); for (i, driver) in bsp::driver::driver_manager() .all_device_drivers() .iter() .enumerate() { println! (" {}. {}", i + 1, driver.compatible()); } println! ( "[3] Chars written: {}", bsp::console::console().chars_written() ); // The following information is displayed: println! ("[4] Echoing input now"); // Before entering echo mode, Discard any spurious received characters before going into echo mode.console ().clear_rx(); Discard any spurious received characters before going into echo mode.console (). loop { let c = bsp::console::console().read_char(); bsp::console::console().write_char(c); }}Copy the code

The memory mapping

Physical memory MMIO mapping code defined in SRC/BSP/raspberrypi/memory. Rs.

//-------------------------------------------------------------------------------------------------- // Public Definitions //-------------------------------------------------------------------------------------------------- /// The  board's physical memory map. #[rustfmt::skip] pub(super) mod map { pub const GPIO_OFFSET: usize = 0x0020_0000; pub const UART_OFFSET: usize = 0x0020_1000; /// Physical devices. #[cfg(feature = "bsp_rpi3")] pub mod mmio { use super::*; pub const START: usize = 0x3F00_0000; pub const GPIO_START: usize = START + GPIO_OFFSET; pub const PL011_UART_START: usize = START + UART_OFFSET; } // Note that #[CFG (feature = "bsp_rpi4")] pub mod mmio {use super::*; pub const START: usize = 0xFE00_0000; pub const GPIO_START: usize = START + GPIO_OFFSET; pub const PL011_UART_START: usize = START + UART_OFFSET; }}Copy the code

drive

In the SRC/BSP /raspberrypi.rs code:

//-------------------------------------------------------------------------------------------------- // Global instances // We define two global static variables GPIO and PL011_UART to hold the corresponding base address. // -------------------------------------------------------------------------------------------------- use super::device_driver; static GPIO: device_driver::GPIO = unsafe { device_driver::GPIO::new(memory::map::mmio::GPIO_START) }; static PL011_UART: device_driver::PL011Uart = unsafe { device_driver::PL011Uart::new(memory::map::mmio::PL011_UART_START) }; //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- /// Board identification. pub fn board_name() -> &'static str { #[cfg(feature = "bsp_rpi3")] { "Raspberry Pi 3" } #[cfg(feature = "bsp_rpi4")] { "Raspberry Pi 4" } }Copy the code

Let’s take a look at the SRC /driver.rs code.

// Define an interface module for the driver when the namespace uses // This module defines two traits, Pub mod interface {/// / DeviceDriver functions. Pub trait DeviceDriver {// // Return a compatibility string for identifying the driver. fn compatible(&self) -> &'static STR; // The Unsafe Rust function init is an Unsafe operation, because the driver may affect the entire system during initialization. Therefore, the '#Safety' annotation is added to indicate this situation. And the 'unsafe' tag for the function. // Called by the kernel to bring up the device. /// /// # Safety /// /// - During init, drivers might do stuff with system-wide impact. unsafe fn init(&self) -> Result<(), &'static STR > {Ok(())}} /// The 'BSP' is supposed to provide a global instance Supply one global instance. pub trait DriverManager {// Returns a set of references to all instantiated drivers (slices) // This function returns the DeviceDriver trait object, 'static 'is used because methods in this trait return 'static STR'. /// Return a slice of references to all `BSP`-instantiated drivers. /// /// # Safety /// /// - The order of devices is The order in which 'DeviceDriver::init()' is called.fn all_device_drivers(&self) -> &[&'static (dyn DeviceDriver + Sync)]; // Initialization code that runs after driver init. /// /// For example, // Initialization code that runs after driver init. /// /// device driver code that depends on other drivers already being online. fn post_device_driver_init(&self); }}Copy the code

The specific driver code is implemented in SRC/BSP/Device_driver/BCM. rs module.

Take a look at SRC/BSP /device_driver/common.rs

/ /! Common device driver code. use core::{marker::PhantomData, ops}; //-------------------------------------------------------------------------------------------------- // Public Definitions / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / right The MMIO pointer address has a Rust wrapper, where 'PhantomData<fn() -> T>' is introduced to ensure that only 'static 'references are passed in. pub struct MMIODerefWrapper<T> { start_addr: usize, phantom: PhantomData<fn() -> T>, } //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- impl<T> MMIODerefWrapper<T> { /// Create an instance. pub const unsafe fn new(start_addr: usize) -> Self { Self { start_addr, phantom: PhantomData,}}} // Implement Deref for 'MMIODerefWrapper<T>', use it as a smart pointer Return a reference to impl<T> ops::Deref for MMIODerefWrapper<T> {type Target = T; fn deref(&self) -> &Self::Target { unsafe { &*(self.start_addr as *const _) } } }Copy the code

SRC/BSP /device_driver/ BCM/bcm2xxx_gPIo. rs is a concrete implementation of the GPIO driver. Here are some key snippets of code that only relate to raspberry PI 4B:

use crate::{ bsp::device_driver::common::MMIODerefWrapper, driver, synchronization, synchronization::NullLock, }; // Use the register-RS library, which is a type-safe MMIO and CPU register access library. use register::{mmio::*, register_bitfields, register_structs}; // GPIO registers // Descriptions taken from // - https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf // - HTTP: / / https://datasheets.raspberrypi.org/bcm2711/bcm2711-peripherals.pdf / / Raspberry Pi 3/4 has two UART devices: PL011 UART and Mini UART. // While the PL011 UART is connected to the Bluetooth module, the Mini UART is used as the main UART. // But we can initialize the GPIO register to use PL011 UART directly instead of the mini UART. // This macro is provided for the register-rs library to define the MMIO register register_bitfields! {u32, // To use PL011 UART // the FSEL14 and fsel15-bit fields of the GPFSEL1 register need to be set to the 0b100 address corresponding to the AltFunc0 function. /// GPIO Function Select 1 GPFSEL1 [ /// Pin 15 FSEL15 OFFSET(15) NUMBITS(3) [ Input = 0b000, Output = 0b001, AltFunc0 = 0b100 // PL011 UART RX ], /// Pin 14 FSEL14 OFFSET(12) NUMBITS(3) [ Input = 0b000, Output = 0b001, AltFunc0 = 0b100 // PL011 UART TX]], // In order to use PL011 UART // by setting the GPPUD register to 0 // raspberry PI 3 needs, here ellipse //... . // To use PL011 UART // The GPIO_PUP_PDN_CNTRL_REG0 registers GPIO_PUP_PDN_CNTRL15 and gPIo_pup_PDN_cntrl14-bit fields are set to 1 to turn off Pullup and enable these pins.  /// GPIO Pull-up / Pull-down Register 0 /// /// BCM2711 only. GPIO_PUP_PDN_CNTRL_REG0 [ /// Pin 15 GPIO_PUP_PDN_CNTRL15 OFFSET(30) NUMBITS(2) [ NoResistor = 0b00, PullUp = 0b01 ], /// Pin 14 GPIO_PUP_PDN_CNTRL14 OFFSET(28) NUMBITS(2) [ NoResistor = 0b00, PullUp = 0b01 ] ] } register_structs! { #[allow(non_snake_case)] RegisterBlock { (0x00 => _reserved1), (0x04 => GPFSEL1: ReadWrite<u32, GPFSEL1::Register>), (0x08 => _reserved2), (0x94 => GPPUD: ReadWrite<u32, GPPUD::Register>), (0x98 => GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>), (0x9C => _reserved3), (0xE4 => GPIO_PUP_PDN_CNTRL_REG0: ReadWrite<u32, GPIO_PUP_PDN_CNTRL_REG0::Register>), (0xE8 => @END), }} // Abstraction for the associated MMIO registers MMIODerefWrapper<RegisterBlock>; //-------------------------------------------------------------------------------------------------- // Public Definitions //-------------------------------------------------------------------------------------------------- pub struct GPIOInner { registers: Registers, } // Export the inner struct so that BSPs can use it for the panic handler. pub use GPIOInner as PanicGPIO; // Representation of the GPIO hw. pub struct GPIO {inner: NullLock<GPIOInner>, } //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- impl GPIOInner { /// Create an instance. /// /// # Safety /// - The user must ensure to provide a correct MMIO start address. Pub const unsafe fn new(mmio_start_addr: usize) -> Self { Self { registers: Registers::new(mmio_start_addr), // Disable pull-up/down on pins 14 and 15. #[CFG (feature = "bsp_rpi4")] fn disable_pud_14_15_bcm2711(&mut self) { self.registers.GPIO_PUP_PDN_CNTRL_REG0.write( GPIO_PUP_PDN_CNTRL_REG0::GPIO_PUP_PDN_CNTRL15::PullUp + GPIO_PUP_PDN_CNTRL_REG0::GPIO_PUP_PDN_CNTRL14::PullUp, ); // Map PL011 UART as standard output. /// /// TX to pin 14 // RX to pin 15 pub fn map_pl011_uart(&mut self) { // Select the UART on pins 14 and 15. self.registers .GPFSEL1 .modify(GPFSEL1::FSEL15::AltFunc0 + GPFSEL1::FSEL14::AltFunc0); // Disable pull-up/down on pins 14 and 15. #[cfg(feature = "bsp_rpi3")] self.disable_pud_14_15_bcm2837(); #[cfg(feature = "bsp_rpi4")] self.disable_pud_14_15_bcm2711(); } } impl GPIO { /// Create an instance. /// /// # Safety /// /// - The user must ensure to provide a correct MMIO start address. pub const unsafe fn new(mmio_start_addr: usize) -> Self { Self { inner: NullLock::new(GPIOInner::new(mmio_start_addr)), } } /// Concurrency safe version of `GPIOInner.map_pl011_uart()` pub fn map_pl011_uart(&self) { self.inner.lock(|inner| inner.map_pl011_uart()) } } //------------------------------------------------------------------------------ // OS Interface Code //------------------------------------------------------------------------------ use synchronization::interface::Mutex; Note that there is also an init method that uses the default implementation. impl driver::interface::DeviceDriver for GPIO { fn compatible(&self) -> &'static str { "BCM GPIO" } }Copy the code

SRC/BSP /device_driver/ BCM/bcm2xxX_pl011_uart. rs PL011 Uart driver implementation.

Just excerpt the key code and talk about it. Defining registers is similar to GPIO drivers. Some code about uART reading is not posted.

use register::{mmio::*, register_bitfields, register_structs}; impl PL011UartInner { // ... . pub fn init(&mut self) { self.flush(); // Turn the UART off temporarily. self.registers.CR.set(0); // Clear all pending interrupts. self.registers.ICR.write(ICR::ALL::CLEAR); // Set the baud rate, 8N1 and FIFO enabled. The baud rate of this comment is now set to '921600', but when executed on real raspberry PI hardware, some UTf2TTL may not support such a high baud rate, so garbled characters may appear. / / if the code output can try to change the baud rate to 115200, corresponding to the set (26, 3) / / self. Registers. The IBRD. Write (IBRD: : BAUD_DIVINT. Val (3)); // self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(16)); self.registers.IBRD.write(IBRD::BAUD_DIVINT.val(26)); self.registers.FBRD.write(FBRD::BAUD_DIVFRAC.val(3)); self.registers .LCR_H .write(LCR_H::WLEN::EightBit + LCR_H::FEN::FifosEnabled); // Turn the UART on. self.registers .CR .write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled); }} / /... . impl driver::interface::DeviceDriver for PL011Uart { fn compatible(&self) -> &'static str { "BCM PL011 UART" } // Synchronous locks are used here, and in the current example, Don't need the can / / because when the kernel initialization only bind the mononuclear unsafe fn init (& self) - > < (), the Result & 'static STR > {self. Inner. The lock (| inner | inner. The init ()); Ok(()) } }Copy the code

Driver management

Once the driver is defined, you can manage it. Look at the SRC/BSP/raspberrypi/driver. Rs code in:

// Device DriverManager type. struct BSPDriverManager {device_drivers: [&'static (dyn DeviceDriver + Sync); 2],} // Create a static constant with a reference to GPIO and PL011_UART static BSP_DRIVER_MANAGER: BSPDriverManager = BSPDriverManager { device_drivers: [&super::GPIO, &super::PL011_UART], }; //-------------------------------------------------------------------------------------------------- // Public Code //-------------------------------------------------------------------------------------------------- /// Return a reference to the driver manager. pub fn driver_manager() -> &'static impl driver::interface::DriverManager { &BSP_DRIVER_MANAGER } //------------------------------------------------------------------------------ // OS Interface Code //------------------------------------------------------------------------------ use driver::interface::DeviceDriver; / / implementation DriverManager trait impl driver: : interface: : DriverManager for BSPDriverManager {fn all_device_drivers (& self) - > &[&'static (dyn DeviceDriver + Sync)] { &self.device_drivers[..] } pl011_uart pin fn post_device_driver_init(&self) {Configure PL011Uart's output pins. super::GPIO.map_pl011_uart(); }}Copy the code

The code executes on a real raspberry PI

If you don’t have raspberry pie, you can do it at QEMU. But if you boot the kernel on real hardware, you need to be careful to avoid some potholes. Let me share my process:

  1. Before testing the kernel, install raspberry PI’s official Respbian OS operating system. Doing so fully tests the functional integrity of the PI hardware and pits the kernel for subsequent booting of rust implementations.
  2. If the OS cannot be started, check whether the green light blinks and whether EEPROM is damaged.
  3. If you encounter garbled output characters, make sure that the baud rate set by the kernel is consistent with that set by your serial debugging tool.
  4. Ensure that the USB2TTL RX/TX connection is correct. The USB2TTL driver is correctly installed.
  5. Burn tool recommended: Balenaetcher. The official recommendation doesn’t work very well. If the display burning fails, click Skip to skip the verification.
  6. For serial output, the tool implemented by Rust is recommended: github.com/umaYnit/rus… The official tutorial comes with this Ruby implementation because it’s a bit of a pothole under MAC M1.

summary

The first five chapters, with a very short and elegant Rust kernel program, show us how to run Rust code on a raspberry PI bare machine. It has laid a preliminary foundation for the realization of the following operating system.

When implementing the actual raspberry PI hardware, do not give up easily when encountering difficulties, mostly because of lack of knowledge and experience. This is the opportunity to learn.

reference

  1. www.raspberrypi.com.tw/tutorial/fa…
  2. zhuanlan.zhihu.com/p/136806005
  3. Github.com/bztsrc/rasp…
  4. Datasheets.raspberrypi.org/bcm2711/bcm…
  5. E – mailky. Making. IO / 2016-12-06 -…