Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

set(DBB-FIRMWARE-SOURCES
${CMAKE_SOURCE_DIR}/src/firmware_main_loop.c
${CMAKE_SOURCE_DIR}/src/delay.c
${CMAKE_SOURCE_DIR}/src/keystore.c
${CMAKE_SOURCE_DIR}/src/random.c
${CMAKE_SOURCE_DIR}/src/hardfault.c
Expand All @@ -36,7 +37,6 @@ set(DBB-FIRMWARE-SOURCES
${CMAKE_SOURCE_DIR}/src/touch/gestures.c
${CMAKE_SOURCE_DIR}/src/reset.c
${CMAKE_SOURCE_DIR}/src/cipher/cipher.c
${CMAKE_SOURCE_DIR}/src/workflow/orientation_screen.c
${CMAKE_SOURCE_DIR}/src/queue.c
${CMAKE_SOURCE_DIR}/src/usb/usb_processing.c
)
Expand Down Expand Up @@ -400,7 +400,7 @@ foreach(type ${RUST_LIBS})
# We build the rust standard libs so that everything from the rust runtime
# is included (like eh_personality).
if(type STREQUAL "c-unit-tests")
set(RUST_CARGO_FLAGS ${RUST_CARGO_FLAGS} -Zbuild-std)
set(RUST_CARGO_FLAGS ${RUST_CARGO_FLAGS} -Zbuild-std=std,panic_abort)
endif()
set(lib ${RUST_BINARY_DIR}/feature-${type}/${RUST_TARGET_ARCH_DIR}/${RUST_PROFILE}/libbitbox02_rust_c.a)
# The dummy output is to always trigger rebuild (cargo is clever enough to
Expand Down
78 changes: 78 additions & 0 deletions src/delay.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2025 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <delay.h>
#include <hal_timer.h>
#include <platform/driver_init.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <utils_assert.h>

struct task {
struct timer_task timer;
volatile bool done;
};

static struct task _tasks[10] = {0};
static struct task _empty = {0};

static void _hal_timer_cb(const struct timer_task* const timer)
{
for (int i = 0; i < (int)(sizeof(_tasks) / sizeof(struct task)); i++) {
if (&_tasks[i].timer == timer) {
_tasks[i].done = true;
}
}
}

bool delay_init_ms(delay_t* self, uint32_t ms)
{
// find an unused slot in tasks
int i;
CRITICAL_SECTION_ENTER();
for (i = 0; i < (int)(sizeof(_tasks) / sizeof(struct task)); i++) {
if (memcmp(&_tasks[i], &_empty, sizeof(struct task)) == 0) {
break;
}
}
CRITICAL_SECTION_LEAVE();
if (i == sizeof(_tasks)) {
return false;
}
_tasks[i].done = false;
memset(&_tasks[i], 0, sizeof(struct task));
_tasks[i].timer.interval = ms;
_tasks[i].timer.cb = _hal_timer_cb;
_tasks[i].timer.mode = TIMER_TASK_ONE_SHOT;
self->id = i;
return true;
}

void delay_start(const delay_t* self)
{
ASSERT(self->id < (sizeof(_tasks) / sizeof(struct task)));
ASSERT(!_tasks[self->id].done);
timer_add_task(&TIMER_0, &_tasks[self->id].timer);
}

bool delay_poll(const delay_t* self)
{
ASSERT(self->id < (sizeof(_tasks) / sizeof(struct task)));
if (_tasks[self->id].done) {
memset(&_tasks[self->id], 0, sizeof(struct task));
return true;
}
return false;
}
28 changes: 18 additions & 10 deletions test/hardware-fakes/src/fake_delay.c → src/delay.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 Shift Cryptosecurity AG
// Copyright 2025 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,15 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef DELAY_H
#define DELAY_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>

void delay_ms(const uint16_t ms)
{
usleep(1000 * ms);
}
typedef struct {
size_t id;
} delay_t;

void delay_us(const uint16_t us)
{
usleep(us);
}
// Create a non-blocking delay instance. Poll with delay_poll to completion
// Limited to 10 concurrent delays, will return false if it fails to allocate one
bool delay_init_ms(delay_t* self, uint32_t ms);

// Start the delay
void delay_start(const delay_t* self);

// returns true if time has passed. After it has returned true once it must not be called again
bool delay_poll(const delay_t* self);
#endif
35 changes: 33 additions & 2 deletions src/firmware_main_loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
#include "usb/usb.h"
#include "usb/usb_frame.h"
#include "usb/usb_processing.h"
#include "workflow/orientation_screen.h"
#include <rust/rust.h>
#include <ui/fonts/monogram_5X9.h>
#include <utils_ringbuffer.h>
#include <version.h>
#if APP_U2F == 1
#include "u2f.h"
#include "u2f/u2f_packet.h"
Expand All @@ -45,6 +45,36 @@
// Must be power of 2
#define UART_OUT_BUF_LEN 2048

// Currently we have one firmware for both BB02 and BB02_PLUS, and only the
// PRODUCT_BITBOX_MULTI/BTCONLY definitions apply. The PRODUCT_BITBOX_PLUS_MULTI/BTCONLY defs
// currently only apply in the bootloader, which we don't need here.
#if PRODUCT_BITBOX_MULTI == 1
#define PRODUCT_STRING_SUFFIX "multi"
#elif PRODUCT_BITBOX_BTCONLY == 1
#define PRODUCT_STRING_SUFFIX "btconly"
#elif PRODUCT_BITBOX02_FACTORYSETUP == 1
// Dummy, not actually needed, but this file is currently needlessly compiled for factorysetup.
#define PRODUCT_STRING_SUFFIX "factory"
#else
#error "unknown edition"
#endif

#define DEVICE_MODE \
"{\"p\":\"bb02p-" PRODUCT_STRING_SUFFIX "\",\"v\":\"" DIGITAL_BITBOX_VERSION "\"}"

static void _orientation_done_cb(void* user_data)
{
struct ringbuffer* uart_out_queue = (struct ringbuffer*)user_data;
// hww handler in usb_process must be setup before we can allow ble connections
if (memory_get_platform() == MEMORY_PLATFORM_BITBOX02_PLUS) {
da14531_handler_current_product = (const uint8_t*)DEVICE_MODE;
da14531_handler_current_product_len = sizeof(DEVICE_MODE) - 1;
da14531_set_product(
da14531_handler_current_product, da14531_handler_current_product_len, uart_out_queue);
}
usb_start();
}

void firmware_main_loop(void)
{
// Set the size of uart_read_buf to the size of the ringbuffer in the UART driver so we can read
Expand All @@ -63,7 +93,8 @@ void firmware_main_loop(void)
da14531_set_name(buf, strlen(buf), &uart_write_queue);

// This starts the async orientation screen workflow, which is processed by the loop below.
orientation_screen(&uart_write_queue);

rust_workflow_spawn_orientation_screen(_orientation_done_cb, &uart_write_queue);

const uint8_t* hww_data = NULL;
uint8_t hww_frame[USB_REPORT_SIZE] = {0};
Expand Down
3 changes: 1 addition & 2 deletions src/rust/bitbox02-rust-c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ extern crate util;
// Whenever execution reaches somewhere it isn't supposed to rust code will "panic". Our panic
// handler will print the available information on the screen and over RTT. If we compile with
// `panic=abort` this code will never get executed.
#[cfg(not(test))]
#[cfg(not(feature = "testing"))]
#[cfg(not(any(test, feature = "testing", feature = "c-unit-testing")))]
#[cfg_attr(feature = "bootloader", allow(unused_variables))]
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
Expand Down
45 changes: 44 additions & 1 deletion src/rust/bitbox02-rust-c/src/workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extern crate alloc;

use alloc::boxed::Box;
use alloc::string::String;
use bitbox02_rust::workflow::confirm;
use bitbox02_rust::workflow::{confirm, orientation_screen};
use core::task::Poll;
use util::bb02_async::{Task, spin};

Expand All @@ -42,6 +42,8 @@ static mut CONFIRM_PARAMS: Option<confirm::Params> = None;
static mut CONFIRM_STATE: TaskState<'static, Result<(), confirm::UserAbort>> = TaskState::Nothing;
static mut BITBOX02_HAL: bitbox02_rust::hal::BitBox02Hal = bitbox02_rust::hal::BitBox02Hal::new();

static mut ORIENTATION_SCREEN_STATE: TaskState<'static, ()> = TaskState::Nothing;

#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_spawn_unlock() {
unsafe {
Expand Down Expand Up @@ -71,6 +73,22 @@ pub unsafe extern "C" fn rust_workflow_spawn_confirm(
}
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_spawn_orientation_screen(
orientation_selected_cb: Option<extern "C" fn(*mut core::ffi::c_void)>,
user_data: *mut core::ffi::c_void,
) {
let callback = move || {
if let Some(cb) = orientation_selected_cb {
cb(user_data)
}
};
unsafe {
ORIENTATION_SCREEN_STATE =
TaskState::Running(Box::pin(orientation_screen::create(callback)));
}
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_spin() {
unsafe {
Expand All @@ -92,6 +110,15 @@ pub unsafe extern "C" fn rust_workflow_spin() {
}
_ => (),
}
match ORIENTATION_SCREEN_STATE {
TaskState::Running(ref mut task) => {
let result = spin(task);
if let Poll::Ready(result) = result {
ORIENTATION_SCREEN_STATE = TaskState::ResultAvailable(result);
}
}
_ => (),
}
}
}

Expand Down Expand Up @@ -131,6 +158,20 @@ pub unsafe extern "C" fn rust_workflow_confirm_poll(result_out: &mut bool) -> bo
}
}

/// Returns true if there was a result.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_orientation_screen_poll() -> bool {
unsafe {
match ORIENTATION_SCREEN_STATE {
TaskState::ResultAvailable(_) => {
ORIENTATION_SCREEN_STATE = TaskState::Nothing;
true
}
_ => false,
}
}
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_abort_current() {
unsafe {
Expand All @@ -140,5 +181,7 @@ pub unsafe extern "C" fn rust_workflow_abort_current() {
CONFIRM_BODY = None;
CONFIRM_PARAMS = None;
CONFIRM_STATE = TaskState::Nothing;

ORIENTATION_SCREEN_STATE = TaskState::Nothing;
}
}
1 change: 1 addition & 0 deletions src/rust/bitbox02-rust/src/workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod confirm;
pub mod menu;
#[cfg_attr(feature = "c-unit-testing", path = "workflow/mnemonic_c_unit_tests.rs")]
pub mod mnemonic;
pub mod orientation_screen;
pub mod pairing;
pub mod password;
pub mod sdcard;
Expand Down
43 changes: 43 additions & 0 deletions src/rust/bitbox02-rust/src/workflow/orientation_screen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2025 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use util::bb02_async::option;

pub async fn create<CB>(orientation_selected_cb: CB)
where
CB: FnOnce(),
{
let result = core::cell::RefCell::new(None as Option<()>);
let mut orientation_arrows = bitbox02::ui::orientation_arrows(|upside_down| {
if upside_down {
bitbox02::screen_rotate()
}
*result.borrow_mut() = Some(());
});
orientation_arrows.screen_stack_push();

// Wait until orientation has been chosen
option(&result).await;
drop(orientation_arrows);

// During this delay the bb02 logotype is shown
if let Ok(delay) = bitbox02::delay::Delay::from_ms(1300) {
delay.await;
}

// Switch to lockscreen that shows "See the bitbox app" and device name
bitbox02::ui::screen_process_waiting_switch_to_lockscreen();

orientation_selected_cb();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a double take seeing a callback called at the end of an async function. Would it be possible to simply return whatever value you need and use it when polling is done (in if let Poll::Ready(result) = result { ... )

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so because this future isn't run in a traditional async runtime, it is run with our "glue layer". I could return the callback and then call it in the rust_workflow_spin() function instead. But not sure if that is great.

I was hoping it would be helpful if the orientation workflow was in rust for the simulator. But in the end it didn't play out so well, I still have to call the glue-layer because I don't have a proper async runtime in the simulator either.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the rust version of the orientation selection is still more readable IMO than the c version with interrupts, so I still think it is a net win.)

}
9 changes: 7 additions & 2 deletions src/rust/bitbox02-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,15 @@ const ALLOWLIST_FNS: &[&str] = &[
"confirm_create",
"confirm_transaction_address_create",
"confirm_transaction_fee_create",
"delay_init_ms",
"delay_ms",
"delay_poll",
"delay_start",
"delay_us",
"empty_create",
"unlock_animation_create",
"keystore_bip39_mnemonic_to_seed",
"keystore_copy_seed",
"unlock_animation_create",
"keystore_copy_bip39_seed",
"keystore_create_and_store_seed",
"keystore_encrypt_and_store_seed",
Expand Down Expand Up @@ -119,6 +122,7 @@ const ALLOWLIST_FNS: &[&str] = &[
"menu_create",
"fake_memory_factoryreset",
"spi_mem_full_erase",
"orientation_arrows_create",
"printf",
"progress_create",
"progress_set",
Expand All @@ -131,8 +135,10 @@ const ALLOWLIST_FNS: &[&str] = &[
"screen_print_debug",
"screen_process",
"screen_process_waiting_switch_to_logo",
"screen_process_waiting_switch_to_lockscreen",
"screen_saver_disable",
"screen_saver_enable",
"screen_rotate",
"sd_card_inserted",
"sd_erase_file_in_subdir",
"sd_format",
Expand Down Expand Up @@ -252,7 +258,6 @@ const BITBOX02_SOURCES: &[&str] = &[
"src/usb/usb_processing.c",
"src/usb/usb.c",
"src/util.c",
"src/workflow/orientation_screen.c",
"external/asf4-drivers/hal/utils/src/utils_ringbuffer.c",
];

Expand Down
Loading