From ed3770691519f025b5e7ebe353afa0fd3b224fd6 Mon Sep 17 00:00:00 2001 From: hexkyz Date: Thu, 29 Nov 2018 23:32:31 +0000 Subject: [PATCH] fusee: Add full 6.2.0 support via SMMU virtualization. --- fusee/fusee-primary/src/mc.h | 2 + fusee/fusee-secondary/src/key_derivation.c | 59 ++--- fusee/fusee-secondary/src/key_derivation.h | 2 +- fusee/fusee-secondary/src/mc.h | 2 + fusee/fusee-secondary/src/nxboot.c | 45 +++- fusee/fusee-secondary/src/nxboot_iram.c | 19 +- fusee/fusee-secondary/src/package1.c | 3 +- fusee/fusee-secondary/src/smmu.c | 259 +++++++++++++++++++++ fusee/fusee-secondary/src/smmu.h | 63 +++++ fusee/fusee-secondary/src/tsec.c | 111 ++++++--- fusee/fusee-secondary/src/tsec.h | 4 + 11 files changed, 495 insertions(+), 74 deletions(-) create mode 100644 fusee/fusee-secondary/src/smmu.c create mode 100644 fusee/fusee-secondary/src/smmu.h diff --git a/fusee/fusee-primary/src/mc.h b/fusee/fusee-primary/src/mc.h index 53ecd9c67..dfba6052c 100644 --- a/fusee/fusee-primary/src/mc.h +++ b/fusee/fusee-primary/src/mc.h @@ -36,8 +36,10 @@ #define MC_SMMU_PTB_DATA 0x20 #define MC_SMMU_TLB_FLUSH 0x30 #define MC_SMMU_PTC_FLUSH 0x34 +#define MC_SMMU_ASID_SECURITY 0x38 #define MC_SMMU_AFI_ASID 0x238 #define MC_SMMU_AVPC_ASID 0x23c +#define MC_SMMU_TSEC_ASID 0x294 #define MC_SMMU_PPCS1_ASID 0x298 #define MC_SMMU_TRANSLATION_ENABLE_0 0x228 #define MC_SMMU_TRANSLATION_ENABLE_1 0x22c diff --git a/fusee/fusee-secondary/src/key_derivation.c b/fusee/fusee-secondary/src/key_derivation.c index b997e32a7..0daab0f38 100644 --- a/fusee/fusee-secondary/src/key_derivation.c +++ b/fusee/fusee-secondary/src/key_derivation.c @@ -20,7 +20,6 @@ #include "se.h" #include "exocfg.h" #include "fuse.h" -#include "tsec.h" #include "extkeys.h" #include "utils.h" @@ -61,10 +60,6 @@ static const uint8_t AL16 new_master_kek_seeds[1][0x10] = { static nx_dec_keyblob_t AL16 g_dec_keyblobs[32]; -static int get_tsec_key(void *dst, const void *tsec_fw, size_t tsec_fw_size, uint32_t tsec_key_id) { - return tsec_get_key(dst, tsec_key_id, tsec_fw, tsec_fw_size); -} - static int get_keyblob(nx_keyblob_t *dst, uint32_t revision, const nx_keyblob_t *keyblobs, uint32_t available_revision) { if (revision >= 0x20) { return -1; @@ -123,20 +118,18 @@ int load_package1_key(uint32_t revision) { } /* Derive all Switch keys. */ -int derive_nx_keydata(uint32_t target_firmware, const nx_keyblob_t *keyblobs, uint32_t available_revision, const void *tsec_fw, size_t tsec_fw_size, unsigned int *out_keygen_type) { - uint8_t AL16 tsec_key[0x10]; +int derive_nx_keydata(uint32_t target_firmware, const nx_keyblob_t *keyblobs, uint32_t available_revision, const void *tsec_key, void *tsec_root_key, unsigned int *out_keygen_type) { uint8_t AL16 work_buffer[0x10]; uint8_t AL16 zeroes[0x10] = {0}; + + /* Initialize keygen type. */ + *out_keygen_type = 0; /* TODO: Set keyslot flags properly in preparation of derivation. */ set_aes_keyslot_flags(0xE, 0x15); set_aes_keyslot_flags(0xD, 0x15); - - /* Set TSEC key. */ - if (get_tsec_key(tsec_key, tsec_fw, tsec_fw_size, 1) != 0) { - return -1; - } + /* Set the TSEC key. */ set_aes_keyslot(0xD, tsec_key, 0x10); /* Decrypt all keyblobs, setting keyslot 0xF correctly. */ @@ -146,29 +139,37 @@ int derive_nx_keydata(uint32_t target_firmware, const nx_keyblob_t *keyblobs, ui return ret; } } - - - /* TODO: Eventually do 6.2.0+ keygen properly? */ - *out_keygen_type = 0; + + /* Do 6.2.0+ keygen. */ if (target_firmware >= EXOSPHERE_TARGET_FIRMWARE_620) { - const char *keyfile = fuse_get_retail_type() != 0 ? "atmosphere/prod.keys" : "atmosphere/dev.keys"; - FILE *extkey_file = fopen(keyfile, "r"); - AL16 fusee_extkeys_t extkeys = {0}; - if (extkey_file == NULL) { - fatal_error("Error: failed to read %s, needed for 6.2.0+ key derivation!", keyfile); - } - extkeys_initialize_keyset(&extkeys, extkey_file); - fclose(extkey_file); - - if (memcmp(extkeys.tsec_root_key, zeroes, 0x10) != 0) { - set_aes_keyslot(0xC, extkeys.tsec_root_key, 0x10); + if (memcmp(tsec_root_key, zeroes, 0x10) != 0) { + /* We got a valid key from emulation. */ + set_aes_keyslot(0xC, tsec_root_key, 0x10); for (unsigned int rev = MASTERKEY_REVISION_620_CURRENT; rev < MASTERKEY_REVISION_MAX; rev++) { se_aes_ecb_decrypt_block(0xC, work_buffer, 0x10, new_master_kek_seeds[rev - MASTERKEY_REVISION_620_CURRENT], 0x10); memcpy(g_dec_keyblobs[rev].master_kek, work_buffer, 0x10); } } else { - for (unsigned int rev = MASTERKEY_REVISION_620_CURRENT; rev < MASTERKEY_REVISION_MAX; rev++) { - memcpy(g_dec_keyblobs[rev].master_kek, extkeys.master_keks[rev], 0x10); + /* Try reading the keys from a file. */ + const char *keyfile = fuse_get_retail_type() != 0 ? "atmosphere/prod.keys" : "atmosphere/dev.keys"; + FILE *extkey_file = fopen(keyfile, "r"); + AL16 fusee_extkeys_t extkeys = {0}; + if (extkey_file == NULL) { + fatal_error("Error: failed to read %s, needed for 6.2.0+ key derivation!", keyfile); + } + extkeys_initialize_keyset(&extkeys, extkey_file); + fclose(extkey_file); + + if (memcmp(extkeys.tsec_root_key, zeroes, 0x10) != 0) { + set_aes_keyslot(0xC, extkeys.tsec_root_key, 0x10); + for (unsigned int rev = MASTERKEY_REVISION_620_CURRENT; rev < MASTERKEY_REVISION_MAX; rev++) { + se_aes_ecb_decrypt_block(0xC, work_buffer, 0x10, new_master_kek_seeds[rev - MASTERKEY_REVISION_620_CURRENT], 0x10); + memcpy(g_dec_keyblobs[rev].master_kek, work_buffer, 0x10); + } + } else { + for (unsigned int rev = MASTERKEY_REVISION_620_CURRENT; rev < MASTERKEY_REVISION_MAX; rev++) { + memcpy(g_dec_keyblobs[rev].master_kek, extkeys.master_keks[rev], 0x10); + } } } diff --git a/fusee/fusee-secondary/src/key_derivation.h b/fusee/fusee-secondary/src/key_derivation.h index 0cc475037..33e645cca 100644 --- a/fusee/fusee-secondary/src/key_derivation.h +++ b/fusee/fusee-secondary/src/key_derivation.h @@ -47,7 +47,7 @@ typedef struct nx_keyblob_t { }; } nx_keyblob_t; -int derive_nx_keydata(uint32_t target_firmware, const nx_keyblob_t *keyblobs, uint32_t available_revision, const void *tsec_fw, size_t tsec_fw_size, unsigned int *out_keygen_type); +int derive_nx_keydata(uint32_t target_firmware, const nx_keyblob_t *keyblobs, uint32_t available_revision, const void *tsec_key, void *tsec_root_key, unsigned int *out_keygen_type); int load_package1_key(uint32_t revision); void finalize_nx_keydata(uint32_t target_firmware); void derive_bis_key(void *dst, BisPartition partition_id, uint32_t target_firmware); diff --git a/fusee/fusee-secondary/src/mc.h b/fusee/fusee-secondary/src/mc.h index 53ecd9c67..dfba6052c 100644 --- a/fusee/fusee-secondary/src/mc.h +++ b/fusee/fusee-secondary/src/mc.h @@ -36,8 +36,10 @@ #define MC_SMMU_PTB_DATA 0x20 #define MC_SMMU_TLB_FLUSH 0x30 #define MC_SMMU_PTC_FLUSH 0x34 +#define MC_SMMU_ASID_SECURITY 0x38 #define MC_SMMU_AFI_ASID 0x238 #define MC_SMMU_AVPC_ASID 0x23c +#define MC_SMMU_TSEC_ASID 0x294 #define MC_SMMU_PPCS1_ASID 0x298 #define MC_SMMU_TRANSLATION_ENABLE_0 0x228 #define MC_SMMU_TRANSLATION_ENABLE_1 0x22c diff --git a/fusee/fusee-secondary/src/nxboot.c b/fusee/fusee-secondary/src/nxboot.c index 4851d3e3b..eec1fd2f9 100644 --- a/fusee/fusee-secondary/src/nxboot.c +++ b/fusee/fusee-secondary/src/nxboot.c @@ -36,6 +36,8 @@ #include "key_derivation.h" #include "package1.h" #include "package2.h" +#include "smmu.h" +#include "tsec.h" #include "loader.h" #include "splash_screen.h" #include "exocfg.h" @@ -311,9 +313,28 @@ uint32_t nxboot_main(void) { print(SCREEN_LOG_LEVEL_MANDATORY, "[NXBOOT]: Loaded firmware from eMMC...\n"); + /* Get the TSEC keys. */ + uint8_t tsec_key[0x10] = {0}; + uint8_t tsec_root_key[0x10] = {0}; + if (target_firmware >= EXOSPHERE_TARGET_FIRMWARE_620) { + uint8_t tsec_keys[0x20] = {0}; + + /* Emulate the TSEC payload on 6.2.0+. */ + smmu_emulate_tsec((void *)tsec_keys, package1loader, package1loader_size, package1loader); + + /* Copy back the keys. */ + memcpy((void *)tsec_key, (void *)tsec_keys, 0x10); + memcpy((void *)tsec_root_key, (void *)tsec_keys + 0x10, 0x10); + } else { + /* Run the TSEC payload and get the key. */ + if (tsec_get_key(tsec_key, 1, tsec_fw, tsec_fw_size) != 0) { + fatal_error("[NXBOOT]: Failed to get TSEC key!\n"); + } + } + /* Derive keydata. */ unsigned int keygen_type = 0; - if (derive_nx_keydata(target_firmware, g_keyblobs, available_revision, tsec_fw, tsec_fw_size, &keygen_type) != 0) { + if (derive_nx_keydata(target_firmware, g_keyblobs, available_revision, tsec_key, tsec_root_key, &keygen_type) != 0) { fatal_error("[NXBOOT]: Key derivation failed!\n"); } @@ -343,16 +364,26 @@ uint32_t nxboot_main(void) { fatal_error("[NXBOOT]: Could not read the warmboot firmware from %s!\n", loader_ctx->warmboot_path); } } else { - uint8_t ctr[16]; - package1_size = package1_get_encrypted_package1(&package1, ctr, package1loader, package1loader_size); - if (package1_decrypt(package1, package1_size, ctr)) { + if (target_firmware >= EXOSPHERE_TARGET_FIRMWARE_620) { + /* Package1 was decrypted during TSEC emulation. */ + const uint8_t *package1_hdr = (const uint8_t *)package1loader + 0x7000 - 0x20; + package1 = (package1_header_t *)(package1_hdr + 0x20); + package1_size = *(uint32_t *)package1_hdr; warmboot_fw = package1_get_warmboot_fw(package1); warmboot_fw_size = package1->warmboot_size; } else { - warmboot_fw = NULL; - warmboot_fw_size = 0; + /* Decrypt package1 and extract the warmboot firmware. */ + uint8_t ctr[16]; + package1_size = package1_get_encrypted_package1(&package1, ctr, package1loader, package1loader_size); + if (package1_decrypt(package1, package1_size, ctr)) { + warmboot_fw = package1_get_warmboot_fw(package1); + warmboot_fw_size = package1->warmboot_size; + } else { + warmboot_fw = NULL; + warmboot_fw_size = 0; + } } - + if (warmboot_fw_size == 0) { fatal_error("[NXBOOT]: Could not read the warmboot firmware from Package1!\n"); } diff --git a/fusee/fusee-secondary/src/nxboot_iram.c b/fusee/fusee-secondary/src/nxboot_iram.c index 18745ba45..86466a072 100644 --- a/fusee/fusee-secondary/src/nxboot_iram.c +++ b/fusee/fusee-secondary/src/nxboot_iram.c @@ -23,7 +23,9 @@ #include "mc.h" #include "nxboot.h" #include "se.h" +#include "smmu.h" #include "timers.h" +#include "sysreg.h" void nxboot_finish(uint32_t boot_memaddr) { volatile tegra_se_t *se = se_get_regs(); @@ -69,8 +71,21 @@ void nxboot_finish(uint32_t boot_memaddr) { /* Terminate the display. */ display_end(); - /* Boot CPU0. */ - cluster_boot_cpu0(boot_memaddr); + /* Check if SMMU emulation has been used. */ + uint32_t smmu_magic = *(uint32_t *)(SMMU_AARCH64_PAYLOAD_ADDR + 0xFC); + if (smmu_magic == 0xDEADC0DE) { + /* Clear the magic. */ + *(uint32_t *)(SMMU_AARCH64_PAYLOAD_ADDR + 0xFC) = 0; + + /* Pass the boot address to the already running payload. */ + *(uint32_t *)(SMMU_AARCH64_PAYLOAD_ADDR + 0xF0) = boot_memaddr; + + /* Wait a while. */ + mdelay(500); + } else { + /* Boot CPU0. */ + cluster_boot_cpu0(boot_memaddr); + } /* Wait for Exosphère to wake up. */ while (MAILBOX_NX_BOOTLOADER_IS_SECMON_AWAKE == 0) { diff --git a/fusee/fusee-secondary/src/package1.c b/fusee/fusee-secondary/src/package1.c index 4f62310b6..a0f016352 100644 --- a/fusee/fusee-secondary/src/package1.c +++ b/fusee/fusee-secondary/src/package1.c @@ -131,7 +131,7 @@ void *package1_get_warmboot_fw(const package1_header_t *package1) { https://github.com/ARM-software/arm-trusted-firmware/blob/master/plat/nvidia/tegra/common/aarch64/tegra_helpers.S#L312 and thus by 0xD5034FDF. - Nx-bootloader seems to always start by 0xE328F0C0 (msr cpsr_f, 0xc0). + Nx-bootloader starts by 0xE328F0C0 (msr cpsr_f, 0xc0) before 6.2.0 and by 0xF0C0A7F0 afterwards. */ const uint32_t *data = (const uint32_t *)package1->data; for (size_t i = 0; i < 3; i++) { @@ -140,6 +140,7 @@ void *package1_get_warmboot_fw(const package1_header_t *package1) { data += package1->secmon_size / 4; break; case 0xE328F0C0: + case 0xF0C0A7F0: data += package1->nx_bootloader_size / 4; break; default: diff --git a/fusee/fusee-secondary/src/smmu.c b/fusee/fusee-secondary/src/smmu.c new file mode 100644 index 000000000..23bd3d37d --- /dev/null +++ b/fusee/fusee-secondary/src/smmu.c @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "smmu.h" +#include "cluster.h" +#include "mc.h" +#include "timers.h" +#include "tsec.h" + +void *smmu_heap = (void *)SMMU_HEAP_BASE_ADDR; + +static void safe_memcpy(void *dst, void *src, uint32_t sz) { + /* Aligned memcpy to read MMIO correctly. */ + for (size_t i = 0; i < (sz/4); i++) { + ((volatile uint32_t *)dst)[i] = ((volatile uint32_t *)src)[i]; + } +} + +static void smmu_flush_ppsb() { + /* Read-back barrier for interactions between the PPSB and the APB/AHB. */ + (void)MAKE_MC_REG(MC_SMMU_TLB_CONFIG); +} + +static void smmu_flush_regs() { + /* Flush all TLB and PTC entries. */ + MAKE_MC_REG(MC_SMMU_PTC_FLUSH) = 0; + smmu_flush_ppsb(); + MAKE_MC_REG(MC_SMMU_TLB_FLUSH) = 0; + smmu_flush_ppsb(); +} + +static void *smmu_alloc_page(uint32_t page_count) { + void *cur_page = smmu_heap; + smmu_heap += (page_count * SMMU_PAGE_SIZE); + memset(cur_page, 0, (page_count * SMMU_PAGE_SIZE)); + return cur_page; +} + +static uint32_t *smmu_alloc_pdir() { + uint32_t *pdir = (uint32_t *)smmu_alloc_page(1); + for (int pdn = 0; pdn < SMMU_PDIR_COUNT; pdn++) { + pdir[pdn] = _PDE_VACANT(pdn); + } + return pdir; +} + +static uint32_t *smmu_locate_pte(uint32_t *pdir_page, uint32_t iova) { + uint32_t ptn = SMMU_ADDR_TO_PFN(iova); + uint32_t pdn = SMMU_ADDR_TO_PDN(iova); + uint32_t *pdir = pdir_page; + uint32_t *ptbl; + + if (pdir[pdn] != _PDE_VACANT(pdn)) { + /* Mapped entry table already exists. */ + ptbl = (uint32_t *)SMMU_EX_PTBL_PAGE(pdir[pdn]); + } else { + /* Allocate page table. */ + ptbl = (uint32_t *)smmu_alloc_page(1); + uint32_t addr = SMMU_PDN_TO_ADDR(pdn); + for (int pn = 0; pn < SMMU_PTBL_COUNT; pn++, addr += SMMU_PAGE_SIZE) { + ptbl[pn] = _PTE_VACANT(addr); + } + pdir[pdn] = SMMU_MK_PDE((uint32_t)ptbl, _PDE_ATTR | _PDE_NEXT); + smmu_flush_regs(); + } + + return &ptbl[ptn % SMMU_PTBL_COUNT]; +} + +static void smmu_map(uint32_t *pdir, uint32_t addr, uint32_t ptpage, int pcount, uint32_t pte_attr) { + for (int i = 0; i < pcount; i++) { + uint32_t *pte = smmu_locate_pte(pdir, addr); + *pte = SMMU_PFN_TO_PTE(SMMU_ADDR_TO_PFN(ptpage), pte_attr); + addr += SMMU_PAGE_SIZE; + ptpage += SMMU_PAGE_SIZE; + } + smmu_flush_regs(); +} + +static uint32_t *smmu_setup_tsec_as(uint32_t asid) { + /* Allocate the page directory. */ + uint32_t *pdir_page = smmu_alloc_pdir(); + + /* Set the PTB ASID and point it to the PDIR. */ + MAKE_MC_REG(MC_SMMU_PTB_ASID) = asid; + MAKE_MC_REG(MC_SMMU_PTB_DATA) = SMMU_MK_PDIR((uint32_t)pdir_page, _PDIR_ATTR); + smmu_flush_ppsb(); + + /* Assign the ASID to TSEC. */ + MAKE_MC_REG(MC_SMMU_TSEC_ASID) = SMMU_ASID_ENABLE((asid << 24) | (asid << 16) | (asid << 8) | asid); + smmu_flush_ppsb(); + + return pdir_page; +} + +static void smmu_clear_tsec_as(uint32_t asid) { + /* Set the PTB ASID and clear it's data. */ + MAKE_MC_REG(MC_SMMU_PTB_ASID) = asid; + MAKE_MC_REG(MC_SMMU_PTB_DATA) = 0; + + /* Clear the ASID from TSEC. */ + MAKE_MC_REG(MC_SMMU_TSEC_ASID) = SMMU_ASID_DISABLE; + smmu_flush_ppsb(); +} + +static void smmu_enable() { + /* AARCH64 payload for enabling the SMMU. */ + /* Write 1 to MC_SMMU_CONFIG, read back and write the result to 0x40003F80. */ + /* This will leave the CPU waiting until 0x40003FF0 is set to Exosphère's address. */ + static const uint32_t aarch64_payload[20] = { + 0x52800020, 0x58000162, 0x58000183, 0xB9000040, + 0xB9400041, 0xB9000061, 0x58000142, 0xF9400040, + 0xF100001F, 0x54FFFFA0, 0xD61F0000, 0x00000000, + 0x70019010, 0x00000000, 0x40003F80, 0x00000000, + 0x40003FF0, 0x00000000, 0x00000000, 0x00000000 + }; + + /* Reset Translation Enable Registers. */ + MAKE_MC_REG(MC_SMMU_TRANSLATION_ENABLE_0) = 0xFFFFFFFF; + MAKE_MC_REG(MC_SMMU_TRANSLATION_ENABLE_1) = 0xFFFFFFFF; + MAKE_MC_REG(MC_SMMU_TRANSLATION_ENABLE_2) = 0xFFFFFFFF; + MAKE_MC_REG(MC_SMMU_TRANSLATION_ENABLE_3) = 0xFFFFFFFF; + MAKE_MC_REG(MC_SMMU_TRANSLATION_ENABLE_4) = 0xFFFFFFFF; + + /* Setup initial TLB and PTC configuration. */ + MAKE_MC_REG(MC_SMMU_PTB_ASID) = 0; + MAKE_MC_REG(MC_SMMU_PTB_DATA) = 0; + MAKE_MC_REG(MC_SMMU_TLB_CONFIG) = 0x30000030; + MAKE_MC_REG(MC_SMMU_PTC_CONFIG) = 0x2800003F; + smmu_flush_regs(); + + /* Power on the CCPLEX to enable the SMMU globally (requires a secure write). */ + volatile uint32_t *aarch64_payload_res = (volatile uint32_t *)(SMMU_AARCH64_PAYLOAD_ADDR + 0x80); + memset((void *)SMMU_AARCH64_PAYLOAD_ADDR, 0, 0x100); + memcpy((void *)SMMU_AARCH64_PAYLOAD_ADDR, aarch64_payload, 20 * 4); + cluster_boot_cpu0(SMMU_AARCH64_PAYLOAD_ADDR); + mdelay(500); + if (*aarch64_payload_res != 1) { + fatal_error("[SMMU]: Failed to enable SMMU!\n"); + } + + /* Write magic for nxboot. */ + *(uint32_t *)(SMMU_AARCH64_PAYLOAD_ADDR + 0xFC) = 0xDEADC0DE; + + /* Flush TLB and PTC entries. */ + smmu_flush_regs(); +} + +void smmu_emulate_tsec(void *tsec_keys, const void *package1, size_t package1_size, void *package1_dec) { + volatile tegra_tsec_t *tsec = tsec_get_regs(); + + /* Backup IRAM to DRAM. */ + memcpy((void *)SMMU_IRAM_BACKUP_ADDR, (void *)0x40010000, 0x30000); + + /* Copy package1 into IRAM. */ + memcpy((void *)0x40010000, package1, package1_size); + + /* Load the TSEC firmware from IRAM. */ + if (tsec_load_fw((void *)(0x40010000 + 0xE00), 0x2900) < 0) { + fatal_error("[SMMU]: Failed to load TSEC firmware!\n"); + } + + /* Disable the aperture since it has precedence over the SMMU. */ + mc_disable_ahb_redirect(); + + /* Setup TSEC's address space. */ + uint32_t *pdir = smmu_setup_tsec_as(1); + + /* Allocate pages for MMIO and IRAM. */ + volatile uint32_t *car_page = smmu_alloc_page(1); + volatile uint32_t *fuse_page = smmu_alloc_page(1); + volatile uint32_t *pmc_page = smmu_alloc_page(1); + volatile uint32_t *flow_page = smmu_alloc_page(1); + volatile uint32_t *se_page = smmu_alloc_page(1); + volatile uint32_t *mc_page = smmu_alloc_page(1); + volatile uint32_t *iram_pages = smmu_alloc_page(48); + volatile uint32_t *expv_page = smmu_alloc_page(1); + + /* Copy CAR, MC and FUSE. */ + safe_memcpy((void *)car_page, (void *)0x60006000, 0x1000); + safe_memcpy((void *)mc_page, (void *)0x70019000, 0x1000); + safe_memcpy((void *)&fuse_page[0x800/4], (void *)0x7000F800, 0x400); + + /* Copy IRAM. */ + memcpy((void *)iram_pages, (void *)0x40010000, 0x30000); + + /* TSEC wants CLK_RST_CONTROLLER_CLK_SOURCE_TSEC_0 to be equal to 2. */ + car_page[0x1F4/4] = 2; + + /* TSEC wants the aperture fully open. */ + mc_page[0x65C/4] = 0; + mc_page[0x660/4] = 0x80000000; + + /* Map all necessary pages. */ + smmu_map(pdir, 0x60006000, (uint32_t)car_page, 1, _READABLE | _WRITABLE | _NONSECURE); + smmu_map(pdir, 0x7000F000, (uint32_t)fuse_page, 1, _READABLE | _NONSECURE); + smmu_map(pdir, 0x7000E000, (uint32_t)pmc_page, 1, _READABLE | _NONSECURE); + smmu_map(pdir, 0x60007000, (uint32_t)flow_page, 1, _WRITABLE | _NONSECURE); + smmu_map(pdir, 0x70012000, (uint32_t)se_page, 1, _READABLE | _WRITABLE | _NONSECURE); + smmu_map(pdir, 0x70019000, (uint32_t)mc_page, 1, _READABLE | _NONSECURE); + smmu_map(pdir, 0x40010000, (uint32_t)iram_pages, 48, _READABLE | _WRITABLE | _NONSECURE); + smmu_map(pdir, 0x6000F000, (uint32_t)expv_page, 1, _READABLE | _WRITABLE | _NONSECURE); + + /* Enable the SMMU. */ + smmu_enable(); + + /* Run the TSEC firmware. */ + tsec_run_fw(); + + /* Extract the keys from SE. */ + uint32_t key_buf[0x20/4] = {0}; + volatile uint32_t *key_data = (volatile uint32_t *)((void *)se_page + 0x320); + uint32_t old_key_data = *key_data; + uint32_t buf_counter = 0; + while (!(tsec->FALCON_CPUCTL & 0x10)) { + if (*key_data != old_key_data) { + old_key_data = *key_data; + key_buf[buf_counter] = *key_data; + buf_counter++; + } + } + + /* Check if the TSEC firmware wrote over the exception vectors. */ + volatile uint32_t *tsec_done_check = (volatile uint32_t *)((void *)expv_page + 0x200); + if (!(*tsec_done_check)) { + fatal_error("[SMMU]: Failed to emulate the TSEC firmware!\n"); + } + + /* Copy back the extracted keys. */ + memcpy((void *)tsec_keys, (void *)key_buf, 0x20); + + /* Manually disable TSEC clocks. */ + tsec_disable_clkrst(); + + /* Clear TSEC's address space. */ + smmu_clear_tsec_as(1); + + /* Enable back the aperture. */ + mc_enable_ahb_redirect(); + + /* Return the decrypted package1 from emulated IRAM. */ + memcpy(package1_dec, (void *)iram_pages, package1_size); + + /* Restore IRAM from DRAM. */ + memcpy((void *)0x40010000, (void *)SMMU_IRAM_BACKUP_ADDR, 0x30000); +} \ No newline at end of file diff --git a/fusee/fusee-secondary/src/smmu.h b/fusee/fusee-secondary/src/smmu.h new file mode 100644 index 000000000..ba6a01684 --- /dev/null +++ b/fusee/fusee-secondary/src/smmu.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef FUSEE_SMMU_H_ +#define FUSEE_SMMU_H_ + +#include +#include +#include + +#define SMMU_HEAP_BASE_ADDR 0x81000000 +#define SMMU_IRAM_BACKUP_ADDR 0x82000000 +#define SMMU_AARCH64_PAYLOAD_ADDR 0x40003F00 + +#define SMMU_PAGE_SHIFT 12 +#define SMMU_PAGE_SIZE (1 << SMMU_PAGE_SHIFT) +#define SMMU_PDIR_COUNT 1024 +#define SMMU_PDIR_SIZE (sizeof(uint32_t) * SMMU_PDIR_COUNT) +#define SMMU_PTBL_COUNT 1024 +#define SMMU_PTBL_SIZE (sizeof(uint32_t) * SMMU_PTBL_COUNT) +#define SMMU_PDIR_SHIFT 12 +#define SMMU_PDE_SHIFT 12 +#define SMMU_PTE_SHIFT 12 +#define SMMU_PFN_MASK 0x000fffff +#define SMMU_PDE_NEXT_SHIFT 28 +#define SMMU_ADDR_TO_PFN(addr) ((addr) >> 12) +#define SMMU_ADDR_TO_PDN(addr) ((addr) >> 22) +#define SMMU_PDN_TO_ADDR(pdn) ((pdn) << 22) +#define _READABLE (1 << 31) +#define _WRITABLE (1 << 30) +#define _NONSECURE (1 << 29) +#define _PDE_NEXT (1 << SMMU_PDE_NEXT_SHIFT) +#define _MASK_ATTR (_READABLE | _WRITABLE | _NONSECURE) +#define _PDIR_ATTR (_READABLE | _WRITABLE | _NONSECURE) +#define _PDE_ATTR (_READABLE | _WRITABLE | _NONSECURE) +#define _PDE_ATTR_N (_PDE_ATTR | _PDE_NEXT) +#define _PDE_VACANT(pdn) (((pdn) << 10) | _PDE_ATTR) +#define _PTE_ATTR (_READABLE | _WRITABLE | _NONSECURE) +#define _PTE_VACANT(addr) (((addr) >> SMMU_PAGE_SHIFT) | _PTE_ATTR) +#define SMMU_MK_PDIR(page, attr) (((page) >> SMMU_PDIR_SHIFT) | (attr)) +#define SMMU_MK_PDE(page, attr) (((page) >> SMMU_PDE_SHIFT) | (attr)) +#define SMMU_EX_PTBL_PAGE(pde) (((pde) & SMMU_PFN_MASK) << SMMU_PDIR_SHIFT) +#define SMMU_PFN_TO_PTE(pfn, attr) ((pfn) | (attr)) +#define SMMU_ASID_ENABLE(asid) ((asid) | (1 << 31)) +#define SMMU_ASID_DISABLE 0 +#define SMMU_ASID_ASID(n) ((n) & ~SMMU_ASID_ENABLE(0)) + +void smmu_emulate_tsec(void *tsec_keys, const void *package1, size_t package1_size, void *package1_dec); + +#endif \ No newline at end of file diff --git a/fusee/fusee-secondary/src/tsec.c b/fusee/fusee-secondary/src/tsec.c index b864df405..75ec724a3 100644 --- a/fusee/fusee-secondary/src/tsec.c +++ b/fusee/fusee-secondary/src/tsec.c @@ -49,17 +49,34 @@ static int tsec_dma_phys_to_flcn(bool is_imem, uint32_t flcn_offset, uint32_t ph return tsec_dma_wait_idle(); } -int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw_size) +void tsec_enable_clkrst() { - volatile tegra_tsec_t *tsec = tsec_get_regs(); - - /* Enable clocks. */ + /* Enable all devices used by TSEC. */ clkrst_reboot(CARDEVICE_HOST1X); clkrst_reboot(CARDEVICE_TSEC); clkrst_reboot(CARDEVICE_SOR_SAFE); clkrst_reboot(CARDEVICE_SOR0); clkrst_reboot(CARDEVICE_SOR1); clkrst_reboot(CARDEVICE_KFUSE); +} + +void tsec_disable_clkrst() +{ + /* Disable all devices used by TSEC. */ + clkrst_disable(CARDEVICE_KFUSE); + clkrst_disable(CARDEVICE_SOR1); + clkrst_disable(CARDEVICE_SOR0); + clkrst_disable(CARDEVICE_SOR_SAFE); + clkrst_disable(CARDEVICE_TSEC); + clkrst_disable(CARDEVICE_HOST1X); +} + +int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw_size) +{ + volatile tegra_tsec_t *tsec = tsec_get_regs(); + + /* Enable clocks. */ + tsec_enable_clkrst(); /* Configure Falcon. */ tsec->FALCON_DMACTL = 0; @@ -70,12 +87,7 @@ int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw if (!tsec_dma_wait_idle()) { /* Disable clocks. */ - clkrst_disable(CARDEVICE_KFUSE); - clkrst_disable(CARDEVICE_SOR1); - clkrst_disable(CARDEVICE_SOR0); - clkrst_disable(CARDEVICE_SOR_SAFE); - clkrst_disable(CARDEVICE_TSEC); - clkrst_disable(CARDEVICE_HOST1X); + tsec_disable_clkrst(); return -1; } @@ -87,12 +99,7 @@ int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw if (!tsec_dma_phys_to_flcn(true, addr, addr)) { /* Disable clocks. */ - clkrst_disable(CARDEVICE_KFUSE); - clkrst_disable(CARDEVICE_SOR1); - clkrst_disable(CARDEVICE_SOR0); - clkrst_disable(CARDEVICE_SOR_SAFE); - clkrst_disable(CARDEVICE_TSEC); - clkrst_disable(CARDEVICE_HOST1X); + tsec_disable_clkrst(); return -2; } @@ -110,12 +117,7 @@ int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw if (!tsec_dma_wait_idle()) { /* Disable clocks. */ - clkrst_disable(CARDEVICE_KFUSE); - clkrst_disable(CARDEVICE_SOR1); - clkrst_disable(CARDEVICE_SOR0); - clkrst_disable(CARDEVICE_SOR_SAFE); - clkrst_disable(CARDEVICE_TSEC); - clkrst_disable(CARDEVICE_HOST1X); + tsec_disable_clkrst(); return -3; } @@ -126,12 +128,7 @@ int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw if (get_time_ms() > timeout) { /* Disable clocks. */ - clkrst_disable(CARDEVICE_KFUSE); - clkrst_disable(CARDEVICE_SOR1); - clkrst_disable(CARDEVICE_SOR0); - clkrst_disable(CARDEVICE_SOR_SAFE); - clkrst_disable(CARDEVICE_TSEC); - clkrst_disable(CARDEVICE_HOST1X); + tsec_disable_clkrst(); return -4; } @@ -140,12 +137,7 @@ int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw if (tsec->FALCON_SCRATCH1 != 0xB0B0B0B0) { /* Disable clocks. */ - clkrst_disable(CARDEVICE_KFUSE); - clkrst_disable(CARDEVICE_SOR1); - clkrst_disable(CARDEVICE_SOR0); - clkrst_disable(CARDEVICE_SOR_SAFE); - clkrst_disable(CARDEVICE_TSEC); - clkrst_disable(CARDEVICE_HOST1X); + tsec_disable_clkrst(); return -5; } @@ -170,4 +162,55 @@ int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw memcpy(key, &tmp, 0x10); return 0; +} + +int tsec_load_fw(const void *tsec_fw, size_t tsec_fw_size) +{ + volatile tegra_tsec_t *tsec = tsec_get_regs(); + + /* Enable clocks. */ + tsec_enable_clkrst(); + + /* Configure Falcon. */ + tsec->FALCON_DMACTL = 0; + tsec->FALCON_IRQMSET = 0xFFF2; + tsec->FALCON_IRQDEST = 0xFFF0; + tsec->FALCON_ITFEN = 3; + + if (!tsec_dma_wait_idle()) + { + /* Disable clocks. */ + tsec_disable_clkrst(); + + return -1; + } + + /* Load firmware. */ + tsec->FALCON_DMATRFBASE = (uint32_t)tsec_fw >> 8; + for (uint32_t addr = 0; addr < tsec_fw_size; addr += 0x100) + { + if (!tsec_dma_phys_to_flcn(true, addr, addr)) + { + /* Disable clocks. */ + tsec_disable_clkrst(); + + return -2; + } + } + + return 0; +} + +void tsec_run_fw() +{ + volatile tegra_tsec_t *tsec = tsec_get_regs(); + + /* Unknown host1x write. */ + MAKE_HOST1X_REG(0x3300) = 0x34C2E1DA; + + /* Execute firmware. */ + tsec->FALCON_SCRATCH1 = 0; + tsec->FALCON_SCRATCH0 = 1; + tsec->FALCON_BOOTVEC = 0; + tsec->FALCON_CPUCTL = 2; } \ No newline at end of file diff --git a/fusee/fusee-secondary/src/tsec.h b/fusee/fusee-secondary/src/tsec.h index c16c2f9a3..54842bdfb 100644 --- a/fusee/fusee-secondary/src/tsec.h +++ b/fusee/fusee-secondary/src/tsec.h @@ -109,6 +109,10 @@ static inline volatile tegra_tsec_t *tsec_get_regs(void) return (volatile tegra_tsec_t *)TSEC_BASE; } +void tsec_enable_clkrst(); +void tsec_disable_clkrst(); int tsec_get_key(uint8_t *key, uint32_t rev, const void *tsec_fw, size_t tsec_fw_size); +int tsec_load_fw(const void *tsec_fw, size_t tsec_fw_size); +void tsec_run_fw(); #endif \ No newline at end of file