1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-23 15:08:19 +00:00

chore(core/rust): Add uPy-exported io_usb_start fn

This commit is contained in:
Jan Pochyla 2022-03-09 11:17:13 -03:00 committed by grdddj
parent 47d56deed3
commit a581ace9d5
9 changed files with 269 additions and 148 deletions

View File

@ -129,6 +129,7 @@ fn generate_micropython_bindings() {
.allowlist_function("trezor_obj_get_ll_checked") .allowlist_function("trezor_obj_get_ll_checked")
.allowlist_function("trezor_obj_get_ull_checked") .allowlist_function("trezor_obj_get_ull_checked")
.allowlist_function("trezor_obj_str_from_rom_text") .allowlist_function("trezor_obj_str_from_rom_text")
.allowlist_var("mp_const_empty_tuple_obj")
// buffer // buffer
.allowlist_function("mp_get_buffer") .allowlist_function("mp_get_buffer")
.allowlist_var("MP_BUFFER_READ") .allowlist_var("MP_BUFFER_READ")

View File

@ -1,5 +1,7 @@
#include "librust_qstr.h" #include "librust_qstr.h"
mp_obj_t io_usb_start(mp_obj_t serial_number);
mp_obj_t protobuf_type_for_name(mp_obj_t name); mp_obj_t protobuf_type_for_name(mp_obj_t name);
mp_obj_t protobuf_type_for_wire(mp_obj_t wire_id); mp_obj_t protobuf_type_for_wire(mp_obj_t wire_id);
mp_obj_t protobuf_decode(mp_obj_t buf, mp_obj_t def, mp_obj_t protobuf_decode(mp_obj_t buf, mp_obj_t def,
@ -16,4 +18,4 @@ extern mp_obj_module_t mp_module_trezorui2;
#ifdef TREZOR_EMULATOR #ifdef TREZOR_EMULATOR
mp_obj_t ui_debug_layout_type(); mp_obj_t ui_debug_layout_type();
#endif #endif

View File

@ -1,7 +1,16 @@
use core::ops::RangeFrom;
use cstr_core::{cstr, CStr};
use crate::{ use crate::{
error::Error, error::Error,
micropython::{buffer::Buffer, obj::Obj},
time::{Duration, Instant}, time::{Duration, Instant},
trezorhal::usb::{usb_is_ready_to_read, usb_is_ready_to_write, IfaceTicket}, trezorhal::usb::{
usb_is_ready_to_read, usb_is_ready_to_write, usb_open, IfaceTicket, UsbConfig, UsbError,
WebUsbConfig,
},
util,
util::wait_in_busy_loop, util::wait_in_busy_loop,
}; };
@ -87,3 +96,190 @@ pub fn poll_io(resources: &[Resource], timeout: Duration) -> Result<Event, Error
} }
} }
} }
extern "C" fn io_usb_start(serial_number: Obj) -> Obj {
#[cfg(feature = "ui_debug")]
use crate::trezorhal::usb::VcpConfig;
#[cfg(not(feature = "bitcoin_only"))]
use crate::trezorhal::usb::HidConfig;
// SAFETY:
// In this function we often take mut refs to static mut buffers. This requires
// an unsafe block. Safety rationale:
// - We are in a single-threaded context.
// - `io_usb_start` is required to be called with USB turned off.
// - Underlying USB stack is required to manage the buffers on its own,
// guarding i.e. against escaping of stale buffer content.
const UDP_PORT: u16 = 0;
const WIRE_PORT_OFFSET: u16 = 0;
const DEBUGLINK_PORT_OFFSET: u16 = 1;
const WEBAUTHN_PORT_OFFSET: u16 = 2;
const VCP_PORT_OFFSET: u16 = 3;
fn usb_config(serial_number: &CStr) -> UsbConfig {
UsbConfig {
vendor_id: 0x1209,
product_id: 0x53C1,
release_num: 0x0200,
manufacturer: cstr!("SatoshiLabs"),
product: cstr!("TREZOR"),
serial_number: set_global_serial_number(serial_number),
interface: cstr!("TREZOR Interface"),
usb21_landing: false,
..UsbConfig::default()
}
}
fn create_wire_iface(ids: &mut RangeFrom<u8>) -> WebUsbConfig {
static mut WIRE_RX_BUFFER: [u8; 64] = [0; 64];
let id = ids.next().unwrap();
WebUsbConfig {
rx_buffer: unsafe { &mut WIRE_RX_BUFFER },
iface_num: id,
ep_in: 0x81 + id,
ep_out: 0x01 + id,
emu_port: UDP_PORT + WIRE_PORT_OFFSET,
}
}
#[cfg(feature = "ui_debug")]
fn create_debug_iface(ids: &mut RangeFrom<u8>) -> WebUsbConfig {
static mut DEBUG_RX_BUFFER: [u8; 64] = [0; 64];
let id = ids.next().unwrap();
WebUsbConfig {
rx_buffer: unsafe { &mut DEBUG_RX_BUFFER },
iface_num: id,
ep_in: 0x81 + id,
ep_out: 0x01 + id,
emu_port: UDP_PORT + DEBUGLINK_PORT_OFFSET,
}
}
#[cfg(not(feature = "bitcoin_only"))]
fn create_webauthn_iface(ids: &mut RangeFrom<u8>) -> HidConfig {
static mut WEBAUTHN_RX_BUFFER: [u8; 64] = [0; 64];
let id = ids.next().unwrap();
HidConfig {
report_desc: &[
0x06, 0xd0, 0xf1, // USAGE_PAGE (FIDO Alliance)
0x09, 0x01, // USAGE (U2F HID Authenticator Device)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x20, // USAGE (Input Report Data)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x40, // REPORT_COUNT (64)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x09, 0x21, // USAGE (Output Report Data)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x40, // REPORT_COUNT (64)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
],
rx_buffer: unsafe { &mut WEBAUTHN_RX_BUFFER },
iface_num: id,
ep_in: 0x81 + id,
ep_out: 0x01 + id,
emu_port: UDP_PORT + WEBAUTHN_PORT_OFFSET,
}
}
#[cfg(feature = "ui_debug")]
fn create_vcp_iface(ids: &mut RangeFrom<u8>) -> VcpConfig {
static mut VCP_RX_BUFFER: [u8; 1024] = [0; 1024];
static mut VCP_TX_BUFFER: [u8; 1024] = [0; 1024];
static mut VCP_RX_PACKET: [u8; 64] = [0; 64];
static mut VCP_TX_PACKET: [u8; 64] = [0; 64];
let id = ids.next().unwrap();
let id_data = ids.next().unwrap();
VcpConfig {
rx_buffer: unsafe { &mut VCP_RX_BUFFER },
tx_buffer: unsafe { &mut VCP_TX_BUFFER },
rx_packet: unsafe { &mut VCP_RX_PACKET },
tx_packet: unsafe { &mut VCP_TX_PACKET },
rx_intr_fn: None, // Use pendsv_kbd_intr here.
rx_intr_byte: 3, // Ctrl-C
iface_num: id,
data_iface_num: id_data,
ep_in: 0x81 + id,
ep_out: 0x01 + id,
ep_cmd: 0x81 + id_data,
emu_port: UDP_PORT + VCP_PORT_OFFSET,
}
}
fn set_global_serial_number(sn: &CStr) -> &'static CStr {
static mut GLOBAL_BUFFER: [u8; 64] = [0; 64];
// SAFETY: We are in a single threaded context, so the only possible race on
// `GLOBAL_BUFFER` is with the USB stack. We should take care to only call
// `set_global_serial_number` with the USB stopped.
unsafe {
let sn_nul = sn.to_bytes_with_nul();
// Panics if `sn_nul` is bigger then `GLOBAL_BUFFER`.
GLOBAL_BUFFER[..sn_nul.len()].copy_from_slice(sn_nul);
CStr::from_bytes_with_nul_unchecked(&GLOBAL_BUFFER[..sn_nul.len()])
}
}
let block = || {
let serial_number: Buffer = serial_number.try_into()?;
let serial_number =
CStr::from_bytes_with_nul(&serial_number).map_err(|_| Error::TypeError)?;
let mut ids = 0u8..; // Iterator of interface IDS.
let mut usb = usb_config(serial_number);
// Create the interfaces we use and add them to the config. Note that the order
// matters.
let wire_id = create_wire_iface(&mut ids).add(&mut usb)?.iface_num();
#[cfg(feature = "ui_debug")]
let debug_id = Some(create_debug_iface(&mut ids).add(&mut usb)?.iface_num());
#[cfg(not(feature = "ui_debug"))]
let debug_id: Option<u8> = None;
#[cfg(feature = "bitcoin_only")]
let webauthn_id: Option<u8> = None;
#[cfg(not(feature = "bitcoin_only"))]
let webauthn_id = Some(create_webauthn_iface(&mut ids).add(&mut usb)?.iface_num());
#[cfg(feature = "ui_debug")]
{
create_vcp_iface(&mut ids).add(&mut usb)?;
// TODO: Enable MicroPython VCP support.
}
// Convert used interface IDs to MicroPython objects.
let wire_id_obj = Obj::try_from(wire_id as u32)?;
let debug_id_obj = Obj::from(debug_id.map(Obj::try_from).transpose()?);
let webauthn_id_obj = Obj::from(webauthn_id.map(Obj::try_from).transpose()?);
let tuple = Obj::try_from((wire_id_obj, debug_id_obj, webauthn_id_obj))?;
// Initialize the USB and start the configured interfaces.
usb_open(usb)?;
Ok(tuple)
};
unsafe { util::try_or_raise(block) }
}
impl From<UsbError> for Error {
fn from(usb: UsbError) -> Self {
match usb {
UsbError::FailedToAddInterface => Error::ValueError(cstr!("Failed to add interface")),
UsbError::InterfaceNotFound => Error::ValueError(cstr!("Interface not found")),
}
}
}

View File

@ -22,7 +22,7 @@ mod util;
#[cfg(not(feature = "test"))] #[cfg(not(feature = "test"))]
#[panic_handler] #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { fn panic(_info: &core::panic::PanicInfo) -> ! {
use cstr_core::CStr; use cstr_core::cstr;
// Although it would be ideal to use the original error message, ignoring it // Although it would be ideal to use the original error message, ignoring it
// lets us avoid the `fmt` machinery and its code size and is also important for // lets us avoid the `fmt` machinery and its code size and is also important for
@ -30,9 +30,8 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
// should also avoid printing "panic" or "rust" on the user screen to avoid any // should also avoid printing "panic" or "rust" on the user screen to avoid any
// confusion. // confusion.
// SAFETY: Safe because we are passing in \0-terminated literals. let empty = cstr!("");
let empty = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; let msg = cstr!("rs");
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"rs\0") };
// TODO: Ideally we would take the file and line info out of // TODO: Ideally we would take the file and line info out of
// `PanicInfo::location()`. // `PanicInfo::location()`.

View File

@ -1,5 +1,6 @@
#![allow(dead_code)]
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
#![allow(dead_code)] #![allow(unsafe_op_in_unsafe_fn)]
include!(concat!(env!("OUT_DIR"), "/micropython.rs")); include!(concat!(env!("OUT_DIR"), "/micropython.rs"));

View File

@ -305,6 +305,48 @@ impl TryFrom<(Obj, Obj)> for Obj {
} }
} }
impl From<()> for Obj {
fn from(_value: ()) -> Self {
// micropython/py/obj.h
// #define mp_const_empty_tuple (MP_OBJ_FROM_PTR(&mp_const_empty_tuple_obj))
unsafe {
let empty_tuple_ptr = &ffi::mp_const_empty_tuple_obj as *const _ as *mut _;
Obj::from_ptr(empty_tuple_ptr)
}
}
}
// TODO: WARNING: while rebasing I chose randomly to ignore this implementation
// and use the abovementioned
// impl TryFrom<(Obj, Obj)> for Obj {
// type Error = Error;
// fn try_from(value: (Obj, Obj)) -> Result<Self, Self::Error> {
// // SAFETY: `mp_obj_new_tuple` does not retain pointer to `items`,
// expects to // find `items.len()` elements and does not modify them.
// // EXCEPTION: Will raise if allocation fails.
// catch_exception(|| unsafe {
// let items = [value.0, value.1];
// ffi::mp_obj_new_tuple(items.len(), items.as_ptr())
// })
// }
// }
impl TryFrom<(Obj, Obj, Obj)> for Obj {
type Error = Error;
fn try_from(value: (Obj, Obj, Obj)) -> Result<Self, Self::Error> {
// SAFETY: `mp_obj_new_tuple` does not retain pointer to `items`, expects to
// find `items.len()` elements and does not modify them.
// EXCEPTION: Will raise if allocation fails.
catch_exception(|| unsafe {
let items = [value.0, value.1, value.2];
ffi::mp_obj_new_tuple(items.len(), items.as_ptr())
})
}
}
// //
// # Additional conversions based on the methods above. // # Additional conversions based on the methods above.
// //

View File

@ -1,31 +1,30 @@
use cstr_core::CStr; use cstr_core::cstr;
use crate::{error::Error, micropython::qstr::Qstr}; use crate::{error::Error, micropython::qstr::Qstr};
// XXX const version of `from_bytes_with_nul_unchecked` is nightly-only. // XXX const version of `from_bytes_with_nul_unchecked` is nightly-only.
pub fn experimental_not_enabled() -> Error { pub fn experimental_not_enabled() -> Error {
let msg = let msg = cstr!("Experimental features are disabled");
unsafe { CStr::from_bytes_with_nul_unchecked(b"Experimental features are disabled.\0") };
Error::ValueError(msg) Error::ValueError(msg)
} }
pub fn unknown_field_type() -> Error { pub fn unknown_field_type() -> Error {
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Unknown field type.\0") }; let msg = cstr!("Unknown field type");
Error::ValueError(msg) Error::ValueError(msg)
} }
pub fn missing_required_field(field: Qstr) -> Error { pub fn missing_required_field(field: Qstr) -> Error {
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Missing required field\0") }; let msg = cstr!("Missing required field");
Error::ValueErrorParam(msg, field.into()) Error::ValueErrorParam(msg, field.into())
} }
pub fn invalid_value(field: Qstr) -> Error { pub fn invalid_value(field: Qstr) -> Error {
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Invalid value for field\0") }; let msg = cstr!("Invalid value for field");
Error::ValueErrorParam(msg, field.into()) Error::ValueErrorParam(msg, field.into())
} }
pub fn end_of_buffer() -> Error { pub fn end_of_buffer() -> Error {
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"End of buffer.\0") }; let msg = cstr!("End of buffer");
Error::ValueError(msg) Error::ValueError(msg)
} }

View File

@ -107,19 +107,19 @@ pub fn usb_close() {
const MAX_INTERFACE_COUNT: usize = 4; const MAX_INTERFACE_COUNT: usize = 4;
#[derive(Default)] #[derive(Default)]
pub struct UsbConfig<'a> { pub struct UsbConfig {
pub vendor_id: u16, pub vendor_id: u16,
pub product_id: u16, pub product_id: u16,
pub release_num: u16, pub release_num: u16,
pub manufacturer: &'static CStr, pub manufacturer: &'static CStr,
pub product: &'static CStr, pub product: &'static CStr,
pub interface: &'static CStr, pub interface: &'static CStr,
pub serial_number: &'a CStr, pub serial_number: &'static CStr,
pub usb21_landing: bool, pub usb21_landing: bool,
pub interfaces: Vec<IfaceConfig, MAX_INTERFACE_COUNT>, pub interfaces: Vec<IfaceConfig, MAX_INTERFACE_COUNT>,
} }
impl UsbConfig<'_> { impl UsbConfig {
fn as_dev_info(&self) -> ffi::usb_dev_info_t { fn as_dev_info(&self) -> ffi::usb_dev_info_t {
ffi::usb_dev_info_t { ffi::usb_dev_info_t {
device_class: 0, device_class: 0,
@ -131,8 +131,7 @@ impl UsbConfig<'_> {
manufacturer: self.manufacturer.as_ptr(), manufacturer: self.manufacturer.as_ptr(),
product: self.product.as_ptr(), product: self.product.as_ptr(),
interface: self.interface.as_ptr(), interface: self.interface.as_ptr(),
// Because we set the SN dynamically, we need to copy it to the static storage first. serial_number: self.serial_number.as_ptr(),
serial_number: set_global_serial_number(self.serial_number).as_ptr(),
usb21_enabled: secbool::TRUE, usb21_enabled: secbool::TRUE,
usb21_landing: if self.usb21_landing { usb21_landing: if self.usb21_landing {
secbool::TRUE secbool::TRUE
@ -151,6 +150,15 @@ pub enum IfaceTicket {
WebUsb(u8), WebUsb(u8),
} }
impl IfaceTicket {
pub fn iface_num(&self) -> u8 {
match self {
IfaceTicket::Hid(iface_num) => *iface_num,
IfaceTicket::WebUsb(iface_num) => *iface_num,
}
}
}
pub enum IfaceConfig { pub enum IfaceConfig {
WebUsb(WebUsbConfig), WebUsb(WebUsbConfig),
Hid(HidConfig), Hid(HidConfig),
@ -383,130 +391,3 @@ impl VcpConfig {
} }
} }
} }
#[cfg(test)]
mod tests {
use cstr_core::cstr;
use super::*;
#[test]
fn test_usb() {
// Example port of usb.py.
const UDP_PORT: u16 = 0;
const WIRE_PORT_OFFSET: u16 = 0;
const DEBUGLINK_PORT_OFFSET: u16 = 1;
const WEBAUTHN_PORT_OFFSET: u16 = 2;
const VCP_PORT_OFFSET: u16 = 3;
let mut iface_iter = 0u8..;
const ENABLE_IFACE_DEBUG: bool = true;
const ENABLE_IFACE_WEBAUTHN: bool = true;
const ENABLE_IFACE_VCP: bool = true;
let mut config = UsbConfig {
vendor_id: 0x1209,
product_id: 0x53C1,
release_num: 0x0200,
manufacturer: cstr!("SatoshiLabs"),
product: cstr!("TREZOR"),
serial_number: cstr!(""),
interface: cstr!("TREZOR Interface"),
usb21_landing: false,
..UsbConfig::default()
};
let id_wire = iface_iter.next().unwrap();
let iface_wire = WebUsbConfig {
rx_buffer: &mut [0; 64],
iface_num: id_wire,
ep_in: 0x81 + id_wire,
ep_out: 0x01 + id_wire,
emu_port: UDP_PORT + WIRE_PORT_OFFSET,
};
let ticket_wire = iface_wire.add(&mut config).unwrap();
if ENABLE_IFACE_DEBUG {
let id_debug = iface_iter.next().unwrap();
let iface_debug = WebUsbConfig {
rx_buffer: &mut [0; 64],
iface_num: id_debug,
ep_in: 0x81 + id_debug,
ep_out: 0x01 + id_debug,
emu_port: UDP_PORT + DEBUGLINK_PORT_OFFSET,
};
let ticket_debug = iface_debug.add(&mut config).unwrap();
}
if ENABLE_IFACE_WEBAUTHN {
let id_webauthn = iface_iter.next().unwrap();
let iface_webauthn = HidConfig {
report_desc: &[
0x06, 0xd0, 0xf1, // USAGE_PAGE (FIDO Alliance)
0x09, 0x01, // USAGE (U2F HID Authenticator Device)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x20, // USAGE (Input Report Data)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x40, // REPORT_COUNT (64)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x09, 0x21, // USAGE (Output Report Data)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x40, // REPORT_COUNT (64)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
],
rx_buffer: &mut [0; 64],
iface_num: id_webauthn,
ep_in: 0x81 + id_webauthn,
ep_out: 0x01 + id_webauthn,
emu_port: UDP_PORT + WEBAUTHN_PORT_OFFSET,
};
let ticket_webauthn = iface_webauthn.add(&mut config).unwrap();
}
if ENABLE_IFACE_VCP {
let id_vcp = iface_iter.next().unwrap();
let id_vcp_data = iface_iter.next().unwrap();
let iface_vcp = VcpConfig {
rx_buffer: &mut [0; 1024],
tx_buffer: &mut [0; 1024],
rx_packet: &mut [0; 64],
tx_packet: &mut [0; 64],
rx_intr_fn: None, // Use pendsv_kbd_intr here.
rx_intr_byte: 3, // Ctrl-C
iface_num: id_vcp,
data_iface_num: id_vcp_data,
ep_in: 0x81 + id_vcp,
ep_out: 0x01 + id_vcp,
ep_cmd: 0x81 + id_vcp_data,
emu_port: UDP_PORT + VCP_PORT_OFFSET,
};
iface_vcp.add(&mut config).unwrap();
}
usb_open(config).unwrap();
usb_close();
}
}
fn set_global_serial_number(sn: &CStr) -> &'static CStr {
static mut GLOBAL_BUFFER: [u8; 64] = [0; 64];
// SAFETY: We are in a single threaded context, so the only possible race on
// `GLOBAL_BUFFER` is with the USB stack. We should take care to only call
// `set_global_serial_number` with the USB stopped.
unsafe {
let sn_nul = sn.to_bytes_with_nul();
// Panics if `sn_nul` is bigger then `GLOBAL_BUFFER`.
GLOBAL_BUFFER[..sn_nul.len()].copy_from_slice(sn_nul);
CStr::from_bytes_with_nul_unchecked(&GLOBAL_BUFFER[..sn_nul.len()])
}
}

View File

@ -1,14 +1,14 @@
use core::slice; use core::slice;
use crate::micropython::time;
use crate::time::Duration;
use crate::{ use crate::{
error::Error, error::Error,
micropython::{ micropython::{
map::{Map, MapElem}, map::{Map, MapElem},
obj::Obj, obj::Obj,
runtime::raise_exception, runtime::raise_exception,
time,
}, },
time::Duration,
}; };
/// Wait an unspecified short amount of time. To be used in busy loops. /// Wait an unspecified short amount of time. To be used in busy loops.