Bootloader restore


#1

I’m working with hifive1-revB using rust and have just learned about the bootloader recovery “pre-main” piece of code that is inserted as part of metal/freedom-sdk programs.

Rust doesn’t make use of this by default and override memory at 0x20000000 meaning I lost this “bootloader”. My board is fine and I can flash in rust programs but because of how the metal ones work, I can’t make them work because they only override memory starting at 0x20010000 meaning “after” the rust code. Result is that the rust code still runs after flash & restart using the C code.

Is there any way to get this bootloader code so I can flash it back in? I’ll make rust think memory starts at 0x20010000 which should hopefully avoid this override in the future.


(Bruce Hoult) #2

This is a very dangerous condition, because it means if your Rust code does something sufficiently bad then it can get into a state where the JLINK can not communicate with the CPU to load new programs.

An example of something sufficiently bad would be turning off the clock, or setting it to a speed the board can’t run at. Or things such as a trap or interrupt happening without an appropriate and correct trap handler installed.

The bootloader code used on the original HiFive1 is here:

I don’t know what code is used on the Rev B. I don’t know of any reason this code would not work, other than the obvious one that FINAL_ADDRESS needs changing to something that is inside the small flash the Rev B has (normally 0x2001000, as you mention).

If you wanted to, and you never wanted to run anything except Rust binaries, you could simply include (a translation of) this code at the start of each Rust binary.


#3

Thanks. I suppose if I manage to flash the (fixed dest address) version of this over and coerce Rust to use 0x20010000 starting address it should “just work”.

Question is, how do I flash this to 0x20000000 given this comment.


(Bruce Hoult) #4

If you (in the v1_0 branch) make the following temporary patch:

--- a/bsp/env/freedom-e300-hifive1/flash.lds
+++ b/bsp/env/freedom-e300-hifive1/flash.lds
@@ -4,7 +4,7 @@ ENTRY( _start )
 
 MEMORY
 {
-  flash (rxai!w) : ORIGIN = 0x20400000, LENGTH = 512M
+  flash (rxai!w) : ORIGIN = 0x20000000, LENGTH = 512M
   ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 16K
 }

Then when you do “make software PROGRAM=double_tap_dontboot” then it will be linked for 0x20000000.

The v1_0 “make upload” won’t work for the RevB, but you can extract the elf file to a hex file and flash that.

Note that this is NOT TESTED but I don’t think there is any incompatibility between the FE310-G000 and FE310-G002 that would stop it working.

I’m trying to get a tested and supported solution to this. It will probably consist of a hex file that can be simply downloaded and copied onto the USB drive.


#5

Thanks! I’ll probably wait for the tested solution.

In the meantime I’m asking the rust guys to see if there’s maybe something like this rescue “main-main” written in rust anywhere yet. If not it might be an interesting project to tackle at some point.


(Markis Maraldo) #6

In case you need it, I dumped the original bootloader in a .bin file. It can be easily loaded using Jlink/JFlash. (If it still responds)

Edit: Link
https://1drv.ms/u/s!AgAu-o-Onwezo01wyQDOJSn_lRZa


(Bruce Hoult) #7

I’ve just got hold of the hex file used to flash the production units. I’ve uploaded it temporarily at

http://hoult.org/hifive1_revb_bootloader.hex

We’ll find somewhere to put it permanently … probably linked from the product page I think.

I haven’t tested it myself yet. You might like to compare it to your .bin file (which you haven’t given a link to)


(Markis Maraldo) #8

I converted the .hex to a .bin file and ran checksum on my binary and the one generated. They are indeed the same.


#9

Thanks guys! I just flashed the hex file from Bruce and it worked fine! I get the green blink on start and it went back to my last “C” program (sifive-welcome) since I guess that was still sitting up in the flash memory.

I also found out that the proper rust setup uses .text sections correctly, but because I followed a real barebones guide and just used MEMORY with flash on 0x20000000 I overrode the bootloader.

The actual way to do this is part of the hifive1 crate at https://github.com/riscv-rust/hifive1/blob/master/memory-hifive1-revb.x which does skip to 0x20010000 to keep the bootloader.

I followed a guide (which sadly I can’t find a link to again) that described how to setup a bare bones main in great detail. Really good learning experience but they just used 0x20000000 directly.


#10

Would it be possible to get the source code to the revB bootloader? I’m getting strange behavior where any kind of “write” operation (except for serial) seems to cause it to stop working. It works fine “direct” (if I risk it and go 0x20000000 again) and I’m wondering if it could be aborting/stopping due to say not being in machine mode or something because the bootloader set it to umode or such.


(Bruce Hoult) #11

What do you mean by “write operation”?

The bootloader certainly shouldn’t be doing any messing with PMP or switching to User mode, but unfortunately I don’t have source code for it.


#12

Well the odd thing is that if I just use serial (e.g. “hello world” writes) it all works. As soon as I try to set a LED, GPIO or even read mstatus or friends it stops working.

I’ve confirmed that the same program works if I do the override 0x20000000 so I’m wondering if something funny is going on due to what the bootloader does.

I’ll try and get any of those things working with C code to see if I get the same outcome.


#13

Figured this out. It seems to be something wrong with clock setting in the rust code. Everything works fine both with or without the bootloader as long as the clock is set to full 320.

If I lower the clock things break but only behind the bootloader. We suspect it’s a bug in the clock setup in rust tho, will ping back if we figure this out. I forgot I even lowered the clock in this program so I was looking everywhere but :slight_smile:


(Carrot) #14

Hi,
I also managed to overwrite my bootloader trying to load a rust program so thanks for doing it before me :slight_smile: Anyway, I’m trying to use the hifive1 crate, but my programs are always trying to write to “Entry point address: 0x20400000”.

Do you have a repo with a project I can look at or can you explain how I can get the program to load into the correct addresses?

Thanks!


#15

The rust situation is a bit of a pickle atm. Disasm is working on getting it working using .cargo/config that specifies the memory layout provided by a new version of the hifive1 crate and a patched version of the rust-rt crate. You can find that all in his workspace here.

NOTE: all those repos are modified, so if you get the originals/master branches it won’t work.

You can also use the current versions of all the crates and get the same result by providing your own memory.x file with

{
	RAM : ORIGIN = 0x80000000, LENGTH = 0x4000
	FLASH : ORIGIN = 0x20010000, LENGTH = 0x6a120
}

that will force it to go to 0x20010000 as a workaround for now. The way this works is that the risc-rt crate provides a link.x with all the section definitions, but it expects a INCLUDE memory.x on linktime in there. By providing one with the right offset you override the default old-version hifive1 one. That’s how I do it atm. but I don’t have any repo published yet.

You need the memory.x file to be placed in the right build directory by creating a build.rs file in your project root with:

use std::{env, fs};
use std::path::PathBuf;
use std::io::Write;

fn main() {
    // Put the linker script somewhere the linker can find it
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    println!("cargo:rustc-link-search={}", out_dir.display());

    fs::File::create(out_dir.join("memory.x")).unwrap()
        .write_all(include_bytes!("memory.x")).unwrap();
    println!("cargo:rerun-if-changed=memory.x");
}

WARNING: make sure to check the final binary (elf) with riscv objdump to ensure it was linked properly and points to 0x20010000 as entrypoint before attempting an upload, otherwise you’ll wipe the bootloader again :smiley:

Hope this helps for now, I plan to put some things together and start a blog series about all the little tidbits as I go along.


#16

@cat, what board are you using? HiFive1 and HiFive1 RevB use different entry addresses.


(Carrot) #17

Thanks a bunch! woo my program works!
Okay so for others out there, what I’ve done might not be correct but it worked, so feel free to try it :slight_smile:
my memory.x file contains what almindor wrote but I put MEMORY on the first line above the {
i add the build.rs file like suggested
I add build = “build.rs” to my Cargo.toml
I use this .cargo/config
[target.riscv32imac-unknown-none-elf]
runner = ‘riscv64-linux-gnu-gdb -q’
rustflags = [
“-C”, “link-arg=-Tlink.x”,
]

[build]
target = “riscv32imac-unknown-none-elf”

I build with
cargo build
I do
readelf -h /path/to/program
it shows me this line:
Entry point address: 0x20010000 So that’s good.

In one terminal I connect with JLinkGDBServerCLExe -device FE310
you have to use sudo if you haven’t set up permissions from the sifive quickstart guide
I’m on arch and it doesn’t have riscv64-unknown-elf-gdb in the main repository so I use riscv64-linux-gnu-gdb instead to run and it seems to work.
so in another terminal:
$ riscv64-linux-gnu-gdb -q /path/to/program (if you setup the .cargo config just do cargo run) (gdb) target remote :2331 (or port listed by jlink) (gdb) load (gdb) c

@Disasm yeah rev-b. Sorry I was writing at 2 am or something after 10+ hours at work, I could have been a bit more detailed :slight_smile:

[ If some formatting is messed up, I apologize.]


#18

Good news everyone! Disasm just updated the hifive1 and e310x crates. You don’t need any hacks anymore to get basic linking working.

Just use version 0.5.0 of hifive1 crate and specify the board with the feature, e.g. in Cargo.toml

hifive1 = { version = "0.5.0", features = ["board-hifive1-revb"] }

Of course you still need the other jazz like riscv-rt etc. but this means now that you don’t need a custom build.rs or memory.x definitions. Always doublecheck with objdump/readelf before uploading tho!