Bootloader infrastructure and main logic (#404)
* adds bootloader code without a SHA256 implementation * small fixes and typos
This commit is contained in:
2
bootloader/.cargo/config
Normal file
2
bootloader/.cargo/config
Normal file
@@ -0,0 +1,2 @@
|
||||
[target.thumbv7em-none-eabi]
|
||||
linker = "arm-none-eabi-gcc"
|
||||
28
bootloader/Cargo.toml
Normal file
28
bootloader/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "bootloader"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
"Fabian Kaczmarczyck <kaczmarczyck@google.com>",
|
||||
]
|
||||
build = "build.rs"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
byteorder = { version = "1", default-features = false }
|
||||
cortex-m = "^0.6.0"
|
||||
cortex-m-rt = "*"
|
||||
cortex-m-rt-macros = "*"
|
||||
panic-abort = "0.3.2"
|
||||
rtt-target = { version = "*", features = ["cortex-m"] }
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
# Level "z" may decrease the binary size more of necessary.
|
||||
opt-level = 3
|
||||
36
bootloader/README.md
Normal file
36
bootloader/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# OpenSK Bootloader
|
||||
|
||||
This bootloader supports upgradability for OpenSK. Its functionality is to
|
||||
|
||||
- check images on A/B partitions,
|
||||
- boot the most recent valid partition.
|
||||
|
||||
## How to use
|
||||
|
||||
The bootloader is built and deployed by OpenSK's `deploy.py`. If your board
|
||||
defines a metadata address, it is detected as an upgradable board and this
|
||||
bootloader is flashed to memory address 0.
|
||||
|
||||
## How to debug
|
||||
|
||||
The bootloader prints debug message over RTT when compiled in debug mode. Using
|
||||
`nrfjprog` for flashing and inspecting memory is recommended for debugging.
|
||||
|
||||
```shell
|
||||
RUSTFLAGS="-C link-arg=-Wl,-Tlink.x -C link-arg=-nostartfiles" \
|
||||
cargo build --target thumbv7em-none-eabi
|
||||
llvm-objcopy -O ihex target/thumbv7em-none-eabi/debug/bootloader \
|
||||
target/thumbv7em-none-eabi/debug/bootloader.hex
|
||||
nrfjprog --program target/thumbv7em-none-eabi/debug/bootloader.hex \
|
||||
--sectorerase -f nrf52 --reset
|
||||
```
|
||||
|
||||
To read the debug messages, open two terminals for:
|
||||
|
||||
```shell
|
||||
JLinkRTTLogger -device NRF52840_XXAA -if swd -speed 1000 -RTTchannel 0
|
||||
JLinkRTTClient
|
||||
```
|
||||
|
||||
The first command also logs the output to a file. The second shows all output in
|
||||
real time.
|
||||
17
bootloader/build.rs
Normal file
17
bootloader/build.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
||||
21
bootloader/memory.x
Normal file
21
bootloader/memory.x
Normal file
@@ -0,0 +1,21 @@
|
||||
/* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
MEMORY
|
||||
{
|
||||
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 16K
|
||||
RAM (rwx) : ORIGIN = 0x20020000, LENGTH = 128K
|
||||
}
|
||||
|
||||
4
bootloader/rust-toolchain
Normal file
4
bootloader/rust-toolchain
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2021-03-25"
|
||||
components = ["clippy", "rustfmt"]
|
||||
targets = ["thumbv7em-none-eabi"]
|
||||
149
bootloader/src/main.rs
Normal file
149
bootloader/src/main.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
extern crate cortex_m;
|
||||
extern crate cortex_m_rt as rt;
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use core::convert::TryInto;
|
||||
use core::ptr;
|
||||
use cortex_m::asm;
|
||||
use panic_abort as _;
|
||||
use rt::entry;
|
||||
#[cfg(debug_assertions)]
|
||||
use rtt_target::{rprintln, rtt_init_print};
|
||||
|
||||
/// Size of a flash page in bytes.
|
||||
const PAGE_SIZE: usize = 0x1000;
|
||||
|
||||
/// A flash page.
|
||||
type Page = [u8; PAGE_SIZE];
|
||||
|
||||
/// Reads a page of memory.
|
||||
unsafe fn read_page(address: usize) -> Page {
|
||||
debug_assert!(address % PAGE_SIZE == 0);
|
||||
let address_pointer = address as *const Page;
|
||||
ptr::read(address_pointer)
|
||||
}
|
||||
|
||||
/// Parsed metadata for a firmware partition.
|
||||
struct Metadata {
|
||||
checksum: [u8; 32],
|
||||
timestamp: u32,
|
||||
address: u32,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub const DATA_LEN: usize = 40;
|
||||
}
|
||||
|
||||
/// Reads the metadata from a flash page.
|
||||
impl From<Page> for Metadata {
|
||||
fn from(page: Page) -> Self {
|
||||
Metadata {
|
||||
checksum: page[0..32].try_into().unwrap(),
|
||||
timestamp: LittleEndian::read_u32(&page[32..36]),
|
||||
address: LittleEndian::read_u32(&page[36..Metadata::DATA_LEN]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Location of a firmware partition's data.
|
||||
struct BootPartition {
|
||||
firmware_address: usize,
|
||||
metadata_address: usize,
|
||||
}
|
||||
|
||||
impl BootPartition {
|
||||
const _FIRMWARE_LENGTH: usize = 0x00040000;
|
||||
|
||||
/// Reads the metadata, returns the timestamp if all checks pass.
|
||||
pub fn read_timestamp(&self) -> Result<u32, ()> {
|
||||
let metadata_page = unsafe { read_page(self.metadata_address) };
|
||||
let hash_value = self.compute_upgrade_hash(&metadata_page);
|
||||
let metadata = Metadata::from(metadata_page);
|
||||
if self.firmware_address != metadata.address as usize {
|
||||
#[cfg(debug_assertions)]
|
||||
rprintln!(
|
||||
"Firmware address mismatch: expected 0x{:08X}, metadata 0x{:08X}",
|
||||
self.firmware_address,
|
||||
metadata.address as usize
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
if hash_value != metadata.checksum {
|
||||
#[cfg(debug_assertions)]
|
||||
rprintln!("Hash mismatch");
|
||||
return Err(());
|
||||
}
|
||||
Ok(metadata.timestamp)
|
||||
}
|
||||
|
||||
/// Placeholder for the SHA256 implementation.
|
||||
///
|
||||
/// TODO implemented in next PR
|
||||
/// Without it, the bootloader will never boot anything.
|
||||
fn compute_upgrade_hash(&self, _metadata_page: &[u8]) -> [u8; 32] {
|
||||
[0; 32]
|
||||
}
|
||||
|
||||
/// Jump to the firmware.
|
||||
pub fn boot(&self) -> ! {
|
||||
let address = self.firmware_address;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
rprintln!("Boot jump to {:08X}", address);
|
||||
let address_pointer = address as *const u32;
|
||||
// https://docs.rs/cortex-m/0.7.2/cortex_m/asm/fn.bootload.html
|
||||
unsafe { asm::bootload(address_pointer) };
|
||||
}
|
||||
}
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
#[cfg(debug_assertions)]
|
||||
rtt_init_print!();
|
||||
#[cfg(debug_assertions)]
|
||||
rprintln!("Starting bootloader");
|
||||
let partition_a = BootPartition {
|
||||
firmware_address: 0x20000,
|
||||
metadata_address: 0x4000,
|
||||
};
|
||||
let partition_b = BootPartition {
|
||||
firmware_address: 0x60000,
|
||||
metadata_address: 0x5000,
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
rprintln!("Reading partition A");
|
||||
let timestamp_a = partition_a.read_timestamp();
|
||||
#[cfg(debug_assertions)]
|
||||
rprintln!("Reading partition B");
|
||||
let timestamp_b = partition_b.read_timestamp();
|
||||
|
||||
match (timestamp_a, timestamp_b) {
|
||||
(Ok(t1), Ok(t2)) => {
|
||||
if t1 >= t2 {
|
||||
partition_a.boot()
|
||||
} else {
|
||||
partition_b.boot()
|
||||
}
|
||||
}
|
||||
(Ok(_), Err(_)) => partition_a.boot(),
|
||||
(Err(_), Ok(_)) => partition_b.boot(),
|
||||
(Err(_), Err(_)) => panic!(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user