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:
parent
47d56deed3
commit
a581ace9d5
@ -129,6 +129,7 @@ fn generate_micropython_bindings() {
|
||||
.allowlist_function("trezor_obj_get_ll_checked")
|
||||
.allowlist_function("trezor_obj_get_ull_checked")
|
||||
.allowlist_function("trezor_obj_str_from_rom_text")
|
||||
.allowlist_var("mp_const_empty_tuple_obj")
|
||||
// buffer
|
||||
.allowlist_function("mp_get_buffer")
|
||||
.allowlist_var("MP_BUFFER_READ")
|
||||
|
@ -1,5 +1,7 @@
|
||||
#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_wire(mp_obj_t wire_id);
|
||||
mp_obj_t protobuf_decode(mp_obj_t buf, mp_obj_t def,
|
||||
|
@ -1,7 +1,16 @@
|
||||
use core::ops::RangeFrom;
|
||||
|
||||
use cstr_core::{cstr, CStr};
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::{buffer::Buffer, obj::Obj},
|
||||
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,
|
||||
};
|
||||
|
||||
@ -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")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ mod util;
|
||||
#[cfg(not(feature = "test"))]
|
||||
#[panic_handler]
|
||||
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
|
||||
// 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
|
||||
// confusion.
|
||||
|
||||
// SAFETY: Safe because we are passing in \0-terminated literals.
|
||||
let empty = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
|
||||
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"rs\0") };
|
||||
let empty = cstr!("");
|
||||
let msg = cstr!("rs");
|
||||
|
||||
// TODO: Ideally we would take the file and line info out of
|
||||
// `PanicInfo::location()`.
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/micropython.rs"));
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -1,31 +1,30 @@
|
||||
use cstr_core::CStr;
|
||||
use cstr_core::cstr;
|
||||
|
||||
use crate::{error::Error, micropython::qstr::Qstr};
|
||||
|
||||
// XXX const version of `from_bytes_with_nul_unchecked` is nightly-only.
|
||||
|
||||
pub fn experimental_not_enabled() -> Error {
|
||||
let msg =
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(b"Experimental features are disabled.\0") };
|
||||
let msg = cstr!("Experimental features are disabled");
|
||||
Error::ValueError(msg)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -107,19 +107,19 @@ pub fn usb_close() {
|
||||
const MAX_INTERFACE_COUNT: usize = 4;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UsbConfig<'a> {
|
||||
pub struct UsbConfig {
|
||||
pub vendor_id: u16,
|
||||
pub product_id: u16,
|
||||
pub release_num: u16,
|
||||
pub manufacturer: &'static CStr,
|
||||
pub product: &'static CStr,
|
||||
pub interface: &'static CStr,
|
||||
pub serial_number: &'a CStr,
|
||||
pub serial_number: &'static CStr,
|
||||
pub usb21_landing: bool,
|
||||
pub interfaces: Vec<IfaceConfig, MAX_INTERFACE_COUNT>,
|
||||
}
|
||||
|
||||
impl UsbConfig<'_> {
|
||||
impl UsbConfig {
|
||||
fn as_dev_info(&self) -> ffi::usb_dev_info_t {
|
||||
ffi::usb_dev_info_t {
|
||||
device_class: 0,
|
||||
@ -131,8 +131,7 @@ impl UsbConfig<'_> {
|
||||
manufacturer: self.manufacturer.as_ptr(),
|
||||
product: self.product.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: set_global_serial_number(self.serial_number).as_ptr(),
|
||||
serial_number: self.serial_number.as_ptr(),
|
||||
usb21_enabled: secbool::TRUE,
|
||||
usb21_landing: if self.usb21_landing {
|
||||
secbool::TRUE
|
||||
@ -151,6 +150,15 @@ pub enum IfaceTicket {
|
||||
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 {
|
||||
WebUsb(WebUsbConfig),
|
||||
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()])
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
use core::slice;
|
||||
|
||||
use crate::micropython::time;
|
||||
use crate::time::Duration;
|
||||
use crate::{
|
||||
error::Error,
|
||||
micropython::{
|
||||
map::{Map, MapElem},
|
||||
obj::Obj,
|
||||
runtime::raise_exception,
|
||||
time,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// Wait an unspecified short amount of time. To be used in busy loops.
|
||||
|
Loading…
Reference in New Issue
Block a user