Git Product home page Git Product logo

rust-raspberrypi-os-tutorials's Introduction

Operating System development tutorials in Rust on the Raspberry Pi


ℹ️ Introduction

This is a tutorial series for hobby OS developers who are new to ARM's 64 bit ARMv8-A architecture. The tutorials will give a guided, step-by-step tour of how to write a monolithic Operating System kernel for an embedded system from scratch. They cover implementation of common Operating Systems tasks, like writing to the serial console, setting up virtual memory and handling HW exceptions. All while leveraging Rust's unique features to provide for safety and speed.

Have fun!

Best regards,
Andre (@andre-richter)

P.S.: For other languages, please look out for alternative README files. For example, README.CN.md or README.ES.md. Many thanks to our translators 🙌.

📑 Organization

  • Each tutorial contains a stand-alone, bootable kernel binary.
  • Each new tutorial extends the previous one.
  • Each tutorial README will have a short tl;dr section giving a brief overview of the additions, and show the source code diff to the previous tutorial, so that you can conveniently inspect the changes/additions.
    • Some tutorials have a full-fledged, detailed text in addition to the tl;dr section. The long-term plan is that all tutorials get a full text, but for now this is exclusive to tutorials where I think that tl;dr and diff are not enough to get the idea.
  • The code written in these tutorials supports and runs on the Raspberry Pi 3 and the Raspberry Pi 4.
    • Tutorials 1 till 5 are groundwork code which only makes sense to run in QEMU.
    • Starting with tutorial 5, you can load and run the kernel on the real Raspberrys and observe output over UART.
  • Although the Raspberry Pi 3 and 4 are the main target boards, the code is written in a modular fashion which allows for easy porting to other CPU architectures and/or boards.
    • I would really love if someone takes a shot at a RISC-V implementation!
  • For editing, I recommend Visual Studio Code with Rust Analyzer.
  • In addition to the tutorial text, also check out the make doc command in each tutorial. It lets you browse the extensively documented code in a convenient way.

Output of make doc

make doc

🛠 System Requirements

The tutorials are primarily targeted at Linux-based distributions. Most stuff will also work on macOS, but this is only experimental.

🚀 The tl;dr Version

  1. Install Docker Engine.

  2. (Linux only) Ensure your user account is in the docker group.

  3. Prepare the Rust toolchain. Most of it will be handled on first use through the rust-toolchain.toml file. What's left for us to do is:

    1. If you already have a version of Rust installed:

      cargo install cargo-binutils rustfilt
    2. If you need to install Rust from scratch:

      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
      
      source $HOME/.cargo/env
      cargo install cargo-binutils rustfilt
  4. In case you use Visual Studio Code, I strongly recommend installing the Rust Analyzer extension.

  5. (macOS only) Install a few Ruby gems.

This was last tested by the author with Ruby version 3.0.2 on macOS Monterey. If you are using rbenv, the respective .ruby-version file is already in place. If you never heard of rbenv, try using this little guide.

Run this in the repository root folder:

bundle config set --local path '.vendor/bundle'
bundle config set --local without 'development'
bundle install

🧰 More Details: Eliminating Toolchain Hassle

This series tries to put a strong focus on user friendliness. Therefore, efforts were made to eliminate the biggest painpoint in embedded development as much as possible: Toolchain hassle.

Rust itself is already helping a lot in that regard, because it has built-in support for cross-compilation. All that we need for cross-compiling from an x86 host to the Raspberry Pi's AArch64 architecture will be automatically installed by rustup. However, besides the Rust compiler, we will use some more tools. Among others:

  • QEMU to emulate our kernel on the host system.
  • A self-made tool called Minipush to load a kernel onto the Raspberry Pi on-demand over UART.
  • OpenOCD and GDB for debugging on the target.

There is a lot that can go wrong while installing and/or compiling the correct version of each tool on your host machine. For example, your distribution might not provide the latest version that is needed. Or you are missing some hard-to-get dependencies for the compilation of one of these tools.

This is why we will make use of Docker whenever possible. We are providing an accompanying container that has all the needed tools or dependencies pre-installed, and it gets pulled in automagically once it is needed. If you want to know more about Docker and peek at the provided container, please refer to the repository's docker folder.

📟 USB Serial Output

Since the kernel developed in the tutorials runs on the real hardware, it is highly recommended to get a USB serial cable to get the full experience.

  • You can find USB-to-serial cables that should work right away at [1] [2], but many others will work too. Ideally, your cable is based on the CP2102 chip.
  • You connect it to GND and GPIO pins 14/15 as shown below.
  • Tutorial 5 is the first where you can use it. Check it out for instructions on how to prepare the SD card to boot your self-made kernel from it.
  • Starting with tutorial 6, booting kernels on your Raspberry is getting really comfortable. In this tutorial, a so-called chainloader is developed, which will be the last file you need to manually copy on the SD card for a while. It will enable you to load the tutorial kernels during boot on demand over UART.

UART wiring diagram

🙌 Acknowledgements

The original version of the tutorials started out as a fork of Zoltan Baldaszti's awesome tutorials on bare metal programming on RPi3 in C. Thanks for giving me a head start!

Translations of this repository

  • Chinese
  • Spanish
    • @zanezhub.
    • In the future there'll be tutorials translated to spanish.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

rust-raspberrypi-os-tutorials's People

Contributors

am009 avatar andre-richter avatar berkus avatar cfsamson avatar colachg avatar douglasurner avatar eldruin avatar feilongfl avatar hamishpoole avatar heaven0sky avatar ifantsai avatar jetjinser avatar jjyr avatar jzow avatar kolektiv avatar liby avatar light4 avatar maxdesiatov avatar naotaco avatar nmpluta avatar paulnice avatar peaknon avatar pfriesch avatar rahealy avatar readlnh avatar tjhu avatar tylerchr avatar wizofe avatar zanezhub avatar zicklag avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rust-raspberrypi-os-tutorials's Issues

Uart don't work on RPi4

I am not able to make the UART example (06) work on my Raspberry Pi 4.

I started from an up-to-date Raspbian SD Card, deleted everything from the boot partition except:

  • start4.elf
  • fixup4.dat
  • bcm2711-rpi-4-b.dtb (isn't this a Linux file? why is it needed?)

Then compiled example 06 with BSP=rpi4 make and copied kernel8.img to the SD. I also created the config.txt with content:

init_uart_clock=48000000

I know the UART cable is working since I enabled the bootloader UART (as per https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711_bootloader_config.md) and I'm getting the bootloader output.

However, I do not get any output from the Rust code, and I'm not sure how to debug. The last lines of the bootloader are:

Loading start4.elf hnd: 0x0002e101
Loading fixup4.dat hnd: 0x00033c3d
MEM GPU: 76 ARM: 948 TOTAL: 1024
FIXUP src: 128 256 dst: 948 1024
Starting start4.elf @ 0xfec00200

Chainload fails sometimes

I am trying 07_uart_chainloader.

I found a problem on minipush.rb .
It fails sometimes like below.

$ DEV_SERIAL=/dev/tty.usbserial-1420 BSP=rpi4 make chainboot

Minipush 1.0

[MP] ✅ Serial connected
[MP] 🔌 Please power the target now
 __  __ _      _ _                 _
|  \/  (_)_ _ (_) |   ___  __ _ __| |
| |\/| | | ' \| | |__/ _ \/ _` / _` |
|_|  |_|_|_||_|_|____\___/\__,_\__,_|

           Raspberry Pi 4

[ML] Requesting binary

[MP] ⚡ Connection or protocol Error: Remove power and USB serial. Reinsert serial first, then power

Unexpected Error: #<Errno::EINVAL: Invalid argument - tcsetattr>

I have a problem when trying 06_drivers_gpio_uart.
I got following error.

$ DEV_SERIAL=/dev/tty.usbserial-1420 make miniterm

Miniterm 1.0


[MT] ⚡ Unexpected Error: #<Errno::EINVAL: Invalid argument - tcsetattr>

[MT] Bye 👋

I think the cause of the error is baud.
The baud is depending on the platform according to the documentation.
I'm using Mac OS.

Integer from 50 to 256000, depending on platform)
https://rubydoc.info/gems/serialport/SerialPort:set_modem_params

The current value is 576000

@target_serial = SerialPort.new(@target_serial_name, 576_000, 8, 1, SerialPort::NONE)

Reproduce process

  1. Use Mac OS.
  2. Compile source.
  3. Write binary to SD card.
  4. Power on.
  5. Run miniterm.

UART not working when compiling in debug.

This is not a big problem, but I was wondering if anybody knows. The code from the repository is working without problems, but when I remove the --release flag compile and run it. Qemu loads the kernel but nothing is written to the terminal.

Is this a bug in the rust compiler?

Question about aarch64 memory access

I am running into an interesting issue where a slightly modified version of 05_uart0 example keeps hanging on my rpi3 hardware.

I modified the example and for clarify moved code to get the board serial number into its own function. The function takes a slice (&mut [u32]) to fill in the part of mbox buffer that represents request data and uses it to collect response output.

The issue I'm running into is around how my slice gets copied into the mbox buffer.

If I simply do a loop and iterate over slice values, it works fine.

    for i in 0..data.len() {
       mbox.buffer[5 + i] = data[i];
    }

However, if I use rust's copy_from_slice function, the program hangs

mbox.buffer[5..7].copy_from_slice(&data[0..2]);

This feels to me like some unaligned access issue but I'm not sure how to further debug it. Is there anything I'm doing wrong when trying to use copy_from_slice?

Thanks
-Seb

Here is the complete modified source code for main.rs.

#![no_std]
#![no_main]
#![feature(asm)]

const MMIO_BASE: u32 = 0x3F00_0000;

mod gpio;
mod mbox;
mod uart;

use core::sync::atomic::{compiler_fence, Ordering};

fn kernel_entry() -> ! {
    let mut mbox = mbox::Mbox::new();
    let uart = uart::Uart::new();

    // set up serial console
    match uart.init(&mut mbox) {
        Ok(_) => uart.puts("\n[0] UART is live!\n"),
        Err(_) => loop {
            unsafe { asm!("wfe" :::: "volatile") }; // If UART fails, abort early
        },
    }

    uart.puts("[1] Press a key to continue booting... ");
    uart.getc();
    uart.puts("Greetings fellow Rustacean!\n");

    let mut data = [0, 0];
    let serial_avail = get_serial(&mut mbox, &mut data[0..2]);
    if serial_avail {
        uart.puts("[i] My serial number is: 0x");
        uart.hex(data[1]);
        uart.hex(data[0]);
        uart.puts("\n");
    } else {
        uart.puts("[i] Unable to query serial!\n");
    }

    // echo everything back
    loop {
        uart.send(uart.getc());
    }
}

#[inline(never)]
fn get_serial(mbox: &mut mbox::Mbox, data: &mut [u32]) -> bool {
    // get the board's unique serial number with a mailbox call
    mbox.buffer[0] = 8 * 4; // length of the message
    mbox.buffer[1] = mbox::REQUEST; // this is a request message
    mbox.buffer[2] = mbox::tag::GETSERIAL; // get serial number command
    mbox.buffer[3] = 8; // buffer size
    mbox.buffer[4] = 8;
    mbox.buffer[5..7].copy_from_slice(&data[0..2]);
    //for i in 0..data.len() {
      //  mbox.buffer[5 + i] = data[i];
    //}
    //mbox.buffer[5] = data[0]; // clear output buffer
    //mbox.buffer[6] = data[1];
    mbox.buffer[7] = mbox::tag::LAST;

    // Insert a compiler fence that ensures that all stores to the
    // mbox buffer are finished before the GPU is signaled (which is
    // done by a store operation as well).
    compiler_fence(Ordering::Release);

    // send the message to the GPU and receive answer
    match mbox.call(mbox::channel::PROP) {
        Err(_) => false,
        Ok(()) => {
            //data[0..2].copy_from_slice(&mbox.buffer[5..7]);
            for i in 0..data.len() {
                data[i] = mbox.buffer[5 + i];
            }
            //data[0] = mbox.buffer[5];
            //data[1] = mbox.buffer[6];
            true
        },
    }
}
raspi3_boot::entry!(kernel_entry);

Cannot do final step in [Prerequisites]

Cannot do final step in [Prerequisites]:
cmd: rustup component add clippy-preview --toolchain=nightly
error: component 'clippy' for target 'x86_64-unknown-linux-gnu' is unavailable for download for channel 'nightly'

Reading 64 bit MMIO

I think there's an error in reading the 64 bit value in delays::get_system_timer. I would expect to read hi, read lo, read hi again and if different (because lo has wrapped since our first read) to read lo again (which can't now wrap because it is near zero).

In your code, if lo wraps after the if but before the read of lo, you get a bogus result.

UART flushing may use hardware implemented flag

Hi, thanks for this amazing tutorials.
For now I'm following the 06_drivers_gpio_uart. Reading the bcm2xxx_pl011_uart driver, I looked at which registers/field were not used in this tutorial, and I found a BUSY flag in the FR register. According to the BCM2711 ARM Peripherals:

UART busy. If this bit is set to 1, the UART is busy
transmitting data. This bit remains set until the complete
byte, including all the stop bits, has been sent from the shift
register.
This bit is set as soon as the transmit FIFO becomes non-
empty, regardless of whether the UART is enabled or not.

I don't know much about how UART works, so I may be mistaken, but I think that is the hardware equivalent of the calculations made in PL011UartInner::flush. So instead of waiting that the TX FIFO is empty and then waiting for some time, we could just wait for the BUSY flag to be 0.

If that is true, this allow us to make this code independent of baud rate, the number of bits to be sent and the CPU frequency; and make us sure that the UART is flushed properly.

[07_uart_chainloader] Make chainboot only works if I power my RPi3 after status `[MP] ✅ Connected`

Hi,

I encountered quite a weird issue in 07_uart_chainloader. After starting make chainboot, I never got the desired output

[MP] ✅ Connected
 __  __ _      _ _                 _ 
|  \/  (_)_ _ (_) |   ___  __ _ __| |
| |\/| | | ' \| | |__/ _ \/ _` / _` |
|_|  |_|_|_||_|_|____\___/\__,_\__,_|

           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] Booting on: Raspberry Pi 3
[1] Drivers loaded:
      1. BCM GPIO
      2. BCM PL011 UART
[2] Chars written: 93
[3] Echoing input now

Instead, I frequently got one of the following outputs:

[MP] ✅ Connected

[MP] ⚡ Protocol Error: Remove and insert the USB serial again
[MP] ✅ Connected
?? $Error ? ?$Error ?
[MP] ⚡ Protocol Error: Remove and insert the USB serial again
[MP] ✅ Connected
ting the payload now

[ML] Loaded! Executing the payload now

[ML] Loaded! Executing the payload now

[ML] Loaded! Executing the payload now

<repeats until I stop>

[MP] Bye 👋
[MP] ✅ Connected
?????????????????????????????????????????????????????????????????????????????????????
?????????????????????????????????????????????????????????????????????????????????????
?????????????????????????????????????????????????????????????????????????????????????

<repeats until I stop>

[MP] Bye 👋

After several hours I finally figured out how to work around the issue:

  • If I power my RPi3 like described in the wiring diagram, I always get the issues I described above
  • If I power my RPi3 via micro-usb, I mostly get the issues described above, but sometimes it works.

It always works if I do it like this:

  1. make chainboot
  2. Plug in my USB serial cable without using the red +5V wire (I linked the cable I use, in case this is important)
  3. Wait until [MP] ✅ Connected gets displayed
  4. Use micro-usb to power the RPi3.

I don't really understand the issue in detail, but to me it seems like it's somehow connected to timings/delays. The only place in the code I could find that directly influences said timings/delays is const DELAY: usize = 2000 in src/bsp/device_driver/bcm/bcm2xxx_gpio.rs. I tried some values here and if I set it to const DELAY: usize = 20000, it also works sometimes (but at this point I have no clue if the change really reduces my error rate, or if it just happened by chance and led me to wrong conclusions).

Do you have any idea what really causes the error? If so, I would appreciate if you could provide a little write-up what is happening on the lower levels and what causes the error I got. Since chasing this down took more than half of my day but I still didn't really understand it, I'm quite curious now 😄

Suggested Paragraph on Docker

I'm uncertain if a github pull request or an issue (or a mailing list?) is appropriate for this so I'm just putting it here. Please let me know if it needs to go somewhere else.

Based on my own experience working through the tutorials I think a small paragraph about docker might be desirable. Here's a possible suggestion:

Docker

To facilitate downloading, building and running the required development tools some of the tutorials in this repository use a piece of software called docker. While docker specifics are outside of this tutorial's scope it is worth mentioning that human readable docker configuration files named "Dockerfile" exist in various repository sub-directories. These files may be of interest to those who want details about what will happen when docker is invoked.

"arm_64bit=1" needed in config.txt?

First of all thanks for this tutorial, it's a great start for programming a Raspberry Pi with embedded Rust!

Now to my problem: I am working on Windows 10, and I managed to build the 06_drivers_gpio_uart example kernel without the supplied Makefile. I verified that the kernel8.img runs in QEMU, but then I had problems to boot it on a real Raspberry Pi 3 B+.

The instructions in the 06_drivers_gpio_uart/README.md say this:

  1. On the card, generate a file named config.txt with the following contents:
    init_uart_clock=48000000

But with such a config.txt file the kernel doesn't boot for me.

After looking at some other bare metal kernel examples for the Raspberry Pi I found the arm_64bit option, and with that the kernel boots on my Raspberry Pi 3 B+. This is the config.txt file that works for me:

arm_64bit=1
init_uart_clock=48000000
kernel=kernel8.img

Is this an omission in the documentation of the 06_drivers_gpio_uart example, or might I have made some mistake that caused the proposed config.txt to fail in my case?

MiniLoad fails on latest PI 4 8G

I have thoroughly enjoyed your tutorials, but I couldn't get the MiniLoad to work over the UART correctly. I spent a while debugging (and learned a ton in the process), and tracked the issue down to this section:

unsafe {
    // Read the kernel byte by byte.
    for i in 0..size {
        *kernel_addr.offset(i as isize) = console().read_char() as u8;
        // console().read_char() as u8;
    }
}

(from main.rs)

If I comment out the memory assignment and just throw away each byte as I read it (the commented line) then all is well, except that obviously it doesn't actually boot the new kernel. If I leave the code as shown however, the PI 4 will hang (it will never complete this loop) and I get no information back on why. I am assuming that it is some kind of memory protection issue and perhaps it is new to the latest PI 4 version? Anyway, I don't know how to get around this and I'm hoping some brighter spark out there might. If it matters, I am cross-compiling from a Mac running Mac OS X 10.15.7 and using rustc 1.49.0-nightly (25c8c53dd 2020-10-03)

While following the tutorial I get "can't find crate for `test`" while compiling for unit test execution

Hi there,

I'm using a custom build target for my Raspberry Pi bare metal kernel. After setting everything up and compiling the binary kernel for unit testing I get the compiler error "can't find crate for test".

Any clue what might be the root cause for this?

I'm not using cargo xbuild, but the new cross compile configuration in /.cargo/config like so:

[unstable]
build-std = ["core", "compiler_builtins", "alloc"]

Any help would be much appreciated.

Thanks in advance.

how about make qemu can also do chainboot test

just use the virtual serial port like tty in linux, i make a simple python scirpt to do that. is't work, but I don’t know if there will be other problems

#! /usr/bin/env python3
#coding=utf-8

import pty
import os
import select

def mkpty():
    # open tty
    master1, slave = pty.openpty()
    slaveName1 = os.ttyname(slave)
    master2, slave = pty.openpty()
    slaveName2 = os.ttyname(slave)
    print ('\nslave device names: ', slaveName1, slaveName2)
    return master1, master2

if __name__ == "__main__":

    master1, master2 = mkpty()
    while True:
        rl, wl, el = select.select([master1,master2], [], [], 1)
        for master in rl:
            data = os.read(master, 128)
            print ("read %d data." % len(data))
            if master==master1:
                os.write(master2, data)
            else:
                os.write(master1, data)

The tutorials begs for this question : Could the information be used to make a Redox kernel?

I know that the Redox OS(1) only supports the x86-64 architecture(2), but using a Redox kernel on a Raspberry Pi Zero (w/wo WiFi) could be really interesting.

I know that a Raspberry Pi Zero has a ARM1176JZF-S (32-bit) ARMv6 CPU, but it has 512 MB of memory, and that is a LOT of memory in the embedded world, and when there is the price tag (US$ 10 in the US) for a RPI Zero ...

ad (1)
Redox OS

ad (2)
only supports the x86-64 architecture

Question about interrupts

I may be jumping the gun a little bit with this question....

Is there any special initialization code (raspi3_boot/etc) needed to enable interrupt handling outside of setting a particular IRQ enable bit in one of the 3 interrupt enable registers ( 0x7E00_B210, 214, 218)?

I'm trying to setup a DMA transfer that generates an IRQ when it finishes (by providing DMA Channel 0 with a control block that has INTEN bit set and enabling Irq 16 in IRQ Enable_1 register) but the handler I provide in the current_elx_irq vector never gets called.

I seem to be out of ideas. Any thoughts?

Thanks in advance.

GPIO driver implementation does not use conform to BCM2711 (RPi4)

According to https://github.com/RPi-Distro/raspi-gpio/blob/master/raspi-gpio.c#L194 and to BC2711 ARM Peripherals, there is no more a GPPUD0 register. Instead GPPUPPDN0 is the register to use. Additionally, the procedure for pull-up/pull-down activation does not use the GPPUDCLK0 for BCM2711.

    if (is_2711)
    {
        int pullreg = GPPUPPDN0 + (gpio>>4);
        int pullshift = (gpio & 0xf) << 1;
        unsigned int pullbits;
        unsigned int pull;

        switch (type)
        {
        case PULL_NONE:
            pull = 0;
            break;
        case PULL_UP:
            pull = 1;
            break;
        case PULL_DOWN:
            pull = 2;
            break;
        default:
            return 1; /* An illegal value */
        }

        pullbits = *(gpio_base + pullreg);
        pullbits &= ~(3 << pullshift);
        pullbits |= (pull << pullshift);
        *(gpio_base + pullreg) = pullbits;
    }
    else
    {
        int clkreg = GPPUDCLK0 + (gpio>>5);
        int clkbit = 1 << (gpio & 0x1f);

        *(gpio_base + GPPUD) = type;
        delay_us(10);
        *(gpio_base + clkreg) = clkbit;
        delay_us(10);
        *(gpio_base + GPPUD) = 0;
        delay_us(10);
        *(gpio_base + clkreg) = 0;
        delay_us(10);
    }

Could this be behind some of the glitches we randomly see when using the chainloader?

I have made changes as per the below diffs and tested several times on Pi 4 with no issues when plugging/unplugging the USB serial cable. I am not sure though if I should enable pup/pdn for the TX or RX when using a serial to USB cable. Maybe you could enlighten me there.

diff --git a/07_uart_chainloader/src/_arch/aarch64/cpu.rs b/07_uart_chainloader/src/_arch/aarch64/cpu.rs
index 34cf2ab..b1518d5 100644
--- a/07_uart_chainloader/src/_arch/aarch64/cpu.rs
+++ b/07_uart_chainloader/src/_arch/aarch64/cpu.rs
@@ -39,14 +39,6 @@ pub unsafe fn _start() -> ! {
 
 pub use asm::nop;
 
-/// Spin for `n` cycles.
-#[inline(always)]
-pub fn spin_for_cycles(n: usize) {
-    for _ in 0..n {
-        asm::nop();
-    }
-}
-
 /// Pause execution on the core.
 #[inline(always)]
 pub fn wait_forever() -> ! {
diff --git a/07_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs b/07_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs
index 0b01db4..85ee230 100644
--- a/07_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs
+++ b/07_uart_chainloader/src/bsp/device_driver/bcm/bcm2xxx_gpio.rs
@@ -5,7 +5,7 @@
 //! GPIO Driver.
 
 use crate::{
-    bsp::device_driver::common::MMIODerefWrapper, cpu, driver, synchronization,
+    bsp::device_driver::common::MMIODerefWrapper, driver, synchronization,
     synchronization::NullLock,
 };
 use register::{mmio::*, register_bitfields, register_structs};
@@ -39,18 +39,22 @@ register_bitfields! {
         ]
     ],
 
-    /// GPIO Pull-up/down Clock Register 0
-    GPPUDCLK0 [
-        /// Pin 15
-        PUDCLK15 OFFSET(15) NUMBITS(1) [
-            NoEffect = 0,
-            AssertClock = 1
+    /// GPIO Pull-up/down Register 0
+    GPPUPPDN0 [
+        /// GPIO 15
+        PUPPDN15 OFFSET(30) NUMBITS(2) [
+            NoResistor = 0,
+            PullUp = 1,
+            PullDown = 2,
+            Reserved = 3
         ],
 
-        /// Pin 14
-        PUDCLK14 OFFSET(14) NUMBITS(1) [
-            NoEffect = 0,
-            AssertClock = 1
+        /// GPIO 14
+        PUPPDN14 OFFSET(28) NUMBITS(2) [
+            NoResistor = 0,
+            PullUp = 1,
+            PullDown = 2,
+            Reserved = 3
         ]
     ]
 }
@@ -65,9 +69,7 @@ register_structs! {
         (0x10 => GPFSEL4: ReadWrite<u32>),
         (0x14 => GPFSEL5: ReadWrite<u32>),
         (0x18 => _reserved1),
-        (0x94 => GPPUD: ReadWrite<u32>),
-        (0x98 => GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>),
-        (0x9C => GPPUDCLK1: ReadWrite<u32>),
+        (0xe4 => GPPUPPDN0: ReadWrite<u32, GPPUPPDN0::Register>),
         (0xA0 => @END),
     }
 }
@@ -117,16 +119,10 @@ impl GPIOInner {
             .GPFSEL1
             .modify(GPFSEL1::FSEL14::AltFunc0 + GPFSEL1::FSEL15::AltFunc0);
 
-        // Enable pins 14 and 15.
-        self.registers.GPPUD.set(0);
-        cpu::spin_for_cycles(150);
-
+        // PUPDN to 0 for GPIO14 and GPIO15
         self.registers
-            .GPPUDCLK0
-            .write(GPPUDCLK0::PUDCLK14::AssertClock + GPPUDCLK0::PUDCLK15::AssertClock);
-        cpu::spin_for_cycles(150);
-
-        self.registers.GPPUDCLK0.set(0);
+            .GPPUPPDN0
+            .modify(GPPUPPDN0::PUPPDN14::NoResistor + GPPUPPDN0::PUPPDN15::NoResistor);
     }
 }

Failing building the kernel

Hey,

I'm having the following problem when I try to execute make qemu:

RUSTFLAGS="-C link-arg=-Tsrc/bsp/raspberrypi/link.ld -C target-cpu=cortex-a53 -D warnings -D missing_docs" cargo rustc --target=aarch64-unknown-none-softfloat --features bsp_rpi3 --release
    Finished release [optimized] target(s) in 0.07s
Failed to execute tool: objcopy
No such file or directory (os error 2)
Makefile:62: recipe for target 'kernel8.img' failed
make: *** [kernel8.img] Error 101

I have no clue of what is wrong, I tried looking it up but can't find a fix. The objcopy command also works fine in the terminal. I'm running the following configuration by the way:

Default host: x86_64-unknown-linux-gnu
rustup home:  /home/aurelien/.rustup

installed toolchains
--------------------

stable-x86_64-unknown-linux-gnu
nightly-2019-12-20-x86_64-unknown-linux-gnu
nightly-x86_64-unknown-linux-gnu (default)

installed targets for active toolchain
--------------------------------------

aarch64-unknown-linux-gnu
aarch64-unknown-none-softfloat
x86_64-unknown-linux-gnu

active toolchain
----------------

nightly-x86_64-unknown-linux-gnu (default)
rustc 1.45.0-nightly (4bd32c980 2020-05-29)

Do I need to do something with the docker files? Thanks in advance :)

How to get start?

My environment is Ubuntu 18.04.3 LTS with docker-ce 19.03.2. I follow the README.md and have install the suitable Rust toolchain.
When I run make qemu, get this output:

rm -rf target
RUSTFLAGS="-C link-arg=-Tsrc/bsp/rpi/link.ld -C target-cpu=cortex-a53 -D warnings -D missing_docs" cargo xrustc --target=aarch64-unknown-none-softfloat --features bsp_rpi3 --release
error: The sysroot can't be built for the Stable channel. Switch to nightly.
note: run with `RUST_BACKTRACE=1` for a backtrace
Makefile:56: recipe for target 'target/aarch64-unknown-none-softfloat/release/kernel' failed
make: *** [target/aarch64-unknown-none-softfloat/release/kernel] Error 1

I checked the Makefile and don't know what to do.

UART Baud Rate calculation

Let me first thank you for this fantastic tutorial. I'm having loads of fun going through it all and the code is just beautifully organized!

I have some problems on chapter 6 (which, by excluding everything else is probably due to a broken SD card) and I had to go through every line of code and double check everything.

This is nitpicking since the result is still within ~5 % error range of the tx/rx channel which should be acceptable anyway but since this is a tutorial it might still be worth bringing up:

in bcm20xx_pl011_uart.rs we have the PL011UartInner::init method which sets up UART.

The calculations for the baud rate divisor given a target baud rate of 230400 is as follows: (48_000_000/16)/230400 = 13,02083. The integer part is 13 is written to the IBRD bitfield. However, the fractional part is written to a the 6-bit field FBRD. Now, as I understand it, since the fractional field can only hold values from 0-63, the right thing to do is to multiply the decimal fractional by 64 (in contrast to 100): 0,0208*64 = 1,3312 which gives a nearest value of 1 instead of 2.

As you might understand I'm on the learning path here so I'm wary of making too bald claims about the correctness but I spent quite some time to check how you got the numbers you got and I leaned on this resource - 11.2.2. TM4C UART Details since I wondered what would be the right thing to do if the fractional was a value higher than 63.

I know this is very nit picky but it would be interesting to add at least the basic calculations in the comments for the code block as well since it's not easy to understand and it really does connect some dots that are hard to find otherwise.

Again, thanks for creating this great resource.

This is great!

Hi,

Not really an issue, I'd just like to say how much I like what you did with my little tutorials! Good job, well done! I've recommended your repo in the README in case someone is looking for Rust examples.

I was wondering if your 07_uart_chainloader example could be used with USBImager -S. That works with my 14_raspbootin64 example, so I think it should work with yours too out-of-the-box!

Cheers,
bzt

Suggestion: Instructions for people who already have a Rust toolchain

Hi there,

Thanks for this effort -- it's amazing!
I'd just like to make a quick suggestion. It might be nice to add instructions for people who already have Rust installed, like myself. I had a lot of trouble getting started before I finally realised I needed to run rustup component add llvm-tools-preview. I think this, plus instructions to switch to nightly, would make a nice addition to "The tl;dr Version" of System Requirements for other people like myself. (Related: #57)

Happy to make a PR if this sounds good.

[Tutorial-6] - Miniterm shows two questions marks after connecting RPI4 to power.

Hey 👋 and thanks again for this tutorials series.

Issue Description

I get these questions marks (see images below) after connecting my Raspberry Pi4 to power after completing this section of the tutorial. I've correctly setup the pins. I've loaded the correct files to the SDCard, and have even tried switching between start.elf vs start4.elf.

It seems that the Miniterm software does recognize that a device is connected, however it does not shown anything other than two questions marks once I connect the power. I have even cloned and used the tutorial that is in this repo just incase I made a typo or mistake anywhere.

Pin Connections

20210309_085447

Error

me$ DEV_SERIAL=/dev/tty.usbserial-14220 make miniterm
Miniterm 1.0

me/kernel/.vendor/bundle/ruby/2.7.0/gems/serialport-1.3.1/lib/serialport.rb:25: warning: rb_secure will be removed in Ruby 3.0
[MT] ✅ Serial connected
��

Screenshot of issue

image

Arduino emulator on bare metal PI.

I found this project in Google and am asserting that here is a good a place as any for this post.

I had an interesting discussion on https://youtu.be/cWuFLEb-pUM
The idea there was that since an Arduino does less it has some advantage over the less expensive PI zero W($20 cheeper). My point was that it would be entirely possible to have a PI do less. This repo very effectively demonstrates this, I think.

The challenge is to write something that looks like an Aurduino, but is a PI running a not so fancy OS.

Raspberi PI 2 B+

Would that make sens to make this tutorial portable to older model like the raspi2 B+ ?

__bss_start and __bss_end are empty

Hello!
In the step 05_safe_globals, I've tried to add the following code in the runtime_init/bss_range function:

26 |       panic!("Bss start:{:?}, end:{:?}", __bss_start, __bss_end);

This is the resulting output:

Kernel panic: Bss start:26, end:0

I've tried the same in step 09_timestamps:

Kernel panic: Bss start:0, end:0

Additional background:

  • I've run all of them with make qemu
  • I've rustc 1.43.1 installed and running Linux Mint. I've added a rust-toolchain with "nightly" in those directories, because I'm too lazy to switch rustup default.

Any clues on what's going on or how to fix this? Thanks!

Improve JTAG tutorial

  • To the JTAG fritzing wiring image, add a red dot indicating the LED on the Olimex HW for easier orientation
  • OpenOCD spews lots of errors while debugging. Check if they are harmless or can be fixed.
  • CPSR in GDB seems to show wrong EL. Maybe other fields as well?
  • Build GDB from source in the Docker container to be more bleeding edge regarding Rust support?
  • The real-life wiring image has been flipped to horizontal from originally being vertical, in order to save space. It looks distored now. Take a new "native" horizontal version?
  • Add additional resources section to end of README with links for using/learning GDB.
  • Check if it works with JLink @berkus

Tutorial still lives in branch JTAG

CC @naotaco if you like to help.

How can chainloader jump to the new location?

Hello,
In part 7: https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials/tree/master/07_uart_chainloader
I cannot understand how the kernel can be correctly loaded, even if you set this in the linker file:

-    /* Set current address to the value from which the RPi starts execution */
-    . = 0x80000;
+    /* Set the link address to 32 MiB */
+    . = 0x2000000;

(Correct me if I'm mistaken) when you set .=0x2000000 the kernel will be loaded at that position in memory.
Then as soon as GPU initialization is done, the cpu will start running instruction at location 80_000. But how can this works if the kernel is in position 32Mib?
I know we could overwrite the kernel start location by putting kernel_address=0x4000000 inside the config.txt, but since this wasn't requested in the tutorial, I didn't do it but still it's working and also nicely verifiable via qemuasm.
How is this possible? I think it has something to do with .got section you added in the linker, can I have some more insight on that?
Thanks!

[Announce] tock-based registers

Adding some people to whom it might be of interest. You are kindly invited to review, or just silently ignore ;)

@alevy @bradjc @niklasad1 @brghena @phil-opp @ryankurte @ppannuto

Hi all,

In tock/tock#930, I started my idea of having a common register interface for both CPU and MMIO registers based on the tock register interface. Thank you very much to the tock folks for carving out a crate for it!
Today, I finally have a first peek ready for one of my RaspberryPI 3 subprojects.
Check it out at https://github.com/andre-richter/rust-raspi3-tutorial/tree/registers/09_delays

There are some distributed components involved.

In the end, you can see, for example in https://github.com/andre-richter/rust-raspi3-tutorial/blob/registers/09_delays/src/delays.rs, that MMIO and CPU accesses are side by side, using the same tock-regs API. Very very neat!

Gemfile Error while running bundle install on cloned repo

Hello! Thanks again for this tutorial, been learning quite a lot.

I cloned the repository and checked into one of the lessons (lesson 6) where we connect to the raspberrypi 4.

I ran the following command as instructed in the README.md

sudo gem install bundler
bundle config set path '.vendor/bundle'
bundle install

I continuously get the following error. I have attempted to install ruby dev tools etc, to no avail.

me@me /me.../rust-raspberrypi-OS-tutorials/06_drivers_gpio_uart > 
bundle install


Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Using ast 2.4.2
Using bundler 2.2.11
Using colorize 0.8.1
Using parallel 1.20.1
Using rainbow 3.0.0
Using regexp_parser 2.0.3
Using rexml 3.2.4
Using ruby-progressbar 1.11.0
Using unicode-display_width 2.0.0
Using parser 3.0.0.0
Using rubocop-ast 1.4.1
Fetching serialport 1.3.1
Using rubocop 1.10.0
Installing serialport 1.3.1 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

current directory:
/me.../rust-raspberrypi-OS-tutorials/.vendor/bundle/ruby/2.6.0/gems/serialport-1.3.1/ext/native
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby -I /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0 -r
./siteconf20210222-79147-1k74z03.rb extconf.rb
checking for OS... darwin
checking for termios.h... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
        --with-opt-include
        --without-opt-include=${opt-dir}/include
        --with-opt-lib
        --without-opt-lib=${opt-dir}/lib
        --with-make-prog
        --without-make-prog
        --srcdir=.
        --curdir
        --ruby=/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/$(RUBY_BASE_NAME)
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:467:in `try_do': The compiler failed to generate an executable file.
(RuntimeError)
You have to install development tools first.
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:585:in `block in try_compile'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:534:in `with_werror'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:585:in `try_compile'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:1109:in `block in have_header'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:959:in `block in checking_for'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:361:in `block (2 levels) in postpone'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:331:in `open'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:361:in `block in postpone'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:331:in `open'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:357:in `postpone'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:958:in `checking_for'
        from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:1108:in `have_header'
        from extconf.rb:10:in `<main>'

To see why this extension failed to compile, please check the mkmf.log which can be found here:

/me.../rust-raspberrypi-OS-tutorials/.vendor/bundle/ruby/2.6.0/extensions/universal-darwin-19/2.6.0/serialport-1.3.1/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in
/me.../rust-raspberrypi-OS-tutorials/.vendor/bundle/ruby/2.6.0/gems/serialport-1.3.1 for
inspection.
Results logged to
/me.../rust-raspberrypi-OS-tutorials/.vendor/bundle/ruby/2.6.0/extensions/universal-darwin-19/2.6.0/serialport-1.3.1/gem_make.out

An error occurred while installing serialport (1.3.1), and Bundler cannot continue.
Make sure that `gem install serialport -v '1.3.1' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  serialport
me.../rust-raspberrypi-OS-tutorials/06_drivers_gpio_uart > 

Here is the output of the mkmf.log

"xcrun clang -o conftest -I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/universal-darwin19 -I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby/backward -I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0 -I. -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT    -g -Os -pipe -DHAVE_GCC_ATOMIC_BUILTINS -DOS_DARWIN conftest.c  -L. -L/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib -L. -L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.Internal.sdk/usr/local/lib   -arch x86_64   -lruby.2.6   "
In file included from conftest.c:1:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby.h:33:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby/ruby.h:24:10: fatal error: 'ruby/config.h' file not found
#include "ruby/config.h"
         ^~~~~~~~~~~~~~~
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby/ruby.h:24:10: note: did not find header 'config.h' in framework 'ruby' (loaded from '/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/System/Library/Frameworks')
1 error generated.
checked program was:
/* begin */
1: #include "ruby.h"
2: 
3: int main(int argc, char **argv)
4: {
5:   return 0;
6: }
/* end */

Not sure if you know what to do with such an error

OS: MacOS 10.15.7
Raspberry PI: 4

Rust ToolChain

stable-x86_64-apple-darwin
nightly-2021-01-08-x86_64-apple-darwin (override)
nightly-x86_64-apple-darwin (default)

remove deprecated --path flag from Readme

When following the install scripts in the main README, i get the following warning:

bundle install --path .vendor/bundle

[DEPRECATED] The --path flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use bundle config set path '.vendor/bundle', and stop using this flag

Is the change in 04_zero_overhead_abstraction valid?

First of all, thank you for organizing this material. This is of great help for me to learn Rust.

I have a question about the change in "04_zero_overhead_abstraction" where assembly code is replaced with Rust.
In _start function, there are at least two function calls (cpu::smp::core_id and SP.set) before the stack pointer is initialized. For this code to work, the Rust compiler has to inline these function calls.
The inline attribute, however, is just a hint and, in my understanding, the Rust compiler can ignore the hint.
https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute

I feel like anything before the stack pointer initialization needs to be written in assembly to avoid function calls.
Curious to hear your thoughts.

Examples 6 and 7 not working in RPI4B

Hey. Firs of all, thanks for this great tutorial. Is really fun & interesting!!

As for the problems I'm experiencing:

Example 6

  • Ran BSP=rpi4 make and copied kernel8.img together with fixup4.dat, start4.elf (renamed to start.elf as per README comment) and bcm2711-rpi-4-b.dtb and config.txt with the specified contents to the SD.
  • Ran sudo screen /dev/ttyUSB0 230400. Black screen appears and then after pressing Enter nothing happens.

I tried with start4.elf and doesn't work. Also tried to download the latest firmware and same outcome..

Example 7

  • Ran BSP=rpi4 make and copied the kernel8.img to the SD.
  • Ran BSP=rpi4 make chainboot and this was the outcome:
BSP=rpi4 make chainboot
Minipush 1.0


[MP] ✅ Connected


[MP] ⚡ Protocol Error: Remove and insert the USB serial again
[MP] ⏳ Waiting for /dev/ttyUSB0
[MP] ✅ Connected

[MP] ⚡ Protocol Error: Remove and insert the USB serial again
[MP] ⏳ Waiting for /dev/ttyUSB0
[MP] ✅ Connected

[MP] ⚡ Protocol Error: Remove and insert the USB serial again
[MP] ⏳ Waiting for /dev/ttyUSB0
[MP] ✅ Connected

[MP] ⚡ Protocol Error: Remove and insert the USB serial again
^C
[MP] Bye 👋

The error is very similar to what I've seen in #79 . I checked whether I had everything correct with the toolchains and targets and the rest of the config. But since I've seen new commits have been done since this issue was close, maybe one of them broke this.

If you need any kind of extra info, just ping me. Sorry that I cannot provide more, but I don't really know how to debug it.

minipush.rb default timout of 7 seconds is too short when externally powering an RPi 4B

When powering a RPi 4B externally (+5v power pin is disconnected) and running 'BSP=rpi4 make chainboot', the minipush timeout of 7 seconds occurs before the RPi has booted the kernel8.img and output what minipush is expecting. This causes minipush to display the generic "Protocol Error" message.

Increasing the timeout to 30 seconds allows the user to perform the following steps without the timeout occurring.

  1. Assume that USB to serial device is already "working"
  2. Run 'BSP=rpi4 make chainboot'
  3. Manually power up the RPi 4B (1 - 5 seconds for user to toggle power)
  4. Allow the RPi 4B time to boot the kernel8.img (12 seconds)
  5. Allow the transfer of the image to the RPi

Please increase the default timeout to more than 7 seconds. Please note that pushing larger images appears to take more time during which the timeout can occur.

Can't get past `sudo screen /dev/ttyUSB0 230400` with RaspPi 3 A+

Ive had not problems with any of the first 5 tutorials but on Tutorial 6 I can't get the screen part to work.

I get Cannot exec '/dev/ttyUSB0': No such file or directory

I have tried ls /dev/ttyUSB*
and it doesn't return any results.

Can it have anything to do with using a USB->Thunderbolt adapter?

Any ideas?

Unequal delays between CPU and BCM timers

I think there are two timing-related logic bugs in the implementation of delays.rs in 09_delays. Each is a source of divergent behavior between the CPU-based timers and the BCM ones, such that delays::SysTmr::wait_msec_st(n) and delays::wait_msec(n) wait for different durations.

Bug 1: Bad Comparison

The first is in SysTmr::wait_msec_st, which is documented to "Wait N microsec" using the Pi's BCM system timer (empirically this seems to increment monotonically at a rate of 1Mhz).

loop {
    if self.get_system_timer() < (t + n) {
        break;
    }
}

I think this needs to be > (t + n). Since the timer increases monotonically, don't we want to break only when the system timer reaches t + n? As written, this code will break in the very first iteration for any meaningful value of n because the system timer will be < (t + n) for the entire duration of the intended wait.

UPDATE: Looking at the original C source, I can see how this came to be. A more direct port of the [correct] original could be this:

while self.get_system_timer() < (t + n) {
    asm::nop();
}

Bug 2: Bad Unit Conversion

The second bug is an incorrect unit conversion in the delays::wait_msec function. It's also documented to "Wait N microsec" but uses the ARM CPU timer. The conversion occurs in this line:

// Calculate number of ticks
let tval = (frq as u32 / 1000) * n;

Dividing by 1000 gives the number of ticks per millisecond but as n is in microseconds, tval winds up being "milliticks" rather than just ticks.

A more correct implementation would divide by 1,000,000:

// Calculate number of ticks
let tval = ((frq as u64) * (n as u64) / 1_000_000) as u32;

Funnily, this doesn't always result in 1000X longer waits. On my Pi hardware, where frq is reported as 0x0124F800 = 19,200,000 Hz, calling delays::wait_msec(1_000_000) causes tval to overflow the 32-bit register and waits 2_020_130_816 ticks, or only 1m45s.

(Also, this version does the math in u64 for accuracy; the simpler (frq as u32 / 1000000) * n implementation results delays::wait_msec(1_000_000) ending nearly 11 milliseconds early due to integer truncation of 19.2 to 19.)

Using a RPi4 as the host

Hi,
Have you ever used a RPi4 as a host for a RiP3 as the target. In this case for me the makefile does not build the kernel. It just seems to silently fail.

Everything works fine for me if I use an X64 machine as the host.

Thanks

Regards
Tim.

_boot_cores.rs and multiple cores

Hi,

I'm quite new in embedded development and I had to figure out how to start all of the cores.

 const CORE_1_START: *mut u32 = (0xe0) as *mut u32;
 const CORE_2_START: *mut u32 = (0xe8) as *mut u32;
 const CORE_3_START: *mut u32 = (0xf0) as *mut u32;

 write_volatile(CORE_1_START, 0x80000);
 write_volatile(CORE_2_START, 0x80000);
 write_volatile(CORE_3_START, 0x80000);

Hope it helps someone, it would be nice if I could read somewhere.
(I spent hours because I read an outdated documentation)

Cheers,
Attila

Rewrite implementation ideas

First of all these tutorials are excellent. They are an excellent resource overall for Rust embedded work so thank you for that!

I have been working through some of these examples and I wanted to provide my 2 cents for implementation details for v2.

There are two main details that I found confusing or inefficient that I thought would be good to at least have documented somewhere so people understand trade offs.

  1. Mailbox API - The documentation for this interface is absolutely awful (not at all your fault). One of the things I would suggest would be abstracting raw Mailbox calls into separate functions (ex. https://github.com/r0ck3tAKATrashPanda/raspi-os/blob/master/src/bsp/rpi3/mbox.rs#L623) This will significantly reduce the chance of botching one of these mailbox calls which are a nightmare to debug. Since this is a RPi3 OS it is less of an issue, but it may even be worth pushing the mailbox creation, usage, and destruction into this function if you ever wanted to add a BSP that didn't support something similar (Again that is probably just something that people may find as food for thought in the tutorials). The cons of this method is that we bloat code size fairly significantly. We can use LTO in order to throw out unused functions at link time, which should help remove that, but it is just a trade-off to mention.

I was curious on the design choice of using modules and set values instead of enums that held all the variants for things such as Tags within the mailbox api. (ex. https://github.com/r0ck3tAKATrashPanda/raspi-os/blob/master/src/bsp/rpi3/mbox.rs#L77)

Additionally, not that it affects anything, but mbox.buffer[4] (5th byte of the buffer) doesn't need to be filled out, apparently that is used by the VideoCore to send back the size of the response buffer. It should be parsed in order to determine if the size changed when the mail was responded too. Again this isn't an issue by any means, just hoping to make more information available to people about the mailbox API.

  1. println! macro and console() - Currently in v2 you are using the magical QEMU structure to print. In the current implementation we have a console() function that is building the magical structure each time it is called, which is every time print! or println! is called. Obviously this isn't an issue in QEMU and it is an empty struct, but this design has major performance implications when you move to hardware and start to use UART, the amount of overhead for re-initializing and the risk of having multiple consumers attempting to access the same peripheral is quite high. Instead this should be moved to a single static ref (maybe even for the QEMU struct for good practice early on in the process of learning)

If we implement UART something like

lazy_static! {
    pub static ref CONSOLE: Mutex<Uart> = {
        let mut uart = Uart::new();
        let mut mbox = mbox::Mbox::new();
        {
            uart.init(&mut mbox, 4_000_000);
        }

        Mutex::new(uart)
    };
}

We A) don't initialize UART until we need it B) We have a mutexed (spin) UART access so we can println! from anywhere without worry (sorta).

Again, these tutorials are excellent and I am not trying to nit pick, but these are some pain points I ran into, that would be great if we could get covered in one way or another. It would be great to hear your thoughts and let me know if there is anything I can do to contribute!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.