Merge branch 'master' into wipe
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
# Target configuration for the CTAP2 app on the nRF52840 chip
|
||||
[target.thumbv7em-none-eabi]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tnrf52840dk_layout.ld",
|
||||
"-C", "relocation-model=static",
|
||||
"-D", "warnings",
|
||||
]
|
||||
6
.github/workflows/boards_build.yml
vendored
6
.github/workflows/boards_build.yml
vendored
@@ -26,6 +26,12 @@ jobs:
|
||||
run: python -m pip install --upgrade pip setuptools wheel
|
||||
- name: Set up OpenSK
|
||||
run: ./setup.sh
|
||||
|
||||
- name: Building board nrf52840_dongle_dfu
|
||||
run: ./deploy.py --board=nrf52840_dongle_dfu --no-app --programmer=none
|
||||
- name: Building board nrf52840_mdk_dfu
|
||||
run: ./deploy.py --board=nrf52840_mdk_dfu --no-app --programmer=none
|
||||
|
||||
- name: Create a long build directory
|
||||
run: mkdir this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz && mv third_party this-is-a-long-build-directory-0123456789abcdefghijklmnopqrstuvwxyz/
|
||||
|
||||
|
||||
10
.github/workflows/cargo_check.yml
vendored
10
.github/workflows/cargo_check.yml
vendored
@@ -64,17 +64,23 @@ jobs:
|
||||
command: check
|
||||
args: --target thumbv7em-none-eabi --release --features ram_storage
|
||||
|
||||
- name: Check OpenSK verbose
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --target thumbv7em-none-eabi --release --features verbose
|
||||
|
||||
- name: Check OpenSK debug_ctap,with_ctap1
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1
|
||||
|
||||
- name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations
|
||||
- name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,panic_console,debug_allocations
|
||||
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
|
||||
|
||||
- name: Check examples
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
8
.github/workflows/opensk_build.yml
vendored
8
.github/workflows/opensk_build.yml
vendored
@@ -24,8 +24,16 @@ jobs:
|
||||
- name: Set up OpenSK
|
||||
run: ./setup.sh
|
||||
|
||||
- name: Building sha256sum tool
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
|
||||
|
||||
- name: Building OpenSK
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --target=thumbv7em-none-eabi --features with_ctap1
|
||||
- name: Compute SHA-256 sum
|
||||
run: ./third_party/tock/tools/sha256sum/target/debug/sha256sum target/thumbv7em-none-eabi/release/ctap2
|
||||
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -20,6 +20,7 @@
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.lintOnSave": true,
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.pylintPath": "pylint",
|
||||
"[python]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
|
||||
@@ -18,12 +18,13 @@ arrayref = "0.3.6"
|
||||
subtle = { version = "2.2", default-features = false, features = ["nightly"] }
|
||||
|
||||
[features]
|
||||
std = ["cbor/std", "crypto/std", "crypto/derive_debug"]
|
||||
debug_ctap = ["crypto/derive_debug"]
|
||||
with_ctap1 = ["crypto/with_ctap1"]
|
||||
panic_console = ["libtock/panic_console"]
|
||||
debug_allocations = ["libtock/debug_allocations"]
|
||||
debug_ctap = ["crypto/derive_debug"]
|
||||
panic_console = ["libtock/panic_console"]
|
||||
std = ["cbor/std", "crypto/std", "crypto/derive_debug"]
|
||||
ram_storage = []
|
||||
verbose = ["debug_ctap"]
|
||||
with_ctap1 = ["crypto/with_ctap1"]
|
||||
|
||||
[dev-dependencies]
|
||||
elf2tab = "0.4.0"
|
||||
|
||||
23
README.md
23
README.md
@@ -19,8 +19,9 @@ successfully tested on the following boards:
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This project is proof-of-concept and a research platform. It's still under
|
||||
development and as such comes with a few limitations:
|
||||
This project is **proof-of-concept and a research platform**. It is **NOT**
|
||||
meant for a daily usage. It's still under development and as such comes with a
|
||||
few limitations:
|
||||
|
||||
### FIDO2
|
||||
|
||||
@@ -53,25 +54,17 @@ For a more detailed guide, please refer to our
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
2. If Tock OS is already installed on your board, move to the next step.
|
||||
Otherwise, just run one of the following commands, depending on the board
|
||||
you have:
|
||||
2. Next step is to install Tock OS as well as the OpenSK application on your
|
||||
board (**Warning**: it will erase the locally stored credentials). Run:
|
||||
|
||||
```shell
|
||||
# Nordic nRF52840-DK board
|
||||
./deploy.py os --board=nrf52840_dk
|
||||
./deploy.py --board=nrf52840dk --opensk
|
||||
# Nordic nRF52840-Dongle
|
||||
./deploy.py os --board=nrf52840_dongle
|
||||
./deploy.py --board=nrf52840_dongle --opensk
|
||||
```
|
||||
|
||||
3. Next step is to install/update the OpenSK application on your board
|
||||
(**Warning**: it will erase the locally stored credentials). Run:
|
||||
|
||||
```shell
|
||||
./deploy.py app --opensk
|
||||
```
|
||||
|
||||
4. On Linux, you may want to avoid the need for `root` privileges to interact
|
||||
3. On Linux, you may want to avoid the need for `root` privileges to interact
|
||||
with the key. For that purpose we provide a udev rule file that can be
|
||||
installed with the following command:
|
||||
|
||||
|
||||
30
boards/nrf52840_dongle_dfu/Cargo.toml
Normal file
30
boards/nrf52840_dongle_dfu/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "nrf52840_dongle_dfu"
|
||||
version = "0.1.0"
|
||||
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
|
||||
build = "build.rs"
|
||||
edition = "2018"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
lto = false
|
||||
opt-level = "z"
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
opt-level = "z"
|
||||
debug = true
|
||||
|
||||
[[bin]]
|
||||
path = "../../third_party/tock/boards/nordic/nrf52840_dongle/src/main.rs"
|
||||
name = "nrf52840_dongle_dfu"
|
||||
|
||||
[dependencies]
|
||||
components = { path = "../../third_party/tock/boards/components" }
|
||||
cortexm4 = { path = "../../third_party/tock/arch/cortex-m4" }
|
||||
capsules = { path = "../../third_party/tock/capsules" }
|
||||
kernel = { path = "../../third_party/tock/kernel" }
|
||||
nrf52840 = { path = "../../third_party/tock/chips/nrf52840" }
|
||||
nrf52dk_base = { path = "../../third_party/tock/boards/nordic/nrf52dk_base" }
|
||||
29
boards/nrf52840_dongle_dfu/Makefile
Normal file
29
boards/nrf52840_dongle_dfu/Makefile
Normal file
@@ -0,0 +1,29 @@
|
||||
# Makefile for building the tock kernel for the nRF development kit
|
||||
|
||||
TOCK_ARCH=cortex-m4
|
||||
TARGET=thumbv7em-none-eabi
|
||||
PLATFORM=nrf52840_dongle_dfu
|
||||
|
||||
include ../../third_party/tock/boards/Makefile.common
|
||||
|
||||
TOCKLOADER=tockloader
|
||||
|
||||
# Where in the nrf52 flash to load the kernel with `tockloader`
|
||||
KERNEL_ADDRESS=0x01000
|
||||
|
||||
# Upload programs over uart with tockloader
|
||||
ifdef PORT
|
||||
TOCKLOADER_GENERAL_FLAGS += --port $(PORT)
|
||||
endif
|
||||
|
||||
TOCKLOADER_JTAG_FLAGS = --jlink --arch $(TOCK_ARCH) --board $(PLATFORM) --page-size 4096 --jlink-device nrf52840_xxaa
|
||||
|
||||
# Upload the kernel over JTAG
|
||||
.PHONY: flash
|
||||
flash: target/$(TARGET)/release/$(PLATFORM).bin
|
||||
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_JTAG_FLAGS) $<
|
||||
|
||||
# Upload the kernel over serial/bootloader
|
||||
.PHONY: program
|
||||
program: target/$(TARGET)/release/$(PLATFORM).hex
|
||||
$(error Cannot program nRF52 Dongle over USB. Use \`make flash\` and JTAG)
|
||||
4
boards/nrf52840_dongle_dfu/build.rs
Normal file
4
boards/nrf52840_dongle_dfu/build.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=layout.ld");
|
||||
println!("cargo:rerun-if-changed=../../third_party/tock/boards/kernel_layout.ld");
|
||||
}
|
||||
10
boards/nrf52840_dongle_dfu/layout.ld
Normal file
10
boards/nrf52840_dongle_dfu/layout.ld
Normal file
@@ -0,0 +1,10 @@
|
||||
MEMORY
|
||||
{
|
||||
rom (rx) : ORIGIN = 0x00001000, LENGTH = 188K
|
||||
prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K
|
||||
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
||||
|
||||
MPU_MIN_ALIGN = 8K;
|
||||
|
||||
INCLUDE ../../third_party/tock/boards/kernel_layout.ld
|
||||
26
boards/nrf52840_mdk_dfu/Cargo.toml
Normal file
26
boards/nrf52840_mdk_dfu/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "nrf52840_mdk_dfu"
|
||||
version = "0.1.0"
|
||||
authors = ["Yihui Xiong <yihui.xiong@hotmail.com>"]
|
||||
build = "build.rs"
|
||||
edition = "2018"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
lto = false
|
||||
opt-level = "z"
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
opt-level = "z"
|
||||
debug = true
|
||||
|
||||
[dependencies]
|
||||
components = { path = "../../third_party/tock/boards/components" }
|
||||
cortexm4 = { path = "../../third_party/tock/arch/cortex-m4" }
|
||||
capsules = { path = "../../third_party/tock/capsules" }
|
||||
kernel = { path = "../../third_party/tock/kernel" }
|
||||
nrf52840 = { path = "../../third_party/tock/chips/nrf52840" }
|
||||
nrf52dk_base = { path = "../../third_party/tock/boards/nordic/nrf52dk_base" }
|
||||
29
boards/nrf52840_mdk_dfu/Makefile
Normal file
29
boards/nrf52840_mdk_dfu/Makefile
Normal file
@@ -0,0 +1,29 @@
|
||||
# Makefile for building the tock kernel for the nRF development kit
|
||||
|
||||
TOCK_ARCH=cortex-m4
|
||||
TARGET=thumbv7em-none-eabi
|
||||
PLATFORM=nrf52840_mdk_dfu
|
||||
|
||||
include ../../third_party/tock/boards/Makefile.common
|
||||
|
||||
TOCKLOADER=tockloader
|
||||
|
||||
# Where in the nrf52 flash to load the kernel with `tockloader`
|
||||
KERNEL_ADDRESS=0x01000
|
||||
|
||||
# Upload programs over uart with tockloader
|
||||
ifdef PORT
|
||||
TOCKLOADER_GENERAL_FLAGS += --port $(PORT)
|
||||
endif
|
||||
|
||||
TOCKLOADER_JTAG_FLAGS = --jlink --arch $(TOCK_ARCH) --board $(PLATFORM) --page-size 4096 --jlink-device nrf52840_xxaa
|
||||
|
||||
# Upload the kernel over JTAG
|
||||
.PHONY: flash
|
||||
flash: target/$(TARGET)/release/$(PLATFORM).bin
|
||||
$(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_JTAG_FLAGS) $<
|
||||
|
||||
# Upload the kernel over serial/bootloader
|
||||
.PHONY: program
|
||||
program: target/$(TARGET)/release/$(PLATFORM).hex
|
||||
$(error Cannot program nRF52 Dongle over USB. Use \`make flash\` and JTAG)
|
||||
4
boards/nrf52840_mdk_dfu/build.rs
Normal file
4
boards/nrf52840_mdk_dfu/build.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=layout.ld");
|
||||
println!("cargo:rerun-if-changed=../../third_party/tock/boards/kernel_layout.ld");
|
||||
}
|
||||
10
boards/nrf52840_mdk_dfu/layout.ld
Normal file
10
boards/nrf52840_mdk_dfu/layout.ld
Normal file
@@ -0,0 +1,10 @@
|
||||
MEMORY
|
||||
{
|
||||
rom (rx) : ORIGIN = 0x00001000, LENGTH = 188K
|
||||
prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K
|
||||
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
||||
|
||||
MPU_MIN_ALIGN = 8K;
|
||||
|
||||
INCLUDE ../../third_party/tock/boards/kernel_layout.ld
|
||||
65
boards/nrf52840_mdk_dfu/src/io.rs
Normal file
65
boards/nrf52840_mdk_dfu/src/io.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use core::fmt::Write;
|
||||
use core::panic::PanicInfo;
|
||||
use cortexm4;
|
||||
use kernel::debug;
|
||||
use kernel::debug::IoWrite;
|
||||
use kernel::hil::led;
|
||||
use kernel::hil::uart::{self, Configure};
|
||||
use nrf52840::gpio::Pin;
|
||||
|
||||
use crate::CHIP;
|
||||
use crate::PROCESSES;
|
||||
|
||||
struct Writer {
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
static mut WRITER: Writer = Writer { initialized: false };
|
||||
|
||||
impl Write for Writer {
|
||||
fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
|
||||
self.write(s.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IoWrite for Writer {
|
||||
fn write(&mut self, buf: &[u8]) {
|
||||
let uart = unsafe { &mut nrf52840::uart::UARTE0 };
|
||||
if !self.initialized {
|
||||
self.initialized = true;
|
||||
uart.configure(uart::Parameters {
|
||||
baud_rate: 115200,
|
||||
stop_bits: uart::StopBits::One,
|
||||
parity: uart::Parity::None,
|
||||
hw_flow_control: false,
|
||||
width: uart::Width::Eight,
|
||||
});
|
||||
}
|
||||
for &c in buf {
|
||||
unsafe {
|
||||
uart.send_byte(c);
|
||||
}
|
||||
while !uart.tx_ready() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[no_mangle]
|
||||
#[panic_handler]
|
||||
/// Panic handler
|
||||
pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! {
|
||||
// The nRF52840 Dongle LEDs (see back of board)
|
||||
const LED1_PIN: Pin = Pin::P0_23;
|
||||
let led = &mut led::LedLow::new(&mut nrf52840::gpio::PORT[LED1_PIN]);
|
||||
let writer = &mut WRITER;
|
||||
debug::panic(
|
||||
&mut [led],
|
||||
writer,
|
||||
pi,
|
||||
&cortexm4::support::nop,
|
||||
&PROCESSES,
|
||||
&CHIP,
|
||||
)
|
||||
}
|
||||
123
boards/nrf52840_mdk_dfu/src/main.rs
Normal file
123
boards/nrf52840_mdk_dfu/src/main.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
//! Tock kernel for the Makerdiary nRF52840 MDK USB dongle.
|
||||
//!
|
||||
//! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with
|
||||
//! many exported I/O and peripherals.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use kernel::component::Component;
|
||||
#[allow(unused_imports)]
|
||||
use kernel::{debug, debug_gpio, debug_verbose, static_init};
|
||||
use nrf52840::gpio::Pin;
|
||||
use nrf52dk_base::{SpiPins, UartChannel, UartPins};
|
||||
|
||||
// The nRF52840 MDK USB Dongle LEDs
|
||||
const LED1_R_PIN: Pin = Pin::P0_23;
|
||||
const LED1_G_PIN: Pin = Pin::P0_22;
|
||||
const LED1_B_PIN: Pin = Pin::P0_24;
|
||||
|
||||
// The nRF52840 Dongle button
|
||||
const BUTTON_PIN: Pin = Pin::P0_18;
|
||||
const BUTTON_RST_PIN: Pin = Pin::P0_02;
|
||||
|
||||
const UART_RTS: Option<Pin> = Some(Pin::P0_21);
|
||||
const UART_TXD: Pin = Pin::P0_20;
|
||||
const UART_CTS: Option<Pin> = Some(Pin::P0_03);
|
||||
const UART_RXD: Pin = Pin::P0_19;
|
||||
|
||||
const SPI_MOSI: Pin = Pin::P0_05;
|
||||
const SPI_MISO: Pin = Pin::P0_06;
|
||||
const SPI_CLK: Pin = Pin::P0_07;
|
||||
|
||||
/// UART Writer
|
||||
pub mod io;
|
||||
|
||||
// State for loading and holding applications.
|
||||
// How should the kernel respond when a process faults.
|
||||
const FAULT_RESPONSE: kernel::procs::FaultResponse = kernel::procs::FaultResponse::Panic;
|
||||
|
||||
// Number of concurrent processes this platform supports.
|
||||
const NUM_PROCS: usize = 8;
|
||||
|
||||
// RAM to be shared by all application processes.
|
||||
#[link_section = ".app_memory"]
|
||||
static mut APP_MEMORY: [u8; 0x3C000] = [0; 0x3C000];
|
||||
|
||||
static mut PROCESSES: [Option<&'static dyn kernel::procs::ProcessType>; NUM_PROCS] =
|
||||
[None, None, None, None, None, None, None, None];
|
||||
|
||||
// Static reference to chip for panic dumps
|
||||
static mut CHIP: Option<&'static nrf52840::chip::Chip> = None;
|
||||
|
||||
/// Dummy buffer that causes the linker to reserve enough space for the stack.
|
||||
#[no_mangle]
|
||||
#[link_section = ".stack_buffer"]
|
||||
pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000];
|
||||
|
||||
/// Entry point in the vector table called on hard reset.
|
||||
#[no_mangle]
|
||||
pub unsafe fn reset_handler() {
|
||||
// Loads relocations and clears BSS
|
||||
nrf52840::init();
|
||||
|
||||
let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES));
|
||||
// GPIOs
|
||||
let gpio = components::gpio::GpioComponent::new(board_kernel).finalize(
|
||||
components::gpio_component_helper!(
|
||||
&nrf52840::gpio::PORT[Pin::P0_04],
|
||||
&nrf52840::gpio::PORT[Pin::P0_05],
|
||||
&nrf52840::gpio::PORT[Pin::P0_06],
|
||||
&nrf52840::gpio::PORT[Pin::P0_07],
|
||||
&nrf52840::gpio::PORT[Pin::P0_08]
|
||||
),
|
||||
);
|
||||
let button = components::button::ButtonComponent::new(board_kernel).finalize(
|
||||
components::button_component_helper!((
|
||||
&nrf52840::gpio::PORT[BUTTON_PIN],
|
||||
kernel::hil::gpio::ActivationMode::ActiveLow,
|
||||
kernel::hil::gpio::FloatingState::PullUp
|
||||
)),
|
||||
);
|
||||
|
||||
let led = components::led::LedsComponent::new().finalize(components::led_component_helper!(
|
||||
(
|
||||
&nrf52840::gpio::PORT[LED1_R_PIN],
|
||||
kernel::hil::gpio::ActivationMode::ActiveLow
|
||||
),
|
||||
(
|
||||
&nrf52840::gpio::PORT[LED1_G_PIN],
|
||||
kernel::hil::gpio::ActivationMode::ActiveLow
|
||||
),
|
||||
(
|
||||
&nrf52840::gpio::PORT[LED1_B_PIN],
|
||||
kernel::hil::gpio::ActivationMode::ActiveLow
|
||||
)
|
||||
));
|
||||
let chip = static_init!(nrf52840::chip::Chip, nrf52840::chip::new());
|
||||
CHIP = Some(chip);
|
||||
|
||||
nrf52dk_base::setup_board(
|
||||
board_kernel,
|
||||
BUTTON_RST_PIN,
|
||||
&nrf52840::gpio::PORT,
|
||||
gpio,
|
||||
LED1_R_PIN,
|
||||
LED1_G_PIN,
|
||||
LED1_B_PIN,
|
||||
led,
|
||||
UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)),
|
||||
&SpiPins::new(SPI_MOSI, SPI_MISO, SPI_CLK),
|
||||
&None,
|
||||
button,
|
||||
true,
|
||||
&mut APP_MEMORY,
|
||||
&mut PROCESSES,
|
||||
FAULT_RESPONSE,
|
||||
nrf52840::uicr::Regulator0Output::V3_0,
|
||||
false,
|
||||
&Some(&nrf52840::usbd::USBD),
|
||||
chip,
|
||||
);
|
||||
}
|
||||
601
deploy.py
601
deploy.py
@@ -20,6 +20,7 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import copy
|
||||
import os
|
||||
import shutil
|
||||
@@ -27,15 +28,125 @@ import subprocess
|
||||
import sys
|
||||
|
||||
import colorama
|
||||
from six.moves import input
|
||||
from tockloader import tab
|
||||
from tockloader import tbfh
|
||||
from tockloader import tockloader as loader
|
||||
from tockloader.exceptions import TockLoaderException
|
||||
|
||||
# This structure allows us in the future to also support out-of-tree boards.
|
||||
PROGRAMMERS = frozenset(("jlink", "openocd", "pyocd", "nordicdfu", "none"))
|
||||
|
||||
# This structure allows us to support out-of-tree boards as well as (in the
|
||||
# future) more achitectures.
|
||||
OpenSKBoard = collections.namedtuple(
|
||||
"OpenSKBoard",
|
||||
[
|
||||
# Location of the Tock board (where the Makefile file is)
|
||||
"path",
|
||||
# Target architecture (e.g. thumbv7em-none-eabi)
|
||||
"arch",
|
||||
# Size of 1 page of flash memory
|
||||
"page_size",
|
||||
# Flash address at which the kernel will be written
|
||||
"kernel_address",
|
||||
# Set to None is padding is not required for the board.
|
||||
# This creates a fake Tock OS application that starts at the
|
||||
# address specified by this parameter (must match the `prog` value
|
||||
# specified on the board's `layout.ld` file) and will end at
|
||||
# `app_address`.
|
||||
"padding_address",
|
||||
# Linker script to produce a working app for this board
|
||||
"app_ldscript",
|
||||
# Flash address at which the app should be written
|
||||
"app_address",
|
||||
# Target name for flashing the board using pyOCD
|
||||
"pyocd_target",
|
||||
# The cfg file in OpenOCD board folder
|
||||
"openocd_board",
|
||||
# Options to tell Tockloader how to work with OpenOCD
|
||||
# Default: []
|
||||
"openocd_options",
|
||||
# Dictionnary specifying custom commands for OpenOCD
|
||||
# Default is an empty dict
|
||||
# Valid keys are: program, read, erase
|
||||
"openocd_commands",
|
||||
# Interface to use with JLink (e.g. swd, jtag, etc.)
|
||||
"jlink_if",
|
||||
# Device name as supported by JLinkExe
|
||||
"jlink_device",
|
||||
# Whether Nordic DFU flashing method is supported
|
||||
"nordic_dfu",
|
||||
])
|
||||
|
||||
SUPPORTED_BOARDS = {
|
||||
"nrf52840_dk": "third_party/tock/boards/nordic/nrf52840dk",
|
||||
"nrf52840_dongle": "third_party/tock/boards/nordic/nrf52840_dongle"
|
||||
"nrf52840dk":
|
||||
OpenSKBoard(
|
||||
path="third_party/tock/boards/nordic/nrf52840dk",
|
||||
arch="thumbv7em-none-eabi",
|
||||
page_size=4096,
|
||||
kernel_address=0,
|
||||
padding_address=0x30000,
|
||||
app_ldscript="nrf52840dk_layout.ld",
|
||||
app_address=0x40000,
|
||||
pyocd_target="nrf52840",
|
||||
openocd_board="nordic_nrf52840_dongle.cfg",
|
||||
openocd_options=[],
|
||||
openocd_commands={},
|
||||
jlink_if="swd",
|
||||
jlink_device="nrf52840_xxaa",
|
||||
nordic_dfu=False,
|
||||
),
|
||||
"nrf52840_dongle":
|
||||
OpenSKBoard(
|
||||
path="third_party/tock/boards/nordic/nrf52840_dongle",
|
||||
arch="thumbv7em-none-eabi",
|
||||
page_size=4096,
|
||||
kernel_address=0,
|
||||
padding_address=0x30000,
|
||||
app_ldscript="nrf52840dk_layout.ld",
|
||||
app_address=0x40000,
|
||||
pyocd_target="nrf52840",
|
||||
openocd_board="nordic_nrf52840_dongle.cfg",
|
||||
openocd_options=[],
|
||||
openocd_commands={},
|
||||
jlink_if="swd",
|
||||
jlink_device="nrf52840_xxaa",
|
||||
nordic_dfu=False,
|
||||
),
|
||||
"nrf52840_dongle_dfu":
|
||||
OpenSKBoard(
|
||||
path="boards/nrf52840_dongle_dfu",
|
||||
arch="thumbv7em-none-eabi",
|
||||
page_size=4096,
|
||||
kernel_address=0x1000,
|
||||
padding_address=0x30000,
|
||||
app_ldscript="nrf52840dk_layout.ld",
|
||||
app_address=0x40000,
|
||||
pyocd_target="nrf52840",
|
||||
openocd_board="nordic_nrf52840_dongle.cfg",
|
||||
openocd_options=[],
|
||||
openocd_commands={},
|
||||
jlink_if="swd",
|
||||
jlink_device="nrf52840_xxaa",
|
||||
nordic_dfu=True,
|
||||
),
|
||||
"nrf52840_mdk_dfu":
|
||||
OpenSKBoard(
|
||||
path="boards/nrf52840_mdk_dfu",
|
||||
arch="thumbv7em-none-eabi",
|
||||
page_size=4096,
|
||||
kernel_address=0x1000,
|
||||
padding_address=0x30000,
|
||||
app_ldscript="nrf52840dk_layout.ld",
|
||||
app_address=0x40000,
|
||||
pyocd_target="nrf52840",
|
||||
openocd_board="nordic_nrf52840_dongle.cfg",
|
||||
openocd_options=[],
|
||||
openocd_commands={},
|
||||
jlink_if="swd",
|
||||
jlink_device="nrf52840_xxaa",
|
||||
nordic_dfu=True,
|
||||
),
|
||||
}
|
||||
|
||||
# The STACK_SIZE value below must match the one used in the linker script
|
||||
@@ -50,9 +161,9 @@ APP_HEAP_SIZE = 90000
|
||||
|
||||
def get_supported_boards():
|
||||
boards = []
|
||||
for name, root in SUPPORTED_BOARDS.items():
|
||||
if all((os.path.exists(os.path.join(root, "Cargo.toml")),
|
||||
os.path.exists(os.path.join(root, "Makefile")))):
|
||||
for name, props in SUPPORTED_BOARDS.items():
|
||||
if all((os.path.exists(os.path.join(props.path, "Cargo.toml")),
|
||||
(props.app_ldscript and os.path.exists(props.app_ldscript)))):
|
||||
boards.append(name)
|
||||
return tuple(set(boards))
|
||||
|
||||
@@ -79,9 +190,23 @@ def info(msg):
|
||||
message=msg))
|
||||
|
||||
|
||||
def assert_mandatory_binary(binary):
|
||||
if not shutil.which(binary):
|
||||
fatal(("Couldn't find {} binary. Make sure it is installed and "
|
||||
"that your PATH is set correctly.").format(binary))
|
||||
|
||||
|
||||
def assert_python_library(module):
|
||||
try:
|
||||
__import__(module)
|
||||
except ModuleNotFoundError:
|
||||
fatal(("Couldn't load python3 module {name}. "
|
||||
"Try to run: pip3 install {name}").format(name=module))
|
||||
|
||||
|
||||
class RemoveConstAction(argparse.Action):
|
||||
|
||||
# pylint: disable=W0622
|
||||
# pylint: disable=redefined-builtin
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
dest,
|
||||
@@ -121,28 +246,34 @@ class OpenSKInstaller:
|
||||
self.args = args
|
||||
# Where all the TAB files should go
|
||||
self.tab_folder = os.path.join("target", "tab")
|
||||
# This is the filename that elf2tab command expects in order
|
||||
# to create a working TAB file.
|
||||
self.target_elf_filename = os.path.join(self.tab_folder, "cortex-m4.elf")
|
||||
board = SUPPORTED_BOARDS[self.args.board]
|
||||
self.tockloader_default_args = argparse.Namespace(
|
||||
arch="cortex-m4",
|
||||
board=getattr(self.args, "board", "nrf52840"),
|
||||
arch=board.arch,
|
||||
board=self.args.board,
|
||||
debug=False,
|
||||
force=False,
|
||||
jlink=True,
|
||||
jlink_device="nrf52840_xxaa",
|
||||
jlink_if="swd",
|
||||
jlink=self.args.programmer == "jlink",
|
||||
jlink_device=board.jlink_device,
|
||||
jlink_if=board.jlink_if,
|
||||
jlink_speed=1200,
|
||||
openocd=self.args.programmer == "openocd",
|
||||
openocd_board=board.openocd_board,
|
||||
jtag=False,
|
||||
no_bootloader_entry=False,
|
||||
page_size=4096,
|
||||
page_size=board.page_size,
|
||||
port=None,
|
||||
)
|
||||
|
||||
def checked_command_output(self, cmd):
|
||||
def checked_command_output(self, cmd, env=None, cwd=None):
|
||||
cmd_output = ""
|
||||
try:
|
||||
cmd_output = subprocess.check_output(cmd)
|
||||
cmd_output = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
timeout=None,
|
||||
check=True,
|
||||
env=env,
|
||||
cwd=cwd).stdout
|
||||
except subprocess.CalledProcessError as e:
|
||||
fatal("Failed to execute {}: {}".format(cmd[0], str(e)))
|
||||
# Unreachable because fatal() will exit
|
||||
@@ -166,38 +297,57 @@ class OpenSKInstaller:
|
||||
# Need to update
|
||||
self.checked_command_output(
|
||||
["rustup", "install", target_toolchain_fullstring])
|
||||
self.checked_command_output(
|
||||
["rustup", "target", "add", "thumbv7em-none-eabi"])
|
||||
self.checked_command_output(
|
||||
["rustup", "target", "add", SUPPORTED_BOARDS[self.args.board].arch])
|
||||
info("Rust toolchain up-to-date")
|
||||
|
||||
def build_and_install_tockos(self):
|
||||
self.checked_command_output(
|
||||
["make", "-C", SUPPORTED_BOARDS[self.args.board], "flash"])
|
||||
def build_tockos(self):
|
||||
info("Building Tock OS for board {}".format(self.args.board))
|
||||
props = SUPPORTED_BOARDS[self.args.board]
|
||||
out_directory = os.path.join(props.path, "target", props.arch, "release")
|
||||
os.makedirs(out_directory, exist_ok=True)
|
||||
self.checked_command_output(["make"], cwd=props.path)
|
||||
|
||||
def build_and_install_example(self):
|
||||
assert self.args.application
|
||||
self.checked_command_output([
|
||||
"cargo", "build", "--release", "--target=thumbv7em-none-eabi",
|
||||
"--features={}".format(",".join(self.args.features)), "--example",
|
||||
self.args.application
|
||||
])
|
||||
self.install_elf_file(
|
||||
os.path.join("target/thumbv7em-none-eabi/release/examples",
|
||||
self.args.application))
|
||||
def build_example(self):
|
||||
info("Building example {}".format(self.args.application))
|
||||
self._build_app_or_example(is_example=True)
|
||||
|
||||
def build_and_install_opensk(self):
|
||||
assert self.args.application
|
||||
def build_opensk(self):
|
||||
info("Building OpenSK application")
|
||||
self.checked_command_output([
|
||||
"cargo",
|
||||
"build",
|
||||
"--release",
|
||||
"--target=thumbv7em-none-eabi",
|
||||
"--features={}".format(",".join(self.args.features)),
|
||||
])
|
||||
self.install_elf_file(
|
||||
os.path.join("target/thumbv7em-none-eabi/release",
|
||||
self.args.application))
|
||||
self._build_app_or_example(is_example=False)
|
||||
|
||||
def _build_app_or_example(self, is_example):
|
||||
assert self.args.application
|
||||
# Ideally we would build a TAB file for all boards at once but depending on
|
||||
# the chip on the board, the link script could be totally different.
|
||||
# And elf2tab doesn't seem to let us set the boards a TAB file has been
|
||||
# created for. So at the moment we only build for the selected board.
|
||||
props = SUPPORTED_BOARDS[self.args.board]
|
||||
rust_flags = [
|
||||
"-C",
|
||||
"link-arg=-T{}".format(props.app_ldscript),
|
||||
"-C",
|
||||
"relocation-model=static",
|
||||
"-D",
|
||||
"warnings",
|
||||
"--remap-path-prefix={}=".format(os.getcwd()),
|
||||
]
|
||||
env = os.environ.copy()
|
||||
env["RUSTFLAGS"] = " ".join(rust_flags)
|
||||
|
||||
command = [
|
||||
"cargo", "build", "--release", "--target={}".format(props.arch),
|
||||
"--features={}".format(",".join(self.args.features))
|
||||
]
|
||||
if is_example:
|
||||
command.extend(["--example", self.args.application])
|
||||
self.checked_command_output(command, env=env)
|
||||
app_path = os.path.join("target", props.arch, "release")
|
||||
if is_example:
|
||||
app_path = os.path.join(app_path, "examples")
|
||||
app_path = os.path.join(app_path, self.args.application)
|
||||
# Create a TAB file
|
||||
self.create_tab_file({props.arch: app_path})
|
||||
|
||||
def generate_crypto_materials(self, force_regenerate):
|
||||
has_error = subprocess.call([
|
||||
@@ -208,8 +358,11 @@ class OpenSKInstaller:
|
||||
error(("Something went wrong while trying to generate ECC "
|
||||
"key and/or certificate for OpenSK"))
|
||||
|
||||
def install_elf_file(self, elf_path):
|
||||
def create_tab_file(self, binaries):
|
||||
assert binaries
|
||||
assert self.args.application
|
||||
info("Generating Tock TAB file for application/example {}".format(
|
||||
self.args.application))
|
||||
package_parameter = "-n"
|
||||
elf2tab_ver = self.checked_command_output(["elf2tab", "--version"]).split(
|
||||
" ", maxsplit=1)[1]
|
||||
@@ -221,17 +374,26 @@ class OpenSKInstaller:
|
||||
os.makedirs(self.tab_folder, exist_ok=True)
|
||||
tab_filename = os.path.join(self.tab_folder,
|
||||
"{}.tab".format(self.args.application))
|
||||
shutil.copyfile(elf_path, self.target_elf_filename)
|
||||
self.checked_command_output([
|
||||
"elf2tab", package_parameter, self.args.application, "-o", tab_filename,
|
||||
self.target_elf_filename, "--stack={}".format(STACK_SIZE),
|
||||
"--app-heap={}".format(APP_HEAP_SIZE), "--kernel-heap=1024",
|
||||
"--protected-region-size=64"
|
||||
elf2tab_args = [
|
||||
"elf2tab", package_parameter, self.args.application, "-o", tab_filename
|
||||
]
|
||||
for arch, app_file in binaries.items():
|
||||
dest_file = os.path.join(self.tab_folder, "{}.elf".format(arch))
|
||||
shutil.copyfile(app_file, dest_file)
|
||||
elf2tab_args.append(dest_file)
|
||||
|
||||
elf2tab_args.extend([
|
||||
"--stack={}".format(STACK_SIZE), "--app-heap={}".format(APP_HEAP_SIZE),
|
||||
"--kernel-heap=1024", "--protected-region-size=64"
|
||||
])
|
||||
self.install_padding()
|
||||
self.checked_command_output(elf2tab_args)
|
||||
|
||||
def install_tab_file(self, tab_filename):
|
||||
assert self.args.application
|
||||
info("Installing Tock application {}".format(self.args.application))
|
||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||
args = copy.copy(self.tockloader_default_args)
|
||||
setattr(args, "app_address", 0x40000)
|
||||
setattr(args, "app_address", board_props.app_address)
|
||||
setattr(args, "erase", self.args.clear_apps)
|
||||
setattr(args, "make", False)
|
||||
setattr(args, "no_replace", False)
|
||||
@@ -244,16 +406,38 @@ class OpenSKInstaller:
|
||||
fatal("Couldn't install Tock application {}: {}".format(
|
||||
self.args.application, str(e)))
|
||||
|
||||
def install_padding(self):
|
||||
def get_padding(self):
|
||||
fake_header = tbfh.TBFHeader("")
|
||||
fake_header.version = 2
|
||||
fake_header.fields["header_size"] = 0x10
|
||||
fake_header.fields["total_size"] = 0x10000
|
||||
fake_header.fields["total_size"] = (
|
||||
SUPPORTED_BOARDS[self.args.board].app_address -
|
||||
SUPPORTED_BOARDS[self.args.board].padding_address)
|
||||
fake_header.fields["flags"] = 0
|
||||
padding = fake_header.get_binary()
|
||||
return fake_header.get_binary()
|
||||
|
||||
def install_tock_os(self):
|
||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||
kernel_file = os.path.join(board_props.path, "target", board_props.arch,
|
||||
"release", "{}.bin".format(self.args.board))
|
||||
info("Flashing file {}.".format(kernel_file))
|
||||
with open(kernel_file, "rb") as f:
|
||||
kernel = f.read()
|
||||
args = copy.copy(self.tockloader_default_args)
|
||||
setattr(args, "address", board_props.app_address)
|
||||
tock = loader.TockLoader(args)
|
||||
tock.open(args)
|
||||
try:
|
||||
tock.flash_binary(kernel, board_props.kernel_address)
|
||||
except TockLoaderException as e:
|
||||
fatal("Couldn't install Tock OS: {}".format(str(e)))
|
||||
|
||||
def install_padding(self):
|
||||
padding = self.get_padding()
|
||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||
info("Flashing padding application")
|
||||
args = copy.copy(self.tockloader_default_args)
|
||||
setattr(args, "address", 0x30000)
|
||||
setattr(args, "address", board_props.padding_address)
|
||||
tock = loader.TockLoader(args)
|
||||
tock.open(args)
|
||||
try:
|
||||
@@ -263,7 +447,8 @@ class OpenSKInstaller:
|
||||
|
||||
def clear_apps(self):
|
||||
args = copy.copy(self.tockloader_default_args)
|
||||
setattr(args, "app_address", 0x40000)
|
||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||
setattr(args, "app_address", board_props.app_address)
|
||||
info("Erasing all installed applications")
|
||||
tock = loader.TockLoader(args)
|
||||
tock.open(args)
|
||||
@@ -271,11 +456,13 @@ class OpenSKInstaller:
|
||||
tock.erase_apps(False)
|
||||
except TockLoaderException as e:
|
||||
# Erasing apps is not critical
|
||||
info(("A non-critical error occured while erasing "
|
||||
info(("A non-critical error occurred while erasing "
|
||||
"apps: {}".format(str(e))))
|
||||
|
||||
# pylint: disable=W0212
|
||||
# pylint: disable=protected-access
|
||||
def verify_flashed_app(self, expected_app):
|
||||
if self.args.programmer not in ("jlink", "openocd"):
|
||||
return False
|
||||
args = copy.copy(self.tockloader_default_args)
|
||||
tock = loader.TockLoader(args)
|
||||
app_found = False
|
||||
@@ -284,51 +471,205 @@ class OpenSKInstaller:
|
||||
app_found = expected_app in apps
|
||||
return app_found
|
||||
|
||||
def create_hex_file(self, dest_file):
|
||||
# We produce an intelhex file with everything in it
|
||||
# https://en.wikipedia.org/wiki/Intel_HEX
|
||||
# pylint: disable=g-import-not-at-top,import-outside-toplevel
|
||||
import intelhex
|
||||
board_props = SUPPORTED_BOARDS[self.args.board]
|
||||
final_hex = intelhex.IntelHex()
|
||||
|
||||
if self.args.tockos:
|
||||
# Process kernel
|
||||
kernel_path = os.path.join(board_props.path, "target", board_props.arch,
|
||||
"release", "{}.bin".format(self.args.board))
|
||||
with open(kernel_path, "rb") as kernel:
|
||||
kern_hex = intelhex.IntelHex()
|
||||
kern_hex.frombytes(kernel.read(), offset=board_props.kernel_address)
|
||||
final_hex.merge(kern_hex, overlap="error")
|
||||
|
||||
if self.args.application:
|
||||
# Add padding
|
||||
if board_props.padding_address:
|
||||
padding_hex = intelhex.IntelHex()
|
||||
padding_hex.frombytes(
|
||||
self.get_padding(), offset=board_props.padding_address)
|
||||
final_hex.merge(padding_hex, overlap="error")
|
||||
|
||||
# Now we can add the application from the TAB file
|
||||
app_tab_path = "target/tab/{}.tab".format(self.args.application)
|
||||
assert os.path.exists(app_tab_path)
|
||||
app_tab = tab.TAB(app_tab_path)
|
||||
if board_props.arch not in app_tab.get_supported_architectures():
|
||||
fatal(("It seems that the TAB file was not produced for the "
|
||||
"architecture {}".format(board_props.arch)))
|
||||
app_hex = intelhex.IntelHex()
|
||||
app_hex.frombytes(
|
||||
app_tab.extract_app(board_props.arch).get_binary(),
|
||||
offset=board_props.app_address)
|
||||
final_hex.merge(app_hex)
|
||||
info("Generating all-merged HEX file: {}".format(dest_file))
|
||||
final_hex.tofile(dest_file, format="hex")
|
||||
|
||||
def check_prerequisites(self):
|
||||
if self.args.programmer == "jlink":
|
||||
assert_mandatory_binary("JLinkExe")
|
||||
|
||||
if self.args.programmer == "openocd":
|
||||
assert_mandatory_binary("openocd")
|
||||
|
||||
if self.args.programmer == "pyocd":
|
||||
assert_mandatory_binary("pyocd")
|
||||
assert_python_library("intelhex")
|
||||
if not SUPPORTED_BOARDS[self.args.board].pyocd_target:
|
||||
fatal("This board doesn't seem to support flashing through pyocd.")
|
||||
|
||||
if self.args.programmer == "nordicdfu":
|
||||
assert_mandatory_binary("nrfutil")
|
||||
assert_python_library("intelhex")
|
||||
assert_python_library("nordicsemi.lister")
|
||||
nrfutil_version = __import__("nordicsemi.version").version.NRFUTIL_VERSION
|
||||
if not nrfutil_version.startswith("6."):
|
||||
fatal(("You need to install nrfutil python3 package v6.0 or above. "
|
||||
"Found: {}".format(nrfutil_version)))
|
||||
if not SUPPORTED_BOARDS[self.args.board].nordic_dfu:
|
||||
fatal("This board doesn't support flashing over DFU.")
|
||||
|
||||
if self.args.programmer == "none":
|
||||
assert_python_library("intelhex")
|
||||
|
||||
def run(self):
|
||||
if self.args.action is None:
|
||||
# Nothing to do
|
||||
if self.args.listing == "boards":
|
||||
print(os.linesep.join(get_supported_boards()))
|
||||
return 0
|
||||
|
||||
if self.args.listing == "programmers":
|
||||
print(os.linesep.join(PROGRAMMERS))
|
||||
return 0
|
||||
|
||||
if self.args.listing:
|
||||
# Missing check?
|
||||
fatal("Listing {} is not implemented.".format(self.args.listing))
|
||||
|
||||
self.check_prerequisites()
|
||||
self.update_rustc_if_needed()
|
||||
|
||||
if self.args.action == "os":
|
||||
info("Installing Tock on board {}".format(self.args.board))
|
||||
self.build_and_install_tockos()
|
||||
if not self.args.tockos and not self.args.application:
|
||||
info("Nothing to do.")
|
||||
return 0
|
||||
|
||||
if self.args.action == "app":
|
||||
if self.args.application is None:
|
||||
fatal("Unspecified application")
|
||||
# Compile what needs to be compiled
|
||||
if self.args.tockos:
|
||||
self.build_tockos()
|
||||
|
||||
if self.args.application == "ctap2":
|
||||
self.generate_crypto_materials(self.args.regenerate_keys)
|
||||
self.build_opensk()
|
||||
elif self.args.application is None:
|
||||
info("No application selected.")
|
||||
else:
|
||||
self.build_example()
|
||||
|
||||
# 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:
|
||||
self.clear_apps()
|
||||
if self.args.application == "ctap2":
|
||||
self.generate_crypto_materials(self.args.regenerate_keys)
|
||||
self.build_and_install_opensk()
|
||||
else:
|
||||
self.build_and_install_example()
|
||||
if self.verify_flashed_app(self.args.application):
|
||||
info("You're all set!")
|
||||
return 0
|
||||
error(("It seems that something went wrong. "
|
||||
"App/example not found on your board."))
|
||||
return 1
|
||||
if self.args.tockos:
|
||||
# Install Tock OS
|
||||
self.install_tock_os()
|
||||
# Install padding and application if needed
|
||||
if self.args.application:
|
||||
self.install_padding()
|
||||
self.install_tab_file("target/tab/{}.tab".format(self.args.application))
|
||||
if self.verify_flashed_app(self.args.application):
|
||||
info("You're all set!")
|
||||
return 0
|
||||
error(
|
||||
("It seems that something went wrong. App/example not found "
|
||||
"on your board. Ensure the connections between the programmer and "
|
||||
"the board are correct."))
|
||||
return 1
|
||||
return 0
|
||||
|
||||
if self.args.programmer in ("pyocd", "nordicdfu", "none"):
|
||||
dest_file = "target/{}_merged.hex".format(self.args.board)
|
||||
os.makedirs("target", exist_ok=True)
|
||||
self.create_hex_file(dest_file)
|
||||
|
||||
if self.args.programmer == "pyocd":
|
||||
info("Flashing HEX file")
|
||||
self.checked_command_output([
|
||||
"pyocd", "flash", "--target={}".format(board_props.pyocd_target),
|
||||
"--format=hex", "--erase=auto", dest_file
|
||||
])
|
||||
if self.args.programmer == "nordicdfu":
|
||||
info("Creating DFU package")
|
||||
dfu_pkg_file = "target/{}_dfu.zip".format(self.args.board)
|
||||
self.checked_command_output([
|
||||
"nrfutil", "pkg", "generate", "--hw-version=52", "--sd-req=0",
|
||||
"--application-version=1", "--application={}".format(dest_file),
|
||||
dfu_pkg_file
|
||||
])
|
||||
info(
|
||||
"Please insert the dongle and switch it to DFU mode by keeping the "
|
||||
"button pressed while inserting...")
|
||||
info("Press [ENTER] when ready.")
|
||||
_ = input()
|
||||
# Search for the DFU devices
|
||||
serial_number = []
|
||||
# pylint: disable=g-import-not-at-top,import-outside-toplevel
|
||||
from nordicsemi.lister import device_lister
|
||||
for device in device_lister.DeviceLister().enumerate():
|
||||
if device.vendor_id == "1915" and device.product_id == "521F":
|
||||
serial_number.append(device.serial_number)
|
||||
if not serial_number:
|
||||
fatal("Couldn't find any DFU device on your system.")
|
||||
if len(serial_number) > 1:
|
||||
fatal("Multiple DFU devices are detected. Please only connect one.")
|
||||
# Run the command without capturing stdout so that we show progress
|
||||
info("Flashing device using DFU...")
|
||||
return subprocess.run(
|
||||
[
|
||||
"nrfutil", "dfu", "usb-serial",
|
||||
"--package={}".format(dfu_pkg_file),
|
||||
"--serial-number={}".format(serial_number[0])
|
||||
],
|
||||
check=False,
|
||||
timeout=None,
|
||||
).returncode
|
||||
return 0
|
||||
|
||||
|
||||
def main(args):
|
||||
# Make sure the current working directory is the right one before running
|
||||
os.chdir(os.path.realpath(os.path.dirname(__file__)))
|
||||
# Check for pre-requisite executable files.
|
||||
if not shutil.which("JLinkExe"):
|
||||
fatal(("Couldn't find JLinkExe binary. Make sure Segger JLink tools "
|
||||
"are installed and correctly set up."))
|
||||
|
||||
OpenSKInstaller(args).run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
shared_parser = argparse.ArgumentParser(add_help=False)
|
||||
shared_parser.add_argument(
|
||||
main_parser = argparse.ArgumentParser()
|
||||
action_group = main_parser.add_mutually_exclusive_group(required=True)
|
||||
action_group.add_argument(
|
||||
"--list",
|
||||
metavar="WHAT",
|
||||
choices=("boards", "programmers"),
|
||||
default=None,
|
||||
dest="listing",
|
||||
help=("List supported boards or programmers, 1 per line and then exit."),
|
||||
)
|
||||
action_group.add_argument(
|
||||
"--board",
|
||||
metavar="BOARD_NAME",
|
||||
dest="board",
|
||||
default=None,
|
||||
choices=get_supported_boards(),
|
||||
help="Indicates which board Tock OS will be compiled for.",
|
||||
)
|
||||
|
||||
main_parser.add_argument(
|
||||
"--dont-clear-apps",
|
||||
action="store_false",
|
||||
default=True,
|
||||
@@ -336,32 +677,26 @@ if __name__ == "__main__":
|
||||
help=("When installing an application, previously installed "
|
||||
"applications won't be erased from the board."),
|
||||
)
|
||||
|
||||
main_parser = argparse.ArgumentParser()
|
||||
commands = main_parser.add_subparsers(
|
||||
dest="action",
|
||||
help=("Indicates which part of the firmware should be compiled and "
|
||||
"flashed to the connected board."))
|
||||
|
||||
os_commands = commands.add_parser(
|
||||
"os",
|
||||
parents=[shared_parser],
|
||||
help=("Compiles and installs Tock OS. The target board must be "
|
||||
"specified by setting the --board argument."),
|
||||
main_parser.add_argument(
|
||||
"--programmer",
|
||||
metavar="METHOD",
|
||||
dest="programmer",
|
||||
choices=PROGRAMMERS,
|
||||
default="jlink",
|
||||
help=("Sets the method to be used to flash Tock OS or the application "
|
||||
"on the target board."),
|
||||
)
|
||||
os_commands.add_argument(
|
||||
"--board",
|
||||
metavar="BOARD_NAME",
|
||||
dest="board",
|
||||
choices=get_supported_boards(),
|
||||
help="Indicates which board Tock OS will be compiled for.",
|
||||
required=True)
|
||||
|
||||
app_commands = commands.add_parser(
|
||||
"app",
|
||||
parents=[shared_parser],
|
||||
help="compiles and installs an application.")
|
||||
app_commands.add_argument(
|
||||
main_parser.add_argument(
|
||||
"--no-tockos",
|
||||
action="store_false",
|
||||
default=True,
|
||||
dest="tockos",
|
||||
help=("Only compiles and flash the application/example. "
|
||||
"Otherwise TockOS will also be bundled and flashed."),
|
||||
)
|
||||
|
||||
main_parser.add_argument(
|
||||
"--panic-console",
|
||||
action="append_const",
|
||||
const="panic_console",
|
||||
@@ -370,7 +705,16 @@ if __name__ == "__main__":
|
||||
"output messages before starting blinking the LEDs on the "
|
||||
"board."),
|
||||
)
|
||||
app_commands.add_argument(
|
||||
main_parser.add_argument(
|
||||
"--debug",
|
||||
action="append_const",
|
||||
const="debug_ctap",
|
||||
dest="features",
|
||||
help=("Compiles and installs the OpenSK application in debug mode "
|
||||
"(i.e. more debug messages will be sent over the console port "
|
||||
"such as hexdumps of packets)."),
|
||||
)
|
||||
main_parser.add_argument(
|
||||
"--debug-allocations",
|
||||
action="append_const",
|
||||
const="debug_allocations",
|
||||
@@ -378,7 +722,15 @@ if __name__ == "__main__":
|
||||
help=("The console will be used to output allocator statistics every "
|
||||
"time an allocation/deallocation happens."),
|
||||
)
|
||||
app_commands.add_argument(
|
||||
main_parser.add_argument(
|
||||
"--verbose",
|
||||
action="append_const",
|
||||
const="verbose",
|
||||
dest="features",
|
||||
help=("The console will be used to output verbose information about the "
|
||||
"OpenSK application. This also automatically activates --debug."),
|
||||
)
|
||||
main_parser.add_argument(
|
||||
"--no-u2f",
|
||||
action=RemoveConstAction,
|
||||
const="with_ctap1",
|
||||
@@ -386,7 +738,7 @@ if __name__ == "__main__":
|
||||
help=("Compiles the OpenSK application without backward compatible "
|
||||
"support for U2F/CTAP1 protocol."),
|
||||
)
|
||||
app_commands.add_argument(
|
||||
main_parser.add_argument(
|
||||
"--regen-keys",
|
||||
action="store_true",
|
||||
default=False,
|
||||
@@ -396,16 +748,7 @@ if __name__ == "__main__":
|
||||
"This is useful to allow flashing multiple OpenSK authenticators "
|
||||
"in a row without them being considered clones."),
|
||||
)
|
||||
app_commands.add_argument(
|
||||
"--debug",
|
||||
action="append_const",
|
||||
const="debug_ctap",
|
||||
dest="features",
|
||||
help=("Compiles and installs the OpenSK application in debug mode "
|
||||
"(i.e. more debug messages will be sent over the console port "
|
||||
"such as hexdumps of packets)."),
|
||||
)
|
||||
app_commands.add_argument(
|
||||
main_parser.add_argument(
|
||||
"--no-persistent-storage",
|
||||
action="append_const",
|
||||
const="ram_storage",
|
||||
@@ -413,7 +756,15 @@ if __name__ == "__main__":
|
||||
help=("Compiles and installs the OpenSK application without persistent "
|
||||
"storage (i.e. unplugging the key will reset the key)."),
|
||||
)
|
||||
apps_group = app_commands.add_mutually_exclusive_group()
|
||||
|
||||
apps_group = main_parser.add_mutually_exclusive_group(required=True)
|
||||
apps_group.add_argument(
|
||||
"--no-app",
|
||||
dest="application",
|
||||
action="store_const",
|
||||
const=None,
|
||||
help=("Doesn't compile nor install any application. Useful when you only "
|
||||
"want to update Tock OS kernel."))
|
||||
apps_group.add_argument(
|
||||
"--opensk",
|
||||
dest="application",
|
||||
@@ -428,6 +779,6 @@ if __name__ == "__main__":
|
||||
help=("Compiles and installs the crypto_bench example that tests "
|
||||
"the performance of the cryptographic algorithms on the board."))
|
||||
|
||||
app_commands.set_defaults(features=["with_ctap1"])
|
||||
main_parser.set_defaults(features=["with_ctap1"])
|
||||
|
||||
main(main_parser.parse_args())
|
||||
|
||||
152
docs/install.md
152
docs/install.md
@@ -16,8 +16,9 @@ You will need one the following supported boards:
|
||||
scenarios as the JTAG probe is already on the board.
|
||||
* [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
|
||||
to have a more practical form factor.
|
||||
* [Makerdiary nRF52840-MDK USB dongle](https://wiki.makerdiary.com/nrf52840-mdk/).
|
||||
|
||||
In the case of the Nordic USB dongle, you will also need the following extra
|
||||
In the case of the Nordic USB dongle, you may also need the following extra
|
||||
hardware:
|
||||
|
||||
* a [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) JTAG
|
||||
@@ -30,13 +31,18 @@ hardware:
|
||||
[Tag-Connect TC2050 retainer clip](http://www.tag-connect.com/TC2050-CLIP)
|
||||
to keep the spring loaded connector pressed to the PCB.
|
||||
|
||||
Although [OpenOCD](http://openocd.org/) should be supported we encountered some
|
||||
issues while trying to flash a firmware with it. Therefore we suggest at the
|
||||
moment to use a
|
||||
[Segger J-Link](https://www.segger.com/products/debug-probes/j-link/) probe
|
||||
instead.
|
||||
Additionnaly, OpenSK supports other ways to flash your board:
|
||||
|
||||
This guide **does not** cover how to setup the JTAG probe on your system.
|
||||
* [OpenOCD](http://openocd.org/).
|
||||
* [Segger J-Link](https://www.segger.com/products/debug-probes/j-link/)
|
||||
(default method).
|
||||
* [pyOCD](https://pypi.org/project/pyocd/).
|
||||
* [nrfutil](https://pypi.org/project/nrfutil/) for the USB dongle boards that
|
||||
supports it, which allows you to directly flash a working board over USB
|
||||
without additional hardware.
|
||||
|
||||
This guide **does not** cover how to setup the JTAG probe and their related
|
||||
tools on your system.
|
||||
|
||||
### Software
|
||||
|
||||
@@ -141,17 +147,17 @@ Our build script `build.rs` is responsible for converting `opensk_cert.pem` and
|
||||
|
||||
1. Connect a micro USB cable to the JTAG USB port.
|
||||
|
||||
1. Run our script for compiling/flashing Tock OS on your device (_output may
|
||||
differ_):
|
||||
1. Run our script for compiling/flashing Tock OS and OpenSK on your device
|
||||
(_output may differ_):
|
||||
|
||||
```shell
|
||||
$ ./deploy.py os --board=nrf52840_dk
|
||||
$ ./deploy.py --board=nrf52840dk --opensk
|
||||
info: Updating rust toolchain to nightly-2020-02-03
|
||||
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
|
||||
info: checking for self-updates
|
||||
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
|
||||
info: Rust toolchain up-to-date
|
||||
info: Installing Tock on board nrf52840_dk
|
||||
info: Building Tock OS for board nrf52840dk
|
||||
Compiling tock-registers v0.5.0 (./third_party/tock/libraries/tock-register-interface)
|
||||
Compiling tock-cells v0.1.0 (./third_party/tock/libraries/tock-cells)
|
||||
Compiling enum_primitive v0.1.0 (./third_party/tock/libraries/enum_primitive)
|
||||
@@ -166,47 +172,17 @@ Our build script `build.rs` is responsible for converting `opensk_cert.pem` and
|
||||
Compiling nrf52840 v0.1.0 (./third_party/tock/chips/nrf52840)
|
||||
Compiling components v0.1.0 (./third_party/tock/boards/components)
|
||||
Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base)
|
||||
Finished release [optimized + debuginfo] target(s) in 11.97s
|
||||
[STATUS ] Flashing binar(y|ies) to board...
|
||||
[INFO ] Using known arch and jtag-device for known board nrf52dk
|
||||
[INFO ] Finished in 0.284 seconds
|
||||
```
|
||||
|
||||
1. Run our script for compiling/flashing the OpenSK application on your device
|
||||
(_output may differ_):
|
||||
|
||||
```shell
|
||||
$ ./deploy.py app --opensk
|
||||
info: Updating rust toolchain to nightly-2020-02-03
|
||||
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
|
||||
info: checking for self-updates
|
||||
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
|
||||
info: Rust toolchain up-to-date
|
||||
info: Erasing all installed applications
|
||||
All apps have been erased.
|
||||
info: Building OpenSK application
|
||||
Compiling autocfg v1.0.0
|
||||
Compiling pkg-config v0.3.17
|
||||
Compiling cc v1.0.50
|
||||
Compiling libc v0.2.66
|
||||
Compiling bitflags v1.2.1
|
||||
Compiling foreign-types-shared v0.1.1
|
||||
Compiling openssl v0.10.28
|
||||
Compiling cfg-if v0.1.10
|
||||
Compiling lazy_static v1.4.0
|
||||
Compiling byteorder v1.3.2
|
||||
Compiling linked_list_allocator v0.6.6
|
||||
Compiling arrayref v0.3.6
|
||||
Compiling cbor v0.1.0 (./libraries/cbor)
|
||||
Compiling subtle v2.2.2
|
||||
Compiling foreign-types v0.3.2
|
||||
Compiling libtock v0.1.0 (./third_party/libtock-rs)
|
||||
Compiling crypto v0.1.0 (./libraries/crypto)
|
||||
Compiling openssl-sys v0.9.54
|
||||
Compiling ctap2 v0.1.0 (.)
|
||||
Finished release [optimized] target(s) in 15.34s
|
||||
info: Flashing padding application
|
||||
info: Installing Tock application ctap2
|
||||
Finished release [optimized + debuginfo] target(s) in 13.15s
|
||||
info: Converting Tock OS file into a binary
|
||||
info: Building OpenSK application
|
||||
Finished release [optimized] target(s) in 0.02s
|
||||
info: Generating Tock TAB file for application/example ctap2
|
||||
info: Erasing all installed applications
|
||||
All apps have been erased.
|
||||
info: Flashing file third_party/tock/boards/nordic/nrf52840dk/target/thumbv7em-none-eabi/release/nrf52840dk.bin.
|
||||
info: Flashing padding application
|
||||
info: Installing Tock application ctap2
|
||||
info: You're all set!
|
||||
```
|
||||
|
||||
1. Connect a micro USB cable to the device USB port.
|
||||
@@ -217,6 +193,8 @@ the board in order to see your OpenSK device on your system.
|
||||
|
||||
#### Nordic nRF52840 Dongle
|
||||
|
||||
##### Using external programmer (JLink, OpenOCD, etc.)
|
||||
|
||||

|
||||
|
||||
1. The JTAG probe used for programming won't provide power to the board.
|
||||
@@ -232,17 +210,18 @@ the board in order to see your OpenSK device on your system.
|
||||
|
||||

|
||||
|
||||
1. Run our script for compiling/flashing Tock OS on your device (_output may
|
||||
differ_):
|
||||
1. Depending on the programmer you're using, you may have to adapt the next
|
||||
command line. Run our script for compiling/flashing Tock OS on your device
|
||||
(_output may differ_):
|
||||
|
||||
```shell
|
||||
$ ./deploy.py os --board=nrf52840_dongle
|
||||
$ ./deploy.py os --board=nrf52840_dongle --programmer=jlink
|
||||
info: Updating rust toolchain to nightly-2020-02-03
|
||||
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
|
||||
info: checking for self-updates
|
||||
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
|
||||
info: Rust toolchain up-to-date
|
||||
info: Installing Tock on board nrf52840_dongle
|
||||
info: Building Tock OS for board nrf52840_dongle
|
||||
Compiling tock-cells v0.1.0 (./third_party/tock/libraries/tock-cells)
|
||||
Compiling tock-registers v0.5.0 (./third_party/tock/libraries/tock-register-interface)
|
||||
Compiling enum_primitive v0.1.0 (./third_party/tock/libraries/enum_primitive)
|
||||
@@ -258,50 +237,39 @@ the board in order to see your OpenSK device on your system.
|
||||
Compiling components v0.1.0 (./third_party/tock/boards/components)
|
||||
Compiling nrf52dk_base v0.1.0 (./third_party/tock/boards/nordic/nrf52dk_base)
|
||||
Finished release [optimized + debuginfo] target(s) in 11.72s
|
||||
[STATUS ] Flashing binar(y|ies) to board...
|
||||
[INFO ] Using known arch and jtag-device for known board nrf52dk
|
||||
[INFO ] Finished in 0.280 seconds
|
||||
```
|
||||
|
||||
1. Run our script for compiling/flashing the OpenSK application on your device
|
||||
(_output may differ_):
|
||||
|
||||
```shell
|
||||
$ ./deploy.py app --opensk
|
||||
info: Updating rust toolchain to nightly-2020-02-03
|
||||
info: syncing channel updates for 'nightly-2020-02-03-x86_64-unknown-linux-gnu'
|
||||
info: checking for self-updates
|
||||
info: component 'rust-std' for target 'thumbv7em-none-eabi' is up to date
|
||||
info: Rust toolchain up-to-date
|
||||
info: Converting Tock OS file into a binary
|
||||
info: Building OpenSK application
|
||||
Finished release [optimized] target(s) in 0.02s
|
||||
info: Generating Tock TAB file for application/example ctap2
|
||||
info: Erasing all installed applications
|
||||
All apps have been erased.
|
||||
info: Building OpenSK application
|
||||
Compiling autocfg v1.0.0
|
||||
Compiling pkg-config v0.3.17
|
||||
Compiling cc v1.0.50
|
||||
Compiling libc v0.2.66
|
||||
Compiling bitflags v1.2.1
|
||||
Compiling foreign-types-shared v0.1.1
|
||||
Compiling openssl v0.10.28
|
||||
Compiling cfg-if v0.1.10
|
||||
Compiling lazy_static v1.4.0
|
||||
Compiling byteorder v1.3.2
|
||||
Compiling linked_list_allocator v0.6.6
|
||||
Compiling arrayref v0.3.6
|
||||
Compiling cbor v0.1.0 (./libraries/cbor)
|
||||
Compiling subtle v2.2.2
|
||||
Compiling foreign-types v0.3.2
|
||||
Compiling libtock v0.1.0 (./third_party/libtock-rs)
|
||||
Compiling crypto v0.1.0 (./libraries/crypto)
|
||||
Compiling openssl-sys v0.9.54
|
||||
Compiling ctap2 v0.1.0 (.)
|
||||
Finished release [optimized] target(s) in 15.34s
|
||||
info: Flashing file third_party/tock/boards/nordic/nrf52840_dongle/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin.
|
||||
info: Flashing padding application
|
||||
info: Installing Tock application ctap2
|
||||
info: You're all set!
|
||||
```
|
||||
|
||||
1. Remove the programming cable and the USB-A extension cable.
|
||||
|
||||
### Advanced installation
|
||||
|
||||
Although flashing using a Segger JLink probe is the officially supported way,
|
||||
our tool, `deploy.py` also supports other methods:
|
||||
|
||||
* OpenOCD: `./deploy.py --board=nrf52840_dongle --opensk --programmer=openocd`
|
||||
* pyOCD: `./deploy.py --board=nrf52840_dongle --opensk --programmer=pyocd`
|
||||
* Nordic DFU: `./deploy.py --board=nrf52840_dongle --opensk
|
||||
--programmer=nordicdfu`
|
||||
* Custom: `./deploy.py --board=nrf52840_dongle --opensk --programmer=none`. In
|
||||
this case, an IntelHex file will be created and how to program a board is
|
||||
left to the user.
|
||||
|
||||
If your board is already flashed with Tock OS, you may skip installing it:
|
||||
`./deploy.py --board=nrf52840dk --opensk --no-tockos`
|
||||
|
||||
For more options, we invite you to read the help of our `deploy.py` script by
|
||||
running `./deploy.py --help`.
|
||||
|
||||
### Installing the udev rule (Linux only)
|
||||
|
||||
By default on Linux, a USB device will require root privilege in order interact
|
||||
|
||||
@@ -286,10 +286,10 @@ index 5abd2d84..5a726fdb 100644
|
||||
+ }
|
||||
+}
|
||||
diff --git a/kernel/src/callback.rs b/kernel/src/callback.rs
|
||||
index ece4a443..9a1afc84 100644
|
||||
index c812e0bf..bd1613b3 100644
|
||||
--- a/kernel/src/callback.rs
|
||||
+++ b/kernel/src/callback.rs
|
||||
@@ -52,6 +52,31 @@ impl AppId {
|
||||
@@ -130,6 +130,31 @@ impl AppId {
|
||||
(start, end)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -124,11 +124,11 @@ index 105f7120..535e5cd8 100644
|
||||
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
|
||||
nvmc: nvmc,
|
||||
diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs
|
||||
index 9305e6a7..40466f44 100644
|
||||
index bfc06429..5858d352 100644
|
||||
--- a/capsules/src/driver.rs
|
||||
+++ b/capsules/src/driver.rs
|
||||
@@ -24,6 +24,7 @@ pub enum NUM {
|
||||
Spi = 0x20001,
|
||||
@@ -25,6 +25,7 @@ pub enum NUM {
|
||||
I2cMaster = 0x20003,
|
||||
UsbUser = 0x20005,
|
||||
I2cMasterSlave = 0x20006,
|
||||
+ UsbCtap = 0x20009,
|
||||
@@ -509,10 +509,10 @@ index 00000000..da3d16d8
|
||||
+}
|
||||
diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
|
||||
new file mode 100644
|
||||
index 00000000..fdf7263a
|
||||
index 00000000..4b1916cf
|
||||
--- /dev/null
|
||||
+++ b/capsules/src/usb/usbc_ctap_hid.rs
|
||||
@@ -0,0 +1,352 @@
|
||||
@@ -0,0 +1,359 @@
|
||||
+//! A USB HID client of the USB hardware interface
|
||||
+
|
||||
+use super::descriptors::Buffer64;
|
||||
@@ -603,8 +603,9 @@ index 00000000..fdf7263a
|
||||
+pub struct ClientCtapHID<'a, 'b, C: 'a> {
|
||||
+ client_ctrl: ClientCtrl<'a, 'static, C>,
|
||||
+
|
||||
+ // A 64-byte buffer for the endpoint
|
||||
+ buffer: Buffer64,
|
||||
+ // 64-byte buffers for the endpoint
|
||||
+ in_buffer: Buffer64,
|
||||
+ out_buffer: Buffer64,
|
||||
+
|
||||
+ // Interaction with the client
|
||||
+ client: OptionalCell<&'b dyn CtapUsbClient>,
|
||||
@@ -648,7 +649,8 @@ index 00000000..fdf7263a
|
||||
+ LANGUAGES,
|
||||
+ STRINGS,
|
||||
+ ),
|
||||
+ buffer: Default::default(),
|
||||
+ in_buffer: Default::default(),
|
||||
+ out_buffer: Default::default(),
|
||||
+ client: OptionalCell::empty(),
|
||||
+ tx_packet: OptionalCell::empty(),
|
||||
+ pending_in: Cell::new(false),
|
||||
@@ -702,7 +704,7 @@ index 00000000..fdf7263a
|
||||
+ fn send_packet_to_client(&'a self) -> bool {
|
||||
+ // Copy the packet into a buffer to send to the client.
|
||||
+ let mut buf: [u8; 64] = [0; 64];
|
||||
+ for (i, x) in self.buffer.buf.iter().enumerate() {
|
||||
+ for (i, x) in self.out_buffer.buf.iter().enumerate() {
|
||||
+ buf[i] = x.get();
|
||||
+ }
|
||||
+
|
||||
@@ -735,11 +737,7 @@ index 00000000..fdf7263a
|
||||
+
|
||||
+ fn cancel_in_transaction(&'a self) -> bool {
|
||||
+ self.tx_packet.take();
|
||||
+ let result = self.pending_in.take();
|
||||
+ if result {
|
||||
+ self.controller().endpoint_cancel_in(1);
|
||||
+ }
|
||||
+ result
|
||||
+ self.pending_in.take()
|
||||
+ }
|
||||
+
|
||||
+ fn cancel_out_transaction(&'a self) -> bool {
|
||||
@@ -758,7 +756,10 @@ index 00000000..fdf7263a
|
||||
+ self.client_ctrl.enable();
|
||||
+
|
||||
+ // Set up the interrupt in-out endpoint
|
||||
+ self.controller().endpoint_set_buffer(1, &self.buffer.buf);
|
||||
+ self.controller()
|
||||
+ .endpoint_set_in_buffer(1, &self.in_buffer.buf);
|
||||
+ self.controller()
|
||||
+ .endpoint_set_out_buffer(1, &self.out_buffer.buf);
|
||||
+ self.controller()
|
||||
+ .endpoint_in_out_enable(TransferType::Interrupt, 1);
|
||||
+ }
|
||||
@@ -808,7 +809,7 @@ index 00000000..fdf7263a
|
||||
+ }
|
||||
+
|
||||
+ if let Some(packet) = self.tx_packet.take() {
|
||||
+ let buf = &self.buffer.buf;
|
||||
+ let buf = &self.in_buffer.buf;
|
||||
+ for i in 0..64 {
|
||||
+ buf[i].set(packet[i]);
|
||||
+ }
|
||||
@@ -861,149 +862,13 @@ index 00000000..fdf7263a
|
||||
+ panic!("Unexpected tx_packet while a packet was being transmitted.");
|
||||
+ }
|
||||
+ self.pending_in.set(false);
|
||||
+
|
||||
+ // Clear any pending packet on the receiving side.
|
||||
+ // It's up to the client to handle the transmitted packet and decide if they want to
|
||||
+ // receive another packet.
|
||||
+ self.cancel_out_transaction();
|
||||
+
|
||||
+ // Notify the client
|
||||
+ self.client.map(|client| client.packet_transmitted());
|
||||
+ }
|
||||
+}
|
||||
diff --git a/chips/nrf52/src/usbd.rs b/chips/nrf52/src/usbd.rs
|
||||
index 8ddb5895..8c1992cc 100644
|
||||
--- a/chips/nrf52/src/usbd.rs
|
||||
+++ b/chips/nrf52/src/usbd.rs
|
||||
@@ -1499,7 +1499,23 @@ impl<'a> Usbd<'a> {
|
||||
if epdatastatus.is_set(status_epin(endpoint)) {
|
||||
let (transfer_type, direction, state) =
|
||||
self.descriptors[endpoint].state.get().bulk_state();
|
||||
- assert_eq!(state, BulkState::InData);
|
||||
+ match state {
|
||||
+ BulkState::InData => {
|
||||
+ // Totally expected state. Nothing to do.
|
||||
+ }
|
||||
+ BulkState::Init => {
|
||||
+ internal_warn!(
|
||||
+ "Received a stale epdata IN in an unexpected state: {:?}",
|
||||
+ state
|
||||
+ );
|
||||
+ }
|
||||
+ BulkState::OutDelay
|
||||
+ | BulkState::OutData
|
||||
+ | BulkState::OutDma
|
||||
+ | BulkState::InDma => {
|
||||
+ internal_err!("Unexpected state: {:?}", state);
|
||||
+ }
|
||||
+ }
|
||||
self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
direction,
|
||||
@@ -1677,7 +1693,7 @@ impl<'a> Usbd<'a> {
|
||||
}
|
||||
|
||||
fn transmit_in(&self, endpoint: usize) {
|
||||
- debug_info!("transmit_in({})", endpoint);
|
||||
+ debug_events!("transmit_in({})", endpoint);
|
||||
let regs = &*self.registers;
|
||||
|
||||
self.client.map(|client| {
|
||||
@@ -1717,7 +1733,7 @@ impl<'a> Usbd<'a> {
|
||||
}
|
||||
|
||||
fn transmit_out(&self, endpoint: usize) {
|
||||
- debug_info!("transmit_out({})", endpoint);
|
||||
+ debug_events!("transmit_out({})", endpoint);
|
||||
|
||||
let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
// Starting the DMA can only happen in the OutData state, i.e. after an EPDATA event.
|
||||
@@ -1882,11 +1898,13 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
|
||||
}
|
||||
|
||||
fn endpoint_resume_in(&self, endpoint: usize) {
|
||||
+ debug_events!("endpoint_resume_in({})", endpoint);
|
||||
+
|
||||
let (_, direction, _) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
assert!(direction.has_in());
|
||||
|
||||
if self.dma_pending.get() {
|
||||
- debug_info!("requesting resume_in[{}]", endpoint);
|
||||
+ debug_events!("requesting resume_in[{}]", endpoint);
|
||||
// A DMA is already pending. Schedule the resume for later.
|
||||
self.descriptors[endpoint].request_transmit_in.set(true);
|
||||
} else {
|
||||
@@ -1896,6 +1914,8 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
|
||||
}
|
||||
|
||||
fn endpoint_resume_out(&self, endpoint: usize) {
|
||||
+ debug_events!("endpoint_resume_out({})", endpoint);
|
||||
+
|
||||
let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
assert!(direction.has_out());
|
||||
|
||||
@@ -1914,7 +1934,7 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
|
||||
// happened in the meantime. This pending transaction will now
|
||||
// continue in transmit_out().
|
||||
if self.dma_pending.get() {
|
||||
- debug_info!("requesting resume_out[{}]", endpoint);
|
||||
+ debug_events!("requesting resume_out[{}]", endpoint);
|
||||
// A DMA is already pending. Schedule the resume for later.
|
||||
self.descriptors[endpoint].request_transmit_out.set(true);
|
||||
} else {
|
||||
@@ -1927,6 +1947,20 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
+
|
||||
+ fn endpoint_cancel_in(&self, endpoint: usize) {
|
||||
+ debug_events!("endpoint_cancel_in({})", endpoint);
|
||||
+
|
||||
+ let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
+ assert!(direction.has_in());
|
||||
+ assert_eq!(state, BulkState::InData);
|
||||
+
|
||||
+ self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
+ transfer_type,
|
||||
+ direction,
|
||||
+ BulkState::Init,
|
||||
+ ));
|
||||
+ }
|
||||
}
|
||||
|
||||
fn status_epin(ep: usize) -> Field<u32, EndpointStatus::Register> {
|
||||
diff --git a/chips/nrf52840/src/lib.rs b/chips/nrf52840/src/lib.rs
|
||||
index 9d58a705..942d0288 100644
|
||||
--- a/chips/nrf52840/src/lib.rs
|
||||
+++ b/chips/nrf52840/src/lib.rs
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
pub use nrf52::{
|
||||
acomp, adc, aes, ble_radio, clock, constants, crt1, ficr, i2c, ieee802154_radio, init, nvmc,
|
||||
- pinmux, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr,
|
||||
+ pinmux, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd,
|
||||
};
|
||||
pub mod chip;
|
||||
pub mod gpio;
|
||||
diff --git a/chips/sam4l/src/usbc/mod.rs b/chips/sam4l/src/usbc/mod.rs
|
||||
index 35f3bb7c..28a0b9f9 100644
|
||||
--- a/chips/sam4l/src/usbc/mod.rs
|
||||
+++ b/chips/sam4l/src/usbc/mod.rs
|
||||
@@ -1547,6 +1547,10 @@ impl hil::usb::UsbController<'a> for Usbc<'a> {
|
||||
requests.resume_out = true;
|
||||
self.requests[endpoint].set(requests);
|
||||
}
|
||||
+
|
||||
+ fn endpoint_cancel_in(&self, _endpoint: usize) {
|
||||
+ unimplemented!()
|
||||
+ }
|
||||
}
|
||||
|
||||
/// Static state to manage the USBC
|
||||
diff --git a/kernel/src/hil/usb.rs b/kernel/src/hil/usb.rs
|
||||
index 846f5e93..64610fa5 100644
|
||||
--- a/kernel/src/hil/usb.rs
|
||||
+++ b/kernel/src/hil/usb.rs
|
||||
@@ -27,6 +27,8 @@ pub trait UsbController<'a> {
|
||||
fn endpoint_resume_in(&self, endpoint: usize);
|
||||
|
||||
fn endpoint_resume_out(&self, endpoint: usize);
|
||||
+
|
||||
+ fn endpoint_cancel_in(&self, endpoint: usize);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
diff --git a/boards/nordic/nrf52840_dongle/layout.ld b/boards/nordic/nrf52840_dongle/layout.ld
|
||||
index 657b0d26..f86b2321 100644
|
||||
--- a/boards/nordic/nrf52840_dongle/layout.ld
|
||||
+++ b/boards/nordic/nrf52840_dongle/layout.ld
|
||||
@@ -1,6 +1,6 @@
|
||||
MEMORY
|
||||
{
|
||||
- rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K
|
||||
+ rom (rx) : ORIGIN = 0x00000000, LENGTH = 192K
|
||||
prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K
|
||||
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
||||
diff --git a/boards/nordic/nrf52840dk/layout.ld b/boards/nordic/nrf52840dk/layout.ld
|
||||
index 657b0d26..f86b2321 100644
|
||||
--- a/boards/nordic/nrf52840dk/layout.ld
|
||||
+++ b/boards/nordic/nrf52840dk/layout.ld
|
||||
@@ -1,6 +1,6 @@
|
||||
MEMORY
|
||||
{
|
||||
- rom (rx) : ORIGIN = 0x00000000, LENGTH = 128K
|
||||
+ rom (rx) : ORIGIN = 0x00000000, LENGTH = 192K
|
||||
prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K
|
||||
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
||||
21
patches/tock/04-nrf52-bootloader.patch
Normal file
21
patches/tock/04-nrf52-bootloader.patch
Normal file
@@ -0,0 +1,21 @@
|
||||
diff --git a/chips/nrf52/src/crt1.rs b/chips/nrf52/src/crt1.rs
|
||||
index 9703aac..281ceeb 100644
|
||||
--- a/chips/nrf52/src/crt1.rs
|
||||
+++ b/chips/nrf52/src/crt1.rs
|
||||
@@ -1,4 +1,4 @@
|
||||
-use cortexm4::{generic_isr, hard_fault_handler, nvic, svc_handler, systick_handler};
|
||||
+use cortexm4::{generic_isr, hard_fault_handler, nvic, scb, svc_handler, systick_handler};
|
||||
use tock_rt0;
|
||||
|
||||
/*
|
||||
@@ -168,5 +168,9 @@ pub unsafe extern "C" fn init() {
|
||||
tock_rt0::init_data(&mut _etext, &mut _srelocate, &mut _erelocate);
|
||||
tock_rt0::zero_bss(&mut _szero, &mut _ezero);
|
||||
|
||||
+ // Ensure that we are compatible with a bootloader.
|
||||
+ // For this we need to offset our vector table
|
||||
+ scb::set_vector_table_offset(BASE_VECTORS.as_ptr() as *const ());
|
||||
+
|
||||
nvic::enable_all();
|
||||
}
|
||||
|
||||
@@ -1,747 +0,0 @@
|
||||
diff --git a/capsules/src/usb/usbc_client.rs b/capsules/src/usb/usbc_client.rs
|
||||
index b0678c23..9fb43781 100644
|
||||
--- a/capsules/src/usb/usbc_client.rs
|
||||
+++ b/capsules/src/usb/usbc_client.rs
|
||||
@@ -115,11 +115,11 @@ impl<'a, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for Client<'a, C>
|
||||
self.client_ctrl.enable();
|
||||
|
||||
// Set up a bulk-in endpoint for debugging
|
||||
- self.controller().endpoint_set_buffer(1, self.buffer(1));
|
||||
+ self.controller().endpoint_set_in_buffer(1, self.buffer(1));
|
||||
self.controller().endpoint_in_enable(TransferType::Bulk, 1);
|
||||
|
||||
// Set up a bulk-out endpoint for debugging
|
||||
- self.controller().endpoint_set_buffer(2, self.buffer(2));
|
||||
+ self.controller().endpoint_set_out_buffer(2, self.buffer(2));
|
||||
self.controller().endpoint_out_enable(TransferType::Bulk, 2);
|
||||
}
|
||||
|
||||
diff --git a/capsules/src/usb/usbc_client_ctrl.rs b/capsules/src/usb/usbc_client_ctrl.rs
|
||||
index 2aaca0cc..5f9b253c 100644
|
||||
--- a/capsules/src/usb/usbc_client_ctrl.rs
|
||||
+++ b/capsules/src/usb/usbc_client_ctrl.rs
|
||||
@@ -201,7 +201,7 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtrl<'a, 'b, C> {
|
||||
pub fn enable(&'a self) {
|
||||
// Set up the default control endpoint
|
||||
self.controller
|
||||
- .endpoint_set_buffer(0, &self.ctrl_buffer.buf);
|
||||
+ .endpoint_set_ctrl_buffer(&self.ctrl_buffer.buf);
|
||||
self.controller
|
||||
.enable_as_device(hil::usb::DeviceSpeed::Full); // must be Full for Bulk transfers
|
||||
self.controller
|
||||
diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
|
||||
index fdf7263a..4b1916cf 100644
|
||||
--- a/capsules/src/usb/usbc_ctap_hid.rs
|
||||
+++ b/capsules/src/usb/usbc_ctap_hid.rs
|
||||
@@ -88,8 +88,9 @@ static HID: HIDDescriptor<'static> = HIDDescriptor {
|
||||
pub struct ClientCtapHID<'a, 'b, C: 'a> {
|
||||
client_ctrl: ClientCtrl<'a, 'static, C>,
|
||||
|
||||
- // A 64-byte buffer for the endpoint
|
||||
- buffer: Buffer64,
|
||||
+ // 64-byte buffers for the endpoint
|
||||
+ in_buffer: Buffer64,
|
||||
+ out_buffer: Buffer64,
|
||||
|
||||
// Interaction with the client
|
||||
client: OptionalCell<&'b dyn CtapUsbClient>,
|
||||
@@ -133,7 +134,8 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> {
|
||||
LANGUAGES,
|
||||
STRINGS,
|
||||
),
|
||||
- buffer: Default::default(),
|
||||
+ in_buffer: Default::default(),
|
||||
+ out_buffer: Default::default(),
|
||||
client: OptionalCell::empty(),
|
||||
tx_packet: OptionalCell::empty(),
|
||||
pending_in: Cell::new(false),
|
||||
@@ -187,7 +189,7 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> {
|
||||
fn send_packet_to_client(&'a self) -> bool {
|
||||
// Copy the packet into a buffer to send to the client.
|
||||
let mut buf: [u8; 64] = [0; 64];
|
||||
- for (i, x) in self.buffer.buf.iter().enumerate() {
|
||||
+ for (i, x) in self.out_buffer.buf.iter().enumerate() {
|
||||
buf[i] = x.get();
|
||||
}
|
||||
|
||||
@@ -220,11 +222,7 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> {
|
||||
|
||||
fn cancel_in_transaction(&'a self) -> bool {
|
||||
self.tx_packet.take();
|
||||
- let result = self.pending_in.take();
|
||||
- if result {
|
||||
- self.controller().endpoint_cancel_in(1);
|
||||
- }
|
||||
- result
|
||||
+ self.pending_in.take()
|
||||
}
|
||||
|
||||
fn cancel_out_transaction(&'a self) -> bool {
|
||||
@@ -243,7 +241,10 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for ClientCtap
|
||||
self.client_ctrl.enable();
|
||||
|
||||
// Set up the interrupt in-out endpoint
|
||||
- self.controller().endpoint_set_buffer(1, &self.buffer.buf);
|
||||
+ self.controller()
|
||||
+ .endpoint_set_in_buffer(1, &self.in_buffer.buf);
|
||||
+ self.controller()
|
||||
+ .endpoint_set_out_buffer(1, &self.out_buffer.buf);
|
||||
self.controller()
|
||||
.endpoint_in_out_enable(TransferType::Interrupt, 1);
|
||||
}
|
||||
@@ -293,7 +294,7 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for ClientCtap
|
||||
}
|
||||
|
||||
if let Some(packet) = self.tx_packet.take() {
|
||||
- let buf = &self.buffer.buf;
|
||||
+ let buf = &self.in_buffer.buf;
|
||||
for i in 0..64 {
|
||||
buf[i].set(packet[i]);
|
||||
}
|
||||
@@ -346,6 +347,12 @@ impl<'a, 'b, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for ClientCtap
|
||||
panic!("Unexpected tx_packet while a packet was being transmitted.");
|
||||
}
|
||||
self.pending_in.set(false);
|
||||
+
|
||||
+ // Clear any pending packet on the receiving side.
|
||||
+ // It's up to the client to handle the transmitted packet and decide if they want to
|
||||
+ // receive another packet.
|
||||
+ self.cancel_out_transaction();
|
||||
+
|
||||
// Notify the client
|
||||
self.client.map(|client| client.packet_transmitted());
|
||||
}
|
||||
diff --git a/chips/nrf52/src/usbd.rs b/chips/nrf52/src/usbd.rs
|
||||
index 8c1992cc..972871d0 100644
|
||||
--- a/chips/nrf52/src/usbd.rs
|
||||
+++ b/chips/nrf52/src/usbd.rs
|
||||
@@ -623,7 +623,7 @@ pub enum UsbState {
|
||||
pub enum EndpointState {
|
||||
Disabled,
|
||||
Ctrl(CtrlState),
|
||||
- Bulk(TransferType, EndpointDirection, BulkState),
|
||||
+ Bulk(TransferType, Option<BulkInState>, Option<BulkOutState>),
|
||||
}
|
||||
|
||||
impl EndpointState {
|
||||
@@ -634,10 +634,10 @@ impl EndpointState {
|
||||
}
|
||||
}
|
||||
|
||||
- fn bulk_state(self) -> (TransferType, EndpointDirection, BulkState) {
|
||||
+ fn bulk_state(self) -> (TransferType, Option<BulkInState>, Option<BulkOutState>) {
|
||||
match self {
|
||||
- EndpointState::Bulk(transfer_type, direction, state) => {
|
||||
- (transfer_type, direction, state)
|
||||
+ EndpointState::Bulk(transfer_type, in_state, out_state) => {
|
||||
+ (transfer_type, in_state, out_state)
|
||||
}
|
||||
_ => panic!("Expected EndpointState::Bulk"),
|
||||
}
|
||||
@@ -651,31 +651,18 @@ pub enum CtrlState {
|
||||
ReadStatus,
|
||||
}
|
||||
|
||||
-#[derive(Copy, Clone, Debug)]
|
||||
-pub enum EndpointDirection {
|
||||
- In,
|
||||
- Out,
|
||||
- InOut,
|
||||
-}
|
||||
-
|
||||
-impl EndpointDirection {
|
||||
- fn has_in(&self) -> bool {
|
||||
- match self {
|
||||
- EndpointDirection::In | EndpointDirection::InOut => true,
|
||||
- EndpointDirection::Out => false,
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- fn has_out(&self) -> bool {
|
||||
- match self {
|
||||
- EndpointDirection::Out | EndpointDirection::InOut => true,
|
||||
- EndpointDirection::In => false,
|
||||
- }
|
||||
- }
|
||||
+#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
+pub enum BulkInState {
|
||||
+ // The endpoint is ready to perform transactions.
|
||||
+ Init,
|
||||
+ // There is a pending DMA transfer on this IN endpoint.
|
||||
+ InDma,
|
||||
+ // There is a pending IN packet transfer on this endpoint.
|
||||
+ InData,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
-pub enum BulkState {
|
||||
+pub enum BulkOutState {
|
||||
// The endpoint is ready to perform transactions.
|
||||
Init,
|
||||
// There is a pending OUT packet in this endpoint's buffer, to be read by
|
||||
@@ -685,14 +672,11 @@ pub enum BulkState {
|
||||
OutData,
|
||||
// There is a pending DMA transfer on this OUT endpoint.
|
||||
OutDma,
|
||||
- // There is a pending DMA transfer on this IN endpoint.
|
||||
- InDma,
|
||||
- // There is a pending IN packet transfer on this endpoint.
|
||||
- InData,
|
||||
}
|
||||
|
||||
pub struct Endpoint<'a> {
|
||||
- slice: OptionalCell<&'a [VolatileCell<u8>]>,
|
||||
+ slice_in: OptionalCell<&'a [VolatileCell<u8>]>,
|
||||
+ slice_out: OptionalCell<&'a [VolatileCell<u8>]>,
|
||||
state: Cell<EndpointState>,
|
||||
// The USB controller can only process one DMA transfer at a time (over all endpoints). The
|
||||
// request_transmit_* bits allow to queue transfers until the DMA becomes available again.
|
||||
@@ -705,7 +689,8 @@ pub struct Endpoint<'a> {
|
||||
impl Endpoint<'_> {
|
||||
const fn new() -> Self {
|
||||
Endpoint {
|
||||
- slice: OptionalCell::empty(),
|
||||
+ slice_in: OptionalCell::empty(),
|
||||
+ slice_out: OptionalCell::empty(),
|
||||
state: Cell::new(EndpointState::Disabled),
|
||||
request_transmit_in: Cell::new(false),
|
||||
request_transmit_out: Cell::new(false),
|
||||
@@ -914,18 +899,12 @@ impl<'a> Usbd<'a> {
|
||||
chip_revision.get()
|
||||
);
|
||||
}
|
||||
- Some(ChipRevision::REV::Value::REVC) => {
|
||||
+ Some(ChipRevision::REV::Value::REVC) | Some(ChipRevision::REV::Value::REVD) => {
|
||||
debug_info!(
|
||||
"Your chip is NRF52840 revision {}. The USB stack was tested on your chip :)",
|
||||
chip_revision.get()
|
||||
);
|
||||
}
|
||||
- Some(ChipRevision::REV::Value::REVD) => {
|
||||
- internal_warn!(
|
||||
- "Your chip is NRF52840 revision {}. Although this USB implementation should be compatible, your chip hasn't been tested.",
|
||||
- chip_revision.get()
|
||||
- );
|
||||
- }
|
||||
None => {
|
||||
internal_warn!(
|
||||
"Your chip is NRF52840 revision {} (unknown revision). Although this USB implementation should be compatible, your chip hasn't been tested.",
|
||||
@@ -1026,7 +1005,7 @@ impl<'a> Usbd<'a> {
|
||||
});
|
||||
self.descriptors[endpoint].state.set(match endpoint {
|
||||
0 => EndpointState::Ctrl(CtrlState::Init),
|
||||
- 1..=7 => EndpointState::Bulk(transfer_type, EndpointDirection::In, BulkState::Init),
|
||||
+ 1..=7 => EndpointState::Bulk(transfer_type, Some(BulkInState::Init), None),
|
||||
8 => unimplemented!("isochronous endpoint"),
|
||||
_ => unreachable!("unexisting endpoint"),
|
||||
});
|
||||
@@ -1064,7 +1043,7 @@ impl<'a> Usbd<'a> {
|
||||
});
|
||||
self.descriptors[endpoint].state.set(match endpoint {
|
||||
0 => EndpointState::Ctrl(CtrlState::Init),
|
||||
- 1..=7 => EndpointState::Bulk(transfer_type, EndpointDirection::Out, BulkState::Init),
|
||||
+ 1..=7 => EndpointState::Bulk(transfer_type, None, Some(BulkOutState::Init)),
|
||||
8 => unimplemented!("isochronous endpoint"),
|
||||
_ => unreachable!("unexisting endpoint"),
|
||||
});
|
||||
@@ -1114,7 +1093,11 @@ impl<'a> Usbd<'a> {
|
||||
});
|
||||
self.descriptors[endpoint].state.set(match endpoint {
|
||||
0 => EndpointState::Ctrl(CtrlState::Init),
|
||||
- 1..=7 => EndpointState::Bulk(transfer_type, EndpointDirection::InOut, BulkState::Init),
|
||||
+ 1..=7 => EndpointState::Bulk(
|
||||
+ transfer_type,
|
||||
+ Some(BulkInState::Init),
|
||||
+ Some(BulkOutState::Init),
|
||||
+ ),
|
||||
8 => unimplemented!("isochronous endpoint"),
|
||||
_ => unreachable!("unexisting endpoint"),
|
||||
});
|
||||
@@ -1304,13 +1287,13 @@ impl<'a> Usbd<'a> {
|
||||
match desc.state.get() {
|
||||
EndpointState::Disabled => {}
|
||||
EndpointState::Ctrl(_) => desc.state.set(EndpointState::Ctrl(CtrlState::Init)),
|
||||
- EndpointState::Bulk(transfer_type, direction, _) => {
|
||||
+ EndpointState::Bulk(transfer_type, in_state, out_state) => {
|
||||
desc.state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
- direction,
|
||||
- BulkState::Init,
|
||||
+ in_state.map(|_| BulkInState::Init),
|
||||
+ out_state.map(|_| BulkOutState::Init),
|
||||
));
|
||||
- if direction.has_out() {
|
||||
+ if out_state.is_some() {
|
||||
// Accept incoming OUT packets.
|
||||
regs.size_epout[ep].set(0);
|
||||
}
|
||||
@@ -1347,13 +1330,13 @@ impl<'a> Usbd<'a> {
|
||||
match endpoint {
|
||||
0 => {}
|
||||
1..=7 => {
|
||||
- let (transfer_type, direction, state) =
|
||||
+ let (transfer_type, in_state, out_state) =
|
||||
self.descriptors[endpoint].state.get().bulk_state();
|
||||
- assert_eq!(state, BulkState::InDma);
|
||||
+ assert_eq!(in_state, Some(BulkInState::InDma));
|
||||
self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
- direction,
|
||||
- BulkState::InData,
|
||||
+ Some(BulkInState::InData),
|
||||
+ out_state,
|
||||
));
|
||||
}
|
||||
8 => unimplemented!("isochronous endpoint"),
|
||||
@@ -1405,25 +1388,25 @@ impl<'a> Usbd<'a> {
|
||||
1..=7 => {
|
||||
// Notify the client about the new packet.
|
||||
let packet_bytes = regs.size_epout[endpoint].get();
|
||||
- let (transfer_type, direction, state) =
|
||||
+ let (transfer_type, in_state, out_state) =
|
||||
self.descriptors[endpoint].state.get().bulk_state();
|
||||
- assert_eq!(state, BulkState::OutDma);
|
||||
+ assert_eq!(out_state, Some(BulkOutState::OutDma));
|
||||
|
||||
- self.debug_packet("out", packet_bytes as usize, endpoint);
|
||||
+ self.debug_out_packet(packet_bytes as usize, endpoint);
|
||||
|
||||
self.client.map(|client| {
|
||||
let result = client.packet_out(transfer_type, endpoint, packet_bytes);
|
||||
debug_packets!("packet_out => {:?}", result);
|
||||
- let newstate = match result {
|
||||
+ let new_out_state = match result {
|
||||
hil::usb::OutResult::Ok => {
|
||||
// Indicate that the endpoint is ready to receive data again.
|
||||
regs.size_epout[endpoint].set(0);
|
||||
- BulkState::Init
|
||||
+ BulkOutState::Init
|
||||
}
|
||||
|
||||
hil::usb::OutResult::Delay => {
|
||||
// We can't send the packet now. Wait for a resume_out call from the client.
|
||||
- BulkState::OutDelay
|
||||
+ BulkOutState::OutDelay
|
||||
}
|
||||
|
||||
hil::usb::OutResult::Error => {
|
||||
@@ -1432,13 +1415,13 @@ impl<'a> Usbd<'a> {
|
||||
+ EndpointStall::IO::Out
|
||||
+ EndpointStall::STALL::Stall,
|
||||
);
|
||||
- BulkState::Init
|
||||
+ BulkOutState::Init
|
||||
}
|
||||
};
|
||||
self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
- direction,
|
||||
- newstate,
|
||||
+ in_state,
|
||||
+ Some(new_out_state),
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -1497,29 +1480,27 @@ impl<'a> Usbd<'a> {
|
||||
// Endpoint 8 (isochronous) doesn't receive any EPDATA event.
|
||||
for endpoint in 1..NUM_ENDPOINTS {
|
||||
if epdatastatus.is_set(status_epin(endpoint)) {
|
||||
- let (transfer_type, direction, state) =
|
||||
+ let (transfer_type, in_state, out_state) =
|
||||
self.descriptors[endpoint].state.get().bulk_state();
|
||||
- match state {
|
||||
- BulkState::InData => {
|
||||
+ assert!(in_state.is_some());
|
||||
+ match in_state.unwrap() {
|
||||
+ BulkInState::InData => {
|
||||
// Totally expected state. Nothing to do.
|
||||
}
|
||||
- BulkState::Init => {
|
||||
+ BulkInState::Init => {
|
||||
internal_warn!(
|
||||
"Received a stale epdata IN in an unexpected state: {:?}",
|
||||
- state
|
||||
+ in_state
|
||||
);
|
||||
}
|
||||
- BulkState::OutDelay
|
||||
- | BulkState::OutData
|
||||
- | BulkState::OutDma
|
||||
- | BulkState::InDma => {
|
||||
- internal_err!("Unexpected state: {:?}", state);
|
||||
+ BulkInState::InDma => {
|
||||
+ internal_err!("Unexpected state: {:?}", in_state);
|
||||
}
|
||||
}
|
||||
self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
- direction,
|
||||
- BulkState::Init,
|
||||
+ Some(BulkInState::Init),
|
||||
+ out_state,
|
||||
));
|
||||
self.client
|
||||
.map(|client| client.packet_transmitted(endpoint));
|
||||
@@ -1530,28 +1511,26 @@ impl<'a> Usbd<'a> {
|
||||
// Endpoint 8 (isochronous) doesn't receive any EPDATA event.
|
||||
for ep in 1..NUM_ENDPOINTS {
|
||||
if epdatastatus.is_set(status_epout(ep)) {
|
||||
- let (transfer_type, direction, state) =
|
||||
+ let (transfer_type, in_state, out_state) =
|
||||
self.descriptors[ep].state.get().bulk_state();
|
||||
- match state {
|
||||
- BulkState::Init => {
|
||||
+ assert!(out_state.is_some());
|
||||
+ match out_state.unwrap() {
|
||||
+ BulkOutState::Init => {
|
||||
// The endpoint is ready to receive data. Request a transmit_out.
|
||||
self.descriptors[ep].request_transmit_out.set(true);
|
||||
}
|
||||
- BulkState::OutDelay => {
|
||||
+ BulkOutState::OutDelay => {
|
||||
// The endpoint will be resumed later by the client application with transmit_out().
|
||||
}
|
||||
- BulkState::OutData
|
||||
- | BulkState::OutDma
|
||||
- | BulkState::InDma
|
||||
- | BulkState::InData => {
|
||||
- internal_err!("Unexpected state: {:?}", state);
|
||||
+ BulkOutState::OutData | BulkOutState::OutDma => {
|
||||
+ internal_err!("Unexpected state: {:?}", out_state);
|
||||
}
|
||||
}
|
||||
// Indicate that the endpoint now has data available.
|
||||
self.descriptors[ep].state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
- direction,
|
||||
- BulkState::OutData,
|
||||
+ in_state,
|
||||
+ Some(BulkOutState::OutData),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1564,8 +1543,8 @@ impl<'a> Usbd<'a> {
|
||||
let state = self.descriptors[endpoint].state.get().ctrl_state();
|
||||
match state {
|
||||
CtrlState::Init => {
|
||||
- let ep_buf = &self.descriptors[endpoint].slice;
|
||||
- let ep_buf = ep_buf.expect("No slice set for this descriptor");
|
||||
+ let ep_buf = &self.descriptors[endpoint].slice_out;
|
||||
+ let ep_buf = ep_buf.expect("No OUT slice set for this descriptor");
|
||||
if ep_buf.len() < 8 {
|
||||
panic!("EP0 DMA buffer length < 8");
|
||||
}
|
||||
@@ -1697,21 +1676,21 @@ impl<'a> Usbd<'a> {
|
||||
let regs = &*self.registers;
|
||||
|
||||
self.client.map(|client| {
|
||||
- let (transfer_type, direction, state) =
|
||||
+ let (transfer_type, in_state, out_state) =
|
||||
self.descriptors[endpoint].state.get().bulk_state();
|
||||
- assert_eq!(state, BulkState::Init);
|
||||
+ assert_eq!(in_state, Some(BulkInState::Init));
|
||||
|
||||
let result = client.packet_in(transfer_type, endpoint);
|
||||
debug_packets!("packet_in => {:?}", result);
|
||||
- let newstate = match result {
|
||||
+ let new_in_state = match result {
|
||||
hil::usb::InResult::Packet(size) => {
|
||||
self.start_dma_in(endpoint, size);
|
||||
- BulkState::InDma
|
||||
+ BulkInState::InDma
|
||||
}
|
||||
|
||||
hil::usb::InResult::Delay => {
|
||||
// No packet to send now. Wait for a resume call from the client.
|
||||
- BulkState::Init
|
||||
+ BulkInState::Init
|
||||
}
|
||||
|
||||
hil::usb::InResult::Error => {
|
||||
@@ -1720,14 +1699,14 @@ impl<'a> Usbd<'a> {
|
||||
+ EndpointStall::IO::In
|
||||
+ EndpointStall::STALL::Stall,
|
||||
);
|
||||
- BulkState::Init
|
||||
+ BulkInState::Init
|
||||
}
|
||||
};
|
||||
|
||||
self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
- direction,
|
||||
- newstate,
|
||||
+ Some(new_in_state),
|
||||
+ out_state,
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -1735,15 +1714,16 @@ impl<'a> Usbd<'a> {
|
||||
fn transmit_out(&self, endpoint: usize) {
|
||||
debug_events!("transmit_out({})", endpoint);
|
||||
|
||||
- let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
+ let (transfer_type, in_state, out_state) =
|
||||
+ self.descriptors[endpoint].state.get().bulk_state();
|
||||
// Starting the DMA can only happen in the OutData state, i.e. after an EPDATA event.
|
||||
- assert_eq!(state, BulkState::OutData);
|
||||
+ assert_eq!(out_state, Some(BulkOutState::OutData));
|
||||
self.start_dma_out(endpoint);
|
||||
|
||||
self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
- direction,
|
||||
- BulkState::OutDma,
|
||||
+ in_state,
|
||||
+ Some(BulkOutState::OutDma),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1751,9 +1731,9 @@ impl<'a> Usbd<'a> {
|
||||
let regs = &*self.registers;
|
||||
|
||||
let slice = self.descriptors[endpoint]
|
||||
- .slice
|
||||
- .expect("No slice set for this descriptor");
|
||||
- self.debug_packet("in", size, endpoint);
|
||||
+ .slice_in
|
||||
+ .expect("No IN slice set for this descriptor");
|
||||
+ self.debug_in_packet(size, endpoint);
|
||||
|
||||
// Start DMA transfer
|
||||
self.set_pending_dma();
|
||||
@@ -1766,8 +1746,8 @@ impl<'a> Usbd<'a> {
|
||||
let regs = &*self.registers;
|
||||
|
||||
let slice = self.descriptors[endpoint]
|
||||
- .slice
|
||||
- .expect("No slice set for this descriptor");
|
||||
+ .slice_out
|
||||
+ .expect("No OUT slice set for this descriptor");
|
||||
|
||||
// Start DMA transfer
|
||||
self.set_pending_dma();
|
||||
@@ -1777,10 +1757,27 @@ impl<'a> Usbd<'a> {
|
||||
}
|
||||
|
||||
// Debug-only function
|
||||
- fn debug_packet(&self, _title: &str, size: usize, endpoint: usize) {
|
||||
+ fn debug_in_packet(&self, size: usize, endpoint: usize) {
|
||||
+ let slice = self.descriptors[endpoint]
|
||||
+ .slice_in
|
||||
+ .expect("No IN slice set for this descriptor");
|
||||
+ if size > slice.len() {
|
||||
+ panic!("Packet is too large: {}", size);
|
||||
+ }
|
||||
+
|
||||
+ let mut packet_hex = [0; 128];
|
||||
+ packet_to_hex(slice, &mut packet_hex);
|
||||
+ debug_packets!(
|
||||
+ "in={}",
|
||||
+ core::str::from_utf8(&packet_hex[..(2 * size)]).unwrap()
|
||||
+ );
|
||||
+ }
|
||||
+
|
||||
+ // Debug-only function
|
||||
+ fn debug_out_packet(&self, size: usize, endpoint: usize) {
|
||||
let slice = self.descriptors[endpoint]
|
||||
- .slice
|
||||
- .expect("No slice set for this descriptor");
|
||||
+ .slice_out
|
||||
+ .expect("No OUT slice set for this descriptor");
|
||||
if size > slice.len() {
|
||||
panic!("Packet is too large: {}", size);
|
||||
}
|
||||
@@ -1788,8 +1785,7 @@ impl<'a> Usbd<'a> {
|
||||
let mut packet_hex = [0; 128];
|
||||
packet_to_hex(slice, &mut packet_hex);
|
||||
debug_packets!(
|
||||
- "{}={}",
|
||||
- _title,
|
||||
+ "out={}",
|
||||
core::str::from_utf8(&packet_hex[..(2 * size)]).unwrap()
|
||||
);
|
||||
}
|
||||
@@ -1807,17 +1803,41 @@ impl<'a> power::PowerClient for Usbd<'a> {
|
||||
}
|
||||
|
||||
impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
|
||||
- fn endpoint_set_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) {
|
||||
+ fn endpoint_set_ctrl_buffer(&self, buf: &'a [VolatileCell<u8>]) {
|
||||
+ if buf.len() < 8 {
|
||||
+ panic!("Endpoint buffer must be at least 8 bytes");
|
||||
+ }
|
||||
+ if !buf.len().is_power_of_two() {
|
||||
+ panic!("Buffer size must be a power of 2");
|
||||
+ }
|
||||
+ self.descriptors[0].slice_in.set(buf);
|
||||
+ self.descriptors[0].slice_out.set(buf);
|
||||
+ }
|
||||
+
|
||||
+ fn endpoint_set_in_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) {
|
||||
+ if buf.len() < 8 {
|
||||
+ panic!("Endpoint buffer must be at least 8 bytes");
|
||||
+ }
|
||||
+ if !buf.len().is_power_of_two() {
|
||||
+ panic!("Buffer size must be a power of 2");
|
||||
+ }
|
||||
+ if endpoint == 0 || endpoint >= NUM_ENDPOINTS {
|
||||
+ panic!("Endpoint number is invalid");
|
||||
+ }
|
||||
+ self.descriptors[endpoint].slice_in.set(buf);
|
||||
+ }
|
||||
+
|
||||
+ fn endpoint_set_out_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) {
|
||||
if buf.len() < 8 {
|
||||
panic!("Endpoint buffer must be at least 8 bytes");
|
||||
}
|
||||
if !buf.len().is_power_of_two() {
|
||||
panic!("Buffer size must be a power of 2");
|
||||
}
|
||||
- if endpoint >= NUM_ENDPOINTS {
|
||||
- panic!("Endpoint number is too high");
|
||||
+ if endpoint == 0 || endpoint >= NUM_ENDPOINTS {
|
||||
+ panic!("Endpoint number is invalid");
|
||||
}
|
||||
- self.descriptors[endpoint].slice.set(buf);
|
||||
+ self.descriptors[endpoint].slice_out.set(buf);
|
||||
}
|
||||
|
||||
fn enable_as_device(&self, speed: hil::usb::DeviceSpeed) {
|
||||
@@ -1900,8 +1920,8 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
|
||||
fn endpoint_resume_in(&self, endpoint: usize) {
|
||||
debug_events!("endpoint_resume_in({})", endpoint);
|
||||
|
||||
- let (_, direction, _) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
- assert!(direction.has_in());
|
||||
+ let (_, in_state, _) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
+ assert!(in_state.is_some());
|
||||
|
||||
if self.dma_pending.get() {
|
||||
debug_events!("requesting resume_in[{}]", endpoint);
|
||||
@@ -1916,20 +1936,21 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
|
||||
fn endpoint_resume_out(&self, endpoint: usize) {
|
||||
debug_events!("endpoint_resume_out({})", endpoint);
|
||||
|
||||
- let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
- assert!(direction.has_out());
|
||||
+ let (transfer_type, in_state, out_state) =
|
||||
+ self.descriptors[endpoint].state.get().bulk_state();
|
||||
+ assert!(out_state.is_some());
|
||||
|
||||
- match state {
|
||||
- BulkState::OutDelay => {
|
||||
+ match out_state.unwrap() {
|
||||
+ BulkOutState::OutDelay => {
|
||||
// The endpoint has now finished processing the last ENDEPOUT. No EPDATA event
|
||||
// happened in the meantime, so the state is now back to Init.
|
||||
self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
transfer_type,
|
||||
- direction,
|
||||
- BulkState::Init,
|
||||
+ in_state,
|
||||
+ Some(BulkOutState::Init),
|
||||
));
|
||||
}
|
||||
- BulkState::OutData => {
|
||||
+ BulkOutState::OutData => {
|
||||
// Although the client reported a delay before, an EPDATA event has
|
||||
// happened in the meantime. This pending transaction will now
|
||||
// continue in transmit_out().
|
||||
@@ -1942,25 +1963,11 @@ impl<'a> hil::usb::UsbController<'a> for Usbd<'a> {
|
||||
self.transmit_out(endpoint);
|
||||
}
|
||||
}
|
||||
- BulkState::Init | BulkState::OutDma | BulkState::InDma | BulkState::InData => {
|
||||
- internal_err!("Unexpected state: {:?}", state);
|
||||
+ BulkOutState::Init | BulkOutState::OutDma => {
|
||||
+ internal_err!("Unexpected state: {:?}", out_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
-
|
||||
- fn endpoint_cancel_in(&self, endpoint: usize) {
|
||||
- debug_events!("endpoint_cancel_in({})", endpoint);
|
||||
-
|
||||
- let (transfer_type, direction, state) = self.descriptors[endpoint].state.get().bulk_state();
|
||||
- assert!(direction.has_in());
|
||||
- assert_eq!(state, BulkState::InData);
|
||||
-
|
||||
- self.descriptors[endpoint].state.set(EndpointState::Bulk(
|
||||
- transfer_type,
|
||||
- direction,
|
||||
- BulkState::Init,
|
||||
- ));
|
||||
- }
|
||||
}
|
||||
|
||||
fn status_epin(ep: usize) -> Field<u32, EndpointStatus::Register> {
|
||||
diff --git a/chips/sam4l/src/usbc/mod.rs b/chips/sam4l/src/usbc/mod.rs
|
||||
index 28a0b9f9..ab5b636f 100644
|
||||
--- a/chips/sam4l/src/usbc/mod.rs
|
||||
+++ b/chips/sam4l/src/usbc/mod.rs
|
||||
@@ -1438,11 +1438,28 @@ fn endpoint_enable_interrupts(endpoint: usize, mask: FieldValue<u32, EndpointCon
|
||||
}
|
||||
|
||||
impl hil::usb::UsbController<'a> for Usbc<'a> {
|
||||
- fn endpoint_set_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) {
|
||||
+ fn endpoint_set_ctrl_buffer(&self, buf: &'a [VolatileCell<u8>]) {
|
||||
if buf.len() != 8 {
|
||||
client_err!("Bad endpoint buffer size");
|
||||
}
|
||||
|
||||
+ self._endpoint_bank_set_buffer(EndpointIndex::new(0), BankIndex::Bank0, buf);
|
||||
+ }
|
||||
+
|
||||
+ fn endpoint_set_in_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) {
|
||||
+ if buf.len() != 8 {
|
||||
+ client_err!("Bad endpoint buffer size");
|
||||
+ }
|
||||
+
|
||||
+ self._endpoint_bank_set_buffer(EndpointIndex::new(endpoint), BankIndex::Bank0, buf);
|
||||
+ }
|
||||
+
|
||||
+ fn endpoint_set_out_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]) {
|
||||
+ if buf.len() != 8 {
|
||||
+ client_err!("Bad endpoint buffer size");
|
||||
+ }
|
||||
+
|
||||
+ // XXX: when implementing in_out endpoints, this should probably set a different slice than endpoint_set_in_buffer.
|
||||
self._endpoint_bank_set_buffer(EndpointIndex::new(endpoint), BankIndex::Bank0, buf);
|
||||
}
|
||||
|
||||
@@ -1547,10 +1564,6 @@ impl hil::usb::UsbController<'a> for Usbc<'a> {
|
||||
requests.resume_out = true;
|
||||
self.requests[endpoint].set(requests);
|
||||
}
|
||||
-
|
||||
- fn endpoint_cancel_in(&self, _endpoint: usize) {
|
||||
- unimplemented!()
|
||||
- }
|
||||
}
|
||||
|
||||
/// Static state to manage the USBC
|
||||
diff --git a/kernel/src/hil/usb.rs b/kernel/src/hil/usb.rs
|
||||
index 64610fa5..a114b30d 100644
|
||||
--- a/kernel/src/hil/usb.rs
|
||||
+++ b/kernel/src/hil/usb.rs
|
||||
@@ -5,7 +5,9 @@ use crate::common::cells::VolatileCell;
|
||||
/// USB controller interface
|
||||
pub trait UsbController<'a> {
|
||||
// Should be called before `enable_as_device()`
|
||||
- fn endpoint_set_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]);
|
||||
+ fn endpoint_set_ctrl_buffer(&self, buf: &'a [VolatileCell<u8>]);
|
||||
+ fn endpoint_set_in_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]);
|
||||
+ fn endpoint_set_out_buffer(&self, endpoint: usize, buf: &'a [VolatileCell<u8>]);
|
||||
|
||||
// Must be called before `attach()`
|
||||
fn enable_as_device(&self, speed: DeviceSpeed);
|
||||
@@ -27,8 +29,6 @@ pub trait UsbController<'a> {
|
||||
fn endpoint_resume_in(&self, endpoint: usize);
|
||||
|
||||
fn endpoint_resume_out(&self, endpoint: usize);
|
||||
-
|
||||
- fn endpoint_cancel_in(&self, endpoint: usize);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -24,6 +24,9 @@ cd libraries/crypto
|
||||
cargo fmt --all -- --check
|
||||
cd ../..
|
||||
|
||||
echo "Building sha256sum tool..."
|
||||
cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
|
||||
|
||||
echo "Checking that CTAP2 builds properly..."
|
||||
cargo check --release --target=thumbv7em-none-eabi
|
||||
cargo check --release --target=thumbv7em-none-eabi --features with_ctap1
|
||||
@@ -31,19 +34,25 @@ cargo check --release --target=thumbv7em-none-eabi --features debug_ctap
|
||||
cargo check --release --target=thumbv7em-none-eabi --features panic_console
|
||||
cargo check --release --target=thumbv7em-none-eabi --features debug_allocations
|
||||
cargo check --release --target=thumbv7em-none-eabi --features ram_storage
|
||||
cargo check --release --target=thumbv7em-none-eabi --features verbose
|
||||
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1
|
||||
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1,panic_console,debug_allocations
|
||||
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
|
||||
|
||||
echo "Checking that examples build properly..."
|
||||
cargo check --release --target=thumbv7em-none-eabi --examples
|
||||
|
||||
echo "Checking that CTAP2 builds and links properly (1 set of features)..."
|
||||
cargo build --release --target=thumbv7em-none-eabi --features with_ctap1
|
||||
./third_party/tock/tools/sha256sum/target/debug/sha256sum target/thumbv7em-none-eabi/release/ctap2
|
||||
|
||||
echo "Checking that supported boards build properly..."
|
||||
make -C third_party/tock/boards/nordic/nrf52840dk
|
||||
make -C third_party/tock/boards/nordic/nrf52840_dongle
|
||||
|
||||
echo "Checking that other boards build properly..."
|
||||
make -C boards/nrf52840_dongle_dfu
|
||||
make -C boards/nrf52840_mdk_dfu
|
||||
|
||||
if [ -z "${TRAVIS_OS_NAME}" -o "${TRAVIS_OS_NAME}" = "linux" ]
|
||||
then
|
||||
echo "Running unit tests on the desktop (release mode)..."
|
||||
|
||||
2
setup.sh
2
setup.sh
@@ -81,7 +81,7 @@ source tools/gen_key_materials.sh
|
||||
generate_crypto_materials N
|
||||
|
||||
rustup install $(head -n 1 rust-toolchain)
|
||||
pip3 install --user --upgrade tockloader
|
||||
pip3 install --user --upgrade tockloader six intelhex
|
||||
rustup target add thumbv7em-none-eabi
|
||||
|
||||
# Install dependency to create applications.
|
||||
|
||||
@@ -220,6 +220,78 @@ impl TryFrom<&cbor::Value> for Extensions {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Extensions> for cbor::Value {
|
||||
fn from(extensions: Extensions) -> Self {
|
||||
cbor_map_btree!(extensions
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|(key, value)| (cbor_text!(key), value))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Extensions {
|
||||
#[cfg(test)]
|
||||
pub fn new(extension_map: BTreeMap<String, cbor::Value>) -> Self {
|
||||
Extensions(extension_map)
|
||||
}
|
||||
|
||||
pub fn has_make_credential_hmac_secret(&self) -> Result<bool, Ctap2StatusCode> {
|
||||
self.0
|
||||
.get("hmac-secret")
|
||||
.map(read_bool)
|
||||
.unwrap_or(Ok(false))
|
||||
}
|
||||
|
||||
pub fn get_assertion_hmac_secret(
|
||||
&self,
|
||||
) -> Option<Result<GetAssertionHmacSecretInput, Ctap2StatusCode>> {
|
||||
self.0
|
||||
.get("hmac-secret")
|
||||
.map(GetAssertionHmacSecretInput::try_from)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||
pub struct GetAssertionHmacSecretInput {
|
||||
pub key_agreement: CoseKey,
|
||||
pub salt_enc: Vec<u8>,
|
||||
pub salt_auth: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<&cbor::Value> for GetAssertionHmacSecretInput {
|
||||
type Error = Ctap2StatusCode;
|
||||
|
||||
fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||
let input_map = read_map(cbor_value)?;
|
||||
let cose_key = read_map(ok_or_missing(input_map.get(&cbor_unsigned!(1)))?)?;
|
||||
let salt_enc = read_byte_string(ok_or_missing(input_map.get(&cbor_unsigned!(2)))?)?;
|
||||
let salt_auth = read_byte_string(ok_or_missing(input_map.get(&cbor_unsigned!(3)))?)?;
|
||||
Ok(Self {
|
||||
key_agreement: CoseKey(cose_key.clone()),
|
||||
salt_enc,
|
||||
salt_auth,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||
pub struct GetAssertionHmacSecretOutput(Vec<u8>);
|
||||
|
||||
impl From<GetAssertionHmacSecretOutput> for cbor::Value {
|
||||
fn from(message: GetAssertionHmacSecretOutput) -> cbor::Value {
|
||||
cbor_bytes!(message.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&cbor::Value> for GetAssertionHmacSecretOutput {
|
||||
type Error = Ctap2StatusCode;
|
||||
|
||||
fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
||||
Ok(GetAssertionHmacSecretOutput(read_byte_string(cbor_value)?))
|
||||
}
|
||||
}
|
||||
|
||||
// Even though options are optional, we can use the default if not present.
|
||||
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
|
||||
pub struct MakeCredentialOptions {
|
||||
@@ -314,6 +386,7 @@ pub struct PublicKeyCredentialSource {
|
||||
pub rp_id: String,
|
||||
pub user_handle: Vec<u8>, // not optional, but nullable
|
||||
pub other_ui: Option<String>,
|
||||
pub cred_random: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl From<PublicKeyCredentialSource> for cbor::Value {
|
||||
@@ -324,12 +397,17 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
|
||||
None => cbor_null!(),
|
||||
Some(other_ui) => cbor_text!(other_ui),
|
||||
};
|
||||
let cred_random = match credential.cred_random {
|
||||
None => cbor_null!(),
|
||||
Some(cred_random) => cbor_bytes!(cred_random),
|
||||
};
|
||||
cbor_array! {
|
||||
credential.credential_id,
|
||||
private_key,
|
||||
credential.rp_id,
|
||||
credential.user_handle,
|
||||
other_ui,
|
||||
cred_random,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,7 +419,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
||||
use cbor::{SimpleValue, Value};
|
||||
|
||||
let fields = read_array(&cbor_value)?;
|
||||
if fields.len() != 5 {
|
||||
if fields.len() != 6 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
let credential_id = read_byte_string(&fields[0])?;
|
||||
@@ -357,6 +435,10 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
||||
Value::Simple(SimpleValue::NullValue) => None,
|
||||
cbor_value => Some(read_text_string(cbor_value)?),
|
||||
};
|
||||
let cred_random = match &fields[5] {
|
||||
Value::Simple(SimpleValue::NullValue) => None,
|
||||
cbor_value => Some(read_byte_string(cbor_value)?),
|
||||
};
|
||||
Ok(PublicKeyCredentialSource {
|
||||
key_type: PublicKeyCredentialType::PublicKey,
|
||||
credential_id,
|
||||
@@ -364,6 +446,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
||||
rp_id,
|
||||
user_handle,
|
||||
other_ui,
|
||||
cred_random,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -993,6 +1076,7 @@ mod test {
|
||||
rp_id: "example.com".to_string(),
|
||||
user_handle: b"foo".to_vec(),
|
||||
other_ui: None,
|
||||
cred_random: None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
@@ -1005,6 +1089,16 @@ mod test {
|
||||
..credential
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
||||
Ok(credential.clone())
|
||||
);
|
||||
|
||||
let credential = PublicKeyCredentialSource {
|
||||
cred_random: Some(vec![0x00; 32]),
|
||||
..credential
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
||||
Ok(credential)
|
||||
|
||||
281
src/ctap/mod.rs
281
src/ctap/mod.rs
@@ -28,9 +28,9 @@ use self::command::{
|
||||
AuthenticatorMakeCredentialParameters, Command,
|
||||
};
|
||||
use self::data_formats::{
|
||||
ClientPinSubCommand, CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor,
|
||||
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
|
||||
SignatureAlgorithm,
|
||||
ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement,
|
||||
PublicKeyCredentialDescriptor, PublicKeyCredentialSource, PublicKeyCredentialType,
|
||||
PublicKeyCredentialUserEntity, SignatureAlgorithm,
|
||||
};
|
||||
use self::hid::ChannelID;
|
||||
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
|
||||
@@ -81,9 +81,14 @@ const PIN_PADDED_LENGTH: usize = 64;
|
||||
// - 32 byte relying party ID hashed with SHA256,
|
||||
// - 32 byte HMAC-SHA256 over everything else.
|
||||
pub const ENCRYPTED_CREDENTIAL_ID_SIZE: usize = 112;
|
||||
// Set this bit when checking user presence.
|
||||
const UP_FLAG: u8 = 0x01;
|
||||
// Set this bit when checking user verification.
|
||||
const UV_FLAG: u8 = 0x04;
|
||||
// Set this bit when performing attestation.
|
||||
const AT_FLAG: u8 = 0x40;
|
||||
// Set this bit when an extension is used.
|
||||
const ED_FLAG: u8 = 0x80;
|
||||
|
||||
pub const TOUCH_TIMEOUT_MS: isize = 30000;
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
@@ -105,6 +110,63 @@ fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> boo
|
||||
)
|
||||
}
|
||||
|
||||
// Decrypts the HMAC secret salt(s) that were encrypted with the shared secret.
|
||||
// The credRandom is used as a secret to HMAC those salts.
|
||||
// The last step is to re-encrypt the outputs.
|
||||
pub fn encrypt_hmac_secret_output(
|
||||
shared_secret: &[u8; 32],
|
||||
salt_enc: &[u8],
|
||||
cred_random: &[u8],
|
||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||
if salt_enc.len() != 32 && salt_enc.len() != 64 {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
if cred_random.len() != 32 {
|
||||
// We are strict here. We need at least 32 byte, but expect exactly 32.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||
// The specification specifically asks for a zero IV.
|
||||
let iv = [0; 16];
|
||||
|
||||
let mut cred_random_secret = [0; 32];
|
||||
cred_random_secret.clone_from_slice(cred_random);
|
||||
|
||||
// Initialization of 4 blocks in any case makes this function more readable.
|
||||
let mut blocks = [[0u8; 16]; 4];
|
||||
let block_len = salt_enc.len() / 16;
|
||||
for i in 0..block_len {
|
||||
blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]);
|
||||
}
|
||||
cbc_decrypt(&aes_dec_key, iv, &mut blocks[..block_len]);
|
||||
|
||||
let mut decrypted_salt1 = [0; 32];
|
||||
decrypted_salt1[..16].clone_from_slice(&blocks[0]);
|
||||
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
|
||||
decrypted_salt1[16..].clone_from_slice(&blocks[1]);
|
||||
for i in 0..2 {
|
||||
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
|
||||
}
|
||||
|
||||
if block_len == 4 {
|
||||
let mut decrypted_salt2 = [0; 32];
|
||||
decrypted_salt2[..16].clone_from_slice(&blocks[2]);
|
||||
decrypted_salt2[16..].clone_from_slice(&blocks[3]);
|
||||
let output2 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt2[..]);
|
||||
for i in 0..2 {
|
||||
blocks[i + 2].copy_from_slice(&output2[16 * i..16 * (i + 1)]);
|
||||
}
|
||||
}
|
||||
|
||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks[..block_len]);
|
||||
let mut encrypted_output = Vec::with_capacity(salt_enc.len());
|
||||
for b in &blocks[..block_len] {
|
||||
encrypted_output.extend(b);
|
||||
}
|
||||
Ok(encrypted_output)
|
||||
}
|
||||
|
||||
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
|
||||
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
|
||||
// We change the return value, since we don't need the bool.
|
||||
@@ -261,6 +323,7 @@ where
|
||||
rp_id: String::from(""),
|
||||
user_handle: vec![],
|
||||
other_ui: None,
|
||||
cred_random: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -324,10 +387,10 @@ where
|
||||
user,
|
||||
pub_key_cred_params,
|
||||
exclude_list,
|
||||
extensions,
|
||||
options,
|
||||
pin_uv_auth_param,
|
||||
pin_uv_auth_protocol,
|
||||
..
|
||||
} = make_credential_params;
|
||||
|
||||
if let Some(auth_param) = &pin_uv_auth_param {
|
||||
@@ -362,6 +425,19 @@ where
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
||||
}
|
||||
|
||||
let use_hmac_extension =
|
||||
extensions.map_or(Ok(false), |e| e.has_make_credential_hmac_secret())?;
|
||||
if use_hmac_extension && !options.rk {
|
||||
// The extension is actually supported, but we need resident keys.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
let cred_random = if use_hmac_extension {
|
||||
Some(self.rng.gen_uniform_u8x32().to_vec())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ed_flag = if use_hmac_extension { ED_FLAG } else { 0 };
|
||||
|
||||
let rp_id = rp.rp_id;
|
||||
if let Some(exclude_list) = exclude_list {
|
||||
for cred_desc in exclude_list {
|
||||
@@ -389,7 +465,7 @@ where
|
||||
if !check_pin_auth(&self.pin_uv_auth_token, &client_data_hash, &pin_auth) {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
UP_FLAG | UV_FLAG | AT_FLAG
|
||||
UP_FLAG | UV_FLAG | AT_FLAG | ed_flag
|
||||
}
|
||||
None => {
|
||||
if self.persistent_store.pin_hash().is_some() {
|
||||
@@ -398,7 +474,7 @@ where
|
||||
if options.uv {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
UP_FLAG | AT_FLAG
|
||||
UP_FLAG | AT_FLAG | ed_flag
|
||||
}
|
||||
};
|
||||
|
||||
@@ -421,6 +497,7 @@ where
|
||||
other_ui: user
|
||||
.user_display_name
|
||||
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
|
||||
cred_random,
|
||||
};
|
||||
self.persistent_store.store_credential(credential_source)?;
|
||||
random_id
|
||||
@@ -441,6 +518,14 @@ where
|
||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR),
|
||||
};
|
||||
auth_data.extend(cose_key);
|
||||
if use_hmac_extension {
|
||||
let extensions = cbor_map! {
|
||||
"hmac-secret" => true,
|
||||
};
|
||||
if !cbor::write(extensions, &mut auth_data) {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
|
||||
}
|
||||
}
|
||||
|
||||
let mut signature_data = auth_data.clone();
|
||||
signature_data.extend(client_data_hash);
|
||||
@@ -481,10 +566,10 @@ where
|
||||
rp_id,
|
||||
client_data_hash,
|
||||
allow_list,
|
||||
extensions,
|
||||
options,
|
||||
pin_uv_auth_param,
|
||||
pin_uv_auth_protocol,
|
||||
..
|
||||
} = get_assertion_params;
|
||||
|
||||
if let Some(auth_param) = &pin_uv_auth_param {
|
||||
@@ -527,6 +612,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let get_assertion_hmac_secret_input = match extensions {
|
||||
Some(extensions) => extensions.get_assertion_hmac_secret().transpose()?,
|
||||
None => None,
|
||||
};
|
||||
if get_assertion_hmac_secret_input.is_some() && !options.up {
|
||||
// The extension is actually supported, but we need user presence.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
|
||||
// The user verification bit depends on the existance of PIN auth, whereas
|
||||
// user presence is requested as an option.
|
||||
let mut flags = match pin_uv_auth_param {
|
||||
@@ -551,6 +645,9 @@ where
|
||||
if options.up {
|
||||
flags |= UP_FLAG;
|
||||
}
|
||||
if get_assertion_hmac_secret_input.is_some() {
|
||||
flags |= ED_FLAG;
|
||||
}
|
||||
|
||||
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
|
||||
let mut decrypted_credential = None;
|
||||
@@ -590,7 +687,36 @@ where
|
||||
|
||||
self.increment_global_signature_counter();
|
||||
|
||||
let auth_data = self.generate_auth_data(&rp_id_hash, flags);
|
||||
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
|
||||
// Process extensions.
|
||||
if let Some(get_assertion_hmac_secret_input) = get_assertion_hmac_secret_input {
|
||||
let GetAssertionHmacSecretInput {
|
||||
key_agreement,
|
||||
salt_enc,
|
||||
salt_auth,
|
||||
} = get_assertion_hmac_secret_input;
|
||||
let pk: crypto::ecdh::PubKey = CoseKey::try_into(key_agreement)?;
|
||||
let shared_secret = self.key_agreement_key.exchange_x_sha256(&pk);
|
||||
// HMAC-secret does the same 16 byte truncated check.
|
||||
if !check_pin_auth(&shared_secret, &salt_enc, &salt_auth) {
|
||||
// Again, hard to tell what the correct error code here is.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
|
||||
let encrypted_output = match &credential.cred_random {
|
||||
Some(cr) => encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cr)?,
|
||||
// This is the case if the credential was not created with HMAC-secret.
|
||||
None => return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION),
|
||||
};
|
||||
|
||||
let extensions = cbor_map! {
|
||||
"hmac-secret" => encrypted_output,
|
||||
};
|
||||
if !cbor::write(extensions, &mut auth_data) {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR);
|
||||
}
|
||||
}
|
||||
|
||||
let mut signature_data = auth_data.clone();
|
||||
signature_data.extend(client_data_hash);
|
||||
let signature = credential
|
||||
@@ -639,7 +765,7 @@ where
|
||||
String::from(U2F_VERSION_STRING),
|
||||
String::from(FIDO2_VERSION_STRING),
|
||||
],
|
||||
extensions: Some(vec![]),
|
||||
extensions: Some(vec![String::from("hmac-secret")]),
|
||||
aaguid: *AAGUID,
|
||||
options: Some(options_map),
|
||||
max_msg_size: Some(1024),
|
||||
@@ -948,7 +1074,7 @@ where
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::data_formats::{
|
||||
GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity,
|
||||
Extensions, GetAssertionOptions, MakeCredentialOptions, PublicKeyCredentialRpEntity,
|
||||
PublicKeyCredentialUserEntity,
|
||||
};
|
||||
use super::*;
|
||||
@@ -970,13 +1096,15 @@ mod test {
|
||||
let mut expected_response = vec![0x00, 0xA6, 0x01];
|
||||
// The difference here is a longer array of supported versions.
|
||||
#[cfg(not(feature = "with_ctap1"))]
|
||||
expected_response.extend(&[
|
||||
0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50,
|
||||
]);
|
||||
expected_response.extend(&[0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30]);
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
expected_response.extend(&[
|
||||
0x82, 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F,
|
||||
0x32, 0x5F, 0x30, 0x02, 0x80, 0x03, 0x50,
|
||||
0x32, 0x5F, 0x30,
|
||||
]);
|
||||
expected_response.extend(&[
|
||||
0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
|
||||
0x03, 0x50,
|
||||
]);
|
||||
expected_response.extend(AAGUID);
|
||||
expected_response.extend(&[
|
||||
@@ -1130,6 +1258,7 @@ mod test {
|
||||
rp_id: String::from("example.com"),
|
||||
user_handle: vec![],
|
||||
other_ui: None,
|
||||
cred_random: None,
|
||||
};
|
||||
assert!(ctap_state
|
||||
.persistent_store
|
||||
@@ -1153,6 +1282,54 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_make_credential_hmac_secret() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let user_immediately_present = |_| Ok(());
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
|
||||
let mut extension_map = BTreeMap::new();
|
||||
extension_map.insert("hmac-secret".to_string(), cbor_bool!(true));
|
||||
let extensions = Some(Extensions::new(extension_map));
|
||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||
make_credential_params.extensions = extensions;
|
||||
let make_credential_response =
|
||||
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||
|
||||
match make_credential_response.unwrap() {
|
||||
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
|
||||
let AuthenticatorMakeCredentialResponse {
|
||||
fmt,
|
||||
auth_data,
|
||||
att_stmt,
|
||||
} = make_credential_response;
|
||||
// The expected response is split to only assert the non-random parts.
|
||||
assert_eq!(fmt, "packed");
|
||||
let mut expected_auth_data = vec![
|
||||
0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80,
|
||||
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
|
||||
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
expected_auth_data.extend(AAGUID);
|
||||
expected_auth_data.extend(&[0x00, 0x20]);
|
||||
assert_eq!(
|
||||
auth_data[0..expected_auth_data.len()],
|
||||
expected_auth_data[..]
|
||||
);
|
||||
let expected_extension_cbor = vec![
|
||||
0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
|
||||
0xF5,
|
||||
];
|
||||
assert_eq!(
|
||||
auth_data[auth_data.len() - expected_extension_cbor.len()..auth_data.len()],
|
||||
expected_extension_cbor[..]
|
||||
);
|
||||
assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64);
|
||||
}
|
||||
_ => panic!("Invalid response type"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_make_credential_cancelled() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
@@ -1216,6 +1393,53 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_residential_process_get_assertion_hmac_secret() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||
let user_immediately_present = |_| Ok(());
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
|
||||
let mut extension_map = BTreeMap::new();
|
||||
extension_map.insert("hmac-secret".to_string(), cbor_bool!(true));
|
||||
let make_extensions = Some(Extensions::new(extension_map));
|
||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||
make_credential_params.extensions = make_extensions;
|
||||
assert!(ctap_state
|
||||
.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID)
|
||||
.is_ok());
|
||||
|
||||
let pk = sk.genpk();
|
||||
let hmac_secret_parameters = cbor_map! {
|
||||
1 => cbor::Value::Map(CoseKey::from(pk).0),
|
||||
2 => vec![0; 32],
|
||||
3 => vec![0; 16],
|
||||
};
|
||||
let mut extension_map = BTreeMap::new();
|
||||
extension_map.insert("hmac-secret".to_string(), hmac_secret_parameters);
|
||||
|
||||
let get_extensions = Some(Extensions::new(extension_map));
|
||||
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
||||
rp_id: String::from("example.com"),
|
||||
client_data_hash: vec![0xCD],
|
||||
allow_list: None,
|
||||
extensions: get_extensions,
|
||||
options: GetAssertionOptions {
|
||||
up: false,
|
||||
uv: false,
|
||||
},
|
||||
pin_uv_auth_param: None,
|
||||
pin_uv_auth_protocol: None,
|
||||
};
|
||||
let get_assertion_response =
|
||||
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
|
||||
|
||||
assert_eq!(
|
||||
get_assertion_response,
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_reset() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
@@ -1231,6 +1455,7 @@ mod test {
|
||||
rp_id: String::from("example.com"),
|
||||
user_handle: vec![],
|
||||
other_ui: None,
|
||||
cred_random: None,
|
||||
};
|
||||
assert!(ctap_state
|
||||
.persistent_store
|
||||
@@ -1294,4 +1519,32 @@ mod test {
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encrypt_hmac_secret_output() {
|
||||
let shared_secret = [0x55; 32];
|
||||
let salt_enc = [0x5E; 32];
|
||||
let cred_random = [0xC9; 32];
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||
assert_eq!(output.unwrap().len(), 32);
|
||||
|
||||
let salt_enc = [0x5E; 48];
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||
assert_eq!(
|
||||
output,
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
||||
);
|
||||
|
||||
let salt_enc = [0x5E; 64];
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||
assert_eq!(output.unwrap().len(), 64);
|
||||
|
||||
let salt_enc = [0x5E; 32];
|
||||
let cred_random = [0xC9; 33];
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||
assert_eq!(
|
||||
output,
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,6 +453,7 @@ mod test {
|
||||
rp_id: String::from(rp_id),
|
||||
user_handle,
|
||||
other_ui: None,
|
||||
cred_random: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,6 +622,7 @@ mod test {
|
||||
rp_id: String::from("example.com"),
|
||||
user_handle: vec![0x00],
|
||||
other_ui: None,
|
||||
cred_random: None,
|
||||
};
|
||||
assert_eq!(found_credential, Some(expected_credential));
|
||||
}
|
||||
|
||||
@@ -165,6 +165,57 @@ pub fn send_or_recv(buf: &mut [u8; 64]) -> SendOrRecvStatus {
|
||||
pub fn recv_with_timeout(
|
||||
buf: &mut [u8; 64],
|
||||
timeout_delay: Duration<isize>,
|
||||
) -> Option<SendOrRecvStatus> {
|
||||
#[cfg(feature = "verbose")]
|
||||
writeln!(
|
||||
Console::new(),
|
||||
"Receiving packet with timeout of {}ms",
|
||||
timeout_delay.ms(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = recv_with_timeout_detail(buf, timeout_delay);
|
||||
|
||||
#[cfg(feature = "verbose")]
|
||||
{
|
||||
if let Some(SendOrRecvStatus::Received) = result {
|
||||
writeln!(Console::new(), "Received packet = {:02x?}", buf as &[u8]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// Same as send_or_recv, but with a timeout.
|
||||
// If the timeout elapses, return None.
|
||||
pub fn send_or_recv_with_timeout(
|
||||
buf: &mut [u8; 64],
|
||||
timeout_delay: Duration<isize>,
|
||||
) -> Option<SendOrRecvStatus> {
|
||||
#[cfg(feature = "verbose")]
|
||||
writeln!(
|
||||
Console::new(),
|
||||
"Sending packet with timeout of {}ms = {:02x?}",
|
||||
timeout_delay.ms(),
|
||||
buf as &[u8]
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = send_or_recv_with_timeout_detail(buf, timeout_delay);
|
||||
|
||||
#[cfg(feature = "verbose")]
|
||||
{
|
||||
if let Some(SendOrRecvStatus::Received) = result {
|
||||
writeln!(Console::new(), "Received packet = {:02x?}", buf as &[u8]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn recv_with_timeout_detail(
|
||||
buf: &mut [u8; 64],
|
||||
timeout_delay: Duration<isize>,
|
||||
) -> Option<SendOrRecvStatus> {
|
||||
let result = syscalls::allow(DRIVER_NUMBER, allow_nr::RECEIVE, buf);
|
||||
if result.is_err() {
|
||||
@@ -225,7 +276,7 @@ pub fn recv_with_timeout(
|
||||
|
||||
// Cancel USB transaction if necessary.
|
||||
if status.get().is_none() {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
#[cfg(feature = "verbose")]
|
||||
writeln!(Console::new(), "Cancelling USB receive due to timeout").unwrap();
|
||||
let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) };
|
||||
match result_code {
|
||||
@@ -249,9 +300,7 @@ pub fn recv_with_timeout(
|
||||
status.get()
|
||||
}
|
||||
|
||||
// Same as send_or_recv, but with a timeout.
|
||||
// If the timeout elapses, return None.
|
||||
pub fn send_or_recv_with_timeout(
|
||||
fn send_or_recv_with_timeout_detail(
|
||||
buf: &mut [u8; 64],
|
||||
timeout_delay: Duration<isize>,
|
||||
) -> Option<SendOrRecvStatus> {
|
||||
@@ -317,7 +366,7 @@ pub fn send_or_recv_with_timeout(
|
||||
|
||||
// Cancel USB transaction if necessary.
|
||||
if status.get().is_none() {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
#[cfg(feature = "verbose")]
|
||||
writeln!(Console::new(), "Cancelling USB transaction due to timeout").unwrap();
|
||||
let result_code = unsafe { syscalls::command(DRIVER_NUMBER, command_nr::CANCEL, 0, 0) };
|
||||
match result_code {
|
||||
|
||||
2
third_party/tock
vendored
2
third_party/tock
vendored
Submodule third_party/tock updated: fbc863faf0...3139864d39
Reference in New Issue
Block a user