From 8bf19b4c92e9e400b6553d99f87e3196c00ac931 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Sat, 20 Jan 2018 22:04:41 +0100 Subject: [PATCH] trezorhal: refactor trezor.io USB stuff into more files, add WebUSB --- embed/extmod/modtrezorio/modtrezorio-hid.h | 108 ++++ embed/extmod/modtrezorio/modtrezorio-msg.h | 519 ------------------ embed/extmod/modtrezorio/modtrezorio-poll.h | 105 ++++ embed/extmod/modtrezorio/modtrezorio-usb.h | 232 ++++++++ embed/extmod/modtrezorio/modtrezorio-vcp.h | 89 +++ embed/extmod/modtrezorio/modtrezorio-webusb.h | 99 ++++ embed/extmod/modtrezorio/modtrezorio.c | 15 +- embed/trezorhal/usb.c | 41 +- embed/trezorhal/usb.h | 3 + embed/trezorhal/usb_webusb-defs.h | 57 ++ embed/trezorhal/usb_webusb-impl.h | 311 +++++++++++ embed/trezorhal/usbd_conf.h | 1 + embed/trezorhal/usbd_def.h | 7 +- embed/unix/usb.c | 135 +++-- embed/unix/usb_webusb-defs.h | 1 + src/main.py | 24 +- 16 files changed, 1182 insertions(+), 565 deletions(-) create mode 100644 embed/extmod/modtrezorio/modtrezorio-hid.h delete mode 100644 embed/extmod/modtrezorio/modtrezorio-msg.h create mode 100644 embed/extmod/modtrezorio/modtrezorio-poll.h create mode 100644 embed/extmod/modtrezorio/modtrezorio-usb.h create mode 100644 embed/extmod/modtrezorio/modtrezorio-vcp.h create mode 100644 embed/extmod/modtrezorio/modtrezorio-webusb.h create mode 100644 embed/trezorhal/usb_webusb-defs.h create mode 100644 embed/trezorhal/usb_webusb-impl.h create mode 120000 embed/unix/usb_webusb-defs.h diff --git a/embed/extmod/modtrezorio/modtrezorio-hid.h b/embed/extmod/modtrezorio/modtrezorio-hid.h new file mode 100644 index 000000000..dddac72fa --- /dev/null +++ b/embed/extmod/modtrezorio/modtrezorio-hid.h @@ -0,0 +1,108 @@ +/// class HID: +/// ''' +/// USB HID interface configuration. +/// ''' +typedef struct _mp_obj_HID_t { + mp_obj_base_t base; + usb_hid_info_t info; +} mp_obj_HID_t; + +/// def __init__(self, +/// iface_num: int, +/// ep_in: int, +/// ep_out: int, +/// report_desc: bytes, +/// subclass: int = 0, +/// protocol: int = 0, +/// polling_interval: int = 1, +/// max_packet_len: int = 64) -> None: +/// ''' +/// ''' +STATIC mp_obj_t mod_trezorio_HID_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_iface_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_ep_in, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_ep_out, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_subclass, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_protocol, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_polling_interval, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_max_packet_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 64} }, + { MP_QSTR_report_desc, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); + + const mp_int_t iface_num = vals[0].u_int; + const mp_int_t ep_in = vals[1].u_int; + const mp_int_t ep_out = vals[2].u_int; + const mp_int_t subclass = vals[3].u_int; + const mp_int_t protocol = vals[4].u_int; + const mp_int_t polling_interval = vals[5].u_int; + const mp_int_t max_packet_len = vals[6].u_int; + mp_buffer_info_t report_desc; + mp_get_buffer_raise(vals[7].u_obj, &report_desc, MP_BUFFER_READ); + + if (report_desc.buf == NULL || report_desc.len == 0 || report_desc.len > 255) { + mp_raise_ValueError("report_desc is invalid"); + } + CHECK_PARAM_RANGE(iface_num, 0, 32) + CHECK_PARAM_RANGE(ep_in, 0, 255) + CHECK_PARAM_RANGE(ep_out, 0, 255) + CHECK_PARAM_RANGE(subclass, 0, 255) + CHECK_PARAM_RANGE(protocol, 0, 255) + CHECK_PARAM_RANGE(polling_interval, 1, 255) + CHECK_PARAM_RANGE(max_packet_len, 64, 64) + + mp_obj_HID_t *o = m_new_obj(mp_obj_HID_t); + o->base.type = type; + + o->info.rx_buffer = m_new(uint8_t, max_packet_len); + o->info.report_desc = report_desc.buf; + o->info.iface_num = (uint8_t)(iface_num); + o->info.ep_in = (uint8_t)(ep_in); + o->info.ep_out = (uint8_t)(ep_out); + o->info.subclass = (uint8_t)(subclass); + o->info.protocol = (uint8_t)(protocol); + o->info.polling_interval = (uint8_t)(polling_interval); + o->info.max_packet_len = (uint8_t)(max_packet_len); + o->info.report_desc_len = (uint8_t)(report_desc.len); + + return MP_OBJ_FROM_PTR(o); +} + +/// def iface_num(self) -> int: +/// ''' +/// Returns the configured number of this interface. +/// ''' +STATIC mp_obj_t mod_trezorio_HID_iface_num(mp_obj_t self) { + mp_obj_HID_t *o = MP_OBJ_TO_PTR(self); + return MP_OBJ_NEW_SMALL_INT(o->info.iface_num); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_HID_iface_num_obj, mod_trezorio_HID_iface_num); + +/// def write(self, msg: bytes) -> int: +/// ''' +/// Sends message using USB HID (device) or UDP (emulator). +/// ''' +STATIC mp_obj_t mod_trezorio_HID_write(mp_obj_t self, mp_obj_t msg) { + mp_obj_HID_t *o = MP_OBJ_TO_PTR(self); + mp_buffer_info_t buf; + mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ); + ssize_t r = usb_hid_write(o->info.iface_num, buf.buf, buf.len); + return MP_OBJ_NEW_SMALL_INT(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_HID_write_obj, mod_trezorio_HID_write); + +STATIC const mp_rom_map_elem_t mod_trezorio_HID_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_iface_num), MP_ROM_PTR(&mod_trezorio_HID_iface_num_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mod_trezorio_HID_write_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorio_HID_locals_dict, mod_trezorio_HID_locals_dict_table); + +STATIC const mp_obj_type_t mod_trezorio_HID_type = { + { &mp_type_type }, + .name = MP_QSTR_HID, + .make_new = mod_trezorio_HID_make_new, + .locals_dict = (void*)&mod_trezorio_HID_locals_dict, +}; diff --git a/embed/extmod/modtrezorio/modtrezorio-msg.h b/embed/extmod/modtrezorio/modtrezorio-msg.h deleted file mode 100644 index 0136f0358..000000000 --- a/embed/extmod/modtrezorio/modtrezorio-msg.h +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright (c) Pavol Rusnak, SatoshiLabs - * - * Licensed under TREZOR License - * see LICENSE file for details - */ - -#include -#include - -#include "usb.h" -#include "touch.h" - -void pendsv_kbd_intr(void); -void mp_hal_set_vcp_iface(int iface_num); - -#define TOUCH_IFACE (255) -#define POLL_READ (0x0000) -#define POLL_WRITE (0x0100) - -#define CHECK_PARAM_RANGE(value, minimum, maximum) \ - if (value < minimum || value > maximum) { \ - mp_raise_ValueError(#value " is out of range"); \ - } - -/// def poll(ifaces: Iterable[int], list_ref: List, timeout_us: int) -> bool: -/// ''' -/// Wait until one of `ifaces` is ready to read or write (using masks -// `io.POLL_READ` and `io.POLL_WRITE`) and assign the result into -/// `list_ref`: -/// -/// `list_ref[0]` - the interface number, including the mask -/// `list_ref[1]` - for touch event, tuple of (event_type, x_position, y_position) -/// - for HID read event, received bytes -/// -/// If timeout occurs, False is returned, True otherwise. -/// ''' -STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref, mp_obj_t timeout_us) { - mp_obj_list_t *ret = MP_OBJ_TO_PTR(list_ref); - if (!MP_OBJ_IS_TYPE(list_ref, &mp_type_list) || ret->len < 2) { - mp_raise_TypeError("invalid list_ref"); - } - - const mp_uint_t timeout = mp_obj_get_int(timeout_us); - const mp_uint_t deadline = mp_hal_ticks_us() + timeout; - mp_obj_iter_buf_t iterbuf; - - for (;;) { - mp_obj_t iter = mp_getiter(ifaces, &iterbuf); - mp_obj_t item; - while ((item = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { - const mp_uint_t i = mp_obj_int_get_truncated(item); - const mp_uint_t iface = i & 0x00FF; - const mp_uint_t mode = i & 0xFF00; - - if (iface == TOUCH_IFACE) { - const uint32_t evt = touch_read(); - if (evt) { - mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); - tuple->items[0] = MP_OBJ_NEW_SMALL_INT((evt >> 24) & 0xFFU); // event type - tuple->items[1] = MP_OBJ_NEW_SMALL_INT((evt >> 12) & 0xFFFU); // x position - tuple->items[2] = MP_OBJ_NEW_SMALL_INT(evt & 0xFFFU); // y position - ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); - ret->items[1] = MP_OBJ_FROM_PTR(tuple); - return mp_const_true; - } - } else - if (mode == POLL_READ) { - if (sectrue == usb_hid_can_read(iface)) { - uint8_t buf[64]; - int len = usb_hid_read(iface, buf, sizeof(buf)); - if (len > 0) { - ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); - ret->items[1] = mp_obj_new_bytes(buf, len); - return mp_const_true; - } - } - } else - if (mode == POLL_WRITE) { - if (sectrue == usb_hid_can_write(iface)) { - ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); - ret->items[1] = mp_const_none; - return mp_const_true; - } - } - } - - if (mp_hal_ticks_us() >= deadline) { - break; - } else { - MICROPY_EVENT_POLL_HOOK - } - } - - return mp_const_false; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorio_poll_obj, mod_trezorio_poll); - -/// class HID: -/// ''' -/// USB HID interface configuration. -/// ''' -typedef struct _mp_obj_HID_t { - mp_obj_base_t base; - usb_hid_info_t info; -} mp_obj_HID_t; - -/// def __init__(self, -/// iface_num: int, -/// ep_in: int, -/// ep_out: int, -/// report_desc: bytes, -/// subclass: int = 0, -/// protocol: int = 0, -/// polling_interval: int = 1, -/// max_packet_len: int = 64) -> None: -/// ''' -/// ''' -STATIC mp_obj_t mod_trezorio_HID_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - - STATIC const mp_arg_t allowed_args[] = { - { MP_QSTR_iface_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_ep_in, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_ep_out, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_subclass, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_protocol, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_polling_interval, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, - { MP_QSTR_max_packet_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 64} }, - { MP_QSTR_report_desc, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - }; - mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); - - const mp_int_t iface_num = vals[0].u_int; - const mp_int_t ep_in = vals[1].u_int; - const mp_int_t ep_out = vals[2].u_int; - const mp_int_t subclass = vals[3].u_int; - const mp_int_t protocol = vals[4].u_int; - const mp_int_t polling_interval = vals[5].u_int; - const mp_int_t max_packet_len = vals[6].u_int; - mp_buffer_info_t report_desc; - mp_get_buffer_raise(vals[7].u_obj, &report_desc, MP_BUFFER_READ); - - if (report_desc.buf == NULL || report_desc.len == 0 || report_desc.len > 255) { - mp_raise_ValueError("report_desc is invalid"); - } - CHECK_PARAM_RANGE(iface_num, 0, 32) - CHECK_PARAM_RANGE(ep_in, 0, 255) - CHECK_PARAM_RANGE(ep_out, 0, 255) - CHECK_PARAM_RANGE(subclass, 0, 255) - CHECK_PARAM_RANGE(protocol, 0, 255) - CHECK_PARAM_RANGE(polling_interval, 1, 255) - CHECK_PARAM_RANGE(max_packet_len, 64, 64) - - mp_obj_HID_t *o = m_new_obj(mp_obj_HID_t); - o->base.type = type; - - o->info.rx_buffer = m_new(uint8_t, max_packet_len); - o->info.report_desc = report_desc.buf; - o->info.iface_num = (uint8_t)(iface_num); - o->info.ep_in = (uint8_t)(ep_in); - o->info.ep_out = (uint8_t)(ep_out); - o->info.subclass = (uint8_t)(subclass); - o->info.protocol = (uint8_t)(protocol); - o->info.polling_interval = (uint8_t)(polling_interval); - o->info.max_packet_len = (uint8_t)(max_packet_len); - o->info.report_desc_len = (uint8_t)(report_desc.len); - - return MP_OBJ_FROM_PTR(o); -} - -/// def iface_num(self) -> int: -/// ''' -/// Returns the configured number of this interface. -/// ''' -STATIC mp_obj_t mod_trezorio_HID_iface_num(mp_obj_t self) { - mp_obj_HID_t *o = MP_OBJ_TO_PTR(self); - return MP_OBJ_NEW_SMALL_INT(o->info.iface_num); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_HID_iface_num_obj, mod_trezorio_HID_iface_num); - -/// def write(self, msg: bytes) -> int: -/// ''' -/// Sends message using USB HID (device) or UDP (emulator). -/// ''' -STATIC mp_obj_t mod_trezorio_HID_write(mp_obj_t self, mp_obj_t msg) { - mp_obj_HID_t *o = MP_OBJ_TO_PTR(self); - mp_buffer_info_t buf; - mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ); - ssize_t r = usb_hid_write(o->info.iface_num, buf.buf, buf.len); - return MP_OBJ_NEW_SMALL_INT(r); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_HID_write_obj, mod_trezorio_HID_write); - -STATIC const mp_rom_map_elem_t mod_trezorio_HID_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_iface_num), MP_ROM_PTR(&mod_trezorio_HID_iface_num_obj) }, - { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mod_trezorio_HID_write_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(mod_trezorio_HID_locals_dict, mod_trezorio_HID_locals_dict_table); - -STATIC const mp_obj_type_t mod_trezorio_HID_type = { - { &mp_type_type }, - .name = MP_QSTR_HID, - .make_new = mod_trezorio_HID_make_new, - .locals_dict = (void*)&mod_trezorio_HID_locals_dict, -}; - -/// class VCP: -/// ''' -/// USB VCP interface configuration. -/// ''' -typedef struct _mp_obj_VCP_t { - mp_obj_base_t base; - usb_vcp_info_t info; -} mp_obj_VCP_t; - -/// def __init__(self, -/// iface_num: int, -/// data_iface_num: int, -/// ep_in: int, -/// ep_out: int, -/// ep_cmd: int) -> None: -/// ''' -/// ''' -STATIC mp_obj_t mod_trezorio_VCP_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - - STATIC const mp_arg_t allowed_args[] = { - { MP_QSTR_iface_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_data_iface_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_ep_in, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_ep_out, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_ep_cmd, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - }; - mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); - - const mp_int_t iface_num = vals[0].u_int; - const mp_int_t data_iface_num = vals[1].u_int; - const mp_int_t ep_in = vals[2].u_int; - const mp_int_t ep_out = vals[3].u_int; - const mp_int_t ep_cmd = vals[4].u_int; - - CHECK_PARAM_RANGE(iface_num, 0, 32) - CHECK_PARAM_RANGE(data_iface_num, 0, 32) - CHECK_PARAM_RANGE(ep_in, 0, 255) - CHECK_PARAM_RANGE(ep_out, 0, 255) - CHECK_PARAM_RANGE(ep_cmd, 0, 255) - - const size_t vcp_buffer_len = 1024; - const size_t vcp_packet_len = 64; - - mp_obj_VCP_t *o = m_new_obj(mp_obj_VCP_t); - o->base.type = type; - - o->info.tx_packet = m_new(uint8_t, vcp_packet_len); - o->info.tx_buffer = m_new(uint8_t, vcp_buffer_len); - o->info.rx_packet = m_new(uint8_t, vcp_packet_len); - o->info.rx_buffer = m_new(uint8_t, vcp_buffer_len); - o->info.tx_buffer_len = vcp_buffer_len; - o->info.rx_buffer_len = vcp_buffer_len; - o->info.rx_intr_fn = pendsv_kbd_intr; - o->info.rx_intr_byte = 3; // Ctrl-C - o->info.iface_num = (uint8_t)(iface_num); - o->info.data_iface_num = (uint8_t)(data_iface_num); - o->info.ep_cmd = (uint8_t)(ep_cmd); - o->info.ep_in = (uint8_t)(ep_in); - o->info.ep_out = (uint8_t)(ep_out); - o->info.polling_interval = 10; - o->info.max_packet_len = (uint8_t)(vcp_packet_len); - - return MP_OBJ_FROM_PTR(o); -} - -/// def iface_num(self) -> int: -/// ''' -/// Returns the configured number of this interface. -/// ''' -STATIC mp_obj_t mod_trezorio_VCP_iface_num(mp_obj_t self) { - mp_obj_VCP_t *o = MP_OBJ_TO_PTR(self); - return MP_OBJ_NEW_SMALL_INT(o->info.iface_num); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_VCP_iface_num_obj, mod_trezorio_VCP_iface_num); - -STATIC const mp_rom_map_elem_t mod_trezorio_VCP_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_iface_num), MP_ROM_PTR(&mod_trezorio_VCP_iface_num_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(mod_trezorio_VCP_locals_dict, mod_trezorio_VCP_locals_dict_table); - -STATIC const mp_obj_type_t mod_trezorio_VCP_type = { - { &mp_type_type }, - .name = MP_QSTR_VCP, - .make_new = mod_trezorio_VCP_make_new, - .locals_dict = (void*)&mod_trezorio_VCP_locals_dict, -}; - -enum { - USB_CLOSED = 0, - USB_OPENED = 1, -}; - -/// class USB: -/// ''' -/// USB device configuration. -/// ''' -typedef struct _mp_obj_USB_t { - mp_obj_base_t base; - mp_obj_list_t ifaces; - usb_dev_info_t info; - mp_int_t state; -} mp_obj_USB_t; - -static const char *get_0str(mp_obj_t o, size_t min_len, size_t max_len) { - size_t len; - const char *s = mp_obj_str_get_data(o, &len); - if ((len >= min_len) && (len <= max_len)) { - if (len == 0 && s == NULL) { - return ""; - } else { - return s; - } - } else { - return NULL; - } -} - -/// def __init__(self, -/// vendor_id: int, -/// product_id: int, -/// release_num: int, -/// manufacturer: str='', -/// product: str='', -/// serial_number: str='', -/// configuration: str='', -/// interface: str='') -> None: -/// ''' -/// ''' -STATIC mp_obj_t mod_trezorio_USB_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - - STATIC const mp_arg_t allowed_args[] = { - { MP_QSTR_vendor_id, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_product_id, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_release_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_manufacturer, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, - { MP_QSTR_product, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, - { MP_QSTR_serial_number, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, - { MP_QSTR_configuration, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, - { MP_QSTR_interface, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, - }; - mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); - - const mp_int_t vendor_id = vals[0].u_int; - const mp_int_t product_id = vals[1].u_int; - const mp_int_t release_num = vals[2].u_int; - const char *manufacturer = get_0str(vals[3].u_obj, 0, 32); - const char *product = get_0str(vals[4].u_obj, 0, 32); - const char *serial_number = get_0str(vals[5].u_obj, 0, 32); - const char *configuration = get_0str(vals[6].u_obj, 0, 32); - const char *interface = get_0str(vals[7].u_obj, 0, 32); - - CHECK_PARAM_RANGE(vendor_id, 0, 65535) - CHECK_PARAM_RANGE(product_id, 0, 65535) - CHECK_PARAM_RANGE(release_num, 0, 65535) - if (manufacturer == NULL) { - mp_raise_ValueError("manufacturer is invalid"); - } - if (product == NULL) { - mp_raise_ValueError("product is invalid"); - } - if (serial_number == NULL) { - mp_raise_ValueError("serial_number is invalid"); - } - if (configuration == NULL) { - mp_raise_ValueError("configuration is invalid"); - } - if (interface == NULL) { - mp_raise_ValueError("interface is invalid"); - } - - mp_obj_USB_t *o = m_new_obj(mp_obj_USB_t); - o->base.type = type; - - o->state = USB_CLOSED; - - o->info.vendor_id = (uint16_t)(vendor_id); - o->info.product_id = (uint16_t)(product_id); - o->info.release_num = (uint16_t)(release_num); - o->info.manufacturer = (const uint8_t *)(manufacturer); - o->info.product = (const uint8_t *)(product); - o->info.serial_number = (const uint8_t *)(serial_number); - o->info.configuration = (const uint8_t *)(configuration); - o->info.interface = (const uint8_t *)(interface); - mp_obj_list_init(&o->ifaces, 0); - - return MP_OBJ_FROM_PTR(o); -} - -/// def add(self, iface: Union[HID, VCP]) -> None: -/// ''' -/// Registers passed interface into the USB stack. -/// ''' -STATIC mp_obj_t mod_trezorio_USB_add(mp_obj_t self, mp_obj_t iface) { - mp_obj_USB_t *o = MP_OBJ_TO_PTR(self); - - if (o->state != USB_CLOSED) { - mp_raise_msg(&mp_type_RuntimeError, "already initialized"); - } - mp_obj_list_append(MP_OBJ_FROM_PTR(&o->ifaces), iface); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_USB_add_obj, mod_trezorio_USB_add); - -/// def open(self) -> None: -/// ''' -/// Initializes the USB stack. -/// ''' -STATIC mp_obj_t mod_trezorio_USB_open(mp_obj_t self) { - mp_obj_USB_t *o = MP_OBJ_TO_PTR(self); - - if (o->state != USB_CLOSED) { - mp_raise_msg(&mp_type_RuntimeError, "already initialized"); - } - - size_t iface_cnt; - mp_obj_t *iface_objs; - mp_obj_get_array(MP_OBJ_FROM_PTR(&o->ifaces), &iface_cnt, &iface_objs); - - // Initialize the USB stack - usb_init(&o->info); - - int vcp_iface_num = -1; - - // Add all interfaces - for (size_t i = 0; i < iface_cnt; i++) { - mp_obj_t iface = iface_objs[i]; - - if (MP_OBJ_IS_TYPE(iface, &mod_trezorio_HID_type)) { - mp_obj_HID_t *hid = MP_OBJ_TO_PTR(iface); - if (sectrue != usb_hid_add(&hid->info)) { - usb_deinit(); - mp_raise_msg(&mp_type_RuntimeError, "failed to add HID interface"); - } - } else if (MP_OBJ_IS_TYPE(iface, &mod_trezorio_VCP_type)) { - mp_obj_VCP_t *vcp = MP_OBJ_TO_PTR(iface); - if (sectrue != usb_vcp_add(&vcp->info)) { - usb_deinit(); - mp_raise_msg(&mp_type_RuntimeError, "failed to add VCP interface"); - } - vcp_iface_num = vcp->info.iface_num; - } else { - usb_deinit(); - mp_raise_TypeError("expected HID or VCP type"); - } - } - - // Start the USB stack - usb_start(); - o->state = USB_OPENED; - - // If we found any VCP interfaces, use the last one for stdio, - // otherwise disable the stdio support - mp_hal_set_vcp_iface(vcp_iface_num); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_USB_open_obj, mod_trezorio_USB_open); - -/// def close(self) -> None: -/// ''' -/// Cleans up the USB stack. -/// ''' -STATIC mp_obj_t mod_trezorio_USB_close(mp_obj_t self) { - mp_obj_USB_t *o = MP_OBJ_TO_PTR(self); - - if (o->state != USB_OPENED) { - mp_raise_msg(&mp_type_RuntimeError, "not initialized"); - } - usb_stop(); - usb_deinit(); - mp_obj_list_set_len(MP_OBJ_FROM_PTR(&o->ifaces), 0); - mp_seq_clear(o->ifaces.items, 0, o->ifaces.alloc, sizeof(*o->ifaces.items)); - o->info.vendor_id = 0; - o->info.product_id = 0; - o->info.release_num = 0; - o->info.manufacturer = NULL; - o->info.product = NULL; - o->info.serial_number = NULL; - o->state = USB_CLOSED; - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_USB_close_obj, mod_trezorio_USB_close); - -STATIC mp_obj_t mod_trezorio_USB___del__(mp_obj_t self) { - mp_obj_USB_t *o = MP_OBJ_TO_PTR(self); - if (o->state != USB_CLOSED) { - usb_stop(); - usb_deinit(); - o->state = USB_CLOSED; - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_USB___del___obj, mod_trezorio_USB___del__); - -STATIC const mp_rom_map_elem_t mod_trezorio_USB_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_add), MP_ROM_PTR(&mod_trezorio_USB_add_obj) }, - { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mod_trezorio_USB_open_obj) }, - { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mod_trezorio_USB_close_obj) }, - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorio_USB___del___obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(mod_trezorio_USB_locals_dict, mod_trezorio_USB_locals_dict_table); - -STATIC const mp_obj_type_t mod_trezorio_USB_type = { - { &mp_type_type }, - .name = MP_QSTR_USB, - .make_new = mod_trezorio_USB_make_new, - .locals_dict = (void*)&mod_trezorio_USB_locals_dict, -}; diff --git a/embed/extmod/modtrezorio/modtrezorio-poll.h b/embed/extmod/modtrezorio/modtrezorio-poll.h new file mode 100644 index 000000000..2c06b9ee9 --- /dev/null +++ b/embed/extmod/modtrezorio/modtrezorio-poll.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) Pavol Rusnak, SatoshiLabs + * + * Licensed under TREZOR License + * see LICENSE file for details + */ + +#include +#include + +#include "usb.h" + +#define TOUCH_IFACE (255) +#define POLL_READ (0x0000) +#define POLL_WRITE (0x0100) + +#define CHECK_PARAM_RANGE(value, minimum, maximum) \ + if (value < minimum || value > maximum) { \ + mp_raise_ValueError(#value " is out of range"); \ + } + +/// def poll(ifaces: Iterable[int], list_ref: List, timeout_us: int) -> bool: +/// ''' +/// Wait until one of `ifaces` is ready to read or write (using masks +// `io.POLL_READ` and `io.POLL_WRITE`) and assign the result into +/// `list_ref`: +/// +/// `list_ref[0]` - the interface number, including the mask +/// `list_ref[1]` - for touch event, tuple of (event_type, x_position, y_position) +/// - for USB read event, received bytes +/// +/// If timeout occurs, False is returned, True otherwise. +/// ''' +STATIC mp_obj_t mod_trezorio_poll(mp_obj_t ifaces, mp_obj_t list_ref, mp_obj_t timeout_us) { + mp_obj_list_t *ret = MP_OBJ_TO_PTR(list_ref); + if (!MP_OBJ_IS_TYPE(list_ref, &mp_type_list) || ret->len < 2) { + mp_raise_TypeError("invalid list_ref"); + } + + const mp_uint_t timeout = mp_obj_get_int(timeout_us); + const mp_uint_t deadline = mp_hal_ticks_us() + timeout; + mp_obj_iter_buf_t iterbuf; + + for (;;) { + mp_obj_t iter = mp_getiter(ifaces, &iterbuf); + mp_obj_t item; + while ((item = mp_iternext(iter)) != MP_OBJ_STOP_ITERATION) { + const mp_uint_t i = mp_obj_int_get_truncated(item); + const mp_uint_t iface = i & 0x00FF; + const mp_uint_t mode = i & 0xFF00; + + if (iface == TOUCH_IFACE) { + const uint32_t evt = touch_read(); + if (evt) { + mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL)); + tuple->items[0] = MP_OBJ_NEW_SMALL_INT((evt >> 24) & 0xFFU); // event type + tuple->items[1] = MP_OBJ_NEW_SMALL_INT((evt >> 12) & 0xFFFU); // x position + tuple->items[2] = MP_OBJ_NEW_SMALL_INT(evt & 0xFFFU); // y position + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = MP_OBJ_FROM_PTR(tuple); + return mp_const_true; + } + } else + if (mode == POLL_READ) { + if (sectrue == usb_hid_can_read(iface)) { + uint8_t buf[64]; + int len = usb_hid_read(iface, buf, sizeof(buf)); + if (len > 0) { + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = mp_obj_new_bytes(buf, len); + return mp_const_true; + } + } else if (sectrue == usb_webusb_can_read(iface)) { + uint8_t buf[64]; + int len = usb_webusb_read(iface, buf, sizeof(buf)); + if (len > 0) { + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = mp_obj_new_bytes(buf, len); + return mp_const_true; + } + } + } else + if (mode == POLL_WRITE) { + if (sectrue == usb_hid_can_write(iface)) { + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = mp_const_none; + return mp_const_true; + } else if (sectrue == usb_webusb_can_write(iface)) { + ret->items[0] = MP_OBJ_NEW_SMALL_INT(i); + ret->items[1] = mp_const_none; + return mp_const_true; + } + } + } + + if (mp_hal_ticks_us() >= deadline) { + break; + } else { + MICROPY_EVENT_POLL_HOOK + } + } + + return mp_const_false; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorio_poll_obj, mod_trezorio_poll); diff --git a/embed/extmod/modtrezorio/modtrezorio-usb.h b/embed/extmod/modtrezorio/modtrezorio-usb.h new file mode 100644 index 000000000..f02e81b14 --- /dev/null +++ b/embed/extmod/modtrezorio/modtrezorio-usb.h @@ -0,0 +1,232 @@ +void mp_hal_set_vcp_iface(int iface_num); + +enum { + USB_CLOSED = 0, + USB_OPENED = 1, +}; + +/// class USB: +/// ''' +/// USB device configuration. +/// ''' +typedef struct _mp_obj_USB_t { + mp_obj_base_t base; + mp_obj_list_t ifaces; + usb_dev_info_t info; + mp_int_t state; +} mp_obj_USB_t; + +static const char *get_0str(mp_obj_t o, size_t min_len, size_t max_len) { + size_t len; + const char *s = mp_obj_str_get_data(o, &len); + if ((len >= min_len) && (len <= max_len)) { + if (len == 0 && s == NULL) { + return ""; + } else { + return s; + } + } else { + return NULL; + } +} + +/// def __init__(self, +/// vendor_id: int, +/// product_id: int, +/// release_num: int, +/// manufacturer: str='', +/// product: str='', +/// serial_number: str='', +/// configuration: str='', +/// interface: str='') -> None: +/// ''' +/// ''' +STATIC mp_obj_t mod_trezorio_USB_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_vendor_id, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_product_id, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_release_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_manufacturer, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, + { MP_QSTR_product, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, + { MP_QSTR_serial_number, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, + { MP_QSTR_configuration, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, + { MP_QSTR_interface, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_empty_bytes} }, + }; + mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); + + const mp_int_t vendor_id = vals[0].u_int; + const mp_int_t product_id = vals[1].u_int; + const mp_int_t release_num = vals[2].u_int; + const char *manufacturer = get_0str(vals[3].u_obj, 0, 32); + const char *product = get_0str(vals[4].u_obj, 0, 32); + const char *serial_number = get_0str(vals[5].u_obj, 0, 32); + const char *configuration = get_0str(vals[6].u_obj, 0, 32); + const char *interface = get_0str(vals[7].u_obj, 0, 32); + + CHECK_PARAM_RANGE(vendor_id, 0, 65535) + CHECK_PARAM_RANGE(product_id, 0, 65535) + CHECK_PARAM_RANGE(release_num, 0, 65535) + if (manufacturer == NULL) { + mp_raise_ValueError("manufacturer is invalid"); + } + if (product == NULL) { + mp_raise_ValueError("product is invalid"); + } + if (serial_number == NULL) { + mp_raise_ValueError("serial_number is invalid"); + } + if (configuration == NULL) { + mp_raise_ValueError("configuration is invalid"); + } + if (interface == NULL) { + mp_raise_ValueError("interface is invalid"); + } + + mp_obj_USB_t *o = m_new_obj(mp_obj_USB_t); + o->base.type = type; + + o->state = USB_CLOSED; + + o->info.vendor_id = (uint16_t)(vendor_id); + o->info.product_id = (uint16_t)(product_id); + o->info.release_num = (uint16_t)(release_num); + o->info.manufacturer = (const uint8_t *)(manufacturer); + o->info.product = (const uint8_t *)(product); + o->info.serial_number = (const uint8_t *)(serial_number); + o->info.configuration = (const uint8_t *)(configuration); + o->info.interface = (const uint8_t *)(interface); + mp_obj_list_init(&o->ifaces, 0); + + return MP_OBJ_FROM_PTR(o); +} + +/// def add(self, iface: Union[HID, VCP, WebUSB]) -> None: +/// ''' +/// Registers passed interface into the USB stack. +/// ''' +STATIC mp_obj_t mod_trezorio_USB_add(mp_obj_t self, mp_obj_t iface) { + mp_obj_USB_t *o = MP_OBJ_TO_PTR(self); + + if (o->state != USB_CLOSED) { + mp_raise_msg(&mp_type_RuntimeError, "already initialized"); + } + mp_obj_list_append(MP_OBJ_FROM_PTR(&o->ifaces), iface); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_USB_add_obj, mod_trezorio_USB_add); + +/// def open(self) -> None: +/// ''' +/// Initializes the USB stack. +/// ''' +STATIC mp_obj_t mod_trezorio_USB_open(mp_obj_t self) { + mp_obj_USB_t *o = MP_OBJ_TO_PTR(self); + + if (o->state != USB_CLOSED) { + mp_raise_msg(&mp_type_RuntimeError, "already initialized"); + } + + size_t iface_cnt; + mp_obj_t *iface_objs; + mp_obj_get_array(MP_OBJ_FROM_PTR(&o->ifaces), &iface_cnt, &iface_objs); + + // Initialize the USB stack + usb_init(&o->info); + + int vcp_iface_num = -1; + + // Add all interfaces + for (size_t i = 0; i < iface_cnt; i++) { + mp_obj_t iface = iface_objs[i]; + + if (MP_OBJ_IS_TYPE(iface, &mod_trezorio_HID_type)) { + mp_obj_HID_t *hid = MP_OBJ_TO_PTR(iface); + if (sectrue != usb_hid_add(&hid->info)) { + usb_deinit(); + mp_raise_msg(&mp_type_RuntimeError, "failed to add HID interface"); + } + } else if (MP_OBJ_IS_TYPE(iface, &mod_trezorio_WebUSB_type)) { + mp_obj_WebUSB_t *webusb = MP_OBJ_TO_PTR(iface); + if (sectrue != usb_webusb_add(&webusb->info)) { + usb_deinit(); + mp_raise_msg(&mp_type_RuntimeError, "failed to add WebUSB interface"); + } + } else if (MP_OBJ_IS_TYPE(iface, &mod_trezorio_VCP_type)) { + mp_obj_VCP_t *vcp = MP_OBJ_TO_PTR(iface); + if (sectrue != usb_vcp_add(&vcp->info)) { + usb_deinit(); + mp_raise_msg(&mp_type_RuntimeError, "failed to add VCP interface"); + } + vcp_iface_num = vcp->info.iface_num; + } else { + usb_deinit(); + mp_raise_TypeError("expected HID, WebUSB or VCP type"); + } + } + + // Start the USB stack + usb_start(); + o->state = USB_OPENED; + + // If we found any VCP interfaces, use the last one for stdio, + // otherwise disable the stdio support + mp_hal_set_vcp_iface(vcp_iface_num); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_USB_open_obj, mod_trezorio_USB_open); + +/// def close(self) -> None: +/// ''' +/// Cleans up the USB stack. +/// ''' +STATIC mp_obj_t mod_trezorio_USB_close(mp_obj_t self) { + mp_obj_USB_t *o = MP_OBJ_TO_PTR(self); + + if (o->state != USB_OPENED) { + mp_raise_msg(&mp_type_RuntimeError, "not initialized"); + } + usb_stop(); + usb_deinit(); + mp_obj_list_set_len(MP_OBJ_FROM_PTR(&o->ifaces), 0); + mp_seq_clear(o->ifaces.items, 0, o->ifaces.alloc, sizeof(*o->ifaces.items)); + o->info.vendor_id = 0; + o->info.product_id = 0; + o->info.release_num = 0; + o->info.manufacturer = NULL; + o->info.product = NULL; + o->info.serial_number = NULL; + o->state = USB_CLOSED; + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_USB_close_obj, mod_trezorio_USB_close); + +STATIC mp_obj_t mod_trezorio_USB___del__(mp_obj_t self) { + mp_obj_USB_t *o = MP_OBJ_TO_PTR(self); + if (o->state != USB_CLOSED) { + usb_stop(); + usb_deinit(); + o->state = USB_CLOSED; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_USB___del___obj, mod_trezorio_USB___del__); + +STATIC const mp_rom_map_elem_t mod_trezorio_USB_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_add), MP_ROM_PTR(&mod_trezorio_USB_add_obj) }, + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mod_trezorio_USB_open_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mod_trezorio_USB_close_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mod_trezorio_USB___del___obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorio_USB_locals_dict, mod_trezorio_USB_locals_dict_table); + +STATIC const mp_obj_type_t mod_trezorio_USB_type = { + { &mp_type_type }, + .name = MP_QSTR_USB, + .make_new = mod_trezorio_USB_make_new, + .locals_dict = (void*)&mod_trezorio_USB_locals_dict, +}; diff --git a/embed/extmod/modtrezorio/modtrezorio-vcp.h b/embed/extmod/modtrezorio/modtrezorio-vcp.h new file mode 100644 index 000000000..4d814e98f --- /dev/null +++ b/embed/extmod/modtrezorio/modtrezorio-vcp.h @@ -0,0 +1,89 @@ +void pendsv_kbd_intr(void); + +/// class VCP: +/// ''' +/// USB VCP interface configuration. +/// ''' +typedef struct _mp_obj_VCP_t { + mp_obj_base_t base; + usb_vcp_info_t info; +} mp_obj_VCP_t; + +/// def __init__(self, +/// iface_num: int, +/// data_iface_num: int, +/// ep_in: int, +/// ep_out: int, +/// ep_cmd: int) -> None: +/// ''' +/// ''' +STATIC mp_obj_t mod_trezorio_VCP_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_iface_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_data_iface_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_ep_in, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_ep_out, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_ep_cmd, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); + + const mp_int_t iface_num = vals[0].u_int; + const mp_int_t data_iface_num = vals[1].u_int; + const mp_int_t ep_in = vals[2].u_int; + const mp_int_t ep_out = vals[3].u_int; + const mp_int_t ep_cmd = vals[4].u_int; + + CHECK_PARAM_RANGE(iface_num, 0, 32) + CHECK_PARAM_RANGE(data_iface_num, 0, 32) + CHECK_PARAM_RANGE(ep_in, 0, 255) + CHECK_PARAM_RANGE(ep_out, 0, 255) + CHECK_PARAM_RANGE(ep_cmd, 0, 255) + + const size_t vcp_buffer_len = 1024; + const size_t vcp_packet_len = 64; + + mp_obj_VCP_t *o = m_new_obj(mp_obj_VCP_t); + o->base.type = type; + + o->info.tx_packet = m_new(uint8_t, vcp_packet_len); + o->info.tx_buffer = m_new(uint8_t, vcp_buffer_len); + o->info.rx_packet = m_new(uint8_t, vcp_packet_len); + o->info.rx_buffer = m_new(uint8_t, vcp_buffer_len); + o->info.tx_buffer_len = vcp_buffer_len; + o->info.rx_buffer_len = vcp_buffer_len; + o->info.rx_intr_fn = pendsv_kbd_intr; + o->info.rx_intr_byte = 3; // Ctrl-C + o->info.iface_num = (uint8_t)(iface_num); + o->info.data_iface_num = (uint8_t)(data_iface_num); + o->info.ep_cmd = (uint8_t)(ep_cmd); + o->info.ep_in = (uint8_t)(ep_in); + o->info.ep_out = (uint8_t)(ep_out); + o->info.polling_interval = 10; + o->info.max_packet_len = (uint8_t)(vcp_packet_len); + + return MP_OBJ_FROM_PTR(o); +} + +/// def iface_num(self) -> int: +/// ''' +/// Returns the configured number of this interface. +/// ''' +STATIC mp_obj_t mod_trezorio_VCP_iface_num(mp_obj_t self) { + mp_obj_VCP_t *o = MP_OBJ_TO_PTR(self); + return MP_OBJ_NEW_SMALL_INT(o->info.iface_num); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_VCP_iface_num_obj, mod_trezorio_VCP_iface_num); + +STATIC const mp_rom_map_elem_t mod_trezorio_VCP_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_iface_num), MP_ROM_PTR(&mod_trezorio_VCP_iface_num_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorio_VCP_locals_dict, mod_trezorio_VCP_locals_dict_table); + +STATIC const mp_obj_type_t mod_trezorio_VCP_type = { + { &mp_type_type }, + .name = MP_QSTR_VCP, + .make_new = mod_trezorio_VCP_make_new, + .locals_dict = (void*)&mod_trezorio_VCP_locals_dict, +}; diff --git a/embed/extmod/modtrezorio/modtrezorio-webusb.h b/embed/extmod/modtrezorio/modtrezorio-webusb.h new file mode 100644 index 000000000..aa85de862 --- /dev/null +++ b/embed/extmod/modtrezorio/modtrezorio-webusb.h @@ -0,0 +1,99 @@ +/// class WebUSB: +/// ''' +/// USB WebUSB interface configuration. +/// ''' +typedef struct _mp_obj_WebUSB_t { + mp_obj_base_t base; + usb_webusb_info_t info; +} mp_obj_WebUSB_t; + +/// def __init__(self, +/// iface_num: int, +/// ep_in: int, +/// ep_out: int, +/// subclass: int = 0, +/// protocol: int = 0, +/// polling_interval: int = 1, +/// max_packet_len: int = 64) -> None: +/// ''' +/// ''' +STATIC mp_obj_t mod_trezorio_WebUSB_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + + STATIC const mp_arg_t allowed_args[] = { + { MP_QSTR_iface_num, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_ep_in, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_ep_out, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_subclass, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_protocol, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_polling_interval, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_max_packet_len, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 64} }, + }; + mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals); + + const mp_int_t iface_num = vals[0].u_int; + const mp_int_t ep_in = vals[1].u_int; + const mp_int_t ep_out = vals[2].u_int; + const mp_int_t subclass = vals[3].u_int; + const mp_int_t protocol = vals[4].u_int; + const mp_int_t polling_interval = vals[5].u_int; + const mp_int_t max_packet_len = vals[6].u_int; + + CHECK_PARAM_RANGE(iface_num, 0, 32) + CHECK_PARAM_RANGE(ep_in, 0, 255) + CHECK_PARAM_RANGE(ep_out, 0, 255) + CHECK_PARAM_RANGE(subclass, 0, 255) + CHECK_PARAM_RANGE(protocol, 0, 255) + CHECK_PARAM_RANGE(polling_interval, 1, 255) + CHECK_PARAM_RANGE(max_packet_len, 64, 64) + + mp_obj_WebUSB_t *o = m_new_obj(mp_obj_WebUSB_t); + o->base.type = type; + + o->info.rx_buffer = m_new(uint8_t, max_packet_len); + o->info.iface_num = (uint8_t)(iface_num); + o->info.ep_in = (uint8_t)(ep_in); + o->info.ep_out = (uint8_t)(ep_out); + o->info.subclass = (uint8_t)(subclass); + o->info.protocol = (uint8_t)(protocol); + o->info.polling_interval = (uint8_t)(polling_interval); + o->info.max_packet_len = (uint8_t)(max_packet_len); + + return MP_OBJ_FROM_PTR(o); +} + +/// def iface_num(self) -> int: +/// ''' +/// Returns the configured number of this interface. +/// ''' +STATIC mp_obj_t mod_trezorio_WebUSB_iface_num(mp_obj_t self) { + mp_obj_WebUSB_t *o = MP_OBJ_TO_PTR(self); + return MP_OBJ_NEW_SMALL_INT(o->info.iface_num); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_WebUSB_iface_num_obj, mod_trezorio_WebUSB_iface_num); + +/// def write(self, msg: bytes) -> int: +/// ''' +/// Sends message using USB WebUSB (device) or UDP (emulator). +/// ''' +STATIC mp_obj_t mod_trezorio_WebUSB_write(mp_obj_t self, mp_obj_t msg) { + mp_obj_WebUSB_t *o = MP_OBJ_TO_PTR(self); + mp_buffer_info_t buf; + mp_get_buffer_raise(msg, &buf, MP_BUFFER_READ); + ssize_t r = usb_webusb_write(o->info.iface_num, buf.buf, buf.len); + return MP_OBJ_NEW_SMALL_INT(r); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorio_WebUSB_write_obj, mod_trezorio_WebUSB_write); + +STATIC const mp_rom_map_elem_t mod_trezorio_WebUSB_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_iface_num), MP_ROM_PTR(&mod_trezorio_WebUSB_iface_num_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mod_trezorio_WebUSB_write_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(mod_trezorio_WebUSB_locals_dict, mod_trezorio_WebUSB_locals_dict_table); + +STATIC const mp_obj_type_t mod_trezorio_WebUSB_type = { + { &mp_type_type }, + .name = MP_QSTR_WebUSB, + .make_new = mod_trezorio_WebUSB_make_new, + .locals_dict = (void*)&mod_trezorio_WebUSB_locals_dict, +}; diff --git a/embed/extmod/modtrezorio/modtrezorio.c b/embed/extmod/modtrezorio/modtrezorio.c index 33c637be1..3de059d57 100644 --- a/embed/extmod/modtrezorio/modtrezorio.c +++ b/embed/extmod/modtrezorio/modtrezorio.c @@ -11,10 +11,16 @@ #if MICROPY_PY_TREZORIO +#include "touch.h" + #include "modtrezorio-flash.h" #include "modtrezorio-sbu.h" #include "modtrezorio-sdcard.h" -#include "modtrezorio-msg.h" +#include "modtrezorio-poll.h" +#include "modtrezorio-hid.h" +#include "modtrezorio-vcp.h" +#include "modtrezorio-webusb.h" +#include "modtrezorio-usb.h" STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_trezorio) }, @@ -28,13 +34,16 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_USB), MP_ROM_PTR(&mod_trezorio_USB_type) }, { MP_ROM_QSTR(MP_QSTR_HID), MP_ROM_PTR(&mod_trezorio_HID_type) }, { MP_ROM_QSTR(MP_QSTR_VCP), MP_ROM_PTR(&mod_trezorio_VCP_type) }, + { MP_ROM_QSTR(MP_QSTR_WebUSB), MP_ROM_PTR(&mod_trezorio_WebUSB_type) }, + { MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&mod_trezorio_poll_obj) }, + { MP_ROM_QSTR(MP_QSTR_POLL_READ), MP_OBJ_NEW_SMALL_INT(POLL_READ) }, + { MP_ROM_QSTR(MP_QSTR_POLL_WRITE), MP_OBJ_NEW_SMALL_INT(POLL_WRITE) }, + { MP_ROM_QSTR(MP_QSTR_TOUCH), MP_OBJ_NEW_SMALL_INT(TOUCH_IFACE) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_START), MP_OBJ_NEW_SMALL_INT((TOUCH_START >> 24) & 0xFFU) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_MOVE), MP_OBJ_NEW_SMALL_INT((TOUCH_MOVE >> 24) & 0xFFU) }, { MP_ROM_QSTR(MP_QSTR_TOUCH_END), MP_OBJ_NEW_SMALL_INT((TOUCH_END >> 24) & 0xFFU) }, - { MP_ROM_QSTR(MP_QSTR_POLL_READ), MP_OBJ_NEW_SMALL_INT(POLL_READ) }, - { MP_ROM_QSTR(MP_QSTR_POLL_WRITE), MP_OBJ_NEW_SMALL_INT(POLL_WRITE) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_trezorio_globals, mp_module_trezorio_globals_table); diff --git a/embed/trezorhal/usb.c b/embed/trezorhal/usb.c index d1d8a65f6..5fcb8acf2 100644 --- a/embed/trezorhal/usb.c +++ b/embed/trezorhal/usb.c @@ -53,7 +53,7 @@ void usb_init(const usb_dev_info_t *dev_info) { // Device descriptor usb_dev_desc.bLength = sizeof(usb_device_descriptor_t); usb_dev_desc.bDescriptorType = USB_DESC_TYPE_DEVICE; - usb_dev_desc.bcdUSB = 0x0200; // USB 2.0 + usb_dev_desc.bcdUSB = 0x0210; // USB 2.1 usb_dev_desc.bDeviceClass = 0xEF; // Composite Device Class usb_dev_desc.bDeviceSubClass = 0x02; // Common Class usb_dev_desc.bDeviceProtocol = 0x01; // Interface Association Descriptor @@ -157,6 +157,7 @@ static uint8_t usb_ep_clear_nak(USBD_HandleTypeDef *dev, uint8_t ep_num) { #include "usb_hid-impl.h" #include "usb_vcp-impl.h" +#include "usb_webusb-impl.h" /* * USB configuration (device & string descriptors) @@ -202,6 +203,29 @@ static uint8_t *usb_get_interface_str_descriptor(USBD_SpeedTypeDef speed, uint16 return usb_str_buf; } +static uint8_t *usb_get_bos_descriptor(USBD_SpeedTypeDef speed, uint16_t *length) { + static const uint8_t bos[] = { + // usb_bos_descriptor { + 0x05, // uint8_t bLength + USB_DESC_TYPE_BOS, // uint8_t bDescriptorType + 0x1d, 0x0, // uint16_t wTotalLength + 0x01, // uint8_t bNumDeviceCaps + // } + // usb_device_capability_descriptor { + 0x18, // uint8_t bLength + USB_DESC_TYPE_DEVICE_CAPABILITY, // uint8_t bDescriptorType + USB_DEVICE_CAPABILITY_PLATFORM, // uint8_t bDevCapabilityType + 0x00, // uint8_t bReserved + 0x38, 0xb6, 0x08, 0x34, 0xa9, 0x09, 0xa0, 0x47, 0x8b, 0xfd, 0xa0, 0x76, 0x88, 0x15, 0xb6, 0x65, // uint128_t platformCompatibilityUUID + 0x00, 0x01, // uint16_t bcdVersion + USB_WEBUSB_VENDOR_CODE, // uint8_t bVendorCode + USB_WEBUSB_LANDING_PAGE, // uint8_t iLandingPage + // } + }; + *length = sizeof(bos); + return UNCONST(bos); +} + static const USBD_DescriptorsTypeDef usb_descriptors = { .GetDeviceDescriptor = usb_get_dev_descriptor, .GetLangIDStrDescriptor = usb_get_langid_str_descriptor, @@ -210,6 +234,7 @@ static const USBD_DescriptorsTypeDef usb_descriptors = { .GetSerialStrDescriptor = usb_get_serial_str_descriptor, .GetConfigurationStrDescriptor = usb_get_configuration_str_descriptor, .GetInterfaceStrDescriptor = usb_get_interface_str_descriptor, + .GetBOSDescriptor = usb_get_bos_descriptor, }; /* @@ -225,6 +250,9 @@ static uint8_t usb_class_init(USBD_HandleTypeDef *dev, uint8_t cfg_idx) { case USB_IFACE_TYPE_VCP: usb_vcp_class_init(dev, &usb_ifaces[i].vcp, cfg_idx); break; + case USB_IFACE_TYPE_WEBUSB: + usb_webusb_class_init(dev, &usb_ifaces[i].webusb, cfg_idx); + break; default: break; } @@ -241,6 +269,9 @@ static uint8_t usb_class_deinit(USBD_HandleTypeDef *dev, uint8_t cfg_idx) { case USB_IFACE_TYPE_VCP: usb_vcp_class_deinit(dev, &usb_ifaces[i].vcp, cfg_idx); break; + case USB_IFACE_TYPE_WEBUSB: + usb_webusb_class_deinit(dev, &usb_ifaces[i].webusb, cfg_idx); + break; default: break; } @@ -261,6 +292,8 @@ static uint8_t usb_class_setup(USBD_HandleTypeDef *dev, USBD_SetupReqTypedef *re return usb_hid_class_setup(dev, &usb_ifaces[req->wIndex].hid, req); case USB_IFACE_TYPE_VCP: return usb_vcp_class_setup(dev, &usb_ifaces[req->wIndex].vcp, req); + case USB_IFACE_TYPE_WEBUSB: + return usb_webusb_class_setup(dev, &usb_ifaces[req->wIndex].webusb, req); default: return USBD_FAIL; } @@ -275,6 +308,9 @@ static uint8_t usb_class_data_in(USBD_HandleTypeDef *dev, uint8_t ep_num) { case USB_IFACE_TYPE_VCP: usb_vcp_class_data_in(dev, &usb_ifaces[i].vcp, ep_num); break; + case USB_IFACE_TYPE_WEBUSB: + usb_webusb_class_data_in(dev, &usb_ifaces[i].webusb, ep_num); + break; default: break; } @@ -291,6 +327,9 @@ static uint8_t usb_class_data_out(USBD_HandleTypeDef *dev, uint8_t ep_num) { case USB_IFACE_TYPE_VCP: usb_vcp_class_data_out(dev, &usb_ifaces[i].vcp, ep_num); break; + case USB_IFACE_TYPE_WEBUSB: + usb_webusb_class_data_out(dev, &usb_ifaces[i].webusb, ep_num); + break; default: break; } diff --git a/embed/trezorhal/usb.h b/embed/trezorhal/usb.h index fb18a4963..31758bdf6 100644 --- a/embed/trezorhal/usb.h +++ b/embed/trezorhal/usb.h @@ -108,15 +108,18 @@ typedef enum { USB_IFACE_TYPE_DISABLED = 0, USB_IFACE_TYPE_VCP = 1, USB_IFACE_TYPE_HID = 2, + USB_IFACE_TYPE_WEBUSB = 3, } usb_iface_type_t; #include "usb_hid-defs.h" #include "usb_vcp-defs.h" +#include "usb_webusb-defs.h" typedef struct { union { usb_hid_state_t hid; usb_vcp_state_t vcp; + usb_webusb_state_t webusb; }; usb_iface_type_t type; } usb_iface_t; diff --git a/embed/trezorhal/usb_webusb-defs.h b/embed/trezorhal/usb_webusb-defs.h new file mode 100644 index 000000000..c88564c49 --- /dev/null +++ b/embed/trezorhal/usb_webusb-defs.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) Jan Pochyla, SatoshiLabs + * + * Licensed under TREZOR License + * see LICENSE file for details + */ + +#define USB_WEBUSB_VENDOR_CODE 0x01 // arbitrary +#define USB_WEBUSB_LANDING_PAGE 0x01 // arbitrary + +typedef struct __attribute__((packed)) { + usb_interface_descriptor_t iface; + usb_endpoint_descriptor_t ep_in; + usb_endpoint_descriptor_t ep_out; +} usb_webusb_descriptor_block_t; + +/* usb_webusb_info_t contains all information for setting up a WebUSB interface. All + * passed pointers need to live at least until the interface is disabled + * (usb_stop is called). */ +typedef struct { + uint8_t *rx_buffer; // With length of max_packet_len bytes + uint8_t iface_num; // Address of this WebUSB interface + uint8_t ep_in; // Address of IN endpoint (with the highest bit set) + uint8_t ep_out; // Address of OUT endpoint + uint8_t subclass; // usb_iface_subclass_t + uint8_t protocol; // usb_iface_protocol_t + uint8_t polling_interval; // In units of 1ms + uint8_t max_packet_len; // Length of the biggest report and of rx_buffer +} usb_webusb_info_t; + +/* usb_webusb_state_t encapsulates all state used by enabled WebUSB interface. It + * needs to be completely initialized in usb_webusb_add and reset in + * usb_webusb_class_init. See usb_webusb_info_t for details of the configuration + * fields. */ +typedef struct { + const usb_webusb_descriptor_block_t *desc_block; + uint8_t *rx_buffer; + uint8_t ep_in; + uint8_t ep_out; + uint8_t max_packet_len; + + uint8_t protocol; // For SET_PROTOCOL/GET_PROTOCOL setup reqs + uint8_t idle_rate; // For SET_IDLE/GET_IDLE setup reqs + uint8_t alt_setting; // For SET_INTERFACE/GET_INTERFACE setup reqs + uint8_t last_read_len; // Length of data read into rx_buffer + uint8_t ep_in_is_idle; // Set to 1 after IN endpoint gets idle +} usb_webusb_state_t; + +secbool __wur usb_webusb_add(const usb_webusb_info_t *webusb_info); +secbool __wur usb_webusb_can_read(uint8_t iface_num); +secbool __wur usb_webusb_can_write(uint8_t iface_num); +int __wur usb_webusb_read(uint8_t iface_num, uint8_t *buf, uint32_t len); +int __wur usb_webusb_write(uint8_t iface_num, const uint8_t *buf, uint32_t len); + +int __wur usb_webusb_read_select(uint32_t timeout); +int __wur usb_webusb_read_blocking(uint8_t iface_num, uint8_t *buf, uint32_t len, int timeout); +int __wur usb_webusb_write_blocking(uint8_t iface_num, const uint8_t *buf, uint32_t len, int timeout); diff --git a/embed/trezorhal/usb_webusb-impl.h b/embed/trezorhal/usb_webusb-impl.h new file mode 100644 index 000000000..a401d63c6 --- /dev/null +++ b/embed/trezorhal/usb_webusb-impl.h @@ -0,0 +1,311 @@ +/* + * Copyright (c) Jan Pochyla, SatoshiLabs + * + * Licensed under TREZOR License + * see LICENSE file for details + */ + +#define USB_CLASS_WEBUSB 0xFF + +#define USB_WEBUSB_REQ_SET_PROTOCOL 0x0B +#define USB_WEBUSB_REQ_GET_PROTOCOL 0x03 +#define USB_WEBUSB_REQ_SET_IDLE 0x0A +#define USB_WEBUSB_REQ_GET_IDLE 0x02 + +#define USB_WEBUSB_REQ_GET_URL 0x02 +#define USB_WEBUSB_DESCRIPTOR_TYPE_URL 0x03 +#define USB_WEBUSB_URL_SCHEME_HTTP 0 +#define USB_WEBUSB_URL_SCHEME_HTTPS 1 + +/* usb_webusb_add adds and configures new USB WebUSB interface according to + * configuration options passed in `info`. */ +secbool usb_webusb_add(const usb_webusb_info_t *info) { + + usb_iface_t *iface = usb_get_iface(info->iface_num); + + if (iface == NULL) { + return secfalse; // Invalid interface number + } + if (iface->type != USB_IFACE_TYPE_DISABLED) { + return secfalse; // Interface is already enabled + } + + usb_webusb_descriptor_block_t *d = usb_desc_alloc_iface(sizeof(usb_webusb_descriptor_block_t)); + + if (d == NULL) { + return secfalse; // Not enough space in the configuration descriptor + } + + if ((info->ep_in & USB_EP_DIR_MSK) != USB_EP_DIR_IN) { + return secfalse; // IN EP is invalid + } + if ((info->ep_out & USB_EP_DIR_MSK) != USB_EP_DIR_OUT) { + return secfalse; // OUT EP is invalid + } + if (info->rx_buffer == NULL) { + return secfalse; + } + + // Interface descriptor + d->iface.bLength = sizeof(usb_interface_descriptor_t); + d->iface.bDescriptorType = USB_DESC_TYPE_INTERFACE; + d->iface.bInterfaceNumber = info->iface_num; + d->iface.bAlternateSetting = 0; + d->iface.bNumEndpoints = 2; + d->iface.bInterfaceClass = USB_CLASS_WEBUSB; + d->iface.bInterfaceSubClass = info->subclass; + d->iface.bInterfaceProtocol = info->protocol; + d->iface.iInterface = USBD_IDX_INTERFACE_STR; + + // IN endpoint (sending) + d->ep_in.bLength = sizeof(usb_endpoint_descriptor_t); + d->ep_in.bDescriptorType = USB_DESC_TYPE_ENDPOINT; + d->ep_in.bEndpointAddress = info->ep_in; + d->ep_in.bmAttributes = USBD_EP_TYPE_INTR; + d->ep_in.wMaxPacketSize = info->max_packet_len; + d->ep_in.bInterval = info->polling_interval; + + // OUT endpoint (receiving) + d->ep_out.bLength = sizeof(usb_endpoint_descriptor_t); + d->ep_out.bDescriptorType = USB_DESC_TYPE_ENDPOINT; + d->ep_out.bEndpointAddress = info->ep_out; + d->ep_out.bmAttributes = USBD_EP_TYPE_INTR; + d->ep_out.wMaxPacketSize = info->max_packet_len; + d->ep_out.bInterval = info->polling_interval; + + // Config descriptor + usb_desc_add_iface(sizeof(usb_webusb_descriptor_block_t)); + + // Interface state + iface->type = USB_IFACE_TYPE_WEBUSB; + iface->webusb.desc_block = d; + iface->webusb.rx_buffer = info->rx_buffer; + iface->webusb.ep_in = info->ep_in; + iface->webusb.ep_out = info->ep_out; + iface->webusb.max_packet_len = info->max_packet_len; + iface->webusb.protocol = 0; + iface->webusb.idle_rate = 0; + iface->webusb.alt_setting = 0; + iface->webusb.last_read_len = 0; + iface->webusb.ep_in_is_idle = 1; + + return sectrue; +} + +secbool usb_webusb_can_read(uint8_t iface_num) { + usb_iface_t *iface = usb_get_iface(iface_num); + if (iface == NULL) { + return secfalse; // Invalid interface number + } + if (iface->type != USB_IFACE_TYPE_WEBUSB) { + return secfalse; // Invalid interface type + } + if (iface->webusb.last_read_len == 0) { + return secfalse; // Nothing in the receiving buffer + } + if (usb_dev_handle.dev_state != USBD_STATE_CONFIGURED) { + return secfalse; // Device is not configured + } + return sectrue; +} + +secbool usb_webusb_can_write(uint8_t iface_num) { + usb_iface_t *iface = usb_get_iface(iface_num); + if (iface == NULL) { + return secfalse; // Invalid interface number + } + if (iface->type != USB_IFACE_TYPE_WEBUSB) { + return secfalse; // Invalid interface type + } + if (iface->webusb.ep_in_is_idle == 0) { + return secfalse; // Last transmission is not over yet + } + if (usb_dev_handle.dev_state != USBD_STATE_CONFIGURED) { + return secfalse; // Device is not configured + } + return sectrue; +} + +int usb_webusb_read(uint8_t iface_num, uint8_t *buf, uint32_t len) { + usb_iface_t *iface = usb_get_iface(iface_num); + if (iface == NULL) { + return -1; // Invalid interface number + } + if (iface->type != USB_IFACE_TYPE_WEBUSB) { + return -2; // Invalid interface type + } + usb_webusb_state_t *state = &iface->webusb; + + // Copy maximum possible amount of data and truncate the buffer length + if (len < state->last_read_len) { + return 0; // Not enough data in the read buffer + } + len = state->last_read_len; + state->last_read_len = 0; + memcpy(buf, state->rx_buffer, len); + + // Clear NAK to indicate we are ready to read more data + usb_ep_clear_nak(&usb_dev_handle, state->ep_out); + + return len; +} + +int usb_webusb_write(uint8_t iface_num, const uint8_t *buf, uint32_t len) { + usb_iface_t *iface = usb_get_iface(iface_num); + if (iface == NULL) { + return -1; // Invalid interface number + } + if (iface->type != USB_IFACE_TYPE_WEBUSB) { + return -2; // Invalid interface type + } + usb_webusb_state_t *state = &iface->webusb; + + state->ep_in_is_idle = 0; + USBD_LL_Transmit(&usb_dev_handle, state->ep_in, UNCONST(buf), (uint16_t)len); + + return len; +} + +int usb_webusb_read_select(uint32_t timeout) { + const uint32_t start = HAL_GetTick(); + for (;;) { + for (int i = 0; i < USBD_MAX_NUM_INTERFACES; i++) { + if (sectrue == usb_webusb_can_read(i)) { + return i; + } + } + if (HAL_GetTick() - start >= timeout) { + break; + } + __WFI(); // Enter sleep mode, waiting for interrupt + } + return -1; // Timeout +} + +int usb_webusb_read_blocking(uint8_t iface_num, uint8_t *buf, uint32_t len, int timeout) { + const uint32_t start = HAL_GetTick(); + while (sectrue != usb_webusb_can_read(iface_num)) { + if (timeout >= 0 && HAL_GetTick() - start >= timeout) { + return 0; // Timeout + } + __WFI(); // Enter sleep mode, waiting for interrupt + } + return usb_webusb_read(iface_num, buf, len); +} + +int usb_webusb_write_blocking(uint8_t iface_num, const uint8_t *buf, uint32_t len, int timeout) { + const uint32_t start = HAL_GetTick(); + while (sectrue != usb_webusb_can_write(iface_num)) { + if (timeout >= 0 && HAL_GetTick() - start >= timeout) { + return 0; // Timeout + } + __WFI(); // Enter sleep mode, waiting for interrupt + } + return usb_webusb_write(iface_num, buf, len); +} + +static void usb_webusb_class_init(USBD_HandleTypeDef *dev, usb_webusb_state_t *state, uint8_t cfg_idx) { + // Open endpoints + USBD_LL_OpenEP(dev, state->ep_in, USBD_EP_TYPE_INTR, state->max_packet_len); + USBD_LL_OpenEP(dev, state->ep_out, USBD_EP_TYPE_INTR, state->max_packet_len); + + // Reset the state + state->protocol = 0; + state->idle_rate = 0; + state->alt_setting = 0; + state->last_read_len = 0; + state->ep_in_is_idle = 1; + + // Prepare the OUT EP to receive next packet + USBD_LL_PrepareReceive(dev, state->ep_out, state->rx_buffer, state->max_packet_len); +} + +static void usb_webusb_class_deinit(USBD_HandleTypeDef *dev, usb_webusb_state_t *state, uint8_t cfg_idx) { + // Flush endpoints + USBD_LL_FlushEP(dev, state->ep_in); + USBD_LL_FlushEP(dev, state->ep_out); + // Close endpoints + USBD_LL_CloseEP(dev, state->ep_in); + USBD_LL_CloseEP(dev, state->ep_out); +} + +static int usb_webusb_class_setup(USBD_HandleTypeDef *dev, usb_webusb_state_t *state, USBD_SetupReqTypedef *req) { + + static const char url[] = { + 3 + 15, // uint8_t bLength + USB_WEBUSB_DESCRIPTOR_TYPE_URL, // uint8_t bDescriptorType + USB_WEBUSB_URL_SCHEME_HTTPS, // uint8_t bScheme + 't', 'r', 'e', 'z', 'o', 'r', '.', 'i', 'o', '/', 's', 't', 'a', 'r', 't', // char URL[] + }; + + switch (req->bmRequest & USB_REQ_TYPE_MASK) { + + case USB_REQ_TYPE_CLASS: // Class request + + switch (req->bRequest) { + case USB_WEBUSB_REQ_SET_PROTOCOL: + state->protocol = req->wValue; + break; + case USB_WEBUSB_REQ_GET_PROTOCOL: + USBD_CtlSendData(dev, &state->protocol, sizeof(state->protocol)); + break; + case USB_WEBUSB_REQ_SET_IDLE: + state->idle_rate = req->wValue >> 8; + break; + case USB_WEBUSB_REQ_GET_IDLE: + USBD_CtlSendData(dev, &state->idle_rate, sizeof(state->idle_rate)); + break; + case USB_WEBUSB_VENDOR_CODE: + switch (req->wIndex) { + case USB_WEBUSB_REQ_GET_URL: + // we should check whether req->wValue == USB_WEBUSB_LANDING_PAGE, + // but let's return always the same url for all indexes + USBD_CtlSendData(dev, (uint8_t *)url, sizeof(url)); + break; + default: + USBD_CtlError(dev, req); + return USBD_FAIL; + } + break; + default: + USBD_CtlError(dev, req); + return USBD_FAIL; + } + break; + + case USB_REQ_TYPE_STANDARD: // Interface & Endpoint request + + switch (req->bRequest) { + case USB_REQ_SET_INTERFACE: + state->alt_setting = req->wValue; + break; + case USB_REQ_GET_INTERFACE: + USBD_CtlSendData(dev, &state->alt_setting, sizeof(state->alt_setting)); + break; + } + break; + } + + return USBD_OK; +} + +static void usb_webusb_class_data_in(USBD_HandleTypeDef *dev, usb_webusb_state_t *state, uint8_t ep_num) { + if ((ep_num | USB_EP_DIR_IN) == state->ep_in) { + state->ep_in_is_idle = 1; + } +} + +static void usb_webusb_class_data_out(USBD_HandleTypeDef *dev, usb_webusb_state_t *state, uint8_t ep_num) { + if (ep_num == state->ep_out) { + state->last_read_len = USBD_LL_GetRxDataSize(dev, ep_num); + + // Prepare the OUT EP to receive next packet + // User should provide state->rx_buffer that is big enough for state->max_packet_len bytes + USBD_LL_PrepareReceive(dev, ep_num, state->rx_buffer, state->max_packet_len); + + if (state->last_read_len > 0) { + // Block the OUT EP until we process received data + usb_ep_set_nak(dev, ep_num); + } + } +} diff --git a/embed/trezorhal/usbd_conf.h b/embed/trezorhal/usbd_conf.h index 53f9de7b9..6cc722ad1 100644 --- a/embed/trezorhal/usbd_conf.h +++ b/embed/trezorhal/usbd_conf.h @@ -68,6 +68,7 @@ #define USBD_SUPPORT_USER_STRING 0 #define USBD_SELF_POWERED 0 #define USBD_DEBUG_LEVEL 0 +#define USBD_LPM_ENABLED 1 /* Exported macro ------------------------------------------------------------*/ /* Memory management macros */ diff --git a/embed/trezorhal/usbd_def.h b/embed/trezorhal/usbd_def.h index a472c8651..615ad316a 100644 --- a/embed/trezorhal/usbd_def.h +++ b/embed/trezorhal/usbd_def.h @@ -99,7 +99,10 @@ #define USB_DESC_TYPE_ENDPOINT 5 #define USB_DESC_TYPE_DEVICE_QUALIFIER 6 #define USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION 7 -#define USB_DESC_TYPE_BOS 0x0F +#define USB_DESC_TYPE_BOS 15 +#define USB_DESC_TYPE_DEVICE_CAPABILITY 16 + +#define USB_DEVICE_CAPABILITY_PLATFORM 5 #define USB_CONFIG_REMOTE_WAKEUP 2 #define USB_CONFIG_SELF_POWERED 1 @@ -108,8 +111,6 @@ #define USB_FEATURE_REMOTE_WAKEUP 1 #define USB_FEATURE_TEST_MODE 2 -#define USB_DEVICE_CAPABILITY_TYPE 0x10 - #define USB_HS_MAX_PACKET_SIZE 512 #define USB_FS_MAX_PACKET_SIZE 64 #define USB_MAX_EP0_SIZE 64 diff --git a/embed/unix/usb.c b/embed/unix/usb.c index 9e0ad1a6f..2b8998d70 100644 --- a/embed/unix/usb.c +++ b/embed/unix/usb.c @@ -20,48 +20,68 @@ void __attribute__((noreturn)) __fatal_error(const char *expr, const char *msg, #define ensure(expr, msg) (((expr) == sectrue) ? (void)0 : __fatal_error(#expr, msg, __FILE__, __LINE__, __func__)) -#define TREZOR_UDP_IFACE 0 -#define TREZOR_UDP_PORT 21324 +// emulator opens UDP server on TREZOR_UDP_PORT port +// and emulates HID/WebUSB interface TREZOR_UDP_IFACE +// gracefully ignores all other USB interfaces -static int sock; +#define TREZOR_UDP_IFACE 0 +#define TREZOR_UDP_PORT 21324 + +static usb_iface_type_t emulator_usb_iface_type = USB_IFACE_TYPE_DISABLED; + +static int sock = -1; static struct sockaddr_in si_me, si_other; static socklen_t slen = 0; void usb_init(const usb_dev_info_t *dev_info) { (void)dev_info; - - sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - ensure(sectrue * (sock >= 0), NULL); - - fcntl(sock, F_SETFL, O_NONBLOCK); - - si_me.sin_family = AF_INET; - const char *ip = getenv("TREZOR_UDP_IP"); - if (ip) { - si_me.sin_addr.s_addr = inet_addr(ip); - } else { - si_me.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - } - const char *port = getenv("TREZOR_UDP_PORT"); - if (port) { - si_me.sin_port = htons(atoi(port)); - } else { - si_me.sin_port = htons(TREZOR_UDP_PORT); - } - - ensure(sectrue * (0 == bind(sock, (struct sockaddr*)&si_me, sizeof(si_me))), NULL); } void usb_deinit(void) { } void usb_start(void) { + // start server only if interface 0 is either HID or WebUSB + if (emulator_usb_iface_type == USB_IFACE_TYPE_HID || emulator_usb_iface_type == USB_IFACE_TYPE_WEBUSB) { + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + ensure(sectrue * (sock >= 0), NULL); + + fcntl(sock, F_SETFL, O_NONBLOCK); + + si_me.sin_family = AF_INET; + const char *ip = getenv("TREZOR_UDP_IP"); + if (ip) { + si_me.sin_addr.s_addr = inet_addr(ip); + } else { + si_me.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + const char *port = getenv("TREZOR_UDP_PORT"); + if (port) { + si_me.sin_port = htons(atoi(port)); + } else { + si_me.sin_port = htons(TREZOR_UDP_PORT); + } + + ensure(sectrue * (0 == bind(sock, (struct sockaddr*)&si_me, sizeof(si_me))), NULL); + } } void usb_stop(void) { } secbool usb_hid_add(const usb_hid_info_t *info) { + // store iface type if it is the emulated iface + if (info->iface_num == TREZOR_UDP_IFACE) { + emulator_usb_iface_type = USB_IFACE_TYPE_HID; + } + return sectrue; +} + +secbool usb_webusb_add(const usb_webusb_info_t *info) { + // store iface type if it is the emulated iface + if (info->iface_num == TREZOR_UDP_IFACE) { + emulator_usb_iface_type = USB_IFACE_TYPE_WEBUSB; + } return sectrue; } @@ -69,10 +89,7 @@ secbool usb_vcp_add(const usb_vcp_info_t *info) { return sectrue; } -secbool usb_hid_can_read(uint8_t iface_num) { - if (iface_num != TREZOR_UDP_IFACE) { - return secfalse; - } +static secbool usb_emulated_can_read(uint8_t iface_num) { struct pollfd fds[] = { { sock, POLLIN, 0 }, }; @@ -80,10 +97,21 @@ secbool usb_hid_can_read(uint8_t iface_num) { return sectrue * (r > 0); } -secbool usb_hid_can_write(uint8_t iface_num) { - if (iface_num != TREZOR_UDP_IFACE) { +secbool usb_hid_can_read(uint8_t iface_num) { + if (iface_num != TREZOR_UDP_IFACE || emulator_usb_iface_type != USB_IFACE_TYPE_HID) { return secfalse; } + return usb_emulated_can_read(iface_num); +} + +secbool usb_webusb_can_read(uint8_t iface_num) { + if (iface_num != TREZOR_UDP_IFACE || emulator_usb_iface_type != USB_IFACE_TYPE_WEBUSB) { + return secfalse; + } + return usb_emulated_can_read(iface_num); +} + +static secbool usb_emulated_can_write(uint8_t iface_num) { struct pollfd fds[] = { { sock, POLLOUT, 0 }, }; @@ -91,10 +119,21 @@ secbool usb_hid_can_write(uint8_t iface_num) { return sectrue * (r > 0); } -int usb_hid_read(uint8_t iface_num, uint8_t *buf, uint32_t len) { - if (iface_num != TREZOR_UDP_IFACE) { - return 0; +secbool usb_hid_can_write(uint8_t iface_num) { + if (iface_num != TREZOR_UDP_IFACE || emulator_usb_iface_type != USB_IFACE_TYPE_HID) { + return secfalse; + } + return usb_emulated_can_write(iface_num); +} + +secbool usb_webusb_can_write(uint8_t iface_num) { + if (iface_num != TREZOR_UDP_IFACE || emulator_usb_iface_type != USB_IFACE_TYPE_WEBUSB) { + return secfalse; } + return usb_emulated_can_write(iface_num); +} + +static int usb_emulated_read(uint8_t iface_num, uint8_t *buf, uint32_t len) { struct sockaddr_in si; socklen_t sl = sizeof(si); ssize_t r = recvfrom(sock, buf, len, MSG_DONTWAIT, (struct sockaddr *)&si, &sl); @@ -114,10 +153,22 @@ int usb_hid_read(uint8_t iface_num, uint8_t *buf, uint32_t len) { return r; } -int usb_hid_write(uint8_t iface_num, const uint8_t *buf, uint32_t len) { - if (iface_num != TREZOR_UDP_IFACE) { +int usb_hid_read(uint8_t iface_num, uint8_t *buf, uint32_t len) { + if (iface_num != TREZOR_UDP_IFACE || emulator_usb_iface_type != USB_IFACE_TYPE_HID) { return 0; } + return usb_emulated_read(iface_num, buf, len); +} + +int usb_webusb_read(uint8_t iface_num, uint8_t *buf, uint32_t len) { + if (iface_num != TREZOR_UDP_IFACE || emulator_usb_iface_type != USB_IFACE_TYPE_WEBUSB) { + return 0; + } + return usb_emulated_read(iface_num, buf, len); +} + +static int usb_emulated_write(uint8_t iface_num, const uint8_t *buf, uint32_t len) +{ ssize_t r = len; if (slen > 0) { r = sendto(sock, buf, len, MSG_DONTWAIT, (const struct sockaddr *)&si_other, slen); @@ -125,6 +176,20 @@ int usb_hid_write(uint8_t iface_num, const uint8_t *buf, uint32_t len) { return r; } +int usb_hid_write(uint8_t iface_num, const uint8_t *buf, uint32_t len) { + if (iface_num != TREZOR_UDP_IFACE || emulator_usb_iface_type != USB_IFACE_TYPE_HID) { + return 0; + } + return usb_emulated_write(iface_num, buf, len); +} + +int usb_webusb_write(uint8_t iface_num, const uint8_t *buf, uint32_t len) { + if (iface_num != TREZOR_UDP_IFACE || emulator_usb_iface_type != USB_IFACE_TYPE_WEBUSB) { + return 0; + } + return usb_emulated_write(iface_num, buf, len); +} + void pendsv_kbd_intr(void) { } diff --git a/embed/unix/usb_webusb-defs.h b/embed/unix/usb_webusb-defs.h new file mode 120000 index 000000000..52d4243ba --- /dev/null +++ b/embed/unix/usb_webusb-defs.h @@ -0,0 +1 @@ +../trezorhal/usb_webusb-defs.h \ No newline at end of file diff --git a/src/main.py b/src/main.py index 46aceb81d..e96c5ea0f 100644 --- a/src/main.py +++ b/src/main.py @@ -6,11 +6,19 @@ from trezor import loop from trezor import wire from trezor import workflow +USE_WEBUSB = False + log.level = log.DEBUG # initialize the USB stack -if True: +if USE_WEBUSB: + usb_wire = io.WebUSB( + iface_num=0, + ep_in=0x81, + ep_out=0x01, + ) +else: usb_wire = io.HID( iface_num=0, ep_in=0x81, @@ -60,7 +68,13 @@ usb_u2f = io.HID( ) if __debug__: - if True: + if USE_WEBUSB: + usb_debug = io.WebUSB( + iface_num=4, + ep_in=0x85, + ep_out=0x04, + ) + else: usb_debug = io.HID( iface_num=4, ep_in=0x85, @@ -84,6 +98,7 @@ if __debug__: 0xc0, # END_COLLECTION ]), ) + usb_vcp = io.VCP( iface_num=1, data_iface_num=2, @@ -95,10 +110,11 @@ if __debug__: usb = io.USB( vendor_id=0x1209, product_id=0x53C1, - release_num=0x0002, + release_num=0x0200, manufacturer="SatoshiLabs", product="TREZOR", - serial_number="000000000000000000000000", + serial_number="44DD6B2C788D760538A78ECA", + interface="TREZOR Interface", ) usb.add(usb_wire)