bootloader source code

pull/25/head
Pavol Rusnak 10 years ago
parent 8f1c40a933
commit 938e8a5966

1
.gitignore vendored

@ -7,4 +7,3 @@
*.list
*.srec
*.log
bootloader

@ -0,0 +1,9 @@
*.o
*.a
*.d
*.bin
*.elf
*.hex
*.list
*.srec
*.log

@ -0,0 +1,23 @@
NAME = bootloader
MCU_DIR=$(subst /bootloader,,$(PWD))
OBJS += bootloader.o
OBJS += signatures.o
OBJS += usb.o
OBJS += $(MCU_DIR)/trezor-crypto/bignum.small.o
OBJS += $(MCU_DIR)/trezor-crypto/ecdsa.small.o
OBJS += $(MCU_DIR)/trezor-crypto/hmac.o
OBJS += $(MCU_DIR)/trezor-crypto/ripemd160.o
OBJS += $(MCU_DIR)/trezor-crypto/secp256k1.small.o
OBJS += $(MCU_DIR)/trezor-crypto/sha2.o
CFLAGS += -DUSE_PRECOMPUTED_IV=0
CFLAGS += -DUSE_PRECOMPUTED_CP=0
CFLAGS += -DUSE_PUBKEY_VALIDATE=0
include $(MCU_DIR)/Makefile.include
align:
./firmware_align.py $(NAME).bin

@ -0,0 +1,132 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2014 Pavol Rusnak <stick@satoshilabs.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/cm3/scb.h>
#include "bootloader.h"
#include "buttons.h"
#include "setup.h"
#include "usb.h"
#include "oled.h"
#include "util.h"
#include "signatures.h"
#include "layout.h"
#include "serialno.h"
#ifdef APPVER
#error Bootloader cannot be used in app mode
#endif
void show_unofficial_warning(void)
{
layoutDialog(DIALOG_ICON_WARNING, "Abort", "I'll take the risk", NULL, "WARNING!", NULL, "Unofficial firmware", "detected.", NULL, NULL);
do {
delay(100000);
buttonUpdate();
} while (!button.YesUp && !button.NoUp);
if (button.YesUp) {
return; // yes button was pressed -> return
}
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Unofficial firmware", "aborted.", NULL, "Unplug your TREZOR", "and see our support", "page at mytrezor.com");
system_halt();
}
void load_app(void)
{
// jump to app
SCB_VTOR = FLASH_APP_START; // & 0xFFFF;
asm volatile("msr msp, %0"::"g" (*(volatile uint32_t *)FLASH_APP_START));
(*(void (**)())(FLASH_APP_START + 4))();
}
void bootloader_loop(void)
{
static char serial[25];
fill_serialno_fixed(serial);
oledDrawBitmap(0, 0, &bmp_logo64);
oledDrawString(52, 0, "TREZOR");
oledDrawString(52, 20, "Serial No.");
oledDrawString(52, 40, serial + 12); // second part of serial
serial[12] = 0;
oledDrawString(52, 30, serial); // first part of serial
oledDrawStringRight(OLED_WIDTH - 1, OLED_HEIGHT - 8, "BLv" VERSTR(VERSION_MAJOR) "." VERSTR(VERSION_MINOR) "." VERSTR(VERSION_PATCH));
oledRefresh();
usbInit();
usbLoop();
}
void check_firmware_sanity(void)
{
int broken = 0;
if (memcmp((void *)FLASH_META_MAGIC, "TRZR", 4)) { // magic does not match
broken++;
}
if (*((uint32_t *)FLASH_META_CODELEN) < 4096) { // firmware reports smaller size than 4kB
broken++;
}
if (*((uint32_t *)FLASH_META_CODELEN) > FLASH_TOTAL_SIZE - (FLASH_APP_START - FLASH_ORIGIN)) { // firmware reports bigger size than flash size
broken++;
}
if (broken) {
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Firmware appears", "to be broken.", NULL, "Unplug your TREZOR", "and see our support", "page at mytrezor.com");
system_halt();
}
}
int main(void)
{
setup();
memory_protect();
oledInit();
// at least one button is unpressed
uint16_t state = gpio_port_read(BTN_PORT);
if ((state & BTN_PIN_YES) == BTN_PIN_YES || (state & BTN_PIN_NO) == BTN_PIN_NO) {
check_firmware_sanity();
oledClear();
oledDrawBitmap(40, 0, &bmp_logo64_empty);
oledRefresh();
if (!signatures_ok()) {
show_unofficial_warning();
}
load_app();
}
bootloader_loop();
return 0;
}

@ -0,0 +1,36 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2014 Pavol Rusnak <stick@satoshilabs.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __BOOTLOADER_H__
#define __BOOTLOADER_H__
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 5
#define STR(X) #X
#define VERSTR(X) STR(X)
#define VERSION_MAJOR_CHAR "\x01"
#define VERSION_MINOR_CHAR "\x02"
#define VERSION_PATCH_CHAR "\x05"
#include "memory.h"
#endif

@ -0,0 +1,10 @@
#!/usr/bin/python
bl = open('bl.bin').read()
fw = open('fw.bin').read()
combined = bl + fw[:256] + (32768-256)*'\x00' + fw[256:]
open('combined.bin', 'w').write(combined)
print 'bootloader : %d bytes' % len(bl)
print 'firmware : %d bytes' % len(fw)
print 'combined : %d bytes' % len(combined)

@ -0,0 +1,2 @@
#!/bin/bash
st-flash write combined.bin 0x8000000

@ -0,0 +1,11 @@
#!/usr/bin/python
import sys
import os
fn = sys.argv[1]
fs = os.stat(fn).st_size
if fs > 32768:
raise Exception('bootloader has to be smaller than 32768 bytes')
with open(fn, 'ab') as f:
f.write(os.urandom(32768 - fs))
f.close()

@ -0,0 +1,189 @@
#!/usr/bin/python
import argparse
import hashlib
import struct
import binascii
import ecdsa
SLOTS = 3
pubkeys = {
1: '04d571b7f148c5e4232c3814f777d8faeaf1a84216c78d569b71041ffc768a5b2d810fc3bb134dd026b57e65005275aedef43e155f48fc11a32ec790a93312bd58',
2: '0463279c0c0866e50c05c799d32bd6bab0188b6de06536d1109d2ed9ce76cb335c490e55aee10cc901215132e853097d5432eda06b792073bd7740c94ce4516cb1',
3: '0443aedbb6f7e71c563f8ed2ef64ec9981482519e7ef4f4aa98b27854e8c49126d4956d300ab45fdc34cd26bc8710de0a31dbdf6de7435fd0b492be70ac75fde58',
4: '04877c39fd7c62237e038235e9c075dab261630f78eeb8edb92487159fffedfdf6046c6f8b881fa407c4a4ce6c28de0b19c1f4e29f1fcbc5a58ffd1432a3e0938a',
5: '047384c51ae81add0a523adbb186c91b906ffb64c2c765802bf26dbd13bdf12c319e80c2213a136c8ee03d7874fd22b70d68e7dee469decfbbb510ee9a460cda45',
}
INDEXES_START = len('TRZR') + struct.calcsize('<I')
SIG_START = INDEXES_START + SLOTS + 1 + 52
def parse_args():
parser = argparse.ArgumentParser(description='Commandline tool for signing Trezor firmware.')
parser.add_argument('-f', '--file', dest='path', help="Firmware file to modify")
parser.add_argument('-s', '--sign', dest='sign', action='store_true', help="Add signature to firmware slot")
parser.add_argument('-p', '--pem', dest='pem', action='store_true', help="Use PEM instead of SECEXP")
parser.add_argument('-g', '--generate', dest='generate', action='store_true', help='Generate new ECDSA keypair')
return parser.parse_args()
def prepare(data):
# Takes raw OR signed firmware and clean out metadata structure
# This produces 'clean' data for signing
meta = 'TRZR' # magic
if data[:4] == 'TRZR':
meta += data[4:4 + struct.calcsize('<I')]
else:
meta += struct.pack('<I', len(data)) # length of the code
meta += '\x00' * SLOTS # signature index #1-#3
meta += '\x01' # flags
meta += '\x00' * 52 # reserved
meta += '\x00' * 64 * SLOTS # signature #1-#3
if data[:4] == 'TRZR':
# Replace existing header
out = meta + data[len(meta):]
else:
# create data from meta + code
out = meta + data
return out
def check_signatures(data):
# Analyses given firmware and prints out
# status of included signatures
indexes = [ ord(x) for x in data[INDEXES_START:INDEXES_START + SLOTS] ]
to_sign = prepare(data)[256:] # without meta
fingerprint = hashlib.sha256(to_sign).hexdigest()
print "Firmware fingerprint:", fingerprint
used = []
for x in range(SLOTS):
signature = data[SIG_START + 64 * x:SIG_START + 64 * x + 64]
if indexes[x] == 0:
print "Slot #%d" % (x + 1), 'is empty'
else:
pk = pubkeys[indexes[x]]
verify = ecdsa.VerifyingKey.from_string(binascii.unhexlify(pk)[1:],
curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256)
try:
verify.verify(signature, to_sign, hashfunc=hashlib.sha256)
if indexes[x] in used:
print "Slot #%d signature: DUPLICATE" % (x + 1), binascii.hexlify(signature)
else:
used.append(indexes[x])
print "Slot #%d signature: VALID" % (x + 1), binascii.hexlify(signature)
except:
print "Slot #%d signature: INVALID" % (x + 1), binascii.hexlify(signature)
def modify(data, slot, index, signature):
# Replace signature in data
# Put index to data
data = data[:INDEXES_START + slot - 1 ] + chr(index) + data[INDEXES_START + slot:]
# Put signature to data
data = data[:SIG_START + 64 * (slot - 1) ] + signature + data[SIG_START + 64 * slot:]
return data
def sign(data, is_pem):
# Ask for index and private key and signs the firmware
slot = int(raw_input('Enter signature slot (1-%d): ' % SLOTS))
if slot < 1 or slot > SLOTS:
raise Exception("Invalid slot")
if is_pem:
print "Paste ECDSA private key in PEM format and press Enter:"
print "(blank private key removes the signature on given index)"
pem_key = ''
while True:
key = raw_input()
pem_key += key + "\n"
if key == '':
break
if pem_key.strip() == '':
# Blank key,let's remove existing signature from slot
return modify(data, slot, 0, '\x00' * 64)
key = ecdsa.SigningKey.from_pem(pem_key)
else:
print "Paste SECEXP (in hex) and press Enter:"
print "(blank private key removes the signature on given index)"
secexp = raw_input()
if secexp.strip() == '':
# Blank key,let's remove existing signature from slot
return modify(data, slot, 0, '\x00' * 64)
key = ecdsa.SigningKey.from_secret_exponent(secexp = int(secexp, 16), curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256)
to_sign = prepare(data)[256:] # without meta
# Locate proper index of current signing key
pubkey = '04' + binascii.hexlify(key.get_verifying_key().to_string())
index = None
for i, pk in pubkeys.iteritems():
if pk == pubkey:
index = i
break
if index == None:
raise Exception("Unable to find private key index. Unknown private key?")
signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256)
return modify(data, slot, index, signature)
def main(args):
if args.generate:
key = ecdsa.SigningKey.generate(
curve=ecdsa.curves.SECP256k1,
hashfunc=hashlib.sha256)
print "PRIVATE KEY (SECEXP):"
print binascii.hexlify(key.to_string())
print
print "PRIVATE KEY (PEM):"
print key.to_pem()
print "PUBLIC KEY:"
print '04' + binascii.hexlify(key.get_verifying_key().to_string())
return
if not args.path:
raise Exception("-f/--file is required")
data = open(args.path, 'rb').read()
assert len(data) % 4 == 0
if data[:4] != 'TRZR':
print "Metadata has been added..."
data = prepare(data)
if data[:4] != 'TRZR':
raise Exception("Firmware header expected")
print "Firmware size %d bytes" % len(data)
check_signatures(data)
if args.sign:
data = sign(data, args.pem)
check_signatures(data)
fp = open(args.path, 'w')
fp.write(data)
fp.close()
if __name__ == '__main__':
args = parse_args()
main(args)

@ -0,0 +1,31 @@
#!/usr/bin/python
import hashlib
import os
import subprocess
import ecdsa
from binascii import hexlify, unhexlify
print 'master secret:',
h = raw_input()
if h:
h = unhexlify(h)
else:
h = hashlib.sha256(os.urandom(1024)).digest()
print
print 'master secret:', hexlify(h)
print
for i in range(1, 6):
se = hashlib.sha256(h + chr(i)).hexdigest()
print 'seckey', i, ':', se
sk = ecdsa.SigningKey.from_secret_exponent(secexp = int(se, 16), curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256)
print 'pubkey', i, ':', '04' + hexlify(sk.get_verifying_key().to_string())
print sk.to_pem()
p = subprocess.Popen('ssss-split -t 3 -n 5 -x'.split(' '), stdin = subprocess.PIPE)
p.communicate(input = hexlify(h) + '\n')
# to recover use:
# $ ssss-combine -t 3 -x

@ -0,0 +1,66 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2014 Pavol Rusnak <stick@satoshilabs.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include "signatures.h"
#include "ecdsa.h"
#include "bootloader.h"
#define PUBKEYS 5
static const uint8_t *pubkey[PUBKEYS] = {
(uint8_t *)"\x04\xd5\x71\xb7\xf1\x48\xc5\xe4\x23\x2c\x38\x14\xf7\x77\xd8\xfa\xea\xf1\xa8\x42\x16\xc7\x8d\x56\x9b\x71\x04\x1f\xfc\x76\x8a\x5b\x2d\x81\x0f\xc3\xbb\x13\x4d\xd0\x26\xb5\x7e\x65\x00\x52\x75\xae\xde\xf4\x3e\x15\x5f\x48\xfc\x11\xa3\x2e\xc7\x90\xa9\x33\x12\xbd\x58",
(uint8_t *)"\x04\x63\x27\x9c\x0c\x08\x66\xe5\x0c\x05\xc7\x99\xd3\x2b\xd6\xba\xb0\x18\x8b\x6d\xe0\x65\x36\xd1\x10\x9d\x2e\xd9\xce\x76\xcb\x33\x5c\x49\x0e\x55\xae\xe1\x0c\xc9\x01\x21\x51\x32\xe8\x53\x09\x7d\x54\x32\xed\xa0\x6b\x79\x20\x73\xbd\x77\x40\xc9\x4c\xe4\x51\x6c\xb1",
(uint8_t *)"\x04\x43\xae\xdb\xb6\xf7\xe7\x1c\x56\x3f\x8e\xd2\xef\x64\xec\x99\x81\x48\x25\x19\xe7\xef\x4f\x4a\xa9\x8b\x27\x85\x4e\x8c\x49\x12\x6d\x49\x56\xd3\x00\xab\x45\xfd\xc3\x4c\xd2\x6b\xc8\x71\x0d\xe0\xa3\x1d\xbd\xf6\xde\x74\x35\xfd\x0b\x49\x2b\xe7\x0a\xc7\x5f\xde\x58",
(uint8_t *)"\x04\x87\x7c\x39\xfd\x7c\x62\x23\x7e\x03\x82\x35\xe9\xc0\x75\xda\xb2\x61\x63\x0f\x78\xee\xb8\xed\xb9\x24\x87\x15\x9f\xff\xed\xfd\xf6\x04\x6c\x6f\x8b\x88\x1f\xa4\x07\xc4\xa4\xce\x6c\x28\xde\x0b\x19\xc1\xf4\xe2\x9f\x1f\xcb\xc5\xa5\x8f\xfd\x14\x32\xa3\xe0\x93\x8a",
(uint8_t *)"\x04\x73\x84\xc5\x1a\xe8\x1a\xdd\x0a\x52\x3a\xdb\xb1\x86\xc9\x1b\x90\x6f\xfb\x64\xc2\xc7\x65\x80\x2b\xf2\x6d\xbd\x13\xbd\xf1\x2c\x31\x9e\x80\xc2\x21\x3a\x13\x6c\x8e\xe0\x3d\x78\x74\xfd\x22\xb7\x0d\x68\xe7\xde\xe4\x69\xde\xcf\xbb\xb5\x10\xee\x9a\x46\x0c\xda\x45",
};
#define SIGNATURES 3
int signatures_ok(void)
{
uint32_t codelen = *((uint32_t *)FLASH_META_CODELEN);
uint8_t sigindex1, sigindex2, sigindex3;
sigindex1 = *((uint8_t *)FLASH_META_SIGINDEX1);
sigindex2 = *((uint8_t *)FLASH_META_SIGINDEX2);
sigindex3 = *((uint8_t *)FLASH_META_SIGINDEX3);
if (sigindex1 < 1 || sigindex1 > PUBKEYS) return 0; // invalid index
if (sigindex2 < 1 || sigindex2 > PUBKEYS) return 0; // invalid index
if (sigindex3 < 1 || sigindex3 > PUBKEYS) return 0; // invalid index
if (sigindex1 == sigindex2) return 0; // duplicate use
if (sigindex1 == sigindex3) return 0; // duplicate use
if (sigindex2 == sigindex3) return 0; // duplicate use
if (ecdsa_verify(pubkey[sigindex1 - 1], (uint8_t *)FLASH_META_SIG1, (uint8_t *)FLASH_APP_START, codelen) != 0) { // failure
return 0;
}
if (ecdsa_verify(pubkey[sigindex2 - 1], (uint8_t *)FLASH_META_SIG2, (uint8_t *)FLASH_APP_START, codelen) != 0) { // failure
return 0;
}
if (ecdsa_verify(pubkey[sigindex3 - 1], (uint8_t *)FLASH_META_SIG3, (uint8_t *)FLASH_APP_START, codelen) != 0) { // failture
return 0;
}
return 1;
}

@ -0,0 +1,25 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2014 Pavol Rusnak <stick@satoshilabs.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __SIGNATURES_H__
#define __SIGNATURES_H__
int signatures_ok(void);
#endif

@ -0,0 +1,518 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2014 Pavol Rusnak <stick@satoshilabs.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libopencm3/usb/usbd.h>
#include <libopencm3/usb/hid.h>
#include <libopencm3/stm32/flash.h>
#include <string.h>
#include "buttons.h"
#include "bootloader.h"
#include "oled.h"
#include "rng.h"
#include "usb.h"
#include "serialno.h"
#include "layout.h"
#include "util.h"
#include "signatures.h"
#include "sha2.h"
static const struct usb_device_descriptor dev_descr = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 64,
.idVendor = 0x534c,
.idProduct = 0x0001,
.bcdDevice = 0x0100,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1,
};
/* got via usbhid-dump from CP2110 */
static const uint8_t hid_report_descriptor[] = {
0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x09, 0x01, 0x75, 0x08, 0x95, 0x40, 0x26, 0xFF, 0x00,
0x15, 0x00, 0x85, 0x01, 0x95, 0x01, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x02,
0x95, 0x02, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x03, 0x95, 0x03, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x04, 0x95, 0x04, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x05, 0x95, 0x05, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x06,
0x95, 0x06, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x07, 0x95, 0x07, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x08, 0x95, 0x08, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x09, 0x95, 0x09, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0A,
0x95, 0x0A, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0B, 0x95, 0x0B, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0C, 0x95, 0x0C, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x0D, 0x95, 0x0D, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0E,
0x95, 0x0E, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0F, 0x95, 0x0F, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x10, 0x95, 0x10, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x11, 0x95, 0x11, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x12,
0x95, 0x12, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x13, 0x95, 0x13, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x14, 0x95, 0x14, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x15, 0x95, 0x15, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x16,
0x95, 0x16, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x17, 0x95, 0x17, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x18, 0x95, 0x18, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x19, 0x95, 0x19, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1A,
0x95, 0x1A, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1B, 0x95, 0x1B, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1C, 0x95, 0x1C, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x1D, 0x95, 0x1D, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1E,
0x95, 0x1E, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1F, 0x95, 0x1F, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x20, 0x95, 0x20, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x21, 0x95, 0x21, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x22,
0x95, 0x22, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x23, 0x95, 0x23, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x24, 0x95, 0x24, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x25, 0x95, 0x25, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x26,
0x95, 0x26, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x27, 0x95, 0x27, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x28, 0x95, 0x28, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x29, 0x95, 0x29, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2A,
0x95, 0x2A, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2B, 0x95, 0x2B, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2C, 0x95, 0x2C, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x2D, 0x95, 0x2D, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2E,
0x95, 0x2E, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2F, 0x95, 0x2F, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x30, 0x95, 0x30, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x31, 0x95, 0x31, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x32,
0x95, 0x32, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x33, 0x95, 0x33, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x34, 0x95, 0x34, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x35, 0x95, 0x35, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x36,
0x95, 0x36, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x37, 0x95, 0x37, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x38, 0x95, 0x38, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x39, 0x95, 0x39, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3A,
0x95, 0x3A, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3B, 0x95, 0x3B, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3C, 0x95, 0x3C, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01,
0x91, 0x02, 0x85, 0x3D, 0x95, 0x3D, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3E,
0x95, 0x3E, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3F, 0x95, 0x3F, 0x09, 0x01,
0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x40, 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x41,
0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x42, 0x95, 0x06, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x43,
0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x44, 0x95, 0x02, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x45,
0x95, 0x04, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x46, 0x95, 0x02, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x47,
0x95, 0x02, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x50, 0x95, 0x08, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x51,
0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x52, 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x60,
0x95, 0x0A, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x61, 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x62,
0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x63, 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x64,
0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x65, 0x95, 0x3E, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x66,
0x95, 0x13, 0x09, 0x01, 0xB1, 0x02, 0xC0,
};
static const struct {
struct usb_hid_descriptor hid_descriptor;
struct {
uint8_t bReportDescriptorType;
uint16_t wDescriptorLength;
} __attribute__((packed)) hid_report;
} __attribute__((packed)) hid_function = {
.hid_descriptor = {
.bLength = sizeof(hid_function),
.bDescriptorType = USB_DT_HID,
.bcdHID = 0x0111,
.bCountryCode = 0,
.bNumDescriptors = 1,
},
.hid_report = {
.bReportDescriptorType = USB_DT_REPORT,
.wDescriptorLength = sizeof(hid_report_descriptor),
}
};
static const struct usb_endpoint_descriptor hid_endpoints[2] = {{
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x81,
.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
.wMaxPacketSize = 64,
.bInterval = 1,
}, {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x02,
.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
.wMaxPacketSize = 64,
.bInterval = 1,
}};
static const struct usb_interface_descriptor hid_iface[] = {{
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_HID,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
.endpoint = hid_endpoints,
.extra = &hid_function,
.extralen = sizeof(hid_function),
}};
static const struct usb_interface ifaces[] = {{
.num_altsetting = 1,
.altsetting = hid_iface,
}};
static const struct usb_config_descriptor config = {
.bLength = USB_DT_CONFIGURATION_SIZE,
.bDescriptorType = USB_DT_CONFIGURATION,
.wTotalLength = 0,
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = 0x80,
.bMaxPower = 0x32,
.interface = ifaces,
};
static const char *usb_strings[] = {
"SatoshiLabs",
"TREZOR",
"", // empty serial
};
static int hid_control_request(usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len,
void (**complete)(usbd_device *, struct usb_setup_data *))
{
(void)complete;
(void)dev;
if ((req->bmRequestType != 0x81) ||
(req->bRequest != USB_REQ_GET_DESCRIPTOR) ||
(req->wValue != 0x2200)) return 0;
*buf = (uint8_t *)hid_report_descriptor;
*len = sizeof(hid_report_descriptor);
return 1;
}
enum {
STATE_READY,
STATE_OPEN,
STATE_FLASHSTART,
STATE_FLASHING,
STATE_CHECK,
STATE_END,
};
static uint32_t flash_pos = 0, flash_len = 0;
static char flash_state = STATE_READY;
static uint8_t flash_anim = 0;
static uint16_t msg_id = 0xFFFF;
static uint32_t msg_size = 0;
static uint8_t meta_backup[FLASH_META_LEN];
static void send_msg_success(usbd_device *dev)
{
// send response: Success message (id 2), payload len 0
while ( usbd_ep_write_packet(dev, 0x81,
"?##" // header
"\x00\x02" // msg_id
"\x00\x00\x00\x00" // payload_len
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
, 64) != 64) {}
}
static void send_msg_failure(usbd_device *dev)
{
// send response: Failure message (id 3), payload len 2
// code = 99 (Failure_FirmwareError)
while ( usbd_ep_write_packet(dev, 0x81,
"?##" // header
"\x00\x03" // msg_id
"\x00\x00\x00\x02" // payload_len
"\x08\x63" // data
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
, 64) != 64) {}
}
static void send_msg_features(usbd_device *dev)
{
// send response: Features message (id 17), payload len 27
// vendor = "bitcointrezor.com"
// major_version = VERSION_MAJOR
// minor_version = VERSION_MINOR
// patch_version = VERSION_PATCH
// bootloader_mode = True
while ( usbd_ep_write_packet(dev, 0x81,
"?##" // header
"\x00\x11" // msg_id
"\x00\x00\x00\x1b" // payload_len
"\x0a\x11" "bitcointrezor.com\x10" VERSION_MAJOR_CHAR "\x18" VERSION_MINOR_CHAR " " VERSION_PATCH_CHAR "(\x01" // data
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
, 64) != 64) {}
}
static void send_msg_buttonrequest_firmwarecheck(usbd_device *dev)
{
// send response: ButtonRequest message (id 26), payload len 2
// code = ButtonRequest_FirmwareCheck (9)
while ( usbd_ep_write_packet(dev, 0x81,
"?##" // header
"\x00\x1a" // msg_id
"\x00\x00\x00\x02" // payload_len
"\x08\x09" // data
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
, 64) != 64) {}
}
static void hid_rx_callback(usbd_device *dev, uint8_t ep)
{
(void)ep;
static uint8_t buf[64] __attribute__((aligned(4)));
uint8_t *p;
static uint8_t towrite[4] __attribute__((aligned(4)));
static int wi;
int i;
uint32_t *w;
static SHA256_CTX ctx;
if ( usbd_ep_read_packet(dev, 0x02, buf, 64) != 64) return;
if (flash_state == STATE_END) {
return;
}
if (flash_state == STATE_READY || flash_state == STATE_OPEN || flash_state == STATE_FLASHSTART || flash_state == STATE_CHECK) {
if (buf[0] != '?' || buf[1] != '#' || buf[2] != '#') { // invalid start - discard
return;
}
// struct.unpack(">HL") => msg, size
msg_id = (buf[3] << 8) + buf[4];
msg_size = (buf[5] << 24)+ (buf[6] << 16) + (buf[7] << 8) + buf[8];
}
if (flash_state == STATE_READY || flash_state == STATE_OPEN) {
if (msg_id == 0x0000) { // Initialize message (id 0)
send_msg_features(dev);
flash_state = STATE_OPEN;
return;
}
if (msg_id == 0x0001) { // Ping message (id 1)
send_msg_success(dev);
return;
}
}
if (flash_state == STATE_OPEN) {
if (msg_id == 0x0006) { // FirmwareErase message (id 6)
layoutDialog(DIALOG_ICON_QUESTION, "Abort", "Continue", NULL, "Install new", "firmware?", NULL, "Never do this without", "your recovery card!", NULL);
do {
delay(100000);
buttonUpdate();
} while (!button.YesUp && !button.NoUp);
if (button.YesUp) {
layoutProgress("INSTALLING ... Please wait", 0, 0);
// backup metadata
memcpy(meta_backup, (void *)FLASH_META_START, FLASH_META_LEN);
flash_unlock();
// erase metadata area
for (i = FLASH_META_SECTOR_FIRST; i <= FLASH_META_SECTOR_LAST; i++) {
flash_erase_sector(i, FLASH_CR_PROGRAM_X32);
}
// erase code area
for (i = FLASH_CODE_SECTOR_FIRST; i <= FLASH_CODE_SECTOR_LAST; i++) {
flash_erase_sector(i, FLASH_CR_PROGRAM_X32);
}
flash_lock();
send_msg_success(dev);
flash_state = STATE_FLASHSTART;
return;
}
send_msg_failure(dev);
flash_state = STATE_END;
layoutDialog(DIALOG_ICON_WARNING, NULL, NULL, NULL, "Firmware installation", "aborted.", NULL, "You may now", "unplug your TREZOR.", NULL);
return;
}
return;
}
if (flash_state == STATE_FLASHSTART) {
if (msg_id == 0x0007) { // FirmwareUpload message (id 7)
if (buf[9] != 0x0a) { // invalid contents
send_msg_failure(dev);
flash_state = STATE_END;
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Error installing ", "firmware.", NULL, "Unplug your TREZOR", "and try again.", NULL);
return;
}
// read payload length
p = buf + 10;
flash_len = readprotobufint(&p);
if (flash_len > FLASH_TOTAL_SIZE + FLASH_META_DESC_LEN - (FLASH_APP_START - FLASH_ORIGIN)) { // firmware is too big
send_msg_failure(dev);
flash_state = STATE_END;
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Firmware is too big.", NULL, "Get official firmware", "from mytrezor.com", NULL, NULL);
return;
}
sha256_Init(&ctx);
flash_state = STATE_FLASHING;
flash_pos = 0;
wi = 0;
flash_unlock();
while (p < buf + 64) {
towrite[wi] = *p;
wi++;
if (wi == 4) {
w = (uint32_t *)towrite;
flash_program_word(FLASH_META_START + flash_pos, *w);
flash_pos += 4;
wi = 0;
}
p++;
}
flash_lock();
return;
}
return;
}
if (flash_state == STATE_FLASHING) {
if (buf[0] != '?') { // invalid contents
send_msg_failure(dev);
flash_state = STATE_END;
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Error installing ", "firmware.", NULL, "Unplug your TREZOR", "and try again.", NULL);
return;
}
p = buf + 1;
layoutProgress("INSTALLING ... Please wait", 1000 * flash_pos / flash_len, flash_anim / 8);
flash_anim++;
flash_unlock();
while (p < buf + 64 && flash_pos < flash_len) {
towrite[wi] = *p;
wi++;
if (wi == 4) {
w = (uint32_t *)towrite;
if (flash_pos < FLASH_META_DESC_LEN) {
flash_program_word(FLASH_META_START + flash_pos, *w); // the first 256 bytes of firmware is metadata descriptor
} else {
flash_program_word(FLASH_APP_START + (flash_pos - FLASH_META_DESC_LEN), *w); // the rest is code
sha256_Update(&ctx, towrite, 4);
}
flash_pos += 4;
wi = 0;
}
p++;
}
flash_lock();
// flashing done
if (flash_pos == flash_len) {
flash_state = STATE_CHECK;
send_msg_buttonrequest_firmwarecheck(dev);
}
return;
}
if (flash_state == STATE_CHECK) {
if (msg_id != 0x001B) { // ButtonAck message (id 27)
return;
}
char digest[64];
sha256_End(&ctx, digest);
char str[4][17];
strlcpy(str[0], digest, 17);
strlcpy(str[1], digest + 16, 17);
strlcpy(str[2], digest + 32, 17);
strlcpy(str[3], digest + 48, 17);
layoutDialog(DIALOG_ICON_QUESTION, "Abort", "Continue", "Compare fingerprints", str[0], str[1], str[2], str[3], NULL, NULL);
do {
delay(100000);
buttonUpdate();
} while (!button.YesUp && !button.NoUp);
bool hash_check_ok = button.YesUp;
layoutProgress("INSTALLING ... Please wait", 1000, 0);
uint8_t flags = *((uint8_t *)FLASH_META_FLAGS);
// check if to restore old storage area but only if signatures are ok
if ((flags & 0x01) && signatures_ok()) {
// copy new stuff
memcpy(meta_backup, (void *)FLASH_META_START, FLASH_META_DESC_LEN);
// replace "TRZR" in header with 0000 when hash not confirmed
if (!hash_check_ok) {
meta_backup[0] = 0;
meta_backup[1] = 0;
meta_backup[2] = 0;
meta_backup[3] = 0;
}
flash_unlock();
// erase storage
for (i = FLASH_META_SECTOR_FIRST; i <= FLASH_META_SECTOR_LAST; i++) {
flash_erase_sector(i, FLASH_CR_PROGRAM_X32);
}
// copy it back
for (i = 0; i < FLASH_META_LEN / 4; i++) {
w = (uint32_t *)(meta_backup + i * 4);
flash_program_word(FLASH_META_START + i * 4, *w);
}
flash_lock();
} else {
// replace "TRZR" in header with 0000 when hash not confirmed
if (!hash_check_ok) {
// no need to erase, because we are just erasing bits
flash_unlock();
flash_program_word(FLASH_META_START, 0x00000000);
flash_lock();
}
}
flash_state = STATE_END;
if (hash_check_ok) {
layoutDialog(DIALOG_ICON_OK, NULL, NULL, NULL, "New firmware", "successfully installed.", NULL, "You may now", "unplug your TREZOR.", NULL);
send_msg_success(dev);
} else {
layoutDialog(DIALOG_ICON_WARNING, NULL, NULL, NULL, "Firmware installation", "aborted.", NULL, "You need to repeat", "the procedure with", "the correct firmware.");
send_msg_failure(dev);
}
return;
}
}
static void hid_set_config(usbd_device *dev, uint16_t wValue)
{
(void)wValue;
usbd_ep_setup(dev, 0x81, USB_ENDPOINT_ATTR_INTERRUPT, 64, 0);
usbd_ep_setup(dev, 0x02, USB_ENDPOINT_ATTR_INTERRUPT, 64, hid_rx_callback);
usbd_register_control_callback(
dev,
USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE,
USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT,
hid_control_request
);
}
static usbd_device *usbd_dev;
static uint8_t usbd_control_buffer[128];
void usbInit(void)
{
usbd_dev = usbd_init(&otgfs_usb_driver, &dev_descr, &config, usb_strings, 3, usbd_control_buffer, sizeof(usbd_control_buffer));
usbd_register_set_config_callback(usbd_dev, hid_set_config);
}
void usbLoop(void)
{
for (;;) {
usbd_poll(usbd_dev);
}
}

@ -0,0 +1,26 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2014 Pavol Rusnak <stick@satoshilabs.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __USB_H__
#define __USB_H__
void usbInit(void);
void usbLoop(void);
#endif
Loading…
Cancel
Save