Bootloader infrastructure and main logic (#404)
* adds bootloader code without a SHA256 implementation * small fixes and typos
This commit is contained in:
6
.github/workflows/cargo_check.yml
vendored
6
.github/workflows/cargo_check.yml
vendored
@@ -82,3 +82,9 @@ jobs:
|
||||
with:
|
||||
command: check
|
||||
args: --target thumbv7em-none-eabi --release --examples
|
||||
|
||||
- name: Check bootloader
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --manifest-path bootloader/Cargo.toml --target thumbv7em-none-eabi --release
|
||||
|
||||
6
.github/workflows/cargo_fmt.yml
vendored
6
.github/workflows/cargo_fmt.yml
vendored
@@ -71,3 +71,9 @@ jobs:
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path tools/heapviz/Cargo.toml --all -- --check
|
||||
|
||||
- name: Cargo format bootloader
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path bootloader/Cargo.toml --all -- --check
|
||||
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
41
deploy.py
41
deploy.py
@@ -384,6 +384,39 @@ class OpenSKInstaller:
|
||||
self._check_invariants()
|
||||
self._build_app_or_example(is_example=False)
|
||||
|
||||
def build_bootloader(self):
|
||||
"""Builds the upgrade bootloader."""
|
||||
props = SUPPORTED_BOARDS[self.args.board]
|
||||
info("Building bootloader")
|
||||
rust_flags = [
|
||||
f"--remap-path-prefix={os.getcwd()}=",
|
||||
"-C",
|
||||
"link-arg=-Wl,-Tlink.x",
|
||||
"-C",
|
||||
"link-arg=-nostartfiles",
|
||||
]
|
||||
env = os.environ.copy()
|
||||
env["RUSTFLAGS"] = " ".join(rust_flags)
|
||||
cargo_command = ["cargo", "build", "--release", f"--target={props.arch}"]
|
||||
self.checked_command(cargo_command, cwd="bootloader", env=env)
|
||||
binary_path = os.path.join("target", props.arch, "release", "bootloader")
|
||||
objcopy_command = [
|
||||
"llvm-objcopy", "-O", "binary", binary_path, f"{binary_path}.bin"
|
||||
]
|
||||
self.checked_command(objcopy_command, cwd="bootloader")
|
||||
|
||||
def flash_bootloader(self):
|
||||
"""Flashes the upgrade bootloader."""
|
||||
props = SUPPORTED_BOARDS[self.args.board]
|
||||
info("Flashing bootloader")
|
||||
bin_file = os.path.join("bootloader", "target", props.arch, "release",
|
||||
"bootloader.bin")
|
||||
if not os.path.exists(bin_file):
|
||||
fatal(f"File not found: {bin_file}")
|
||||
with open(bin_file, "rb") as bootloader_bin:
|
||||
bootloader = bootloader_bin.read()
|
||||
self.write_binary(bootloader, 0)
|
||||
|
||||
def _build_app_or_example(self, is_example: bool):
|
||||
"""Builds the application specified through args.
|
||||
|
||||
@@ -732,9 +765,13 @@ class OpenSKInstaller:
|
||||
return 0
|
||||
|
||||
# Compile what needs to be compiled
|
||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||
if self.args.tockos:
|
||||
self.build_tockos()
|
||||
|
||||
if board_props.metadata_address is not None:
|
||||
self.build_bootloader()
|
||||
|
||||
if self.args.application == "ctap2":
|
||||
self.generate_crypto_materials(self.args.regenerate_keys)
|
||||
self.build_opensk()
|
||||
@@ -748,7 +785,6 @@ class OpenSKInstaller:
|
||||
self.clear_storage()
|
||||
|
||||
# Flashing
|
||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||
if self.args.programmer in ("jlink", "openocd"):
|
||||
# We rely on Tockloader to do the job
|
||||
if self.args.clear_apps:
|
||||
@@ -756,6 +792,9 @@ class OpenSKInstaller:
|
||||
if self.args.tockos:
|
||||
# Install Tock OS
|
||||
self.install_tock_os()
|
||||
if board_props.metadata_address is not None:
|
||||
# Install the bootloader
|
||||
self.flash_bootloader()
|
||||
# Install padding and application if needed
|
||||
if self.args.application:
|
||||
self.install_padding()
|
||||
|
||||
@@ -48,3 +48,25 @@ There are 3 switches that need to be in the correct position:
|
||||
* Power (bottom left): On
|
||||
* nRF power source (center left): VDD
|
||||
* SW6 (top right): DEFAULT
|
||||
|
||||
### Upgradability
|
||||
|
||||
There are variants of the board that introduce A/B partitions for upgrading the
|
||||
firmware. You can bootstrap an upgradable board using one of the two commands:
|
||||
|
||||
```shell
|
||||
./deploy.py --board=nrf52840dk_opensk_a --opensk
|
||||
./deploy.py --board=nrf52840dk_opensk_b --opensk
|
||||
```
|
||||
|
||||
Afterwards, you can upgrade the other partition with
|
||||
|
||||
```shell
|
||||
./tools/perform_upgrade.sh nrf52840dk_opensk_b
|
||||
./tools/perform_upgrade.sh nrf52840dk_opensk_a
|
||||
```
|
||||
|
||||
respectively. You can only upgrade the partition that is not currently running,
|
||||
so always alternate your calls to `perform_upgrade.sh`. Otherwise, this script
|
||||
works like `deploy.py`. You can call it even after you locked down your device,
|
||||
to deploy changes to your development board.
|
||||
|
||||
@@ -28,6 +28,7 @@ following:
|
||||
* `nrfutil` (can be installed using `pip3 install nrfutil`) if you want to flash
|
||||
a device with DFU
|
||||
* `uuid-runtime` if you are missing the `uuidgen` command.
|
||||
* `llvm` if you want to use the upgradability feature.
|
||||
|
||||
The proprietary software to use the default programmer can be found on the
|
||||
[Segger website](https://www.segger.com/downloads/jlink). Please follow their
|
||||
@@ -149,3 +150,23 @@ If your board is already flashed with Tock OS, you may skip installing it:
|
||||
|
||||
For more options, we invite you to read the help of our `deploy.py` script by
|
||||
running `./deploy.py --help`.
|
||||
|
||||
### Upgradability
|
||||
|
||||
We experiment with a new CTAP command to allow upgrading your device without
|
||||
access to its debugging port. For that purpose, the flash storage is split into
|
||||
4 parts:
|
||||
|
||||
* the bootloader to decide with partition to boot
|
||||
* firmware partition A
|
||||
* firmware partition B
|
||||
* the persistent storage for credentials
|
||||
|
||||
The storage is backward compatible to non-upgradable boards. Deploying an
|
||||
upgradable board automatically installs the bootloader. Please keep in mind that
|
||||
you have to safely store your private signing key for upgrades if you want to
|
||||
use this feature. For more information on the cryptographic material, see
|
||||
[Customization](customization.md).
|
||||
|
||||
So far, upgradability is only supported for the development board. See the
|
||||
instructions on the [board specific page](boards/nrf52840dk.md).
|
||||
|
||||
@@ -29,6 +29,9 @@ cd ../..
|
||||
cd tools/heapviz
|
||||
cargo fmt --all -- --check
|
||||
cd ../..
|
||||
cd bootloader
|
||||
cargo fmt --all -- --check
|
||||
cd ..
|
||||
|
||||
echo "Running Clippy lints..."
|
||||
cargo clippy --all-targets --features std -- -A clippy::new_without_default -D warnings
|
||||
@@ -55,6 +58,11 @@ echo "Checking that examples build properly..."
|
||||
cargo check --release --target=thumbv7em-none-eabi --examples
|
||||
cargo check --release --target=thumbv7em-none-eabi --examples --features with_nfc
|
||||
|
||||
echo "Checking that bootloader builds properly..."
|
||||
cd bootloader
|
||||
cargo check --release --target=thumbv7em-none-eabi
|
||||
cd ..
|
||||
|
||||
echo "Checking that fuzz targets build properly..."
|
||||
cargo fuzz build
|
||||
cd libraries/cbor
|
||||
|
||||
Reference in New Issue
Block a user