diff --git a/core/Makefile b/core/Makefile index 78ed915e3..9861c6c8e 100644 --- a/core/Makefile +++ b/core/Makefile @@ -21,9 +21,11 @@ CROSS_PORT_OPTS ?= PRODUCTION ?= 0 PYOPT ?= 1 BITCOIN_ONLY ?= 0 +BOOTLOADER_QA ?= 0 TREZOR_MODEL ?= T TREZOR_MEMPERF ?= 0 ADDRESS_SANITIZER ?= 0 +UI2 ?= 0 # OpenOCD interface default. Alternative: ftdi/olimex-arm-usb-tiny-h OPENOCD_INTERFACE ?= stlink @@ -164,7 +166,7 @@ build_reflash: ## build reflash firmware + reflash image dd if=build/bootloader/bootloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=49152 build_firmware: templates build_cross ## build firmware with frozen modules - $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" $(FIRMWARE_BUILD_DIR)/firmware.bin + $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" BOOTLOADER_QA="$(BOOTLOADER_QA)" $(FIRMWARE_BUILD_DIR)/firmware.bin build_unix: templates ## build unix port $(SCONS) CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) TREZOR_MODEL="$(TREZOR_MODEL)" PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" diff --git a/core/SConscript.firmware b/core/SConscript.firmware index 911f74365..023a53965 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -5,10 +5,15 @@ import os import tools BITCOIN_ONLY = ARGUMENTS.get('BITCOIN_ONLY', '0') +PRODUCTION = ARGUMENTS.get('PRODUCTION', '0') +BOOTLOADER_QA = ARGUMENTS.get('BOOTLOADER_QA', '0') == '1' EVERYTHING = BITCOIN_ONLY != '1' TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') DMA2D = TREZOR_MODEL in ('T', ) +if PRODUCTION != '1' and BOOTLOADER_QA: + raise ValueError('Firmware variant for bootloader upgrade testing must be done with PRODUCTION=1') + FEATURE_FLAGS = { "RDI": True, "SECP256K1_ZKP": True, # required for trezor.crypto.curve.bip340 (BIP340/Taproot) @@ -431,7 +436,7 @@ tools.add_font('MONO', FONT_MONO, CPPDEFINES_MOD, SOURCE_MOD) SOURCE_QSTR = SOURCE_MOD + SOURCE_MICROPYTHON + SOURCE_MICROPYTHON_SPEED -env = Environment(ENV=os.environ, CFLAGS='%s -DPRODUCTION=%s -DPYOPT=%s -DBITCOIN_ONLY=%s' % (ARGUMENTS.get('CFLAGS', ''), ARGUMENTS.get('PRODUCTION', '0'), PYOPT, BITCOIN_ONLY)) +env = Environment(ENV=os.environ, CFLAGS='%s -DPRODUCTION=%s -DPYOPT=%s -DBITCOIN_ONLY=%s' % (ARGUMENTS.get('CFLAGS', ''), PRODUCTION, PYOPT, BITCOIN_ONLY)) tools.configure_board(TREZOR_MODEL, env, CPPDEFINES_MOD, SOURCE_TREZORHAL) @@ -769,12 +774,18 @@ obj_program.extend( ' --rename-section .data=.vendorheader,alloc,load,readonly,contents' ' $SOURCE $TARGET', )) + +BOOTLOADER_SUFFIX = TREZOR_MODEL + ('_QA' if BOOTLOADER_QA else '') + obj_program.extend( env.Command( - target='embed/firmware/bootloader.o', - source='embed/firmware/bootloader.bin', + target='embed/firmware/bootloaders/bootloader.o', + source=f'embed/firmware/bootloaders/bootloader_{BOOTLOADER_SUFFIX}.bin', action='$OBJCOPY -I binary -O elf32-littlearm -B arm' ' --rename-section .data=.bootloader' + f' --redefine-sym _binary_embed_firmware_bootloaders_bootloader_{BOOTLOADER_SUFFIX}_bin_start=_binary_embed_firmware_bootloader_bin_start' + f' --redefine-sym _binary_embed_firmware_bootloaders_bootloader_{BOOTLOADER_SUFFIX}_bin_end=_binary_embed_firmware_bootloader_bin_end' + f' --redefine-sym _binary_embed_firmware_bootloaders_bootloader_{BOOTLOADER_SUFFIX}_bin_size=_binary_embed_firmware_bootloader_bin_size' ' $SOURCE $TARGET', )) env.Depends(obj_program, qstr_generated) diff --git a/core/embed/firmware/bootloaders/bootloader_1.bin b/core/embed/firmware/bootloaders/bootloader_1.bin new file mode 120000 index 000000000..6a019c96b --- /dev/null +++ b/core/embed/firmware/bootloaders/bootloader_1.bin @@ -0,0 +1 @@ +../../../../legacy/firmware/bootloader.dat \ No newline at end of file diff --git a/core/embed/firmware/bootloader.bin b/core/embed/firmware/bootloaders/bootloader_T.bin similarity index 100% rename from core/embed/firmware/bootloader.bin rename to core/embed/firmware/bootloaders/bootloader_T.bin diff --git a/legacy/Makefile.include b/legacy/Makefile.include index f7cbd6ec2..20fd6ae9c 100644 --- a/legacy/Makefile.include +++ b/legacy/Makefile.include @@ -156,6 +156,18 @@ CPUFLAGS += -DPRODUCTION=1 $(info PRODUCTION=1) endif +BOOTLOADER_QA ?= 0 +ifeq ($(BOOTLOADER_QA), 0) +CFLAGS += -DBOOTLOADER_QA=0 +CPUFLAGS += -DBOOTLOADER_QA=0 +$(info BOOTLOADER_QA=0) +else +CFLAGS += -DBOOTLOADER_QA=1 +CPUFLAGS += -DBOOTLOADER_QA=1 +$(info BOOTLOADER_QA=1) +endif + + ifeq ($(DEBUG_RNG), 1) CFLAGS += -DDEBUG_RNG=1 else diff --git a/legacy/bootloader/bootloader.c b/legacy/bootloader/bootloader.c index 0dfa42c72..66c1d46bb 100644 --- a/legacy/bootloader/bootloader.c +++ b/legacy/bootloader/bootloader.c @@ -64,6 +64,7 @@ void show_unplug(const char *line1, const char *line2) { "You may now", "unplug your Trezor.", NULL); } +#if !BOOTLOADER_QA static void show_unofficial_warning(const uint8_t *hash) { // On production bootloader, show warning and wait for user // to accept or reject it @@ -93,6 +94,7 @@ static void show_unofficial_warning(const uint8_t *hash) { delay(100000000); #endif } +#endif static void __attribute__((noreturn)) load_app(int signed_firmware) { // zero out SRAM @@ -159,12 +161,16 @@ int main(void) { uint8_t fingerprint[32] = {0}; int signed_firmware = signatures_match(hdr, fingerprint); if (SIG_OK != signed_firmware) { +#if BOOTLOADER_QA + show_halt("Unsigned firmware", "Won't run on QA device"); +#else show_unofficial_warning(fingerprint); +#endif } -#if !PRODUCTION +#if !PRODUCTION && !BOOTLOADER_QA && !DEBUG_T1_SIGNATURES // try to avoid bricking board SWD debug by accident else { - show_halt("Official firmware", "Won't flash on debug device"); + show_halt("Official firmware", "Won't run on debug device"); } #endif diff --git a/legacy/bootloader/firmware_sign_dev.py b/legacy/bootloader/firmware_sign_dev.py new file mode 100644 index 000000000..af66ce1ee --- /dev/null +++ b/legacy/bootloader/firmware_sign_dev.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python3 +import argparse +import hashlib +import struct + +import ecdsa +from ecdsa import BadSignatureError + +SLOTS = 3 + +pubkeys_dev = { + 1: "042c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991ae31a9c671a36543f46cea8fce6984608aa316aa0472a7eed08847440218cb2f", + 2: "04edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f2211452c88a66eb8ac3c19a1cc3a3fc6d72506f6fce2025f738d8b55f29f22125eb0a4", + 3: "04665f660a5052be7a95546a02179058d93d3e08a779734914594346075bb0afd45113948d72cf3dc8f2b70ee02dc1695d051bb0c6da2a914a69045e3277682d3b", +} + +privkeys_dev = { + 1: "0x4444444444444444444444444444444444444444444444444444444444444444", + 2: "0x4545454545454545454545454545454545454545454545454545454545454545", + 3: "0xbfc4bca9c9c228a16639d3503d999a733a439210b64cebe757a4fd03ca46a5c8", +} + +FWHEADER_SIZE = 1024 +SIGNATURES_START = 6 * 4 + 8 + 512 +INDEXES_START = SIGNATURES_START + 3 * 64 + +INDEXES_START_OLD = len("TRZR") + struct.calcsize(" size: + raise ValueError("Chunk too big already") + if len(data) == size: + return data + return data + b"\xFF" * (size - len(data)) + + +# see memory.h for details + + +def prepare_hashes(data): + # process chunks + start = 0 + end = (64 - 1) * 1024 + hashes = [] + for i in range(16): + sector = data[start:end] + if len(sector) > 0: + chunk = pad_to_size(sector, end - start) + hashes.append(hashlib.sha256(chunk).digest()) + else: + hashes.append(b"\x00" * 32) + start = end + end += 64 * 1024 + return hashes + + +def check_hashes(data): + expected_hashes = data[0x20 : 0x20 + 16 * 32] + hashes = b"" + for h in prepare_hashes(data[FWHEADER_SIZE:]): + hashes += h + + if expected_hashes == hashes: + print("HASHES OK") + else: + print("HASHES NOT OK") + + +def update_hashes_in_header(data): + # Store hashes in the firmware header + data = bytearray(data) + o = 0 + for h in prepare_hashes(data[FWHEADER_SIZE:]): + data[0x20 + o : 0x20 + o + 32] = h + o += 32 + return bytes(data) + + +def get_header(data, zero_signatures=False): + if not zero_signatures: + return data[:FWHEADER_SIZE] + else: + data = bytearray(data[:FWHEADER_SIZE]) + data[SIGNATURES_START : SIGNATURES_START + 3 * 64 + 3] = b"\x00" * (3 * 64 + 3) + return bytes(data) + + +def check_size(data): + size = struct.unpack(" 32768: @@ -18,18 +21,19 @@ with open("bl_data.h", "wt") as f: f.write(f"static const uint8_t bl_hash[32] = {{{bl_hash}}};\n") f.write(f"static const uint8_t bl_data[32768] = {{{bl_data}}};\n") -# make sure the last item listed in known_bootloader function -# is our bootloader -with open("bl_check.c", "rt") as f: - hashes = [] - for l in f.readlines(): - if not len(l) >= 78 or not l.startswith(' "\\x'): - continue - l = l[14:78] - h = "" - for i in range(0, len(l), 4): - h += l[i + 2 : i + 4] - hashes.append(h) - check = hashes[-2] + hashes[-1] - if check != bh.hex(): - raise Exception("bootloader hash not listed in bl_check.c") +if fn != "bootloader_qa.dat": + # make sure the last item listed in known_bootloader function + # is our bootloader + with open("bl_check.c", "rt") as f: + hashes = [] + for l in f.readlines(): + if not len(l) >= 78 or not l.startswith(' "\\x'): + continue + l = l[14:78] + h = "" + for i in range(0, len(l), 4): + h += l[i + 2 : i + 4] + hashes.append(h) + check = hashes[-2] + hashes[-1] + if check != bh.hex(): + raise Exception("bootloader hash not listed in bl_check.c") diff --git a/legacy/firmware/bootloader_qa.dat b/legacy/firmware/bootloader_qa.dat new file mode 100644 index 000000000..2ddcc7534 Binary files /dev/null and b/legacy/firmware/bootloader_qa.dat differ diff --git a/legacy/fw_signatures.c b/legacy/fw_signatures.c index 4317f8b9b..c887b1e40 100644 --- a/legacy/fw_signatures.c +++ b/legacy/fw_signatures.c @@ -64,6 +64,30 @@ static const uint8_t * const pubkey_v3[PUBKEYS] = { // the "new", or second signing scheme keys +#if BOOTLOADER_QA +/* + Previously used for QA bootloader upgrade tests + + Debug private keys for v2 (previously called "new") scheme + corresponding to pubkeys below as python hexstring array: + + + ['4444444444444444444444444444444444444444444444444444444444444444', + '4545454545454545454545454545454545454545454545454545454545454545', + 'bfc4bca9c9c228a16639d3503d999a733a439210b64cebe757a4fd03ca46a5c8', + '5518381d95e93e8eb68a294354989906e3828f36b4556a2ad85d8333294eb1b7', + '1d1d34168760dec092c9ff89377d8659076d2dfd95e0281719c15f90d067e211'] + */ + +static const uint8_t * const pubkey_v2[PUBKEYS] = { + (const uint8_t *)"\x03\x2c\x0b\x7c\xf9\x53\x24\xa0\x7d\x05\x39\x8b\x24\x01\x74\xdc\x0c\x2b\xe4\x44\xd9\x6b\x15\x9a\xa6\xc7\xf7\xb1\xe6\x68\x68\x09\x91", + (const uint8_t *)"\x02\xed\xab\xbd\x16\xb4\x1c\x83\x71\xb9\x2e\xf2\xf0\x4c\x11\x85\xb4\xf0\x3b\x6d\xcd\x52\xba\x9b\x78\xd9\xd7\xc8\x9c\x8f\x22\x11\x45", + (const uint8_t *)"\x03\x66\x5f\x66\x0a\x50\x52\xbe\x7a\x95\x54\x6a\x02\x17\x90\x58\xd9\x3d\x3e\x08\xa7\x79\x73\x49\x14\x59\x43\x46\x07\x5b\xb0\xaf\xd4", + (const uint8_t *)"\x03\x66\x63\x5d\x99\x94\x17\xb6\x55\x66\x86\x6c\x65\x63\x0d\x97\x7a\x7a\xe7\x23\xfe\x5f\x6c\x4c\xd1\x7f\xa0\x0f\x08\x8b\xa1\x84\xc1", + (const uint8_t *)"\x03\xf3\x6c\x7d\x0f\xb6\x15\xad\xa4\x3d\x71\x88\x58\x0f\x15\xeb\xda\x22\xd6\xf6\xb9\xb1\xa9\x2b\xff\x16\xc6\x93\x77\x99\xdc\xbc\x66" + }; +#else + /* Debug private keys for v2 (previously called "new") scheme corresponding to pubkeys below as python hexstring array: @@ -81,8 +105,10 @@ static const uint8_t * const pubkey_v2[PUBKEYS] = { (const uint8_t *)"\x03\x66\x63\x5d\x99\x94\x17\xb6\x55\x66\x86\x6c\x65\x63\x0d\x97\x7a\x7a\xe7\x23\xfe\x5f\x6c\x4c\xd1\x7f\xa0\x0f\x08\x8b\xa1\x84\xc1", (const uint8_t *)"\x03\xf3\x6c\x7d\x0f\xb6\x15\xad\xa4\x3d\x71\x88\x58\x0f\x15\xeb\xda\x22\xd6\xf6\xb9\xb1\xa9\x2b\xff\x16\xc6\x93\x77\x99\xdc\xbc\x66" }; -#else +#endif +#else +#error NOT IMPLEMENTED // These public keys are production keys // - used in production devices diff --git a/legacy/intermediate_fw/Makefile b/legacy/intermediate_fw/Makefile index 35255a2f9..166ddbfb9 100644 --- a/legacy/intermediate_fw/Makefile +++ b/legacy/intermediate_fw/Makefile @@ -44,10 +44,15 @@ endif @printf " MAKO $@\n" $(Q)$(PYTHON) ../vendor/trezor-common/tools/cointool.py render $(MAKO_RENDER_FLAG) $@.mako +ifeq ($(BOOTLOADER_QA), 0) bl_data.h: bl_data.py bootloader.dat - @printf " PYTHON bl_data.py\n" - $(Q)$(PYTHON) bl_data.py - + @printf " PYTHON bl_data.py bootloader.dat\n" + $(Q)$(PYTHON) bl_data.py bootloader.dat +else +bl_data.h: bl_data.py bootloader_qa.dat + @printf " PYTHON bl_data.py bootloader_qa.dat\n" + $(Q)$(PYTHON) bl_data.py bootloader_qa.dat +endif clean:: rm -f bl_data.h find -maxdepth 1 -name "*.mako" | sed 's/.mako$$//' | xargs rm -f diff --git a/legacy/intermediate_fw/bootloader_qa.dat b/legacy/intermediate_fw/bootloader_qa.dat new file mode 120000 index 000000000..b50597d6f --- /dev/null +++ b/legacy/intermediate_fw/bootloader_qa.dat @@ -0,0 +1 @@ +../firmware/bootloader_qa.dat \ No newline at end of file diff --git a/legacy/memory.c b/legacy/memory.c index 39cebb3f9..9f66b94ca 100644 --- a/legacy/memory.c +++ b/legacy/memory.c @@ -30,6 +30,9 @@ void memory_protect(void) { #if PRODUCTION +#if BOOTLOADER_QA +#error BOOTLOADER_QA must be built with PRODUCTION=0 +#endif // Reference STM32F205 Flash programming manual revision 5 // http://www.st.com/resource/en/programming_manual/cd00233952.pdf Section 2.6 // Option bytes @@ -73,11 +76,16 @@ void memory_protect(void) { // example in STM32F427, where the protection bits are read correctly // from OPTION_BYTES and not form FLASH_OPCTR register. // -// Read protection is unaffected and always stays locked to the desired value. +// Read protection is set to level 2. void memory_write_unlock(void) { +#if PRODUCTION +#if BOOTLOADER_QA +#error BOOTLOADER_QA must be built with PRODUCTION=0 +#endif flash_unlock_option_bytes(); flash_program_option_bytes(0x0FFFCCEC); flash_lock_option_bytes(); +#endif } int memory_bootloader_hash(uint8_t *hash) { diff --git a/legacy/script/update_bootloader.py b/legacy/script/update_bootloader.py index 7e32f58ea..14b281430 100755 --- a/legacy/script/update_bootloader.py +++ b/legacy/script/update_bootloader.py @@ -11,12 +11,14 @@ LEGACY_ROOT = Path(__file__).parent.parent.resolve() BOOTLOADER_BUILT = LEGACY_ROOT / "bootloader" / "bootloader.bin" BOOTLOADER_IMAGE = LEGACY_ROOT / "firmware" / "bootloader.dat" +BOOTLOADER_QA_IMAGE = LEGACY_ROOT / "firmware" / "bootloader_qa.dat" BOOTLOADER_VERSION = LEGACY_ROOT / "bootloader" / "version.h" FIRMWARE_VERSION = LEGACY_ROOT / "firmware" / "version.h" BL_CHECK_C = LEGACY_ROOT / "firmware" / "bl_check.c" BL_CHECK_TXT = LEGACY_ROOT / "firmware" / "bl_check.txt" +BL_CHECK_QA_TXT = LEGACY_ROOT / "firmware" / "bl_check_qa.txt" BL_CHECK_PATTERN = """\ if (0 == @@ -30,6 +32,13 @@ BL_CHECK_PATTERN = """\ BL_CHECK_AUTO_BEGIN = " // BEGIN AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt)\n" BL_CHECK_AUTO_END = " // END AUTO-GENERATED BOOTLOADER ENTRIES (bl_check.txt)\n" +BL_CHECK_AUTO_QA_BEGIN = ( + " // BEGIN AUTO-GENERATED QA BOOTLOADER ENTRIES (bl_check_qa.txt)\n" +) +BL_CHECK_AUTO_QA_END = ( + " // END AUTO-GENERATED QA BOOTLOADER ENTRIES (bl_check_qa.txt)\n" +) + def cstrify(data: bytes) -> str: """Convert bytes to C string literal. @@ -53,25 +62,28 @@ def load_version(filename: Path) -> str: return "{major}.{minor}.{patch}".format(**vdict) -def load_hash_entries() -> dict[bytes, str]: +def load_hash_entries(txt_file) -> dict[bytes, str]: """Load hash entries from bl_check.txt""" return { bytes.fromhex(digest): comment for digest, comment in ( - line.split(" ", maxsplit=1) - for line in BL_CHECK_TXT.read_text().splitlines() + line.split(" ", maxsplit=1) for line in txt_file.read_text().splitlines() ) } -def regenerate_bl_check(hash_entries: t.Iterable[tuple[bytes, str]]) -> None: +def regenerate_bl_check( + hash_entries: t.Iterable[tuple[bytes, str]], + begin, + end, +) -> None: """Regenerate bl_check.c with given hash entries.""" bl_check_new = [] with open(BL_CHECK_C) as f: # read up to AUTO-BEGIN for line in f: bl_check_new.append(line) - if line == BL_CHECK_AUTO_BEGIN: + if line == begin: break # generate new sections @@ -86,7 +98,7 @@ def regenerate_bl_check(hash_entries: t.Iterable[tuple[bytes, str]]) -> None: # consume up to AUTO-END for line in f: - if line == BL_CHECK_AUTO_END: + if line == end: bl_check_new.append(line) break @@ -98,7 +110,14 @@ def regenerate_bl_check(hash_entries: t.Iterable[tuple[bytes, str]]) -> None: @click.command() @click.option("-c", "--comment", help="Comment for the hash entry.") -def main(comment: str | None) -> None: +@click.option( + "--qa", + is_flag=True, + show_default=True, + default=False, + help="Install as QA bootloader.", +) +def main(comment: str | None, qa: bool) -> None: """Insert a new bootloader image. Takes bootloader/boootloader.dat, copies over firmware/bootloader.dat, and adds @@ -108,7 +127,12 @@ def main(comment: str | None) -> None: digest = sha256(sha256(bl_bytes).digest()).digest() click.echo("Bootloader digest: " + digest.hex()) - entries = load_hash_entries() + txt_file = BL_CHECK_QA_TXT if qa else BL_CHECK_TXT + begin = BL_CHECK_AUTO_QA_BEGIN if qa else BL_CHECK_AUTO_BEGIN + end = BL_CHECK_AUTO_QA_END if qa else BL_CHECK_AUTO_END + image = BOOTLOADER_QA_IMAGE if qa else BOOTLOADER_IMAGE + + entries = load_hash_entries(txt_file) if digest in entries: click.echo("Bootloader already in bl_check.txt: " + entries[digest]) @@ -119,19 +143,23 @@ def main(comment: str | None) -> None: comment = f"{bl_version} shipped with fw {fw_version}" # insert new bootloader - with open(BL_CHECK_TXT, "a") as f: + with open(txt_file, "a") as f: f.write(f"{digest.hex()} {comment}\n") entries[digest] = comment click.echo("Inserted new entry: " + comment) - + # rewrite bl_check.c - regenerate_bl_check(entries.items()) + regenerate_bl_check(entries.items(), begin, end) click.echo("Regenerated bl_check.c") # overwrite bootloader.dat - BOOTLOADER_IMAGE.write_bytes(bl_bytes) - click.echo("Installed bootloader.dat into firmware") + image.write_bytes(bl_bytes) + + if qa: + click.echo("Installed bootloader_qa.dat into firmware") + else: + click.echo("Installed bootloader.dat into firmware") if __name__ == "__main__": diff --git a/legacy/setup.c b/legacy/setup.c index 21ad3c37d..1537c3fb1 100644 --- a/legacy/setup.c +++ b/legacy/setup.c @@ -221,7 +221,7 @@ void mpu_config_bootloader(void) { // Never use in bootloader! Disables access to PPB (including MPU, NVIC, SCB) void mpu_config_firmware(void) { -#if PRODUCTION +#if PRODUCTION || BOOTLOADER_QA // Disable MPU MPU_CTRL = 0;