firmware/stm32/COLDCARD/modckcc.c
2021-09-16 16:19:50 -04:00

378 lines
11 KiB
C

//
// (c) Copyright 2018 by Coinkite Inc. This file is covered by license found in COPYING-CC.
//
// modckcc.c - module for Coldcard hardware features and glue.
//
#include <stdio.h>
#include <string.h>
#include "modckcc.h"
#include "rng.h"
#include "usb.h"
#include "flash.h"
#include "bufhelper.h"
#include "py/gc.h"
#include "py/runtime.h"
#include "py/mphal.h"
#include "py/mpstate.h"
#include "py/stackctrl.h"
#include "boardctrl.h"
#include "storage.h"
#include "usb.h"
#include "extint.h"
#include "lib/utils/interrupt_char.h" // for mp_interrupt_char
// Presume genuine light is on, at start
STATIC bool presumably_green_light = true;
// see vcp_lockdown.c where this is used
bool ckcc_vcp_enabled;
MP_DECLARE_CONST_FUN_OBJ_0(pyb_rng_get_obj);
MP_DECLARE_CONST_FUN_OBJ_1(pyb_rng_get_bytes_obj);
// See startup.S where this table of entry points/version numbers is defined
typedef struct {
uint32_t callgate_entry;
uint32_t version_number;
uint32_t reserved[4];
} bootloaderInfoTable_t;
#define BOOTLOADER_TABLE (*((bootloaderInfoTable_t *)0x08000040))
STATIC int callgate_lower(uint32_t method_num, uint32_t arg2, mp_buffer_info_t *io_buf)
{
uint32_t dest = BOOTLOADER_TABLE.callgate_entry;
// +4 because that's required by call gate for firewall
// +1 to set LSB because we know a BLX instruction will be used to
// get there and we know its thumb code
// - also 0x100 aligned.
assert((dest & 0xff) == 0x05);
mp_uint_t before = disable_irq();
// XXX this doesn't work with compiler optimizations enabled (ie. -O0 only!!)
// Call the gate; it will trash the normal function-call registers (r0-4)
// but also r9 and r10. Does not use our stack.
//
// references:
// - <http://www.ethernut.de/en/documents/arm-inline-asm.html>
// - <https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html>
//
int rv = -2;
register int r_method asm("r0") = method_num;
register const char *tx asm("r1") = io_buf ? io_buf->buf : NULL;
register uint32_t tx_len asm("r2") = io_buf ? io_buf->len: 0;
register uint32_t r_arg2 asm("r3") = arg2;
asm volatile( "blx %[dest] \n"
"str r0, %[rv]"
: [rv] "=m" (rv)
: [dest] "r" (dest), "r" (tx), "r" (tx_len), "r" (r_arg2), "r" (r_method)
: "r9", "r10"
);
enable_irq(before);
return rv;
}
STATIC mp_obj_t sec_oneway_gate(mp_obj_t method_obj, mp_obj_t arg2_obj)
{
// jump to the callgate, but don't expect to come back. Good for DFU entry.
uint32_t dest = BOOTLOADER_TABLE.callgate_entry;
uint32_t num = mp_obj_get_int(method_obj);
uint32_t arg2 = mp_obj_get_int(arg2_obj);
pyb_usb_dev_deinit();
storage_flush();
// this causes the OLED to blank when we do callgate.show_logout(1)
#if 0
HAL_RCC_DeInit();
HAL_DeInit();
#if (__MPU_PRESENT == 1)
// MPU must be disabled for bootloader to function correctly
HAL_MPU_Disable();
#endif
for(uint i=0; i<EXTI_NUM_VECTORS; i++) {
// XXX doesn't help
extint_disable(i);
}
#endif
// XXX this may not work with compiler optimizations enabled (ie. -O0 only!!)
mp_uint_t before = disable_irq();
int rv = -2; // not really used.
register int method_num asm("r0") = num;
register const char *tx asm("r1") = 0;
register uint32_t tx_len asm("r2") = 0;
register uint32_t r_arg2 asm("r3") = arg2;
asm volatile( "blx %[dest] \n"
"str r0, %[rv]"
: [rv] "=m" (rv)
: [dest] "r" (dest), "r" (tx), "r" (tx_len), "r" (r_arg2), "r" (method_num)
: "r9", "r10"
);
enable_irq(before);
// not reached, except maybe error cases
return mp_obj_new_int(rv);
}
MP_DEFINE_CONST_FUN_OBJ_2(sec_oneway_gate_obj, sec_oneway_gate);
STATIC mp_obj_t sec_gate(mp_obj_t method_obj, mp_obj_t send_arg, mp_obj_t arg2_obj)
{
// first and last args are simple integers
uint32_t method_num = mp_obj_get_int(method_obj);
uint32_t arg2 = mp_obj_get_int(arg2_obj);
// 2nd arg: buffer to send to function, or None for NULL
mp_buffer_info_t send_buf;
bool is_none = false;
if(send_arg != mp_const_none) {
mp_get_buffer_raise(send_arg, &send_buf, MP_BUFFER_RW);
} else {
is_none = true;
}
int rv = callgate_lower(method_num, arg2, is_none ? NULL : &send_buf);
// bugfix: if bootloader (V1.0.1) calls systick_setup(), this will correct register
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return mp_obj_new_int(rv);
}
MP_DEFINE_CONST_FUN_OBJ_3(sec_gate_obj, sec_gate);
// Since we cache state of green light, need a way for us
// to be reset, altho has no bearing on real operation of 508a.
STATIC mp_obj_t presume_green(void)
{
presumably_green_light = true;
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(presume_green_obj, presume_green);
STATIC mp_obj_t is_simulator(void)
{
return MP_OBJ_NEW_SMALL_INT(0);
}
MP_DEFINE_CONST_FUN_OBJ_0(is_simulator_obj, is_simulator);
STATIC mp_obj_t get_cpu_id(void)
{
// Are we running on a STM32L496RG6? If so, expect 0x461
return MP_OBJ_NEW_SMALL_INT(DBGMCU->IDCODE & 0xfff);
}
MP_DEFINE_CONST_FUN_OBJ_0(get_cpu_id_obj, get_cpu_id);
STATIC mp_obj_t vcp_enabled(mp_obj_t new_val)
{
// Report/Control the VCP lockout. Call with None to readback.
if(mp_obj_is_integer(new_val)) {
ckcc_vcp_enabled = !!(mp_obj_get_int_truncated(new_val));
mp_interrupt_char = ckcc_vcp_enabled ? 3 : -1;
}
return MP_OBJ_NEW_SMALL_INT(ckcc_vcp_enabled);
}
MP_DEFINE_CONST_FUN_OBJ_1(vcp_enabled_obj, vcp_enabled);
STATIC mp_obj_t stack_limit(mp_obj_t new_val)
{
// Report or change the stack limit, in bytes. Probably less than 0x4000.
if(mp_obj_is_integer(new_val)) {
mp_int_t limit = mp_obj_get_int_truncated(new_val);
// Small values will cause immediate crash due to stack-depth checking, so avoid.
if((limit < 1024) || (limit > 64*1024)) {
mp_raise_ValueError(NULL);
}
mp_stack_set_limit(limit);
}
return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(stack_limit));
}
MP_DEFINE_CONST_FUN_OBJ_1(stack_limit_obj, stack_limit);
STATIC mp_obj_t wipe_fs(void)
{
// Erase and reformat filesystem
// Before calling do this:
// uos.umount('/flash')
// after, it will be remounted already
// see initfs.c
extern int factory_reset_create_filesystem(void);
return MP_OBJ_NEW_SMALL_INT(factory_reset_create_filesystem());
}
MP_DEFINE_CONST_FUN_OBJ_0(wipe_fs_obj, wipe_fs);
STATIC mp_obj_t breakpoint(void)
{
// drop into the debugger, if connected.
asm("BKPT #0");
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(breakpoint_obj, breakpoint);
STATIC mp_obj_t watchpoint(volatile mp_obj_t arg1)
{
// just be an empty function that we can set as a breakpoint
// in the debugger... also gives some visiblilty into a single object
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(watchpoint_obj, watchpoint);
STATIC mp_obj_t usb_active(void)
{
// NOP on mk3 but here for compatibility
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(usb_active_obj, usb_active);
STATIC const mp_rom_map_elem_t ckcc_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ckcc) },
{ MP_ROM_QSTR(MP_QSTR_rng), MP_ROM_PTR(&pyb_rng_get_obj) },
{ MP_ROM_QSTR(MP_QSTR_rng_bytes), MP_ROM_PTR(&pyb_rng_get_bytes_obj) },
{ MP_ROM_QSTR(MP_QSTR_gate), MP_ROM_PTR(&sec_gate_obj) },
{ MP_ROM_QSTR(MP_QSTR_oneway), MP_ROM_PTR(&sec_oneway_gate_obj) },
{ MP_ROM_QSTR(MP_QSTR_is_simulator), MP_ROM_PTR(&is_simulator_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_cpu_id), MP_ROM_PTR(&get_cpu_id_obj) },
{ MP_ROM_QSTR(MP_QSTR_vcp_enabled), MP_ROM_PTR(&vcp_enabled_obj) },
{ MP_ROM_QSTR(MP_QSTR_wipe_fs), MP_ROM_PTR(&wipe_fs_obj) },
{ MP_ROM_QSTR(MP_QSTR_presume_green), MP_ROM_PTR(&presume_green_obj) },
{ MP_ROM_QSTR(MP_QSTR_breakpoint), MP_ROM_PTR(&breakpoint_obj) },
{ MP_ROM_QSTR(MP_QSTR_watchpoint), MP_ROM_PTR(&watchpoint_obj) },
{ MP_ROM_QSTR(MP_QSTR_stack_limit), MP_ROM_PTR(&stack_limit_obj) },
{ MP_ROM_QSTR(MP_QSTR_usb_active), MP_ROM_PTR(&usb_active_obj) },
};
STATIC MP_DEFINE_CONST_DICT(ckcc_module_globals, ckcc_module_globals_table);
const mp_obj_module_t ckcc_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&ckcc_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_ckcc, ckcc_module, 1);
void ckcc_early_init(void)
{
// Add system-wide init code here.
// Disable ^C to interrupt code.
// cannot find where this might be set by other code to ^C.
mp_interrupt_char = -1;
// Do the equivilent of "py.usb_mode(None)" in boot.py
extern mp_uint_t pyb_usb_flags;
pyb_usb_flags |= PYB_USB_FLAG_USB_MODE_CALLED;
}
void ckcc_boardctrl_before_boot_py(boardctrl_state_t *state)
{
// do not run /boot.py even if it exists
state->run_boot_py = false;
}
void ckcc_boardctrl_after_boot_py(boardctrl_state_t *state)
{
// nothing to do, no way to report failures anyway
}
// ckcc_heap_start()
//
void *
ckcc_heap_start(void)
{
// see layout.ld (linker script)
extern uint32_t _heap_start;
return &_heap_start;
}
// ckcc_heap_start()
//
void *
ckcc_heap_end(void)
{
extern uint32_t _ram_end;
uint8_t *rv = (uint8_t *)&_ram_end;
if((DBGMCU->IDCODE & 0xfff) == 0x461) {
rv += 160*1024;
}
// Mark 1 and 2
return rv;
}
#if 0
// Add a wrapper to this flash_bdev_writeblock() so we
// can detect when any change made to "green lit" firmware,
// and therefore clear the light. It doesn't really matter to anything
// but's a debug aid and (mostly?) prevents the case where user switches
// off the Coldcard with green light on, but unverified changes in flash.
//
bool CKCC_flash_bdev_writeblock(const uint8_t *src, uint32_t block)
{
if(presumably_green_light) {
callgate_lower(4, 1, NULL);
presumably_green_light = false;
}
return flash_bdev_writeblock(src, block);
}
#endif
// There is no classical C heap in bare-metal ports, only Python
// garbage-collected heap. For completeness, emulate C heap via
// GC heap. Note that MicroPython core never uses malloc() and friends,
// but I need these for the C-language extensions I'm using.
void *malloc(size_t size)
{
return m_malloc(size);
}
void free(void *ptr)
{
m_free(ptr);
}
void *realloc(void *ptr, size_t size)
{
return m_realloc(ptr, size);
}
// EOF