Move source files to src/, add Makefile, fix all build and linkage errors, etc.
This commit is contained in:
40
exosphere/src/bootconfig.c
Normal file
40
exosphere/src/bootconfig.c
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "bootconfig.h"
|
||||
|
||||
void bootconfig_load_and_verify(const bootconfig_t *bootconfig) {
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
void bootconfig_clear(void){
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
/* Actual configuration getters. */
|
||||
bool bootconfig_is_package2_plaintext(void) {
|
||||
return false;
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
bool bootconfig_is_package2_unsigned(void) {
|
||||
return false;
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
bool bootconfig_disable_program_verification(void) {
|
||||
return false;
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
bool bootconfig_is_debug_mode(void) {
|
||||
return false;
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
uint64_t bootconfig_get_memory_arrangement(void) {
|
||||
return 0ULL;
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
uint64_t bootconfig_get_kernel_memory_configuration(void) {
|
||||
return 0ULL;
|
||||
/* TODO */
|
||||
}
|
||||
28
exosphere/src/bootconfig.h
Normal file
28
exosphere/src/bootconfig.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef EXOSPHERE_BOOTCONFIG_H
|
||||
#define EXOSPHERE_BOOTCONFIG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* This provides management for Switch BootConfig. */
|
||||
|
||||
typedef struct {
|
||||
uint8_t unsigned_config[0x200];
|
||||
uint8_t signature[0x100];
|
||||
uint8_t signed_config[0x100];
|
||||
uint8_t unknown_config[0x240];
|
||||
} bootconfig_t;
|
||||
|
||||
void bootconfig_load_and_verify(const bootconfig_t *bootconfig);
|
||||
void bootconfig_clear(void);
|
||||
|
||||
/* Actual configuration getters. */
|
||||
bool bootconfig_is_package2_plaintext(void);
|
||||
bool bootconfig_is_package2_unsigned(void);
|
||||
bool bootconfig_disable_program_verification(void);
|
||||
bool bootconfig_is_debug_mode(void);
|
||||
|
||||
uint64_t bootconfig_get_memory_arrangement(void);
|
||||
uint64_t bootconfig_get_kernel_memory_configuration(void);
|
||||
|
||||
#endif
|
||||
20
exosphere/src/cache.h
Normal file
20
exosphere/src/cache.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef EXOSPHERE_CACHE_H
|
||||
#define EXOSPHERE_CACHE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void tlb_invalidate_all(void);
|
||||
void tlb_invalidate_all_inner_shareable(void);
|
||||
|
||||
void tlb_invalidate_page(const volatile void *page);
|
||||
void tlb_invalidate_page_inner_shareable(const void *page);
|
||||
|
||||
void flush_dcache_all(void);
|
||||
void invalidate_dcache_all(void);
|
||||
|
||||
void flush_dcache_range(const void *start, const void *end);
|
||||
void invalidate_dcache_range(const void *start, const void *end);
|
||||
|
||||
void invalidate_icache_inner_shareable(void);
|
||||
|
||||
#endif
|
||||
229
exosphere/src/cache.s
Normal file
229
exosphere/src/cache.s
Normal file
@@ -0,0 +1,229 @@
|
||||
.section .text.tlb_invalidate_all, "ax", %progbits
|
||||
.type tlb_invalidate_all, %function
|
||||
.global tlb_invalidate_all
|
||||
tlb_invalidate_all:
|
||||
dsb sy
|
||||
tlbi alle3
|
||||
dsb sy
|
||||
isb
|
||||
ret
|
||||
|
||||
.section .text.tlb_invalidate_all_inner_shareable, "ax", %progbits
|
||||
.type tlb_invalidate_all_inner_shareable, %function
|
||||
.global tlb_invalidate_all_inner_shareable
|
||||
tlb_invalidate_all_inner_shareable:
|
||||
dsb ish
|
||||
tlbi alle3is
|
||||
dsb ish
|
||||
isb
|
||||
ret
|
||||
|
||||
.section .text.tlb_invalidate_page, "ax", %progbits
|
||||
.type tlb_invalidate_page, %function
|
||||
.global tlb_invalidate_page
|
||||
tlb_invalidate_page:
|
||||
lsr x8, x0, #12
|
||||
dsb sy
|
||||
tlbi vale3, x8
|
||||
dsb sy
|
||||
isb
|
||||
ret
|
||||
|
||||
.section .text.tlb_invalidate_page_inner_shareable, "ax", %progbits
|
||||
.type tlb_invalidate_page_inner_shareable, %function
|
||||
.global tlb_invalidate_page_inner_shareable
|
||||
tlb_invalidate_page_inner_shareable:
|
||||
lsr x8, x0, #12
|
||||
dsb ish
|
||||
tlbi vale3is, x8
|
||||
dsb ish
|
||||
isb
|
||||
ret
|
||||
|
||||
/* The following functions are taken/adapted from https://github.com/u-boot/u-boot/blob/master/arch/arm/cpu/armv8/cache.S */
|
||||
|
||||
/*
|
||||
* (C) Copyright 2013
|
||||
* David Feng <fenghua@phytium.com.cn>
|
||||
*
|
||||
* This file is based on sample code from ARMv8 ARM.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
/*
|
||||
* void __asm_dcache_level(level)
|
||||
*
|
||||
* flush or invalidate one level cache.
|
||||
*
|
||||
* x0: cache level
|
||||
* x1: 0 clean & invalidate, 1 invalidate only
|
||||
* x2~x9: clobbered
|
||||
*/
|
||||
.section .text.__asm_dcache_level, "ax", %progbits
|
||||
.type __asm_dcache_level, %function
|
||||
__asm_dcache_level:
|
||||
lsl x12, x0, #1
|
||||
msr csselr_el1, x12 /* select cache level */
|
||||
isb /* sync change of cssidr_el1 */
|
||||
mrs x6, ccsidr_el1 /* read the new cssidr_el1 */
|
||||
and x2, x6, #7 /* x2 <- log2(cache line size)-4 */
|
||||
add x2, x2, #4 /* x2 <- log2(cache line size) */
|
||||
mov x3, #0x3ff
|
||||
and x3, x3, x6, lsr #3 /* x3 <- max number of #ways */
|
||||
clz w5, w3 /* bit position of #ways */
|
||||
mov x4, #0x7fff
|
||||
and x4, x4, x6, lsr #13 /* x4 <- max number of #sets */
|
||||
/* x12 <- cache level << 1 */
|
||||
/* x2 <- line length offset */
|
||||
/* x3 <- number of cache ways - 1 */
|
||||
/* x4 <- number of cache sets - 1 */
|
||||
/* x5 <- bit position of #ways */
|
||||
|
||||
loop_set:
|
||||
mov x6, x3 /* x6 <- working copy of #ways */
|
||||
loop_way:
|
||||
lsl x7, x6, x5
|
||||
orr x9, x12, x7 /* map way and level to cisw value */
|
||||
lsl x7, x4, x2
|
||||
orr x9, x9, x7 /* map set number to cisw value */
|
||||
tbz w1, #0, 1f
|
||||
dc isw, x9
|
||||
b 2f
|
||||
1: dc cisw, x9 /* clean & invalidate by set/way */
|
||||
2: subs x6, x6, #1 /* decrement the way */
|
||||
b.ge loop_way
|
||||
subs x4, x4, #1 /* decrement the set */
|
||||
b.ge loop_set
|
||||
|
||||
ret
|
||||
|
||||
/*
|
||||
* void __asm_flush_dcache_all(int invalidate_only)
|
||||
*
|
||||
* x0: 0 clean & invalidate, 1 invalidate only
|
||||
*
|
||||
* flush or invalidate all data cache by SET/WAY.
|
||||
*/
|
||||
.section .text.__asm_dcache_all, "ax", %progbits
|
||||
.type __asm_dcache_all, %function
|
||||
__asm_dcache_all:
|
||||
mov x1, x0
|
||||
dsb sy
|
||||
mrs x10, clidr_el1 /* read clidr_el1 */
|
||||
lsr x11, x10, #24
|
||||
and x11, x11, #0x7 /* x11 <- loc */
|
||||
cbz x11, finished /* if loc is 0, exit */
|
||||
mov x15, lr
|
||||
mov x0, #0 /* start flush at cache level 0 */
|
||||
/* x0 <- cache level */
|
||||
/* x10 <- clidr_el1 */
|
||||
/* x11 <- loc */
|
||||
/* x15 <- return address */
|
||||
|
||||
loop_level:
|
||||
lsl x12, x0, #1
|
||||
add x12, x12, x0 /* x0 <- tripled cache level */
|
||||
lsr x12, x10, x12
|
||||
and x12, x12, #7 /* x12 <- cache type */
|
||||
cmp x12, #2
|
||||
b.lt skip /* skip if no cache or icache */
|
||||
bl __asm_dcache_level /* x1 = 0 flush, 1 invalidate */
|
||||
skip:
|
||||
add x0, x0, #1 /* increment cache level */
|
||||
cmp x11, x0
|
||||
b.gt loop_level
|
||||
|
||||
mov x0, #0
|
||||
msr csselr_el1, x0 /* restore csselr_el1 */
|
||||
dsb sy
|
||||
isb
|
||||
mov lr, x15
|
||||
|
||||
finished:
|
||||
ret
|
||||
|
||||
.section .text.flush_dcache_all, "ax", %progbits
|
||||
.type flush_dcache_all, %function
|
||||
.global flush_dcache_all
|
||||
flush_dcache_all:
|
||||
mov x0, #0
|
||||
b __asm_dcache_all
|
||||
|
||||
.section .text.invalidate_dcache_all, "ax", %progbits
|
||||
.type invalidate_dcache_all, %function
|
||||
.global invalidate_dcache_all
|
||||
invalidate_dcache_all:
|
||||
mov x0, #1
|
||||
b __asm_dcache_all
|
||||
|
||||
/*
|
||||
* void __asm_flush_dcache_range(start, end) (renamed -> flush_dcache_range)
|
||||
*
|
||||
* clean & invalidate data cache in the range
|
||||
*
|
||||
* x0: start address
|
||||
* x1: end address
|
||||
*/
|
||||
.section .text.flush_dcache_range, "ax", %progbits
|
||||
.type flush_dcache_range, %function
|
||||
.global flush_dcache_range
|
||||
flush_dcache_range:
|
||||
mrs x3, ctr_el0
|
||||
lsr x3, x3, #16
|
||||
and x3, x3, #0xf
|
||||
mov x2, #4
|
||||
lsl x2, x2, x3 /* cache line size */
|
||||
|
||||
/* x2 <- minimal cache line size in cache system */
|
||||
sub x3, x2, #1
|
||||
bic x0, x0, x3
|
||||
1: dc civac, x0 /* clean & invalidate data or unified cache */
|
||||
add x0, x0, x2
|
||||
cmp x0, x1
|
||||
b.lo 1b
|
||||
dsb sy
|
||||
ret
|
||||
|
||||
/*
|
||||
* void __asm_invalidate_dcache_range(start, end) (-> invalidate_dcache_range)
|
||||
*
|
||||
* invalidate data cache in the range
|
||||
*
|
||||
* x0: start address
|
||||
* x1: end address
|
||||
*/
|
||||
.section .text.invalidate_dcache_range, "ax", %progbits
|
||||
.type invalidate_dcache_range, %function
|
||||
.global invalidate_dcache_range
|
||||
invalidate_dcache_range:
|
||||
mrs x3, ctr_el0
|
||||
ubfm x3, x3, #16, #19
|
||||
mov x2, #4
|
||||
lsl x2, x2, x3 /* cache line size */
|
||||
|
||||
/* x2 <- minimal cache line size in cache system */
|
||||
sub x3, x2, #1
|
||||
bic x0, x0, x3
|
||||
1: dc ivac, x0 /* invalidate data or unified cache */
|
||||
add x0, x0, x2
|
||||
cmp x0, x1
|
||||
b.lo 1b
|
||||
dsb sy
|
||||
ret
|
||||
|
||||
/*
|
||||
* void __asm_invalidate_icache_all(void) (-> invalidate_icache_inner_shareable)
|
||||
*
|
||||
* invalidate all icache entries.
|
||||
*/
|
||||
.section .text.invalidate_icache_inner_shareable, "ax", %progbits
|
||||
.type invalidate_icache_inner_shareable, %function
|
||||
.global invalidate_icache_inner_shareable
|
||||
invalidate_icache_inner_shareable:
|
||||
dsb ish
|
||||
isb
|
||||
ic ialluis
|
||||
dsb ish
|
||||
isb
|
||||
ret
|
||||
89
exosphere/src/coldboot_init.c
Normal file
89
exosphere/src/coldboot_init.c
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "utils.h"
|
||||
#include "mmu.h"
|
||||
#include "memory_map.h"
|
||||
|
||||
/*
|
||||
extern void (*__preinit_array_start[])(void);
|
||||
extern void (*__preinit_array_end[])(void);
|
||||
extern void (*__init_array_start[])(void);
|
||||
extern void (*__init_array_end[])(void);
|
||||
extern void _init(void);
|
||||
|
||||
extern uint8_t __warmboot_crt0_start__[], __warmboot_crt0_end__[], __warmboot_crt0_lma__[];
|
||||
extern uint8_t __main_start__[], __main_end__[], __main_lma__[];
|
||||
extern uint8_t __pk2ldr_start__[], __pk2ldr_end__[], __pk2ldr_lma__[];
|
||||
extern uint8_t __vectors_start__[], __vectors_end__[], __vectors_lma__[];*/
|
||||
extern void flush_dcache_all_tzram_pa(void);
|
||||
extern void invalidate_icache_all_tzram_pa(void);
|
||||
|
||||
uintptr_t get_coldboot_crt0_stack_address(void);
|
||||
|
||||
static void configure_ttbls(void) {
|
||||
uintptr_t *mmu_l1_tbl = (uintptr_t *)(tzram_get_segment_pa(TZRAM_SEGEMENT_ID_SECMON_EVT) + 0x800 - 64);
|
||||
uintptr_t *mmu_l2_tbl = (uintptr_t *)tzram_get_segment_pa(TZRAM_SEGMENT_ID_L2_TRANSLATION_TABLE);
|
||||
uintptr_t *mmu_l3_tbl = (uintptr_t *)tzram_get_segment_pa(TZRAM_SEGMENT_ID_L3_TRANSLATION_TABLE);
|
||||
|
||||
mmu_init_table(mmu_l1_tbl, 64); /* 33-bit address space */
|
||||
mmu_init_table(mmu_l2_tbl, 4096);
|
||||
/*
|
||||
Nintendo uses the same L3 table for everything, but they make sure
|
||||
nothing clashes.
|
||||
*/
|
||||
mmu_init_table(mmu_l3_tbl, 4096);
|
||||
|
||||
mmu_map_table(1, mmu_l1_tbl, 0x40000000, mmu_l2_tbl, 0);
|
||||
mmu_map_table(1, mmu_l1_tbl, 0x1C0000000, mmu_l2_tbl, 0);
|
||||
|
||||
mmu_map_table(2, mmu_l2_tbl, 0x40000000, mmu_l3_tbl, 0);
|
||||
mmu_map_table(2, mmu_l2_tbl, 0x7C000000, mmu_l3_tbl, 0);
|
||||
mmu_map_table(2, mmu_l2_tbl, 0x1F0000000ull, mmu_l3_tbl, 0);
|
||||
|
||||
identity_map_all_mappings(mmu_l1_tbl, mmu_l3_tbl);
|
||||
mmio_map_all_devices(mmu_l3_tbl);
|
||||
lp0_map_all_plaintext_ram_segments(mmu_l3_tbl);
|
||||
lp0_map_all_ciphertext_ram_segments(mmu_l3_tbl);
|
||||
tzram_map_all_segments(mmu_l3_tbl);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
static void copy_lma_to_vma(unsigned int segment_id, void *lma, size_t size, bool vma_is_pa) {
|
||||
uintptr_t vma = vma_is_pa ? tzram_get_segment_pa(segment_id) : tzram_get_segment_address(segment_id);
|
||||
uintptr_t vma_offset = (uintptr_t)lma & 0xFFF;
|
||||
uint64_t *p_vma = (uint64_t *)vma;
|
||||
uint64_t *p_lma = (uint64_t *)lma;
|
||||
for (size_t i = 0; i < size / 8; i++) {
|
||||
p_vma[vma_offset / 8 + i] = p_lma[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void __libc_init_array(void) {
|
||||
for (size_t i = 0; i < __preinit_array_end - __preinit_array_start; i++)
|
||||
__preinit_array_start[i]();
|
||||
_init(); /* FIXME: do we have this gcc-provided symbol if we build with -nostartfiles? */
|
||||
for (size_t i = 0; i < __init_array_end - __init_array_start; i++)
|
||||
__init_array_start[i]();
|
||||
}
|
||||
#endif
|
||||
|
||||
uintptr_t get_coldboot_crt0_stack_address(void) {
|
||||
return tzram_get_segment_pa(TZRAM_SEGMENT_ID_CORE3_STACK) + 0x800;
|
||||
}
|
||||
|
||||
void coldboot_init(void) {
|
||||
/* TODO: Set NX BOOTLOADER clock time field */
|
||||
/*copy_lma_to_vma(TZRAM_SEGMENT_ID_WARMBOOT_CRT0_AND_MAIN, __warmboot_crt0_lma__, __warmboot_crt0_end__ - __warmboot_crt0_start__, true);*/
|
||||
/* TODO: set some mmio regs, etc. */
|
||||
/* TODO: initialize DMA controllers */
|
||||
configure_ttbls();
|
||||
/*copy_lma_to_vma(TZRAM_SEGMENT_ID_WARMBOOT_CRT0_AND_MAIN, __main_lma__, __main_end__ - __main_start__, false);
|
||||
copy_lma_to_vma(TZRAM_SEGMENT_ID_PK2LDR, __pk2ldr_lma__, __pk2ldr_end__ - __pk2ldr_start__, false);
|
||||
copy_lma_to_vma(TZRAM_SEGEMENT_ID_SECMON_EVT, __vectors_lma__, __vectors_end__ - __vectors_start__, false);*/
|
||||
/* TODO: set the MMU regs & tlbi & enable MMU */
|
||||
|
||||
flush_dcache_all_tzram_pa();
|
||||
invalidate_icache_all_tzram_pa();
|
||||
/* TODO: zero-initialize the cpu context */
|
||||
/* Nintendo clears the (emtpy) pk2ldr's BSS section, but we embed it 0-filled in the binary */
|
||||
/*__libc_init_array(); construct global objects */
|
||||
}
|
||||
39
exosphere/src/coldboot_main.c
Normal file
39
exosphere/src/coldboot_main.c
Normal file
@@ -0,0 +1,39 @@
|
||||
#include <string.h>
|
||||
#include "utils.h"
|
||||
#include "mmu.h"
|
||||
#include "memory_map.h"
|
||||
#include "cache.h"
|
||||
|
||||
/*
|
||||
extern void (*__fini_array_start[])(void);
|
||||
extern void (*__fini_array_end[])(void);
|
||||
extern void _fini(void);*/
|
||||
|
||||
extern uint8_t __pk2ldr_start__[], __pk2ldr_end__[];
|
||||
|
||||
extern void __jump_to_lower_el(uint64_t arg, uintptr_t ep, unsigned int el);
|
||||
|
||||
void coldboot_main(void);
|
||||
|
||||
#if 0
|
||||
/* Needs to be called for EL3->EL3 chainloading (and only in that case). TODO: use it */
|
||||
__attribute__((used)) static void __libc_fini_array(void) {
|
||||
for (size_t i = __fini_array_end - __fini_array_start; i > 0; i--)
|
||||
__fini_array_start[i - 1]();
|
||||
_fini(); /* FIXME: do we have this gcc-provided symbol if we build with -nostartfiles? */
|
||||
}
|
||||
#endif
|
||||
|
||||
void coldboot_main(void) {
|
||||
#if 0
|
||||
uintptr_t *mmu_l3_table = (uintptr_t *)tzram_get_segment_address(TZRAM_SEGMENT_ID_L3_TRANSLATION_TABLE);
|
||||
uintptr_t pk2ldr = tzram_get_segment_address(TZRAM_SEGMENT_ID_PK2LDR);
|
||||
|
||||
/* Clear and unmap pk2ldr (which is reused as exception entry stacks) */
|
||||
memset((void *)pk2ldr, 0, __pk2ldr_end__ - __pk2ldr_start__);
|
||||
mmu_unmap_range(3, mmu_l3_table, pk2ldr, __pk2ldr_end__ - __pk2ldr_start__);
|
||||
tlb_invalidate_all_inner_shareable();
|
||||
#endif
|
||||
|
||||
/* TODO: stuff & jump to lower EL */
|
||||
}
|
||||
90
exosphere/src/configitem.c
Normal file
90
exosphere/src/configitem.c
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bootconfig.h"
|
||||
#include "configitem.h"
|
||||
#include "interrupt.h"
|
||||
#include "package2.h"
|
||||
#include "se.h"
|
||||
#include "fuse.h"
|
||||
#include "utils.h"
|
||||
|
||||
int g_battery_profile = 0;
|
||||
|
||||
uint32_t configitem_set(enum ConfigItem item, uint64_t value) {
|
||||
if (item != CONFIGITEM_BATTERYPROFILE) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
g_battery_profile = ((int)(value != 0)) & 1;
|
||||
return 0; /* FIXME: what should we return there */
|
||||
}
|
||||
|
||||
bool configitem_is_recovery_boot(void) {
|
||||
uint64_t is_recovery_boot;
|
||||
if (configitem_get(CONFIGITEM_ISRECOVERYBOOT, &is_recovery_boot) != 0) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
return is_recovery_boot != 0;
|
||||
}
|
||||
|
||||
bool configitem_is_retail(void) {
|
||||
uint64_t is_retail;
|
||||
if (configitem_get(CONFIGITEM_ISRETAIL, &is_retail) != 0) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
return is_retail != 0;
|
||||
}
|
||||
|
||||
uint32_t configitem_get(enum ConfigItem item, uint64_t *p_outvalue) {
|
||||
uint32_t result = 0;
|
||||
switch (item) {
|
||||
case CONFIGITEM_DISABLEPROGRAMVERIFICATION:
|
||||
*p_outvalue = (int)(bootconfig_disable_program_verification());
|
||||
break;
|
||||
case CONFIGITEM_DRAMID:
|
||||
*p_outvalue = fuse_get_dram_id();
|
||||
break;
|
||||
case CONFIGITEM_SECURITYENGINEIRQ:
|
||||
/* SE is interrupt #0x2C. */
|
||||
*p_outvalue = INTERRUPT_ID_USER_SECURITY_ENGINE;
|
||||
break;
|
||||
case CONFIGITEM_VERSION:
|
||||
/* Always returns maxver - 1 on hardware. */
|
||||
*p_outvalue = PACKAGE2_MAXVER_400_CURRENT - 1;
|
||||
break;
|
||||
case CONFIGITEM_HARDWARETYPE:
|
||||
*p_outvalue = fuse_get_hardware_type();
|
||||
break;
|
||||
case CONFIGITEM_ISRETAIL:
|
||||
*p_outvalue = fuse_get_retail_type();
|
||||
break;
|
||||
case CONFIGITEM_ISRECOVERYBOOT:
|
||||
/* TODO: This requires reading values passed to crt0 via NX_Bootloader. TBD pending crt0 implementation. */
|
||||
*p_outvalue = 0;
|
||||
break;
|
||||
case CONFIGITEM_DEVICEID:
|
||||
*p_outvalue = fuse_get_device_id();
|
||||
break;
|
||||
case CONFIGITEM_BOOTREASON:
|
||||
/* TODO: This requires reading values passed to crt0 via NX_Bootloader. TBD pending crt0 implementation. */
|
||||
break;
|
||||
case CONFIGITEM_MEMORYARRANGE:
|
||||
*p_outvalue = bootconfig_get_memory_arrangement();
|
||||
break;
|
||||
case CONFIGITEM_ISDEBUGMODE:
|
||||
*p_outvalue = (int)(bootconfig_is_debug_mode());
|
||||
break;
|
||||
case CONFIGITEM_KERNELMEMORYCONFIGURATION:
|
||||
*p_outvalue = bootconfig_get_kernel_memory_configuration();
|
||||
break;
|
||||
case CONFIGITEM_BATTERYPROFILE:
|
||||
*p_outvalue = g_battery_profile;
|
||||
break;
|
||||
default:
|
||||
result = 2;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
29
exosphere/src/configitem.h
Normal file
29
exosphere/src/configitem.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef EXOSPHERE_CFG_ITEM_H
|
||||
#define EXOSPHERE_CFG_ITEM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum ConfigItem {
|
||||
CONFIGITEM_DISABLEPROGRAMVERIFICATION = 1,
|
||||
CONFIGITEM_DRAMID = 2,
|
||||
CONFIGITEM_SECURITYENGINEIRQ = 3,
|
||||
CONFIGITEM_VERSION = 4,
|
||||
CONFIGITEM_HARDWARETYPE = 5,
|
||||
CONFIGITEM_ISRETAIL = 6,
|
||||
CONFIGITEM_ISRECOVERYBOOT = 7,
|
||||
CONFIGITEM_DEVICEID = 8,
|
||||
CONFIGITEM_BOOTREASON = 9,
|
||||
CONFIGITEM_MEMORYARRANGE = 10,
|
||||
CONFIGITEM_ISDEBUGMODE = 11,
|
||||
CONFIGITEM_KERNELMEMORYCONFIGURATION = 12,
|
||||
CONFIGITEM_BATTERYPROFILE = 13
|
||||
};
|
||||
|
||||
uint32_t configitem_set(enum ConfigItem item, uint64_t value);
|
||||
uint32_t configitem_get(enum ConfigItem item, uint64_t *p_outvalue);
|
||||
|
||||
bool configitem_is_recovery_boot(void);
|
||||
bool configitem_is_retail(void);
|
||||
|
||||
#endif
|
||||
70
exosphere/src/cpu_context.c
Normal file
70
exosphere/src/cpu_context.c
Normal file
@@ -0,0 +1,70 @@
|
||||
#include <stdint.h>
|
||||
#include "cpu_context.h"
|
||||
#include "pmc.h"
|
||||
#include "timers.h"
|
||||
#include "utils.h"
|
||||
|
||||
saved_cpu_context_t g_cpu_contexts[NUM_CPU_CORES] = {0};
|
||||
|
||||
void set_core_entrypoint_and_context_id(uint32_t core, uint64_t entrypoint_addr, uint64_t context_id) {
|
||||
g_cpu_contexts[core].ELR_EL3 = entrypoint_addr;
|
||||
g_cpu_contexts[core].context_id = context_id;
|
||||
}
|
||||
|
||||
uint32_t cpu_on(uint32_t core, uint64_t entrypoint_addr, uint64_t context_id) {
|
||||
/* Is core valid? */
|
||||
if (core >= NUM_CPU_CORES) {
|
||||
return 0xFFFFFFFE;
|
||||
}
|
||||
|
||||
/* Is core already on? */
|
||||
if (g_cpu_contexts[core].is_active) {
|
||||
return 0xFFFFFFFC;
|
||||
}
|
||||
|
||||
set_core_entrypoint_and_context_id(core, entrypoint_addr, context_id);
|
||||
|
||||
const uint32_t status_masks[NUM_CPU_CORES] = {0x4000, 0x200, 0x400, 0x800};
|
||||
const uint32_t toggle_vals[NUM_CPU_CORES] = {0xE, 0x9, 0xA, 0xB};
|
||||
|
||||
/* Check if we're already in the correct state. */
|
||||
if ((APBDEV_PMC_PWRGATE_STATUS_0 & status_masks[core]) != status_masks[core]) {
|
||||
uint32_t counter = 5001;
|
||||
|
||||
/* Poll the start bit until 0 */
|
||||
while (APBDEV_PMC_PWRGATE_TOGGLE_0 & 0x100) {
|
||||
wait(1);
|
||||
counter--;
|
||||
if (counter < 1) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Program PWRGATE_TOGGLE with the START bit set to 1, selecting CE[N] */
|
||||
APBDEV_PMC_PWRGATE_TOGGLE_0 = toggle_vals[core] | 0x100;
|
||||
|
||||
/* Poll until we're in the correct state. */
|
||||
counter = 5001;
|
||||
while (counter > 0) {
|
||||
if ((APBDEV_PMC_PWRGATE_STATUS_0 & status_masks[core]) == status_masks[core]) {
|
||||
break;
|
||||
}
|
||||
wait(1);
|
||||
counter--;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t cpu_off(void) {
|
||||
return 0;
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
uint32_t cpu_suspend(uint64_t power_state, uint64_t entrypoint_addr, uint64_t context_id) {
|
||||
(void)power_state;
|
||||
(void)entrypoint_addr;
|
||||
(void)context_id;
|
||||
return 0;
|
||||
/* TODO */
|
||||
}
|
||||
54
exosphere/src/cpu_context.h
Normal file
54
exosphere/src/cpu_context.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef EXOSPHERE_CPU_CTX_H
|
||||
#define EXOSPHERE_CPU_CTX_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Exosphere CPU Management functionality. */
|
||||
|
||||
typedef struct {
|
||||
uint64_t context_id;
|
||||
uint64_t ELR_EL3;
|
||||
int is_active;
|
||||
int is_saved;
|
||||
uint32_t OSDTRRX_EL1;
|
||||
uint32_t OSDTRTX_EL1;
|
||||
uint32_t MDSCR_EL1;
|
||||
uint32_t OSECCR_EL1;
|
||||
uint32_t MDCCINT_EL1;
|
||||
uint32_t DBGCLAIMCLR_EL1;
|
||||
uint32_t DBGVCR32_EL2;
|
||||
uint32_t SDER32_EL3;
|
||||
uint32_t MDCR_EL2;
|
||||
uint32_t MDCR_EL3;
|
||||
uint64_t DBGBVR0_EL1;
|
||||
uint64_t DBGBCR0_EL1;
|
||||
uint64_t DBGBVR1_EL1;
|
||||
uint64_t DBGBCR1_EL1;
|
||||
uint64_t DBGBVR2_EL1;
|
||||
uint64_t DBGBCR2_EL1;
|
||||
uint64_t DBGBVR3_EL1;
|
||||
uint64_t DBGBCR3_EL1;
|
||||
uint64_t DBGBVR4_EL1;
|
||||
uint64_t DBGBCR4_EL1;
|
||||
uint64_t DBGBVR5_EL1;
|
||||
uint64_t DBGBCR5_EL1;
|
||||
uint64_t DBGWVR0_EL1;
|
||||
uint64_t DBGWCR0_EL1;
|
||||
uint64_t DBGWVR1_EL1;
|
||||
uint64_t DBGWCR1_EL1;
|
||||
uint64_t DBGWVR2_EL1;
|
||||
uint64_t DBGWCR2_EL1;
|
||||
uint64_t DBGWVR3_EL1;
|
||||
uint64_t DBGWCR3_EL1;
|
||||
} saved_cpu_context_t;
|
||||
|
||||
#define NUM_CPU_CORES 4
|
||||
|
||||
void set_core_entrypoint_and_context_id(uint32_t core, uint64_t entrypoint_addr, uint64_t context_id);
|
||||
|
||||
uint32_t cpu_on(uint32_t core, uint64_t entrypoint_addr, uint64_t context_id);
|
||||
uint32_t cpu_off(void); /* TODO */
|
||||
uint32_t cpu_suspend(uint64_t power_state, uint64_t entrypoint_addr, uint64_t context_id); /* TODO */
|
||||
|
||||
|
||||
#endif
|
||||
253
exosphere/src/exceptions.s
Normal file
253
exosphere/src/exceptions.s
Normal file
@@ -0,0 +1,253 @@
|
||||
/* Some macros taken from https://github.com/ARM-software/arm-trusted-firmware/blob/master/include/common/aarch64/asm_macros.S */
|
||||
/*
|
||||
* Copyright (c) 2013-2017, ARM Limited and Contributors. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Declare the exception vector table, enforcing it is aligned on a
|
||||
* 2KB boundary, as required by the ARMv8 architecture.
|
||||
* Use zero bytes as the fill value to be stored in the padding bytes
|
||||
* so that it inserts illegal AArch64 instructions. This increases
|
||||
* security, robustness and potentially facilitates debugging.
|
||||
*/
|
||||
.macro vector_base label, section_name=.vectors
|
||||
.section \section_name, "ax"
|
||||
.align 11, 0
|
||||
\label:
|
||||
.endm
|
||||
|
||||
/*
|
||||
* Create an entry in the exception vector table, enforcing it is
|
||||
* aligned on a 128-byte boundary, as required by the ARMv8 architecture.
|
||||
* Use zero bytes as the fill value to be stored in the padding bytes
|
||||
* so that it inserts illegal AArch64 instructions. This increases
|
||||
* security, robustness and potentially facilitates debugging.
|
||||
*/
|
||||
.macro vector_entry label, section_name=.vectors
|
||||
.cfi_sections .debug_frame
|
||||
.section \section_name, "ax"
|
||||
.align 7, 0
|
||||
.type \label, %function
|
||||
.func \label
|
||||
.cfi_startproc
|
||||
\label:
|
||||
.endm
|
||||
|
||||
/*
|
||||
* This macro verifies that the given vector doesnt exceed the
|
||||
* architectural limit of 32 instructions. This is meant to be placed
|
||||
* immediately after the last instruction in the vector. It takes the
|
||||
* vector entry as the parameter
|
||||
*/
|
||||
.macro check_vector_size since
|
||||
.endfunc
|
||||
.cfi_endproc
|
||||
.if (. - \since) > (32 * 4)
|
||||
.error "Vector exceeds 32 instructions"
|
||||
.endif
|
||||
.endm
|
||||
|
||||
/* Actual Vectors for Exosphere. */
|
||||
.global exosphere_vectors
|
||||
vector_base exosphere_vectors
|
||||
|
||||
/* Current EL, SP0 */
|
||||
.global unknown_exception
|
||||
unknown_exception:
|
||||
vector_entry synch_sp0
|
||||
/* Panic with color FF7700, code 10. */
|
||||
mov x0, #0x10
|
||||
movk x0, #0x07F0,lsl#16
|
||||
b panic
|
||||
check_vector_size synch_sp0
|
||||
|
||||
vector_entry irq_sp0
|
||||
b unknown_exception
|
||||
check_vector_size irq_sp0
|
||||
|
||||
vector_entry fiq_sp0
|
||||
b unknown_exception
|
||||
check_vector_size fiq_sp0
|
||||
|
||||
vector_entry serror_sp0
|
||||
b unknown_exception
|
||||
check_vector_size serror_sp0
|
||||
|
||||
/* Current EL, SPx */
|
||||
vector_entry synch_spx
|
||||
b unknown_exception
|
||||
check_vector_size synch_spx
|
||||
|
||||
vector_entry irq_spx
|
||||
b unknown_exception
|
||||
check_vector_size irq_spx
|
||||
|
||||
vector_entry fiq_spx
|
||||
b unknown_exception
|
||||
check_vector_size fiq_spx
|
||||
|
||||
vector_entry serror_spx
|
||||
b unknown_exception
|
||||
check_vector_size serror_spx
|
||||
|
||||
/* Lower EL, A64 */
|
||||
vector_entry synch_a64
|
||||
stp x29, x30, [sp, #-0x10]!
|
||||
/* Verify SMC. */
|
||||
mrs x30, esr_el3
|
||||
lsr w29, w30, #0x1A
|
||||
cmp w29, #0x17
|
||||
ldp x29, x30, [sp],#0x10
|
||||
b.ne unknown_exception
|
||||
/* Call appropriate handler. */
|
||||
stp x29, x30, [sp, #-0x10]!
|
||||
mrs x29, mpidr_el1
|
||||
and x29, x29, #0x3
|
||||
cmp x29, #0x3
|
||||
b.ne handle_core012_smc_exception
|
||||
bl handle_core3_smc_exception
|
||||
ldp x29, x30, [sp],#0x10
|
||||
eret
|
||||
check_vector_size synch_a64
|
||||
|
||||
vector_entry irq_a64
|
||||
b unknown_exception
|
||||
check_vector_size irq_a64
|
||||
|
||||
vector_entry fiq_a64
|
||||
stp x29, x30, [sp, #-0x10]!
|
||||
mrs x29, mpidr_el1
|
||||
and x29, x29, #0x3
|
||||
cmp x29, #0x3
|
||||
b.ne unknown_exception
|
||||
stp x28, x29, [sp, #-0x10]!
|
||||
stp x26, x27, [sp, #-0x10]!
|
||||
bl handle_fiq_exception
|
||||
ldp x26, x27, [sp],#0x10
|
||||
ldp x28, x29, [sp],#0x10
|
||||
ldp x29, x30, [sp],#0x10
|
||||
eret
|
||||
check_vector_size fiq_a64
|
||||
|
||||
vector_entry serror_a64
|
||||
b unknown_exception
|
||||
.endfunc
|
||||
.cfi_endproc
|
||||
/* To save space, insert in an unused vector segment. */
|
||||
.global handle_core012_smc_exception
|
||||
.type handle_core012_smc_exception, %function
|
||||
handle_core012_smc_exception:
|
||||
stp x6, x7, [sp, #-0x10]!
|
||||
stp x4, x5, [sp, #-0x10]!
|
||||
stp x2, x3, [sp, #-0x10]!
|
||||
stp x0, x1, [sp, #-0x10]!
|
||||
bl set_priv_smc_in_progress
|
||||
bl get_smc_core012_stack_address
|
||||
mov x29, x0
|
||||
ldp x0, x1, [sp],#0x10
|
||||
ldp x2, x3, [sp],#0x10
|
||||
ldp x4, x5, [sp],#0x10
|
||||
ldp x6, x7, [sp],#0x10
|
||||
mov x30, sp
|
||||
mov sp, x29
|
||||
stp x29, x30, [sp, #-0x10]!
|
||||
bl handle_core3_smc_exception
|
||||
ldp x29, x30, [sp],#0x10
|
||||
mov sp, x30
|
||||
stp x6, x7, [sp, #-0x10]!
|
||||
stp x4, x5, [sp, #-0x10]!
|
||||
stp x2, x3, [sp, #-0x10]!
|
||||
stp x0, x1, [sp, #-0x10]!
|
||||
bl clear_priv_smc_in_progress
|
||||
ldp x0, x1, [sp],#0x10
|
||||
ldp x2, x3, [sp],#0x10
|
||||
ldp x4, x5, [sp],#0x10
|
||||
ldp x6, x7, [sp],#0x10
|
||||
ldp x29, x30, [sp],#0x10
|
||||
eret
|
||||
|
||||
/* Lower EL, A32 */
|
||||
vector_entry synch_a32
|
||||
b unknown_exception
|
||||
check_vector_size synch_a32
|
||||
|
||||
vector_entry irq_a32
|
||||
b unknown_exception
|
||||
check_vector_size irq_a32
|
||||
|
||||
vector_entry fiq_a32
|
||||
b fiq_a64
|
||||
.endfunc
|
||||
.cfi_endproc
|
||||
/* To save space, insert in an unused vector segment. */
|
||||
.global handle_fiq_exception
|
||||
.type handle_fiq_exception, %function
|
||||
handle_fiq_exception:
|
||||
stp x29, x30, [sp, #-0x10]!
|
||||
stp x24, x25, [sp, #-0x10]!
|
||||
stp x22, x23, [sp, #-0x10]!
|
||||
stp x20, x21, [sp, #-0x10]!
|
||||
stp x18, x19, [sp, #-0x10]!
|
||||
stp x16, x17, [sp, #-0x10]!
|
||||
stp x14, x15, [sp, #-0x10]!
|
||||
stp x12, x13, [sp, #-0x10]!
|
||||
stp x10, x11, [sp, #-0x10]!
|
||||
stp x8, x9, [sp, #-0x10]!
|
||||
stp x6, x7, [sp, #-0x10]!
|
||||
stp x4, x5, [sp, #-0x10]!
|
||||
stp x2, x3, [sp, #-0x10]!
|
||||
stp x0, x1, [sp, #-0x10]!
|
||||
bl handle_registered_interrupt
|
||||
ldp x0, x1, [sp],#0x10
|
||||
ldp x2, x3, [sp],#0x10
|
||||
ldp x4, x5, [sp],#0x10
|
||||
ldp x6, x7, [sp],#0x10
|
||||
ldp x8, x9, [sp],#0x10
|
||||
ldp x10, x11, [sp],#0x10
|
||||
ldp x12, x13, [sp],#0x10
|
||||
ldp x14, x15, [sp],#0x10
|
||||
ldp x16, x17, [sp],#0x10
|
||||
ldp x18, x19, [sp],#0x10
|
||||
ldp x20, x21, [sp],#0x10
|
||||
ldp x22, x23, [sp],#0x10
|
||||
ldp x24, x25, [sp],#0x10
|
||||
ldp x29, x30, [sp],#0x10
|
||||
ret
|
||||
|
||||
vector_entry serror_a32
|
||||
b unknown_exception
|
||||
.endfunc
|
||||
.cfi_endproc
|
||||
/* To save space, insert in an unused vector segment. */
|
||||
.global handle_core3_smc_exception
|
||||
.type handle_core3_smc_exception, %function
|
||||
handle_core3_smc_exception:
|
||||
stp x29, x30, [sp, #-0x10]!
|
||||
stp x18, x19, [sp, #-0x10]!
|
||||
stp x16, x17, [sp, #-0x10]!
|
||||
stp x14, x15, [sp, #-0x10]!
|
||||
stp x12, x13, [sp, #-0x10]!
|
||||
stp x10, x11, [sp, #-0x10]!
|
||||
stp x8, x9, [sp, #-0x10]!
|
||||
stp x6, x7, [sp, #-0x10]!
|
||||
stp x4, x5, [sp, #-0x10]!
|
||||
stp x2, x3, [sp, #-0x10]!
|
||||
stp x0, x1, [sp, #-0x10]!
|
||||
mrs x0, esr_el3
|
||||
and x0, x0, #0xFFFF
|
||||
mov x1, sp
|
||||
bl call_smc_handler
|
||||
ldp x0, x1, [sp],#0x10
|
||||
ldp x2, x3, [sp],#0x10
|
||||
ldp x4, x5, [sp],#0x10
|
||||
ldp x6, x7, [sp],#0x10
|
||||
ldp x8, x9, [sp],#0x10
|
||||
ldp x10, x11, [sp],#0x10
|
||||
ldp x12, x13, [sp],#0x10
|
||||
ldp x14, x15, [sp],#0x10
|
||||
ldp x16, x17, [sp],#0x10
|
||||
ldp x18, x19, [sp],#0x10
|
||||
ret
|
||||
229
exosphere/src/fuse.c
Normal file
229
exosphere/src/fuse.c
Normal file
@@ -0,0 +1,229 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "fuse.h"
|
||||
#include "utils.h"
|
||||
#include "timers.h"
|
||||
|
||||
/* Prototypes for internal commands. */
|
||||
void fuse_make_regs_visible(void);
|
||||
|
||||
void fuse_enable_power(void);
|
||||
void fuse_disable_power(void);
|
||||
void fuse_wait_idle(void);
|
||||
|
||||
/* Initialize the FUSE driver */
|
||||
void fuse_init(void)
|
||||
{
|
||||
fuse_make_regs_visible();
|
||||
|
||||
/* TODO: Overrides (iROM patches) and various reads happen here */
|
||||
}
|
||||
|
||||
/* Make all fuse registers visible */
|
||||
void fuse_make_regs_visible(void)
|
||||
{
|
||||
/* TODO: Replace this with a proper CLKRST driver */
|
||||
volatile uint32_t* misc_clk_reg = (volatile uint32_t *)mmio_get_device_address(MMIO_DEVID_CLKRST) + 0x48;
|
||||
uint32_t misc_clk_val = *misc_clk_reg;
|
||||
*misc_clk_reg = (misc_clk_val | (1 << 28));
|
||||
}
|
||||
|
||||
/* Enable power to the fuse hardware array */
|
||||
void fuse_enable_power(void)
|
||||
{
|
||||
FUSE_REGS->FUSE_PWR_GOOD_SW = 1;
|
||||
wait(1);
|
||||
}
|
||||
|
||||
/* Disable power to the fuse hardware array */
|
||||
void fuse_disable_power(void)
|
||||
{
|
||||
FUSE_REGS->FUSE_PWR_GOOD_SW = 0;
|
||||
wait(1);
|
||||
}
|
||||
|
||||
/* Wait for the fuse driver to go idle */
|
||||
void fuse_wait_idle(void)
|
||||
{
|
||||
uint32_t ctrl_val = 0;
|
||||
|
||||
/* Wait for STATE_IDLE */
|
||||
while ((ctrl_val & (0xF0000)) != 0x40000)
|
||||
{
|
||||
wait(1);
|
||||
ctrl_val = FUSE_REGS->FUSE_CTRL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read a fuse from the hardware array */
|
||||
uint32_t fuse_hw_read(uint32_t addr)
|
||||
{
|
||||
fuse_wait_idle();
|
||||
|
||||
/* Program the target address */
|
||||
FUSE_REGS->FUSE_REG_ADDR = addr;
|
||||
|
||||
/* Enable read operation in control register */
|
||||
uint32_t ctrl_val = FUSE_REGS->FUSE_CTRL;
|
||||
ctrl_val &= ~0x3;
|
||||
ctrl_val |= 0x1; /* Set FUSE_READ command */
|
||||
FUSE_REGS->FUSE_CTRL = ctrl_val;
|
||||
|
||||
fuse_wait_idle();
|
||||
|
||||
return FUSE_REGS->FUSE_REG_READ;
|
||||
}
|
||||
|
||||
/* Write a fuse in the hardware array */
|
||||
void fuse_hw_write(uint32_t value, uint32_t addr)
|
||||
{
|
||||
fuse_wait_idle();
|
||||
|
||||
/* Program the target address and value */
|
||||
FUSE_REGS->FUSE_REG_ADDR = addr;
|
||||
FUSE_REGS->FUSE_REG_WRITE = value;
|
||||
|
||||
/* Enable write operation in control register */
|
||||
uint32_t ctrl_val = FUSE_REGS->FUSE_CTRL;
|
||||
ctrl_val &= ~0x3;
|
||||
ctrl_val |= 0x2; /* Set FUSE_WRITE command */
|
||||
FUSE_REGS->FUSE_CTRL = ctrl_val;
|
||||
|
||||
fuse_wait_idle();
|
||||
}
|
||||
|
||||
/* Sense the fuse hardware array into the shadow cache */
|
||||
void fuse_hw_sense(void)
|
||||
{
|
||||
fuse_wait_idle();
|
||||
|
||||
/* Enable sense operation in control register */
|
||||
uint32_t ctrl_val = FUSE_REGS->FUSE_CTRL;
|
||||
ctrl_val &= ~0x3;
|
||||
ctrl_val |= 0x3; /* Set FUSE_SENSE command */
|
||||
FUSE_REGS->FUSE_CTRL = ctrl_val;
|
||||
|
||||
fuse_wait_idle();
|
||||
}
|
||||
|
||||
/* Disables all fuse programming. */
|
||||
void fuse_disable_programming(void) {
|
||||
FUSE_REGS->FUSE_DIS_PGM = 1;
|
||||
}
|
||||
|
||||
/* Unknown exactly what this does, but it alters the contents read from the fuse cache. */
|
||||
void fuse_secondary_private_key_disable(void) {
|
||||
FUSE_REGS->FUSE_PRIVATEKEYDISABLE = 0x10;
|
||||
}
|
||||
|
||||
|
||||
/* Read the SKU info register from the shadow cache */
|
||||
uint32_t fuse_get_sku_info(void)
|
||||
{
|
||||
return FUSE_CHIP_REGS->FUSE_SKU_INFO;
|
||||
}
|
||||
|
||||
/* Read the bootrom patch version from a register in the shadow cache */
|
||||
uint32_t fuse_get_bootrom_patch_version(void)
|
||||
{
|
||||
return FUSE_CHIP_REGS->FUSE_SOC_SPEEDO_1;
|
||||
}
|
||||
|
||||
/* Read a spare bit register from the shadow cache */
|
||||
uint32_t fuse_get_spare_bit(uint32_t idx)
|
||||
{
|
||||
uint32_t spare_bit_val = 0;
|
||||
|
||||
if ((idx >= 0) && (idx < 32))
|
||||
spare_bit_val = FUSE_CHIP_REGS->FUSE_SPARE_BIT[idx];
|
||||
|
||||
return spare_bit_val;
|
||||
}
|
||||
|
||||
/* Read a reserved ODM register from the shadow cache */
|
||||
uint32_t fuse_get_reserved_odm(uint32_t idx)
|
||||
{
|
||||
uint32_t reserved_odm_val = 0;
|
||||
|
||||
if ((idx >= 0) && (idx < 8))
|
||||
reserved_odm_val = FUSE_CHIP_REGS->FUSE_RESERVED_ODM[idx];
|
||||
|
||||
return reserved_odm_val;
|
||||
}
|
||||
|
||||
/* Derive the Device ID using values in the shadow cache */
|
||||
uint64_t fuse_get_device_id(void) {
|
||||
uint64_t device_id = 0;
|
||||
uint64_t y_coord = FUSE_CHIP_REGS->FUSE_Y_COORDINATE & 0x1FF;
|
||||
uint64_t x_coord = FUSE_CHIP_REGS->FUSE_X_COORDINATE & 0x1FF;
|
||||
uint64_t wafer_id = FUSE_CHIP_REGS->FUSE_WAFER_ID & 0x3F;
|
||||
uint32_t lot_code = FUSE_CHIP_REGS->FUSE_LOT_CODE_0;
|
||||
uint64_t fab_code = FUSE_CHIP_REGS->FUSE_FAB_CODE & 0x3F;
|
||||
uint64_t derived_lot_code = 0;
|
||||
for (unsigned int i = 0; i < 5; i++) {
|
||||
derived_lot_code = (derived_lot_code * 0x24) + ((lot_code >> (24 - 6*i)) & 0x3F);
|
||||
}
|
||||
derived_lot_code &= 0x03FFFFFF;
|
||||
|
||||
device_id |= y_coord << 0;
|
||||
device_id |= x_coord << 9;
|
||||
device_id |= wafer_id << 18;
|
||||
device_id |= derived_lot_code << 24;
|
||||
device_id |= fab_code << 50;
|
||||
return device_id;
|
||||
}
|
||||
|
||||
/* Get the DRAM ID using values in the shadow cache */
|
||||
uint32_t fuse_get_dram_id(void) {
|
||||
return (FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] >> 3) & 0x7;
|
||||
}
|
||||
|
||||
/* Derive the Hardware Type using values in the shadow cache */
|
||||
uint32_t fuse_get_hardware_type(void) {
|
||||
uint32_t hardware_type = ((FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] >> 7) & 2) | ((FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] >> 2) & 1);
|
||||
if (hardware_type) {
|
||||
if (hardware_type == 1) {
|
||||
return 0;
|
||||
}
|
||||
if (hardware_type == 2) {
|
||||
return 1;
|
||||
}
|
||||
} else if ((FUSE_CHIP_REGS->FUSE_SPARE_BIT[9] & 1) == 0) {
|
||||
return 0;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* Derive the Retail Type using values in the shadow cache */
|
||||
uint32_t fuse_get_retail_type(void) {
|
||||
/* Retail type = IS_RETAIL | UNIT_TYPE */
|
||||
uint32_t retail_type = ((FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] >> 7) & 4) | (FUSE_CHIP_REGS->FUSE_RESERVED_ODM[4] & 3);
|
||||
if (retail_type == 4) { /* Standard retail unit, IS_RETAIL | 0. */
|
||||
return 1;
|
||||
} else if (retail_type == 3) { /* Standard dev unit, 0 | DEV_UNIT. */
|
||||
return 0;
|
||||
}
|
||||
return 2; /* IS_RETAIL | DEV_UNIT */
|
||||
}
|
||||
|
||||
/* Derive the 16-byte Hardware Info using values in the shadow cache, and copy to output buffer. */
|
||||
void fuse_get_hardware_info(void *dst) {
|
||||
uint32_t hw_info[0x4];
|
||||
|
||||
uint32_t unk_hw_fuse = FUSE_CHIP_REGS->_0x120 & 0x3F;
|
||||
uint32_t y_coord = FUSE_CHIP_REGS->FUSE_Y_COORDINATE & 0x1FF;
|
||||
uint32_t x_coord = FUSE_CHIP_REGS->FUSE_X_COORDINATE & 0x1FF;
|
||||
uint32_t wafer_id = FUSE_CHIP_REGS->FUSE_WAFER_ID & 0x3F;
|
||||
uint32_t lot_code_0 = FUSE_CHIP_REGS->FUSE_LOT_CODE_0;
|
||||
uint32_t lot_code_1 = FUSE_CHIP_REGS->FUSE_LOT_CODE_1 & 0x0FFFFFFF;
|
||||
uint32_t fab_code = FUSE_CHIP_REGS->FUSE_FAB_CODE & 0x3F;
|
||||
uint32_t vendor_code = FUSE_CHIP_REGS->FUSE_VENDOR_CODE & 0xF;
|
||||
|
||||
/* Hardware Info = unk_hw_fuse || Y_COORD || X_COORD || WAFER_ID || LOT_CODE || FAB_CODE || VENDOR_ID */
|
||||
hw_info[0] = (uint32_t)((lot_code_1 << 30) | (wafer_id << 24) | (x_coord << 15) | (y_coord << 6) | (unk_hw_fuse));
|
||||
hw_info[1] = (uint32_t)((lot_code_0 << 26) | (lot_code_1 >> 2));
|
||||
hw_info[2] = (uint32_t)((fab_code << 26) | (lot_code_0 >> 6));
|
||||
hw_info[3] = (uint32_t)(vendor_code);
|
||||
|
||||
memcpy(dst, hw_info, 0x10);
|
||||
}
|
||||
193
exosphere/src/fuse.h
Normal file
193
exosphere/src/fuse.h
Normal file
@@ -0,0 +1,193 @@
|
||||
#ifndef EXOSPHERE_FUSE_H
|
||||
#define EXOSPHERE_FUSE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Exosphere driver for the Tegra X1 FUSE registers. */
|
||||
|
||||
typedef struct {
|
||||
uint32_t FUSE_CTRL;
|
||||
uint32_t FUSE_REG_ADDR;
|
||||
uint32_t FUSE_REG_READ;
|
||||
uint32_t FUSE_REG_WRITE;
|
||||
uint32_t FUSE_TIME_RD1;
|
||||
uint32_t FUSE_TIME_RD2;
|
||||
uint32_t FUSE_TIME_PGM1;
|
||||
uint32_t FUSE_TIME_PGM2;
|
||||
uint32_t FUSE_PRIV2INTFC;
|
||||
uint32_t FUSE_FUSEBYPASS;
|
||||
uint32_t FUSE_PRIVATEKEYDISABLE;
|
||||
uint32_t FUSE_DIS_PGM;
|
||||
uint32_t FUSE_WRITE_ACCESS;
|
||||
uint32_t FUSE_PWR_GOOD_SW;
|
||||
uint32_t _0x38[0x32];
|
||||
} fuse_registers_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t FUSE_PRODUCTION_MODE;
|
||||
uint32_t _0x4;
|
||||
uint32_t _0x8;
|
||||
uint32_t _0xC;
|
||||
uint32_t FUSE_SKU_INFO;
|
||||
uint32_t FUSE_CPU_SPEEDO_0;
|
||||
uint32_t FUSE_CPU_IDDQ;
|
||||
uint32_t _0x1C;
|
||||
uint32_t _0x20;
|
||||
uint32_t _0x24;
|
||||
uint32_t FUSE_FT_REV;
|
||||
uint32_t FUSE_CPU_SPEEDO_1;
|
||||
uint32_t FUSE_CPU_SPEEDO_2;
|
||||
uint32_t FUSE_SOC_SPEEDO_0;
|
||||
uint32_t FUSE_SOC_SPEEDO_1;
|
||||
uint32_t FUSE_SOC_SPEEDO_2;
|
||||
uint32_t FUSE_SOC_IDDQ;
|
||||
uint32_t _0x44;
|
||||
uint32_t FUSE_FA;
|
||||
uint32_t _0x4C;
|
||||
uint32_t _0x50;
|
||||
uint32_t _0x54;
|
||||
uint32_t _0x58;
|
||||
uint32_t _0x5C;
|
||||
uint32_t _0x60;
|
||||
uint32_t FUSE_PUBLIC_KEY[0x8];
|
||||
uint32_t FUSE_TSENSOR_1;
|
||||
uint32_t FUSE_TSENSOR_2;
|
||||
uint32_t _0x8C;
|
||||
uint32_t FUSE_CP_REV;
|
||||
uint32_t _0x94;
|
||||
uint32_t FUSE_TSENSOR_0;
|
||||
uint32_t FUSE_FIRST_BOOTROM_PATCH_SIZE_REG;
|
||||
uint32_t FUSE_SECURITY_MODE;
|
||||
uint32_t FUSE_PRIVATE_KEY[0x4];
|
||||
uint32_t FUSE_DEVICE_KEY;
|
||||
uint32_t _0xB8;
|
||||
uint32_t _0xBC;
|
||||
uint32_t FUSE_RESERVED_SW;
|
||||
uint32_t FUSE_VP8_ENABLE;
|
||||
uint32_t FUSE_RESERVED_ODM[0x8];
|
||||
uint32_t _0xE8;
|
||||
uint32_t _0xEC;
|
||||
uint32_t FUSE_SKU_USB_CALIB;
|
||||
uint32_t FUSE_SKU_DIRECT_CONFIG;
|
||||
uint32_t _0xF8;
|
||||
uint32_t _0xFC;
|
||||
uint32_t FUSE_VENDOR_CODE;
|
||||
uint32_t FUSE_FAB_CODE;
|
||||
uint32_t FUSE_LOT_CODE_0;
|
||||
uint32_t FUSE_LOT_CODE_1;
|
||||
uint32_t FUSE_WAFER_ID;
|
||||
uint32_t FUSE_X_COORDINATE;
|
||||
uint32_t FUSE_Y_COORDINATE;
|
||||
uint32_t _0x11C;
|
||||
uint32_t _0x120;
|
||||
uint32_t FUSE_SATA_CALIB;
|
||||
uint32_t FUSE_GPU_IDDQ;
|
||||
uint32_t FUSE_TSENSOR_3;
|
||||
uint32_t _0x130;
|
||||
uint32_t _0x134;
|
||||
uint32_t _0x138;
|
||||
uint32_t _0x13C;
|
||||
uint32_t _0x140;
|
||||
uint32_t _0x144;
|
||||
uint32_t FUSE_OPT_SUBREVISION;
|
||||
uint32_t _0x14C;
|
||||
uint32_t _0x150;
|
||||
uint32_t FUSE_TSENSOR_4;
|
||||
uint32_t FUSE_TSENSOR_5;
|
||||
uint32_t FUSE_TSENSOR_6;
|
||||
uint32_t FUSE_TSENSOR_7;
|
||||
uint32_t FUSE_OPT_PRIV_SEC_DIS;
|
||||
uint32_t FUSE_PKC_DISABLE;
|
||||
uint32_t _0x16C;
|
||||
uint32_t _0x170;
|
||||
uint32_t _0x174;
|
||||
uint32_t _0x178;
|
||||
uint32_t _0x17C;
|
||||
uint32_t FUSE_TSENSOR_COMMON;
|
||||
uint32_t _0x184;
|
||||
uint32_t _0x188;
|
||||
uint32_t _0x18C;
|
||||
uint32_t _0x190;
|
||||
uint32_t _0x194;
|
||||
uint32_t _0x198;
|
||||
uint32_t FUSE_DEBUG_AUTH_OVERRIDE;
|
||||
uint32_t _0x1A0;
|
||||
uint32_t _0x1A4;
|
||||
uint32_t _0x1A8;
|
||||
uint32_t _0x1AC;
|
||||
uint32_t _0x1B0;
|
||||
uint32_t _0x1B4;
|
||||
uint32_t _0x1B8;
|
||||
uint32_t _0x1BC;
|
||||
uint32_t _0x1D0;
|
||||
uint32_t FUSE_TSENSOR_8;
|
||||
uint32_t _0x1D8;
|
||||
uint32_t _0x1DC;
|
||||
uint32_t _0x1E0;
|
||||
uint32_t _0x1E4;
|
||||
uint32_t _0x1E8;
|
||||
uint32_t _0x1EC;
|
||||
uint32_t _0x1F0;
|
||||
uint32_t _0x1F4;
|
||||
uint32_t _0x1F8;
|
||||
uint32_t _0x1FC;
|
||||
uint32_t _0x200;
|
||||
uint32_t FUSE_RESERVED_CALIB;
|
||||
uint32_t _0x208;
|
||||
uint32_t _0x20C;
|
||||
uint32_t _0x210;
|
||||
uint32_t _0x214;
|
||||
uint32_t _0x218;
|
||||
uint32_t FUSE_TSENSOR_9;
|
||||
uint32_t _0x220;
|
||||
uint32_t _0x224;
|
||||
uint32_t _0x228;
|
||||
uint32_t _0x22C;
|
||||
uint32_t _0x230;
|
||||
uint32_t _0x234;
|
||||
uint32_t _0x238;
|
||||
uint32_t _0x23C;
|
||||
uint32_t _0x240;
|
||||
uint32_t _0x244;
|
||||
uint32_t _0x248;
|
||||
uint32_t _0x24C;
|
||||
uint32_t FUSE_USB_CALIB_EXT;
|
||||
uint32_t _0x254;
|
||||
uint32_t _0x258;
|
||||
uint32_t _0x25C;
|
||||
uint32_t _0x260;
|
||||
uint32_t _0x264;
|
||||
uint32_t _0x268;
|
||||
uint32_t _0x26C;
|
||||
uint32_t _0x270;
|
||||
uint32_t _0x274;
|
||||
uint32_t _0x278;
|
||||
uint32_t _0x27C;
|
||||
uint32_t FUSE_SPARE_BIT[0x20];
|
||||
} fuse_chip_registers_t;
|
||||
|
||||
#define FUSE_REGS ((volatile fuse_registers_t *)(mmio_get_device_address(MMIO_DEVID_FUSE) + 0x800))
|
||||
#define FUSE_CHIP_REGS ((volatile fuse_chip_registers_t *)(mmio_get_device_address(MMIO_DEVID_FUSE) + 0x900))
|
||||
|
||||
void fuse_init(void);
|
||||
|
||||
uint32_t fuse_hw_read(uint32_t addr);
|
||||
void fuse_hw_write(uint32_t value, uint32_t addr);
|
||||
void fuse_hw_sense(void);
|
||||
void fuse_disable_programming(void);
|
||||
void fuse_secondary_private_key_disable(void);
|
||||
|
||||
uint32_t fuse_get_sku_info(void);
|
||||
uint32_t fuse_get_spare_bit(uint32_t idx);
|
||||
uint32_t fuse_get_reserved_odm(uint32_t idx);
|
||||
|
||||
uint32_t fuse_get_bootrom_patch_version(void);
|
||||
uint64_t fuse_get_device_id(void);
|
||||
uint32_t fuse_get_dram_id(void);
|
||||
uint32_t fuse_get_hardware_type(void);
|
||||
uint32_t fuse_get_retail_type(void);
|
||||
void fuse_get_hardware_info(void *dst);
|
||||
|
||||
#endif
|
||||
167
exosphere/src/gcm.c
Normal file
167
exosphere/src/gcm.c
Normal file
@@ -0,0 +1,167 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "fuse.h"
|
||||
#include "gcm.h"
|
||||
|
||||
#include "sealedkeys.h"
|
||||
#include "se.h"
|
||||
|
||||
/* Shifts right a little endian 128-bit value. */
|
||||
static void shr_128(uint64_t *val) {
|
||||
val[0] >>= 1;
|
||||
val[0] |= (val[1] & 1) << 63;
|
||||
val[1] >>= 1;
|
||||
}
|
||||
|
||||
/* Shifts left a little endian 128-bit value. */
|
||||
static void shl_128(uint64_t *val) {
|
||||
val[1] <<= 1;
|
||||
val[1] |= (val[0] & (1ULL << 63)) >> 63;
|
||||
val[0] <<= 1;
|
||||
}
|
||||
|
||||
|
||||
/* Multiplies two 128-bit numbers X,Y in the GF(128) Galois Field. */
|
||||
static void gf128_mul(uint8_t *dst, const uint8_t *x, const uint8_t *y) {
|
||||
uint8_t x_work[0x10];
|
||||
uint8_t y_work[0x10];
|
||||
uint8_t dst_work[0x10];
|
||||
|
||||
uint64_t *p_x = (uint64_t *)(&x_work[0]);
|
||||
uint64_t *p_y = (uint64_t *)(&y_work[0]);
|
||||
uint64_t *p_dst = (uint64_t *)(&dst_work[0]);
|
||||
|
||||
/* Initialize buffers. */
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
x_work[i] = x[0xF-i];
|
||||
y_work[i] = y[0xF-i];
|
||||
dst_work[i] = 0;
|
||||
}
|
||||
|
||||
/* Perform operation for each bit in y. */
|
||||
for (unsigned int round = 0; round < 0x80; round++) {
|
||||
p_dst[0] ^= p_x[0] * ((y_work[0xF] & 0x80) >> 7);
|
||||
p_dst[1] ^= p_x[1] * ((y_work[0xF] & 0x80) >> 7);
|
||||
shl_128(p_y);
|
||||
uint8_t xval = 0xE1 * (x_work[0] & 1);
|
||||
shr_128(p_x);
|
||||
x_work[0xF] ^= xval;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
dst[i] = dst_work[0xF-i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Performs an AES-GCM GHASH operation over the data into dst. */
|
||||
static void ghash(void *dst, const void *data, size_t data_size, const void *j_block, bool encrypt) {
|
||||
uint8_t x[0x10];
|
||||
uint8_t h[0x10];
|
||||
|
||||
uint64_t *p_x = (uint64_t *)(&x[0]);
|
||||
uint64_t *p_data = (uint64_t *)data;
|
||||
|
||||
memset(x, 0, 0x10);
|
||||
|
||||
/* H = aes_ecb_encrypt(zeroes) */
|
||||
se_aes_128_ecb_encrypt_block(KEYSLOT_SWITCH_TEMPKEY, h, 0x10, x, 0x10);
|
||||
|
||||
size_t total_size = data_size;
|
||||
|
||||
while (data_size >= 0x10) {
|
||||
/* X = (X ^ current_block) * H */
|
||||
p_x[0] ^= p_data[0];
|
||||
p_x[1] ^= p_data[1];
|
||||
|
||||
gf128_mul(x, x, h);
|
||||
|
||||
/* Increment p_data by 0x10 bytes. */
|
||||
p_data += 2;
|
||||
data_size -= 0x10;
|
||||
}
|
||||
|
||||
/* Nintendo's code *discards all data in the last block* if unaligned. */
|
||||
/* And treats that block as though it were all-zero. */
|
||||
/* This is a bug, they just forget to XOR with the copy of the last block they save. */
|
||||
if (data_size & 0xF) {
|
||||
gf128_mul(x, x, h);
|
||||
}
|
||||
|
||||
/* Due to a Nintendo bug, the wrong QWORD gets XOR'd in the "final output block" case. */
|
||||
if (encrypt) {
|
||||
p_x[1] ^= (uint64_t)(total_size << 3);
|
||||
} else {
|
||||
p_x[0] ^= (uint64_t)(total_size << 3);
|
||||
}
|
||||
|
||||
gf128_mul(x, x, h);
|
||||
|
||||
/* If final output block, XOR with encrypted J block. */
|
||||
if (encrypt) {
|
||||
se_aes_128_ecb_encrypt_block(KEYSLOT_SWITCH_TEMPKEY, h, 0x10, j_block, 0x10);
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
x[i] ^= h[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy output. */
|
||||
memcpy(dst, x, 0x10);
|
||||
}
|
||||
|
||||
|
||||
/* This function is a doozy. It decrypts and validates a (non-standard) AES-GCM wrapped keypair. */
|
||||
size_t gcm_decrypt_key(void *dst, size_t dst_size, const void *src, size_t src_size, const void *sealed_kek, size_t kek_size, const void *wrapped_key, size_t key_size, unsigned int usecase, bool is_personalized) {
|
||||
if (is_personalized == 0) {
|
||||
/* Devkit keys use a different keyformat without a MAC/Device ID. */
|
||||
if (src_size <= 0x10 || src_size - 0x10 > dst_size) {
|
||||
generic_panic();
|
||||
}
|
||||
} else {
|
||||
if (src_size <= 0x30 || src_size - 0x20 > dst_size) {
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
|
||||
/* Unwrap the key */
|
||||
unseal_key(KEYSLOT_SWITCH_TEMPKEY, sealed_kek, kek_size, usecase);
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, KEYSLOT_SWITCH_TEMPKEY, wrapped_key, key_size);
|
||||
|
||||
/* Decrypt the GCM keypair, AES-CTR with CTR = blob[:0x10]. */
|
||||
se_aes_ctr_crypt(KEYSLOT_SWITCH_TEMPKEY, dst, dst_size, src + 0x10, src_size - 0x10, src, 0x10);
|
||||
|
||||
|
||||
if (!is_personalized) {
|
||||
/* Devkit non-personalized keys have no further authentication. */
|
||||
return src_size - 0x10;
|
||||
}
|
||||
|
||||
/* J = GHASH(CTR); */
|
||||
uint8_t j_block[0x10];
|
||||
ghash(j_block, src, 0x10, NULL, false);
|
||||
|
||||
/* MAC = GHASH(PLAINTEXT) ^ ENCRYPT(J) */
|
||||
/* Note: That MAC is calculated over plaintext is non-standard. */
|
||||
/* It is supposed to be over the ciphertext. */
|
||||
uint8_t calc_mac[0x10];
|
||||
ghash(calc_mac, dst, src_size - 0x20, j_block, true);
|
||||
|
||||
/* Const-time memcmp. */
|
||||
const uint8_t *src_bytes = src;
|
||||
int different = 0;
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
different |= src_bytes[src_size - 0x10 + i] ^ calc_mac[i];
|
||||
}
|
||||
if (different) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (read64le(src_bytes, src_size - 0x28) != fuse_get_device_id()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return src_size - 0x30;
|
||||
}
|
||||
13
exosphere/src/gcm.h
Normal file
13
exosphere/src/gcm.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef EXOSPHERE_GCM_H
|
||||
#define EXOSPHERE_GCM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
size_t gcm_decrypt_key(void *dst, size_t dst_size,
|
||||
const void *src, size_t src_size,
|
||||
const void *sealed_kek, size_t kek_size,
|
||||
const void *wrapped_key, size_t key_size,
|
||||
unsigned int usecase, bool is_personalized);
|
||||
|
||||
#endif
|
||||
201
exosphere/src/i2c.c
Normal file
201
exosphere/src/i2c.c
Normal file
@@ -0,0 +1,201 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "i2c.h"
|
||||
#include "utils.h"
|
||||
#include "timers.h"
|
||||
|
||||
/* Prototypes for internal commands. */
|
||||
volatile i2c_registers_t *i2c_get_registers_from_id(unsigned int id);
|
||||
void i2c_load_config(volatile i2c_registers_t *regs);
|
||||
|
||||
bool i2c_query(unsigned int id, uint8_t device, uint8_t r, void *dst, size_t dst_size);
|
||||
bool i2c_send(unsigned int id, uint8_t device, uint8_t r, void *src, size_t src_size);
|
||||
|
||||
bool i2c_write(volatile i2c_registers_t *regs, uint8_t device, void *src, size_t src_size);
|
||||
bool i2c_read(volatile i2c_registers_t *regs, uint8_t device, void *dst, size_t dst_size);
|
||||
|
||||
/* Initialize I2C based on registers. */
|
||||
void i2c_init(unsigned int id) {
|
||||
volatile i2c_registers_t *regs = i2c_get_registers_from_id(id);
|
||||
|
||||
/* Setup divisor, and clear the bus. */
|
||||
regs->I2C_I2C_CLK_DIVISOR_REGISTER_0 = 0x50001;
|
||||
regs->I2C_I2C_BUS_CLEAR_CONFIG_0 = 0x90003;
|
||||
|
||||
/* Load hardware configuration. */
|
||||
i2c_load_config(regs);
|
||||
|
||||
/* Wait a while until BUS_CLEAR_DONE is set. */
|
||||
for (unsigned int i = 0; i < 10; i++) {
|
||||
wait(20000);
|
||||
if (regs->I2C_INTERRUPT_STATUS_REGISTER_0 & 0x800) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read the BUS_CLEAR_STATUS. Result doesn't matter. */
|
||||
regs->I2C_I2C_BUS_CLEAR_STATUS_0;
|
||||
|
||||
/* Read and set the Interrupt Status. */
|
||||
uint32_t int_status = regs->I2C_INTERRUPT_STATUS_REGISTER_0;
|
||||
regs->I2C_INTERRUPT_STATUS_REGISTER_0 = int_status;
|
||||
}
|
||||
|
||||
/* Sets a bit in a PMIC register over I2C during CPU shutdown. */
|
||||
void i2c_send_pmic_cpu_shutdown_cmd(void) {
|
||||
uint32_t val = 0;
|
||||
/* PMIC == Device 4:3C. */
|
||||
i2c_query(4, 0x3C, 0x41, &val, 1);
|
||||
val |= 4;
|
||||
i2c_send(4, 0x3C, 0x41, &val, 1);
|
||||
}
|
||||
|
||||
/* Queries the value of TI charger bit over I2C. */
|
||||
bool i2c_query_ti_charger_bit_7(void) {
|
||||
uint32_t val = 0;
|
||||
/* TI Charger = Device 0:6B. */
|
||||
i2c_query(0, 0x6B, 0, &val, 1);
|
||||
return (val & 0x80) != 0;
|
||||
}
|
||||
|
||||
/* Clears TI charger bit over I2C. */
|
||||
void i2c_clear_ti_charger_bit_7(void) {
|
||||
uint32_t val = 0;
|
||||
/* TI Charger = Device 0:6B. */
|
||||
i2c_query(0, 0x6B, 0, &val, 1);
|
||||
val &= 0x7F;
|
||||
i2c_send(0, 0x6B, 0, &val, 1);
|
||||
}
|
||||
|
||||
/* Sets TI charger bit over I2C. */
|
||||
void i2c_set_ti_charger_bit_7(void) {
|
||||
uint32_t val = 0;
|
||||
/* TI Charger = Device 0:6B. */
|
||||
i2c_query(0, 0x6B, 0, &val, 1);
|
||||
val |= 0x80;
|
||||
i2c_send(0, 0x6B, 0, &val, 1);
|
||||
}
|
||||
|
||||
/* Get registers pointer based on I2C ID. */
|
||||
volatile i2c_registers_t *i2c_get_registers_from_id(unsigned int id) {
|
||||
switch (id) {
|
||||
case 0:
|
||||
return I2C1_REGS;
|
||||
case 1:
|
||||
return I2C2_REGS;
|
||||
case 2:
|
||||
return I2C3_REGS;
|
||||
case 3:
|
||||
return I2C4_REGS;
|
||||
case 4:
|
||||
return I2C5_REGS;
|
||||
case 5:
|
||||
return I2C6_REGS;
|
||||
default:
|
||||
generic_panic();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Load hardware config for I2C4. */
|
||||
void i2c_load_config(volatile i2c_registers_t *regs) {
|
||||
/* Set MSTR_CONFIG_LOAD, TIMEOUT_CONFIG_LOAD, undocumented bit. */
|
||||
regs->I2C_I2C_CONFIG_LOAD_0 = 0x25;
|
||||
|
||||
/* Wait a bit for master config to be loaded. */
|
||||
for (unsigned int i = 0; i < 20; i++) {
|
||||
wait(1);
|
||||
if (!(regs->I2C_I2C_CONFIG_LOAD_0 & 1)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reads a register from a device over I2C, writes result to output. */
|
||||
bool i2c_query(unsigned int id, uint8_t device, uint8_t r, void *dst, size_t dst_size) {
|
||||
volatile i2c_registers_t *regs = i2c_get_registers_from_id(id);
|
||||
uint32_t val = r;
|
||||
|
||||
/* Write single byte register ID to device. */
|
||||
if (!i2c_write(regs, device, &val, 1)) {
|
||||
return false;
|
||||
}
|
||||
/* Limit output size to 32-bits. */
|
||||
if (dst_size > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return i2c_read(regs, device, dst, dst_size);
|
||||
}
|
||||
|
||||
/* Writes a value to a register over I2C. */
|
||||
bool i2c_send(unsigned int id, uint8_t device, uint8_t r, void *src, size_t src_size) {
|
||||
uint32_t val = r;
|
||||
if (src_size <= 3) {
|
||||
memcpy(((uint8_t *)&val) + 1, src, src_size);
|
||||
return i2c_write(i2c_get_registers_from_id(id), device, &val, src_size + 1);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Writes bytes to device over I2C. */
|
||||
bool i2c_write(volatile i2c_registers_t *regs, uint8_t device, void *src, size_t src_size) {
|
||||
if (src_size > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set device for 7-bit write mode. */
|
||||
regs->I2C_I2C_CMD_ADDR0_0 = device << 1;
|
||||
|
||||
/* Load in data to write. */
|
||||
regs->I2C_I2C_CMD_DATA1_0 = read32le(src, 0);
|
||||
|
||||
/* Set config with LENGTH = src_size, NEW_MASTER_FSM, DEBOUNCE_CNT = 4T. */
|
||||
regs->I2C_I2C_CNFG_0 = ((src_size << 1) - 2) | 0x2800;
|
||||
|
||||
i2c_load_config(regs);
|
||||
|
||||
/* Config |= SEND; */
|
||||
regs->I2C_I2C_CNFG_0 |= 0x200;
|
||||
|
||||
|
||||
while (regs->I2C_I2C_STATUS_0 & 0x100) {
|
||||
/* Wait until not busy. */
|
||||
}
|
||||
|
||||
/* Return CMD1_STAT == SL1_XFER_SUCCESSFUL. */
|
||||
return (regs->I2C_I2C_STATUS_0 & 0xF) == 0;
|
||||
}
|
||||
|
||||
/* Reads bytes from device over I2C. */
|
||||
bool i2c_read(volatile i2c_registers_t *regs, uint8_t device, void *dst, size_t dst_size) {
|
||||
if (dst_size > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set device for 7-bit read mode. */
|
||||
regs->I2C_I2C_CMD_ADDR0_0 = (device << 1) | 1;
|
||||
|
||||
/* Set config with LENGTH = dst_size, NEW_MASTER_FSM, DEBOUNCE_CNT = 4T. */
|
||||
regs->I2C_I2C_CNFG_0 = ((dst_size << 1) - 2) | 0x2840;
|
||||
|
||||
i2c_load_config(regs);
|
||||
|
||||
/* Config |= SEND; */
|
||||
regs->I2C_I2C_CNFG_0 |= 0x200;
|
||||
|
||||
|
||||
while (regs->I2C_I2C_STATUS_0 & 0x100) {
|
||||
/* Wait until not busy. */
|
||||
}
|
||||
|
||||
/* Ensure success. */
|
||||
if ((regs->I2C_I2C_STATUS_0 & 0xF) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t val = regs->I2C_I2C_CMD_DATA1_0;
|
||||
memcpy(dst, &val, dst_size);
|
||||
return true;
|
||||
}
|
||||
70
exosphere/src/i2c.h
Normal file
70
exosphere/src/i2c.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef EXOSPHERE_I2C_H
|
||||
#define EXOSPHERE_I2C_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Exosphere driver for the Tegra X1 I2C registers. */
|
||||
|
||||
typedef struct {
|
||||
uint32_t I2C_I2C_CNFG_0;
|
||||
uint32_t I2C_I2C_CMD_ADDR0_0;
|
||||
uint32_t I2C_I2C_CMD_ADDR1_0;
|
||||
uint32_t I2C_I2C_CMD_DATA1_0;
|
||||
uint32_t I2C_I2C_CMD_DATA2_0;
|
||||
uint32_t _0x14;
|
||||
uint32_t _0x18;
|
||||
uint32_t I2C_I2C_STATUS_0;
|
||||
uint32_t I2C_I2C_SL_CNFG_0;
|
||||
uint32_t I2C_I2C_SL_RCVD_0;
|
||||
uint32_t I2C_I2C_SL_STATUS_0;
|
||||
uint32_t I2C_I2C_SL_ADDR1_0;
|
||||
uint32_t I2C_I2C_SL_ADDR2_0;
|
||||
uint32_t I2C_I2C_TLOW_SEXT_0;
|
||||
uint32_t _0x38;
|
||||
uint32_t I2C_I2C_SL_DELAY_COUNT_0;
|
||||
uint32_t I2C_I2C_SL_INT_MASK_0;
|
||||
uint32_t I2C_I2C_SL_INT_SOURCE_0;
|
||||
uint32_t I2C_I2C_SL_INT_SET_0;
|
||||
uint32_t _0x4C;
|
||||
uint32_t I2C_I2C_TX_PACKET_FIFO_0;
|
||||
uint32_t I2C_I2C_RX_FIFO_0;
|
||||
uint32_t I2C_PACKET_TRANSFER_STATUS_0;
|
||||
uint32_t I2C_FIFO_CONTROL_0;
|
||||
uint32_t I2C_FIFO_STATUS_0;
|
||||
uint32_t I2C_INTERRUPT_MASK_REGISTER_0;
|
||||
uint32_t I2C_INTERRUPT_STATUS_REGISTER_0;
|
||||
uint32_t I2C_I2C_CLK_DIVISOR_REGISTER_0;
|
||||
uint32_t I2C_I2C_INTERRUPT_SOURCE_REGISTER_0;
|
||||
uint32_t I2C_I2C_INTERRUPT_SET_REGISTER_0;
|
||||
uint32_t I2C_I2C_SLV_TX_PACKET_FIFO_0;
|
||||
uint32_t I2C_I2C_SLV_RX_FIFO_0;
|
||||
uint32_t I2C_I2C_SLV_PACKET_STATUS_0;
|
||||
uint32_t I2C_I2C_BUS_CLEAR_CONFIG_0;
|
||||
uint32_t I2C_I2C_BUS_CLEAR_STATUS_0;
|
||||
uint32_t I2C_I2C_CONFIG_LOAD_0;
|
||||
uint32_t _0x90;
|
||||
uint32_t I2C_I2C_INTERFACE_TIMING_0_0;
|
||||
uint32_t I2C_I2C_INTERFACE_TIMING_1_0;
|
||||
uint32_t I2C_I2C_HS_INTERFACE_TIMING_0_0;
|
||||
uint32_t I2C_I2C_HS_INTERFACE_TIMING_1_0;
|
||||
} i2c_registers_t;
|
||||
|
||||
|
||||
#define I2C1_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_DTV_I2C234) + 0x000))
|
||||
#define I2C2_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_DTV_I2C234) + 0x400))
|
||||
#define I2C3_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_DTV_I2C234) + 0x500))
|
||||
#define I2C4_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_DTV_I2C234) + 0x700))
|
||||
#define I2C5_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_I2C56_SPI2B) + 0x000))
|
||||
#define I2C6_REGS ((volatile i2c_registers_t *)(mmio_get_device_address(MMIO_DEVID_I2C56_SPI2B) + 0x100))
|
||||
|
||||
void i2c_init(unsigned int id);
|
||||
|
||||
void i2c_send_pmic_cpu_shutdown_cmd(void);
|
||||
|
||||
bool i2c_query_ti_charger_bit_7(void);
|
||||
void i2c_clear_ti_charger_bit_7(void);
|
||||
void i2c_set_ti_charger_bit_7(void);
|
||||
|
||||
#endif
|
||||
99
exosphere/src/interrupt.c
Normal file
99
exosphere/src/interrupt.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "interrupt.h"
|
||||
|
||||
/* Global of registered handlers. */
|
||||
struct {
|
||||
unsigned int id;
|
||||
void (*handler)(void);
|
||||
} g_registered_interrupts[MAX_REGISTERED_INTERRUPTS] = { {0, NULL}, {0, NULL}, {0, NULL}, {0, NULL} };
|
||||
|
||||
static unsigned int get_interrupt_id(void) {
|
||||
return 0;
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
/* Initializes the GIC. TODO: This must be called during wakeup. */
|
||||
void intr_initialize_gic(void) {
|
||||
/* Setup interrupts 0-0x1F as nonsecure with highest non-secure priority. */
|
||||
GICD_IGROUPR[0] = 0xFFFFFFFF;
|
||||
for (unsigned int i = 0; i < 0x20; i++) {
|
||||
GICD_IPRIORITYR[i] = GIC_PRI_HIGHEST_NONSECURE;
|
||||
}
|
||||
|
||||
/* Setup the GICC. */
|
||||
GICC_CTLR = 0x1D9;
|
||||
GICC_PMR = GIC_PRI_HIGHEST_NONSECURE;
|
||||
GICC_BPR = 7;
|
||||
}
|
||||
|
||||
/* Sets an interrupt's group in the GICD. */
|
||||
void intr_set_group(unsigned int id, int group) {
|
||||
GICD_IGROUPR[id >> 5] = (GICD_IGROUPR[id >> 5] & (~(1 << (id & 0x1F)))) | ((group & 1) << (id & 0x1F));
|
||||
}
|
||||
|
||||
/* Sets an interrupt id as pending in the GICD. */
|
||||
void intr_set_pending(unsigned int id) {
|
||||
GICD_ISPENDR[id >> 5] = 1 << (id & 0x1F);
|
||||
}
|
||||
|
||||
/* Sets an interrupt's priority in the GICD. */
|
||||
void intr_set_priority(unsigned int id, uint8_t priority) {
|
||||
GICD_IPRIORITYR[id] = priority;
|
||||
}
|
||||
|
||||
/* Sets an interrupt's target CPU mask in the GICD. */
|
||||
void intr_set_cpu_mask(unsigned int id, uint8_t mask) {
|
||||
GICD_ITARGETSR[id] = mask;
|
||||
}
|
||||
|
||||
/* Sets an interrupt's edge/level bits in the GICD. */
|
||||
void intr_set_edge_level(unsigned int id, int edge_level) {
|
||||
GICD_ICFGR[id >> 4] = GICD_ICFGR[id >> 4] & ((~(3 << ((id & 0xF) << 1))) | (((edge_level & 1) << 1) << ((id & 0xF) << 1)));
|
||||
}
|
||||
|
||||
/* Sets an interrupt's enabled status in the GICD. */
|
||||
void intr_set_enabled(unsigned int id, int enabled) {
|
||||
GICD_ISENABLER[id >> 5] = (enabled & 1) << (id & 0x1F);
|
||||
}
|
||||
|
||||
/* To be called by FIQ handler. */
|
||||
void handle_registered_interrupt(void) {
|
||||
unsigned int interrupt_id = get_interrupt_id();
|
||||
if (interrupt_id <= 0xDF) {
|
||||
bool found_handler = false;
|
||||
for (unsigned int i = 0; i < MAX_REGISTERED_INTERRUPTS; i++) {
|
||||
if (g_registered_interrupts[i].id == interrupt_id) {
|
||||
found_handler = true;
|
||||
g_registered_interrupts[i].handler();
|
||||
/* Mark that interrupt is done. */
|
||||
GICC_EOIR = interrupt_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* We must have found a handler, or something went wrong. */
|
||||
if (!found_handler) {
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Registers an interrupt into the global. */
|
||||
void intr_register_handler(unsigned int id, void (*handler)(void)) {
|
||||
bool registered_handler = false;
|
||||
for (unsigned int i = 0; i < MAX_REGISTERED_INTERRUPTS; i++) {
|
||||
if (g_registered_interrupts[i].id == 0) {
|
||||
g_registered_interrupts[i].handler = handler;
|
||||
g_registered_interrupts[i].id = id;
|
||||
registered_handler = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Failure to register is an error condition. */
|
||||
if (!registered_handler) {
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
|
||||
50
exosphere/src/interrupt.h
Normal file
50
exosphere/src/interrupt.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef EXOSPHERE_INTERRUPT_H
|
||||
#define EXOSPHERE_INTERRUPT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Exosphere driver for the Tegra X1 GIC-400 registers. */
|
||||
|
||||
|
||||
#define MAX_REGISTERED_INTERRUPTS 4
|
||||
#define INTERRUPT_ID_SECURITY_ENGINE 0x5A
|
||||
#define INTERRUPT_ID_USER_SECURITY_ENGINE 0x2C
|
||||
|
||||
#define GICD_BASE (mmio_get_device_address(MMIO_DEVID_GICD))
|
||||
#define GICC_BASE (mmio_get_device_address(MMIO_DEVID_GICC))
|
||||
|
||||
#define GICD_IGROUPR ((volatile uint32_t *)(GICD_BASE + 0x080ULL))
|
||||
#define GICD_ISENABLER ((volatile uint32_t *)(GICD_BASE + 0x100ULL))
|
||||
#define GICD_ISPENDR ((volatile uint32_t *)(GICD_BASE + 0x200ULL))
|
||||
#define GICD_IPRIORITYR ((volatile uint8_t *)(GICD_BASE + 0x400ULL))
|
||||
#define GICD_ITARGETSR ((volatile uint8_t *)(GICD_BASE + 0x800ULL))
|
||||
#define GICD_ICFGR ((volatile uint32_t *)(GICD_BASE + 0xC00ULL))
|
||||
|
||||
#define GICC_CTLR (*((volatile uint32_t *)(GICC_BASE + 0x0000ULL)))
|
||||
#define GICC_PMR (*((volatile uint32_t *)(GICC_BASE + 0x0004ULL)))
|
||||
#define GICC_BPR (*((volatile uint32_t *)(GICC_BASE + 0x0008ULL)))
|
||||
#define GICC_IAR (*((volatile uint32_t *)(GICC_BASE + 0x000CULL)))
|
||||
#define GICC_EOIR (*((volatile uint32_t *)(GICC_BASE + 0x0010ULL)))
|
||||
|
||||
#define GIC_PRI_HIGHEST_SECURE 0x00
|
||||
#define GIC_PRI_HIGHEST_NONSECURE 0x80
|
||||
|
||||
#define GIC_GROUP_SECURE 0
|
||||
#define GIC_GROUP_NONSECURE 1
|
||||
|
||||
/* To be called by FIQ handler. */
|
||||
void handle_registered_interrupt(void);
|
||||
|
||||
/* Initializes the GIC. TODO: This must be called during wakeup. */
|
||||
void intr_initialize_gic(void);
|
||||
|
||||
|
||||
void intr_register_handler(unsigned int id, void (*handler)(void));
|
||||
void intr_set_group(unsigned int id, int group);
|
||||
void intr_set_pending(unsigned int id);
|
||||
void intr_set_priority(unsigned int id, uint8_t priority);
|
||||
void intr_set_cpu_mask(unsigned int id, uint8_t mask);
|
||||
void intr_set_edge_level(unsigned int id, int edge_level);
|
||||
void intr_set_enabled(unsigned int id, int enabled);
|
||||
#endif
|
||||
25
exosphere/src/lock.h
Normal file
25
exosphere/src/lock.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef EXOSPHERE_LOCK_H
|
||||
#define EXOSPHERE_LOCK_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Simple atomics driver for Exosphere. */
|
||||
|
||||
/* Acquire a lock. */
|
||||
static inline void lock_acquire(bool *l) {
|
||||
while (__atomic_test_and_set(l, __ATOMIC_ACQUIRE)) {
|
||||
/* Wait to acquire lock. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Release a lock. */
|
||||
static inline void lock_release(bool *l) {
|
||||
__atomic_clear(l, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
/* Try to acquire a lock. */
|
||||
static inline bool lock_try_acquire(bool *l) {
|
||||
return __atomic_test_and_set(l, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
#endif
|
||||
91
exosphere/src/masterkey.c
Normal file
91
exosphere/src/masterkey.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "masterkey.h"
|
||||
#include "se.h"
|
||||
|
||||
unsigned int g_mkey_revision = 0;
|
||||
bool g_determined_mkey_revision = false;
|
||||
|
||||
uint8_t g_old_masterkeys[MASTERKEY_REVISION_MAX][0x10];
|
||||
|
||||
/* TODO: Dev keys. */
|
||||
|
||||
/* TODO: Extend with new vectors, as needed. */
|
||||
const uint8_t mkey_vectors[MASTERKEY_REVISION_MAX][0x10] =
|
||||
{
|
||||
{0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D}, /* Zeroes encrypted with Master Key 00. */
|
||||
{0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD}, /* Master key 00 encrypted with Master key 01. */
|
||||
{0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72}, /* Master key 01 encrypted with Master key 02. */
|
||||
{0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07}, /* Master key 02 encrypted with Master key 03. */
|
||||
};
|
||||
|
||||
bool check_mkey_revision(unsigned int revision) {
|
||||
uint8_t final_vector[0x10];
|
||||
|
||||
unsigned int check_keyslot = KEYSLOT_SWITCH_MASTERKEY;
|
||||
if (revision > 0) {
|
||||
/* Generate old master key array. */
|
||||
for (unsigned int i = revision; i > 0; i--) {
|
||||
se_aes_ecb_decrypt_block(check_keyslot, g_old_masterkeys[i-1], 0x10, mkey_vectors[i], 0x10);
|
||||
set_aes_keyslot(KEYSLOT_SWITCH_TEMPKEY, g_old_masterkeys[i-1], 0x10);
|
||||
check_keyslot = KEYSLOT_SWITCH_TEMPKEY;
|
||||
}
|
||||
}
|
||||
|
||||
se_aes_ecb_decrypt_block(check_keyslot, final_vector, 0x10, mkey_vectors[0], 0x10);
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
if (final_vector[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void mkey_detect_revision(void) {
|
||||
if (g_determined_mkey_revision) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
for (unsigned int rev = 0; rev < MASTERKEY_REVISION_MAX; rev++) {
|
||||
if (check_mkey_revision(rev)) {
|
||||
g_determined_mkey_revision = true;
|
||||
g_mkey_revision = rev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* We must have determined the master key, or we're not running on a Switch. */
|
||||
/* TODO: When panic is implemented, make this a really distinctive color. */
|
||||
/* Maybe bright red? */
|
||||
if (!g_determined_mkey_revision) {
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int mkey_get_revision(void) {
|
||||
if (!g_determined_mkey_revision) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
return g_mkey_revision;
|
||||
}
|
||||
|
||||
unsigned int mkey_get_keyslot(unsigned int revision) {
|
||||
if (!g_determined_mkey_revision || revision >= MASTERKEY_REVISION_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (revision > g_mkey_revision) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (revision == g_mkey_revision) {
|
||||
return KEYSLOT_SWITCH_MASTERKEY;
|
||||
} else {
|
||||
/* Load into a temp keyslot. */
|
||||
set_aes_keyslot(KEYSLOT_SWITCH_TEMPKEY, g_old_masterkeys[revision], 0x10);
|
||||
return KEYSLOT_SWITCH_TEMPKEY;
|
||||
}
|
||||
}
|
||||
21
exosphere/src/masterkey.h
Normal file
21
exosphere/src/masterkey.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef EXOSPHERE_MASTERKEY_H
|
||||
#define EXOSPHERE_MASTERKEY_H
|
||||
|
||||
/* This is glue code to enable master key support across versions. */
|
||||
|
||||
/* TODO: Update to 0x5 on release of new master key. */
|
||||
#define MASTERKEY_REVISION_MAX 0x4
|
||||
|
||||
#define MASTERKEY_REVISION_100_230 0x00
|
||||
#define MASTERKEY_REVISION_300 0x01
|
||||
#define MASTERKEY_REVISION_301_302 0x02
|
||||
#define MASTERKEY_REVISION_400_CURRENT 0x03
|
||||
|
||||
/* This should be called early on in initialization. */
|
||||
void mkey_detect_revision(void);
|
||||
|
||||
unsigned int mkey_get_revision(void);
|
||||
|
||||
unsigned int mkey_get_keyslot(unsigned int revision);
|
||||
|
||||
#endif
|
||||
104
exosphere/src/mc.c
Normal file
104
exosphere/src/mc.c
Normal file
@@ -0,0 +1,104 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "memory_map.h"
|
||||
#include "mc.h"
|
||||
|
||||
volatile security_carveout_t *get_carveout_by_id(unsigned int carveout) {
|
||||
if (CARVEOUT_ID_MIN <= carveout && carveout <= CARVEOUT_ID_MAX) {
|
||||
return (volatile security_carveout_t *)(MC_BASE + 0xC08ULL + 0x50 * (carveout - CARVEOUT_ID_MIN));
|
||||
}
|
||||
generic_panic();
|
||||
return NULL;
|
||||
switch (carveout) {
|
||||
case 4: /* Kernel carveout */
|
||||
return (volatile security_carveout_t *)(MC_BASE + 0xCF8ULL);
|
||||
case 5: /* Unused Kernel carveout */
|
||||
return (volatile security_carveout_t *)(MC_BASE + 0xD48ULL);
|
||||
default:
|
||||
generic_panic();
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void configure_default_carveouts(void) {
|
||||
/* Configure Carveout 1 (UNUSED) */
|
||||
volatile security_carveout_t *carveout = get_carveout_by_id(1);
|
||||
carveout->paddr_low = 0;
|
||||
carveout->paddr_high = 0;
|
||||
carveout->size_big_pages = 0;
|
||||
carveout->flags_0 = 0;
|
||||
carveout->flags_1 = 0;
|
||||
carveout->flags_2 = 0;
|
||||
carveout->flags_3 = 0;
|
||||
carveout->flags_4 = 0;
|
||||
carveout->flags_5 = 0;
|
||||
carveout->flags_6 = 0;
|
||||
carveout->flags_7 = 0;
|
||||
carveout->flags_8 = 0;
|
||||
carveout->flags_9 = 0;
|
||||
carveout->allowed_clients = 0x04000006;
|
||||
|
||||
/* Configure Carveout 2 (GPU UCODE) */
|
||||
carveout = get_carveout_by_id(2);
|
||||
carveout->paddr_low = 0x80020000;
|
||||
carveout->paddr_high = 0;
|
||||
carveout->size_big_pages = 2; /* 0x40000 */
|
||||
carveout->flags_0 = 0;
|
||||
carveout->flags_1 = 0;
|
||||
carveout->flags_2 = 0x3000000;
|
||||
carveout->flags_3 = 0;
|
||||
carveout->flags_4 = 0x300;
|
||||
carveout->flags_5 = 0;
|
||||
carveout->flags_6 = 0;
|
||||
carveout->flags_7 = 0;
|
||||
carveout->flags_8 = 0;
|
||||
carveout->flags_9 = 0;
|
||||
carveout->allowed_clients = 0x440167E;
|
||||
|
||||
/* Configure Carveout 3 (UNUSED GPU) */
|
||||
carveout = get_carveout_by_id(3);
|
||||
carveout->paddr_low = 0;
|
||||
carveout->paddr_high = 0;
|
||||
carveout->size_big_pages = 0;
|
||||
carveout->flags_0 = 0;
|
||||
carveout->flags_1 = 0;
|
||||
carveout->flags_2 = 0x3000000;
|
||||
carveout->flags_3 = 0;
|
||||
carveout->flags_4 = 0x300;
|
||||
carveout->flags_5 = 0;
|
||||
carveout->flags_6 = 0;
|
||||
carveout->flags_7 = 0;
|
||||
carveout->flags_8 = 0;
|
||||
carveout->flags_9 = 0;
|
||||
carveout->allowed_clients = 0x4401E7E;
|
||||
|
||||
/* Configure default Kernel carveouts based on 2.0.0+. */
|
||||
|
||||
/* Configure Carveout 4 (KERNEL_BUILTINS) */
|
||||
configure_kernel_carveout(5, 0x80060000, KERNEL_CARVEOUT_SIZE_MAX);
|
||||
|
||||
/* Configure Carveout 5 (KERNEL_UNUSED) */
|
||||
configure_kernel_carveout(5, 0, 0);
|
||||
}
|
||||
|
||||
void configure_kernel_carveout(unsigned int carveout_id, uint64_t address, uint64_t size) {
|
||||
if (carveout_id != 4 && carveout_id != 5) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
volatile security_carveout_t *carveout = get_carveout_by_id(carveout_id);
|
||||
carveout->paddr_low = (uint32_t)(address & 0xFFFFFFFF);
|
||||
carveout->paddr_high = (uint32_t)(address >> 32);
|
||||
carveout->size_big_pages = (uint32_t)(size >> 17);
|
||||
carveout->flags_0 = 0x70E3407F;
|
||||
carveout->flags_1 = 0x1A620880;
|
||||
carveout->flags_2 = 0x303C00;
|
||||
carveout->flags_3 = 0xCF0830BB;
|
||||
carveout->flags_4 = 0x3;
|
||||
carveout->flags_5 = 0;
|
||||
carveout->flags_6 = 0;
|
||||
carveout->flags_7 = 0;
|
||||
carveout->flags_8 = 0;
|
||||
carveout->flags_9 = 0;
|
||||
carveout->allowed_clients = 0x8B;
|
||||
}
|
||||
40
exosphere/src/mc.h
Normal file
40
exosphere/src/mc.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef EXOSPHERE_MC_H
|
||||
#define EXOSPHERE_MC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Exosphere driver for the Tegra X1 Memory Controller. */
|
||||
|
||||
#define MC_BASE (mmio_get_device_address(MMIO_DEVID_MC))
|
||||
|
||||
#define CARVEOUT_ID_MIN 1
|
||||
#define CARVEOUT_ID_MAX 5
|
||||
|
||||
#define KERNEL_CARVEOUT_SIZE_MAX 0x1FFE0000
|
||||
|
||||
typedef struct {
|
||||
uint32_t allowed_clients;
|
||||
uint32_t paddr_low;
|
||||
uint32_t paddr_high;
|
||||
uint32_t size_big_pages;
|
||||
uint32_t flags_0;
|
||||
uint32_t flags_1;
|
||||
uint32_t flags_2;
|
||||
uint32_t flags_3;
|
||||
uint32_t flags_4;
|
||||
uint32_t flags_5;
|
||||
uint32_t flags_6;
|
||||
uint32_t flags_7;
|
||||
uint32_t flags_8;
|
||||
uint32_t flags_9;
|
||||
uint8_t padding[0x28];
|
||||
} security_carveout_t;
|
||||
|
||||
|
||||
volatile security_carveout_t *get_carveout_by_id(unsigned int carveout);
|
||||
void configure_default_carveouts(void);
|
||||
void configure_kernel_carveout(unsigned int carveout_id, uint64_t address, uint64_t size);
|
||||
|
||||
|
||||
#endif
|
||||
310
exosphere/src/memory_map.h
Normal file
310
exosphere/src/memory_map.h
Normal file
@@ -0,0 +1,310 @@
|
||||
#ifndef EXOSPHERE_MEMORY_MAP_H
|
||||
#define EXOSPHERE_MEMORY_MAP_H
|
||||
|
||||
#include "mmu.h"
|
||||
|
||||
#define ATTRIB_MEMTYPE_NORMAL MMU_PTE_BLOCK_MEMTYPE(0)
|
||||
#define ATTRIB_MEMTYPE_DEVICE MMU_PTE_BLOCK_MEMTYPE(1)
|
||||
|
||||
static const struct {
|
||||
uintptr_t address;
|
||||
uint64_t size;
|
||||
uint64_t attributes;
|
||||
bool is_block_range;
|
||||
} g_identity_mappings[] = {
|
||||
{ 0x40020000, 0x20000, 0, false }, /* iRAM-C+D (contains the secmon's coldboot crt0) */
|
||||
{ 0x7C010000, 0x10000, 0, false }, /* TZRAM (contains the secmon's warmboot crt0) */
|
||||
{ 0x80000000, 4u << 30, MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_NS, true }, /* DRAM (4GB) */
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t pa;
|
||||
size_t size;
|
||||
bool is_secure;
|
||||
} g_devices[] = {
|
||||
{ 0x50041000, 0x1000, true }, /* ARM Interrupt Distributor */
|
||||
{ 0x50042000, 0x2000, true }, /* Interrupt Controller Physical CPU interface */
|
||||
{ 0x70006000, 0x1000, false }, /* UART-A */
|
||||
{ 0x60006000, 0x1000, false }, /* Clock and Reset */
|
||||
{ 0x7000E000, 0x1000, true }, /* RTC, PMC */
|
||||
{ 0x60005000, 0x1000, true }, /* TMRs, WDTs */
|
||||
{ 0x6000C000, 0x1000, true }, /* System Registers */
|
||||
{ 0x70012000, 0x2000, true }, /* SE */
|
||||
{ 0x700F0000, 0x1000, true }, /* SYSCTR0 */
|
||||
{ 0x70019000, 0x1000, true }, /* MC */
|
||||
{ 0x7000F000, 0x1000, true }, /* FUSE (0x7000F800) */
|
||||
{ 0x70000000, 0x4000, true }, /* MISC */
|
||||
{ 0x60007000, 0x1000, true }, /* Flow Controller */
|
||||
{ 0x40002000, 0x1000, true }, /* NX bootloader mailbox page */
|
||||
{ 0x7000D000, 0x1000, true }, /* I2C-5,6 - SPI 2B-1 to 4 */
|
||||
{ 0x6000D000, 0x1000, true }, /* GPIO-1 - GPIO-8 */
|
||||
{ 0x7000C000, 0x1000, true }, /* I2C-I2C4 */
|
||||
{ 0x6000F000, 0x1000, true }, /* Exception vectors */
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t pa;
|
||||
size_t size;
|
||||
uint64_t attributes;
|
||||
} g_lp0_entry_ram_segments[] = {
|
||||
{ 0x40020000, 0x10000, MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_DEVICE }, /* Encrypted TZRAM */
|
||||
{ 0x40003000, 0x01000, MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_DEVICE }, /* LP0 entry code */
|
||||
{ 0x7C010000, 0x10000, MMU_AP_PRIV_RO | ATTRIB_MEMTYPE_NORMAL }, /* TZRAM to encrypt */
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t pa;
|
||||
size_t size;
|
||||
uint64_t attributes;
|
||||
} g_warmboot_ram_segments[] = {
|
||||
{ 0x8000F000, 0x01000, MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_DEVICE }, /* Encrypted SE state for bootROM */
|
||||
{ 0x80010000, 0x10000, MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_DEVICE }, /* Encrypted TZRAM for warmboot.bin */
|
||||
};
|
||||
|
||||
static const struct {
|
||||
size_t tzram_offset;
|
||||
size_t map_size;
|
||||
size_t increment; /* for alignment, guard pages, etc. */
|
||||
bool is_code_segment; /* note: code is RWX */
|
||||
} g_tzram_segments[] = {
|
||||
{ 0x3000, 0x10000 - 0x2000 - 0x3000, 0x10000, true }, /* Warmboot crt0 sections and main code segment */
|
||||
{ 0x10000 - 0x2000, 0x2000, 0x04000, true }, /* pk2ldr segment */
|
||||
{ 0, 0, 0x02000, false }, /* SPL .bss buffer, NOT mapped at startup */
|
||||
{ 0x10000 - 0x2000, 0x1000, 0x02000, false }, /* Core 0,1,2 stack */
|
||||
{ 0x10000 - 0x1000, 0x1000, 0x02000, false }, /* Core 3 stack */
|
||||
{ 0, 0x1000, 0x02000, true }, /* Secure Monitor exception vectors, some init stacks */
|
||||
{ 0x1000, 0x1000, 0x02000, false }, /* L2 translation table */
|
||||
{ 0x2000, 0x1000, 0x02000, false }, /* L3 translation table */
|
||||
};
|
||||
|
||||
#define MMIO_BASE 0x1F0080000ull
|
||||
#define LP0_ENTRY_RAM_SEGMENT_BASE (MMIO_BASE + 0x000100000)
|
||||
#define WARMBOOT_RAM_SEGMENT_BASE (LP0_ENTRY_RAM_SEGMENT_BASE + 0x000047000) /* increment seems to be arbitrary ? */
|
||||
#define TZRAM_SEGMENT_BASE (MMIO_BASE + 0x0001E0000)
|
||||
|
||||
#define MMIO_DEVID_GICD 0
|
||||
#define MMIO_DEVID_GICC 1
|
||||
#define MMIO_DEVID_UART_A 2
|
||||
#define MMIO_DEVID_CLKRST 3
|
||||
#define MMIO_DEVID_RTC_PMC 4
|
||||
#define MMIO_DEVID_TMRs_WDTs 5
|
||||
#define MMIO_DEVID_SYSREGS 6
|
||||
#define MMIO_DEVID_SE 7
|
||||
#define MMIO_DEVID_SYSCTR0 8
|
||||
#define MMIO_DEVID_MC 9
|
||||
#define MMIO_DEVID_FUSE 10
|
||||
#define MMIO_DEVID_MISC 11
|
||||
#define MMIO_DEVID_FLOWCTRL 12
|
||||
#define MMIO_DEVID_NXBOOTLOADER_MAILBOX 13
|
||||
#define MMIO_DEVID_I2C56_SPI2B 14
|
||||
#define MMIO_DEVID_GPIO 15
|
||||
#define MMIO_DEVID_DTV_I2C234 16
|
||||
#define MMIO_DEVID_EXCEPTION_VECTORS 17
|
||||
|
||||
#define LP0_ENTRY_RAM_SEGMENT_ID_DECRYPTED_TZRAM 0
|
||||
#define LP0_ENTRY_RAM_SEGMENT_ID_LP0_ENTRY_CODE 1
|
||||
#define LP0_ENTRY_RAM_SEGMENT_ID_CURRENT_TZRAM 2
|
||||
|
||||
#define WARMBOOT_RAM_SEGMENT_ID_SE_STATE 0
|
||||
#define WARMBOOT_RAM_SEGMENT_ID_TZRAM 1
|
||||
|
||||
#define TZRAM_SEGMENT_ID_WARMBOOT_CRT0_AND_MAIN 0
|
||||
#define TZRAM_SEGMENT_ID_PK2LDR 1
|
||||
#define TZRAM_SEGMENT_ID_USERPAGE 2
|
||||
#define TZRAM_SEGMENT_ID_CORE012_STACK 3
|
||||
#define TZRAM_SEGMENT_ID_CORE3_STACK 4
|
||||
#define TZRAM_SEGEMENT_ID_SECMON_EVT 5
|
||||
#define TZRAM_SEGMENT_ID_L2_TRANSLATION_TABLE 6
|
||||
#define TZRAM_SEGMENT_ID_L3_TRANSLATION_TABLE 7
|
||||
|
||||
/**********************************************************************************************/
|
||||
|
||||
static inline void identity_map_all_mappings(uintptr_t *mmu_l1_tbl, uintptr_t *mmu_l3_tbl) {
|
||||
static uint64_t base_attributes = MMU_PTE_BLOCK_INNER_SHAREBLE | ATTRIB_MEMTYPE_NORMAL;
|
||||
for(size_t i = 0; i < sizeof(g_identity_mappings) / sizeof(g_identity_mappings[0]); i++) {
|
||||
uint64_t attributes = base_attributes | g_identity_mappings[i].attributes;
|
||||
if(g_identity_mappings[i].is_block_range) {
|
||||
mmu_map_block_range(1, mmu_l1_tbl, g_identity_mappings[i].address, g_identity_mappings[i].address,
|
||||
g_identity_mappings[i].size, attributes);
|
||||
}
|
||||
else {
|
||||
mmu_map_page_range(mmu_l3_tbl, g_identity_mappings[i].address, g_identity_mappings[i].address,
|
||||
g_identity_mappings[i].size, attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void identity_unmap_all_mappings(uintptr_t *mmu_l1_tbl, uintptr_t *mmu_l3_tbl) {
|
||||
for(size_t i = 0; i < sizeof(g_identity_mappings) / sizeof(g_identity_mappings[0]); i++) {
|
||||
if(g_identity_mappings[i].is_block_range) {
|
||||
mmu_unmap_range(1, mmu_l1_tbl, g_identity_mappings[i].address, g_identity_mappings[i].size);
|
||||
}
|
||||
else {
|
||||
mmu_unmap_range(3, mmu_l3_tbl, g_identity_mappings[i].address, g_identity_mappings[i].size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************/
|
||||
|
||||
static inline uintptr_t mmio_get_device_pa(unsigned int device_id) {
|
||||
return g_devices[device_id].pa;
|
||||
}
|
||||
|
||||
#ifndef MEMORY_MAP_USE_IDENTIY_MAPPING
|
||||
static inline uintptr_t mmio_get_device_address(unsigned int device_id) {
|
||||
size_t offset = 0;
|
||||
for(unsigned int i = 0; i < device_id; i++) {
|
||||
offset += g_devices[i].size;
|
||||
offset += 0x1000; /* guard page */
|
||||
}
|
||||
|
||||
return MMIO_BASE + offset;
|
||||
}
|
||||
|
||||
#else
|
||||
static inline uintptr_t mmio_get_device_address(unsigned int device_id) {
|
||||
return mmio_get_device_pa(device_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void mmio_map_all_devices(uintptr_t *mmu_l3_tbl) {
|
||||
static const uint64_t secure_device_attributes = MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_INNER_SHAREBLE | ATTRIB_MEMTYPE_DEVICE;
|
||||
static const uint64_t device_attributes = MMU_PTE_BLOCK_NS | secure_device_attributes;
|
||||
|
||||
for(size_t i = 0, offset = 0; i < sizeof(g_devices) / sizeof(g_devices[0]); i++) {
|
||||
uint64_t attributes = g_devices[i].is_secure ? secure_device_attributes : device_attributes;
|
||||
mmu_map_page_range(mmu_l3_tbl, MMIO_BASE + offset, g_devices[i].pa, g_devices[i].size, attributes);
|
||||
|
||||
offset += g_devices[i].size;
|
||||
offset += 0x1000; /* insert guard page */
|
||||
}
|
||||
}
|
||||
|
||||
static inline void mmio_unmap_all_devices(uintptr_t *mmu_l3_tbl) {
|
||||
for(size_t i = 0, offset = 0; i < sizeof(g_devices) / sizeof(g_devices[0]); i++) {
|
||||
mmu_unmap_range(3, mmu_l3_tbl, MMIO_BASE + offset, g_devices[i].size);
|
||||
|
||||
offset += g_devices[i].size;
|
||||
offset += 0x1000; /* insert guard page */
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************/
|
||||
|
||||
static inline uintptr_t lp0_get_plaintext_ram_segment_pa(unsigned int segment_id) {
|
||||
return g_lp0_entry_ram_segments[segment_id].pa;
|
||||
}
|
||||
|
||||
#ifndef MEMORY_MAP_USE_IDENTIY_MAPPING
|
||||
static inline uintptr_t lp0_get_plaintext_ram_segment_address(unsigned int segment_id) {
|
||||
return LP0_ENTRY_RAM_SEGMENT_BASE + 0x10000 * segment_id;
|
||||
}
|
||||
#else
|
||||
static inline uintptr_t lp0_get_plaintext_ram_segment_address(unsigned int segment_id) {
|
||||
return lp0_get_plaintext_ram_segment_pa(segment_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void lp0_map_all_plaintext_ram_segments(uintptr_t *mmu_l3_tbl) {
|
||||
for(size_t i = 0, offset = 0; i < sizeof(g_lp0_entry_ram_segments) / sizeof(g_lp0_entry_ram_segments[0]); i++) {
|
||||
uint64_t attributes = MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_INNER_SHAREBLE | g_lp0_entry_ram_segments[i].attributes;
|
||||
mmu_map_page_range(mmu_l3_tbl, LP0_ENTRY_RAM_SEGMENT_BASE + offset, g_lp0_entry_ram_segments[i].pa,
|
||||
g_lp0_entry_ram_segments[i].size, attributes);
|
||||
offset += 0x10000;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void lp0_unmap_all_plaintext_ram_segments(uintptr_t *mmu_l3_tbl) {
|
||||
for(size_t i = 0, offset = 0; i < sizeof(g_lp0_entry_ram_segments) / sizeof(g_lp0_entry_ram_segments[0]); i++) {
|
||||
mmu_unmap_range(3, mmu_l3_tbl, LP0_ENTRY_RAM_SEGMENT_BASE + offset, g_lp0_entry_ram_segments[i].size);
|
||||
|
||||
offset += 0x10000;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************/
|
||||
|
||||
static inline uintptr_t lp0_get_ciphertext_ram_segment_pa(unsigned int segment_id) {
|
||||
return g_warmboot_ram_segments[segment_id].pa;
|
||||
}
|
||||
|
||||
#ifndef MEMORY_MAP_USE_IDENTIY_MAPPING
|
||||
static inline uintptr_t lp0_get_ciphertext_ram_segment_address(unsigned int segment_id) {
|
||||
size_t offset = 0;
|
||||
for(unsigned int i = 0; i < segment_id; i++) {
|
||||
offset += g_warmboot_ram_segments[i].size;
|
||||
}
|
||||
|
||||
return WARMBOOT_RAM_SEGMENT_BASE + offset;
|
||||
}
|
||||
#else
|
||||
static inline uintptr_t lp0_get_ciphertext_ram_segment_address(unsigned int segment_id) {
|
||||
return lp0_get_ciphertext_ram_segment_pa(segment_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void lp0_map_all_ciphertext_ram_segments(uintptr_t *mmu_l3_tbl) {
|
||||
for(size_t i = 0, offset = 0; i < sizeof(g_warmboot_ram_segments) / sizeof(g_warmboot_ram_segments[0]); i++) {
|
||||
uint64_t attributes = MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_INNER_SHAREBLE | g_warmboot_ram_segments[i].attributes;
|
||||
mmu_map_page_range(mmu_l3_tbl, WARMBOOT_RAM_SEGMENT_BASE + offset, g_warmboot_ram_segments[i].pa,
|
||||
g_warmboot_ram_segments[i].size, attributes);
|
||||
offset += g_warmboot_ram_segments[i].size;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void lp0_unmap_all_ciphertext_ram_segments(uintptr_t *mmu_l3_tbl) {
|
||||
for(size_t i = 0, offset = 0; i < sizeof(g_warmboot_ram_segments) / sizeof(g_warmboot_ram_segments[0]); i++) {
|
||||
mmu_unmap_range(3, mmu_l3_tbl, WARMBOOT_RAM_SEGMENT_BASE + offset, g_warmboot_ram_segments[i].size);
|
||||
|
||||
offset += g_warmboot_ram_segments[i].size;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************/
|
||||
|
||||
static inline uintptr_t tzram_get_segment_pa(unsigned int segment_id) {
|
||||
return 0x7C010000 + g_tzram_segments[segment_id].tzram_offset;
|
||||
}
|
||||
|
||||
#ifndef MEMORY_MAP_USE_IDENTIY_MAPPING
|
||||
static inline uintptr_t tzram_get_segment_address(unsigned int segment_id) {
|
||||
size_t offset = 0;
|
||||
for(unsigned int i = 0; i < segment_id; i++) {
|
||||
offset += g_tzram_segments[i].increment;
|
||||
}
|
||||
|
||||
return TZRAM_SEGMENT_BASE + offset;
|
||||
}
|
||||
#else
|
||||
static inline uintptr_t tzram_get_segment_address(unsigned int segment_id) {
|
||||
return tzram_get_segment_pa(segment_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void tzram_map_all_segments(uintptr_t *mmu_l3_tbl) {
|
||||
/* Except the SPL userpage */
|
||||
for(size_t i = 0, offset = 0; i < sizeof(g_tzram_segments) / sizeof(g_tzram_segments[0]); i++) {
|
||||
uint64_t attributes = (g_tzram_segments[i].is_code_segment ? 0 : MMU_PTE_BLOCK_XN) | MMU_PTE_BLOCK_INNER_SHAREBLE | ATTRIB_MEMTYPE_NORMAL;
|
||||
if(g_tzram_segments[i].map_size == 0) {
|
||||
continue;
|
||||
}
|
||||
mmu_map_page_range(mmu_l3_tbl, TZRAM_SEGMENT_BASE + offset, 0x7C010000 + g_tzram_segments[i].tzram_offset,
|
||||
g_tzram_segments[i].map_size, attributes);
|
||||
offset += g_tzram_segments[i].increment;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void tzram_unmap_all_segments(uintptr_t *mmu_l3_tbl) {
|
||||
/* Except the SPL userpage */
|
||||
for(size_t i = 0, offset = 0; i < sizeof(g_warmboot_ram_segments) / sizeof(g_warmboot_ram_segments[0]); i++) {
|
||||
if(g_tzram_segments[i].map_size == 0) {
|
||||
continue;
|
||||
}
|
||||
mmu_unmap_range(3, mmu_l3_tbl, TZRAM_SEGMENT_BASE + offset, g_tzram_segments[i].map_size);
|
||||
|
||||
offset += g_tzram_segments[i].increment;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
173
exosphere/src/mmu.h
Normal file
173
exosphere/src/mmu.h
Normal file
@@ -0,0 +1,173 @@
|
||||
#ifndef EXOSPHERE_MMU_H
|
||||
#define EXOSPHERE_MMU_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
#ifndef MMU_GRANULE_TYPE
|
||||
#define MMU_GRANULE_TYPE 0 /* 0: 4KB, 1: 64KB, 2: 16KB. The Switch always uses a 4KB granule size. */
|
||||
#endif
|
||||
|
||||
#if MMU_GRANULE_TYPE == 0
|
||||
#define MMU_Lx_SHIFT(x) (12 + 9 * (3 - (x)))
|
||||
#define MMU_Lx_MASK(x) (BIT(9) - 1)
|
||||
#elif MMU_GRANULE_TYPE == 1
|
||||
/* 64 KB, no L0 here */
|
||||
#define MMU_Lx_SHIFT(x) (16 + 13 * (3 - (x)))
|
||||
#define MMU_Lx_MASK(x) ((x) == 1 ? (BIT(5) - 1) : (BIT(13) - 1))
|
||||
#elif MMU_GRANULE_TYPE == 2
|
||||
#define MMU_Lx_SHIFT(x) (14 + 11 * (3 - (x)))
|
||||
#define MMU_Lx_MASK(x) ((x) == 0 ? 1 : (BIT(11) - 1))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The following defines are adapted from uboot:
|
||||
*
|
||||
* (C) Copyright 2013
|
||||
* David Feng <fenghua@phytium.com.cn>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#define MMU_MT_DEVICE_NGNRNE 0
|
||||
#define MMU_MT_DEVICE_NGNRE 1
|
||||
#define MMU_MT_DEVICE_GRE 2
|
||||
#define MMU_MT_NORMAL_NC 3
|
||||
#define MMU_MT_NORMAL 4
|
||||
|
||||
/*
|
||||
* Hardware page table definitions.
|
||||
*
|
||||
*/
|
||||
|
||||
#define MMU_PTE_TYPE_MASK 3
|
||||
#define MMU_PTE_TYPE_FAULT 0
|
||||
#define MMU_PTE_TYPE_TABLE 3
|
||||
#define MMU_PTE_TYPE_BLOCK 1
|
||||
|
||||
/* L3 only */
|
||||
#define MMU_PTE_TYPE_PAGE 3
|
||||
|
||||
#define MMU_PTE_TABLE_PXN BITL(59)
|
||||
#define MMU_PTE_TABLE_XN BITL(60)
|
||||
#define MMU_PTE_TABLE_AP BITL(61)
|
||||
#define MMU_PTE_TABLE_NS BITL(63)
|
||||
|
||||
/*
|
||||
* Block
|
||||
*/
|
||||
#define MMU_PTE_BLOCK_MEMTYPE(x) ((x) << 2)
|
||||
#define MMU_PTE_BLOCK_NS BIT(5)
|
||||
#define MMU_PTE_BLOCK_NON_SHAREABLE (0 << 8)
|
||||
#define MMU_PTE_BLOCK_OUTER_SHAREABLE (2 << 8)
|
||||
#define MMU_PTE_BLOCK_INNER_SHAREBLE (3 << 8)
|
||||
#define MMU_PTE_BLOCK_AF BIT(10)
|
||||
#define MMU_PTE_BLOCK_NG BIT(11)
|
||||
#define MMU_PTE_BLOCK_PXN BITL(53)
|
||||
#define MMU_PTE_BLOCK_UXN BITL(54)
|
||||
#define MMU_PTE_BLOCK_XN MMU_PTE_BLOCK_UXN
|
||||
|
||||
/*
|
||||
* AP[2:1]
|
||||
*/
|
||||
#define MMU_AP_PRIV_RW (0 << 6)
|
||||
#define MMU_AP_RW (1 << 6)
|
||||
#define MMU_AP_PRIV_RO (2 << 6)
|
||||
#define MMU_AP_RO (3 << 6)
|
||||
|
||||
/*
|
||||
* S2AP[2:1] (for stage2 translations; secmon doesn't use it)
|
||||
*/
|
||||
#define MMU_S2AP_NONE (0 << 6)
|
||||
#define MMU_S2AP_RO (1 << 6)
|
||||
#define MMU_S2AP_WO (2 << 6)
|
||||
#define MMU_S2AP_RW (3 << 6)
|
||||
|
||||
/*
|
||||
* AttrIndx[2:0]
|
||||
*/
|
||||
#define MMU_PMD_ATTRINDX(t) ((t) << 2)
|
||||
#define MMU_PMD_ATTRINDX_MASK (7 << 2)
|
||||
|
||||
/*
|
||||
* TCR flags.
|
||||
*/
|
||||
#define TCR_T0SZ(x) ((64 - (x)) << 0)
|
||||
#define TCR_IRGN_NC (0 << 8)
|
||||
#define TCR_IRGN_WBWA (1 << 8)
|
||||
#define TCR_IRGN_WT (2 << 8)
|
||||
#define TCR_IRGN_WBNWA (3 << 8)
|
||||
#define TCR_IRGN_MASK (3 << 8)
|
||||
#define TCR_ORGN_NC (0 << 10)
|
||||
#define TCR_ORGN_WBWA (1 << 10)
|
||||
#define TCR_ORGN_WT (2 << 10)
|
||||
#define TCR_ORGN_WBNWA (3 << 10)
|
||||
#define TCR_ORGN_MASK (3 << 10)
|
||||
#define TCR_NOT_SHARED (0 << 12)
|
||||
#define TCR_SHARED_OUTER (2 << 12)
|
||||
#define TCR_SHARED_INNER (3 << 12)
|
||||
#define TCR_TG0_4K (0 << 14)
|
||||
#define TCR_TG0_64K (1 << 14)
|
||||
#define TCR_TG0_16K (2 << 14)
|
||||
#define TCR_EPD1_DISABLE BIT(23)
|
||||
|
||||
#define TCR_EL1_RSVD BIT(31)
|
||||
#define TCR_EL2_RSVD (BIT(31) | BIT(23))
|
||||
#define TCR_EL3_RSVD (BIT(31) | BIT(23))
|
||||
|
||||
static inline void mmu_init_table(uintptr_t *tbl, size_t num_entries) {
|
||||
for(size_t i = 0; i < num_entries; i++) {
|
||||
tbl[i] = MMU_PTE_TYPE_FAULT;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
All the functions below assume base_addr is valid.
|
||||
They do not invalidate the TLB, which must be done separately.
|
||||
*/
|
||||
|
||||
static inline unsigned int mmu_compute_index(unsigned int level, uintptr_t base_addr) {
|
||||
return (base_addr >> MMU_Lx_SHIFT(level)) & MMU_Lx_MASK(level);
|
||||
}
|
||||
|
||||
static inline void mmu_map_table(unsigned int level, uintptr_t *tbl, uintptr_t base_addr, uintptr_t *next_lvl_tbl_pa, uint64_t attrs) {
|
||||
tbl[mmu_compute_index(level, base_addr)] = (uintptr_t)next_lvl_tbl_pa | attrs | MMU_PTE_TYPE_TABLE;
|
||||
}
|
||||
|
||||
static inline void mmu_map_block(unsigned int level, uintptr_t *tbl, uintptr_t base_addr, uintptr_t phys_addr, uint64_t attrs) {
|
||||
tbl[mmu_compute_index(level, base_addr)] = phys_addr | attrs | MMU_PTE_BLOCK_AF | MMU_PTE_TYPE_BLOCK;
|
||||
}
|
||||
|
||||
static inline void mmu_map_page(uintptr_t *tbl, uintptr_t base_addr, uintptr_t phys_addr, uint64_t attrs) {
|
||||
tbl[mmu_compute_index(3, base_addr)] = phys_addr | attrs | MMU_PTE_BLOCK_AF | MMU_PTE_TYPE_PAGE;
|
||||
}
|
||||
|
||||
static inline void mmu_unmap(unsigned int level, uintptr_t *tbl, uintptr_t base_addr) {
|
||||
tbl[mmu_compute_index(level, base_addr)] = MMU_PTE_TYPE_FAULT;
|
||||
}
|
||||
|
||||
static inline void mmu_map_block_range(unsigned int level, uintptr_t *tbl, uintptr_t base_addr, uintptr_t phys_addr, size_t size, uint64_t attrs) {
|
||||
size = ((size + (BITL(MMU_Lx_SHIFT(level)) - 1)) >> MMU_Lx_SHIFT(level)) << MMU_Lx_SHIFT(level);
|
||||
for(size_t offset = 0; offset < size; offset += MMU_Lx_SHIFT(level)) {
|
||||
mmu_map_block(level, tbl, base_addr + offset, phys_addr + offset, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void mmu_map_page_range(uintptr_t *tbl, uintptr_t base_addr, uintptr_t phys_addr, size_t size, uint64_t attrs) {
|
||||
size = ((size + (BITL(MMU_Lx_SHIFT(3)) - 1)) >> MMU_Lx_SHIFT(3)) << MMU_Lx_SHIFT(3);
|
||||
for(size_t offset = 0; offset < size; offset += MMU_Lx_SHIFT(3)) {
|
||||
mmu_map_page(tbl, base_addr + offset, phys_addr + offset, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void mmu_unmap_range(unsigned int level, uintptr_t *tbl, uintptr_t base_addr, size_t size) {
|
||||
size = ((size + (BITL(MMU_Lx_SHIFT(level)) - 1)) >> MMU_Lx_SHIFT(level)) << MMU_Lx_SHIFT(level);
|
||||
for(size_t offset = 0; offset < size; offset += MMU_Lx_SHIFT(level)) {
|
||||
mmu_unmap(level, tbl, base_addr + offset);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
451
exosphere/src/package2.c
Normal file
451
exosphere/src/package2.c
Normal file
@@ -0,0 +1,451 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "memory_map.h"
|
||||
|
||||
#include "cpu_context.h"
|
||||
#include "package2.h"
|
||||
#include "configitem.h"
|
||||
#include "se.h"
|
||||
#include "masterkey.h"
|
||||
#include "cache.h"
|
||||
#include "randomcache.h"
|
||||
#include "timers.h"
|
||||
|
||||
/* Hardware init, sets up the RNG and SESSION keyslots, derives new DEVICE key. */
|
||||
static void setup_se(void) {
|
||||
uint8_t work_buffer[0x10];
|
||||
|
||||
/* Sanity check the Security Engine. */
|
||||
se_verify_flags_cleared();
|
||||
se_clear_interrupts();
|
||||
|
||||
/* Perform some sanity initialization. */
|
||||
volatile security_engine_t *p_security_engine = get_security_engine_address();
|
||||
p_security_engine->_0x4 = 0;
|
||||
p_security_engine->AES_KEY_READ_DISABLE_REG = 0;
|
||||
p_security_engine->RSA_KEY_READ_DISABLE_REG = 0;
|
||||
p_security_engine->_0x0 &= 0xFFFFFFFB;
|
||||
|
||||
/* Currently unknown what each flag does. */
|
||||
for (unsigned int i = 0; i < KEYSLOT_AES_MAX; i++) {
|
||||
set_aes_keyslot_flags(i, 0x15);
|
||||
}
|
||||
|
||||
for (unsigned int i = 4; i < KEYSLOT_AES_MAX; i++) {
|
||||
set_aes_keyslot_flags(i, 0x40);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < KEYSLOT_RSA_MAX; i++) {
|
||||
set_rsa_keyslot_flags(i, 0x41);
|
||||
}
|
||||
|
||||
/* Detect Master Key revision. */
|
||||
mkey_detect_revision();
|
||||
|
||||
/* Setup new device key, if necessary. */
|
||||
if (mkey_get_revision() >= MASTERKEY_REVISION_400_CURRENT) {
|
||||
const uint8_t new_devicekey_source_4x[0x10] = {0x8B, 0x4E, 0x1C, 0x22, 0x42, 0x07, 0xC8, 0x73, 0x56, 0x94, 0x08, 0x8B, 0xCC, 0x47, 0x0F, 0x5D};
|
||||
se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_4XNEWDEVICEKEYGENKEY, work_buffer, 0x10, new_devicekey_source_4x, 0x10);
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_DEVICEKEY, KEYSLOT_SWITCH_4XNEWCONSOLEKEYGENKEY, work_buffer, 0x10);
|
||||
clear_aes_keyslot(KEYSLOT_SWITCH_4XNEWCONSOLEKEYGENKEY);
|
||||
set_aes_keyslot_flags(KEYSLOT_SWITCH_DEVICEKEY, 0xFF);
|
||||
}
|
||||
|
||||
se_initialize_rng(KEYSLOT_SWITCH_DEVICEKEY);
|
||||
|
||||
/* Generate random data, transform with device key to get RNG key. */
|
||||
se_generate_random(KEYSLOT_SWITCH_DEVICEKEY, work_buffer, 0x10);
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_RNGKEY, KEYSLOT_SWITCH_DEVICEKEY, work_buffer, 0x10);
|
||||
set_aes_keyslot_flags(KEYSLOT_SWITCH_RNGKEY, 0xFF);
|
||||
|
||||
/* Repeat for Session key. */
|
||||
se_generate_random(KEYSLOT_SWITCH_DEVICEKEY, work_buffer, 0x10);
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_SESSIONKEY, KEYSLOT_SWITCH_DEVICEKEY, work_buffer, 0x10);
|
||||
set_aes_keyslot_flags(KEYSLOT_SWITCH_SESSIONKEY, 0xFF);
|
||||
|
||||
/* TODO: Create Test Vector, to validate keyslot data is unchanged post warmboot. */
|
||||
}
|
||||
|
||||
static void setup_boot_config(void) {
|
||||
/* Load boot config only if dev unit. */
|
||||
if (configitem_is_retail()) {
|
||||
bootconfig_clear();
|
||||
} else {
|
||||
flush_dcache_range((uint8_t *)NX_BOOTLOADER_BOOTCONFIG_POINTER, (uint8_t *)NX_BOOTLOADER_BOOTCONFIG_POINTER + sizeof(bootconfig_t));
|
||||
bootconfig_load_and_verify((bootconfig_t *)NX_BOOTLOADER_BOOTCONFIG_POINTER);
|
||||
}
|
||||
}
|
||||
|
||||
static bool rsa2048_pss_verify(const void *signature, size_t signature_size, const void *modulus, size_t modulus_size, const void *data, size_t data_size) {
|
||||
uint8_t message[RSA_2048_BYTES];
|
||||
uint8_t h_buf[0x24];
|
||||
|
||||
/* Hardcode RSA with keyslot 0. */
|
||||
const uint8_t public_exponent[4] = {0x00, 0x01, 0x00, 0x01};
|
||||
set_rsa_keyslot(0, modulus, modulus_size, public_exponent, sizeof(public_exponent));
|
||||
se_synchronous_exp_mod(0, message, sizeof(message), signature, signature_size);
|
||||
|
||||
/* Validate sanity byte. */
|
||||
if (message[RSA_2048_BYTES - 1] != 0xBC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Copy Salt into MGF1 Hash Buffer. */
|
||||
memset(h_buf, 0, sizeof(h_buf));
|
||||
memcpy(h_buf, message + RSA_2048_BYTES - 0x20 - 0x1, 0x20);
|
||||
|
||||
/* Decrypt maskedDB (via inline MGF1). */
|
||||
uint8_t seed = 0;
|
||||
uint8_t mgf1_buf[0x20];
|
||||
for (unsigned int ofs = 0; ofs < RSA_2048_BYTES - 0x20 - 1; ofs += 0x20) {
|
||||
h_buf[sizeof(h_buf) - 1] = seed++;
|
||||
flush_dcache_range(h_buf, h_buf + sizeof(h_buf));
|
||||
se_calculate_sha256(mgf1_buf, h_buf, sizeof(h_buf));
|
||||
for (unsigned int i = ofs; i < ofs + 0x20 && i < RSA_2048_BYTES - 0x20 - 1; i++) {
|
||||
message[i] ^= mgf1_buf[i - ofs];
|
||||
}
|
||||
}
|
||||
|
||||
/* Constant lmask for rsa-2048-pss. */
|
||||
message[0] &= 0x7F;
|
||||
|
||||
/* Validate DB is of the form 0000...0001. */
|
||||
for (unsigned int i = 0; i < RSA_2048_BYTES - 0x20 - 0x20 - 1 - 1; i++) {
|
||||
if (message[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (message[RSA_2048_BYTES - 0x20 - 0x20 - 1 - 1] != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check hash correctness. */
|
||||
uint8_t validate_buf[8 + 0x20 + 0x20];
|
||||
uint8_t validate_hash[0x20];
|
||||
|
||||
memset(validate_buf, 0, sizeof(validate_buf));
|
||||
flush_dcache_range((uint8_t *)data, (uint8_t *)data + data_size);
|
||||
se_calculate_sha256(&validate_buf[8], data, data_size);
|
||||
memcpy(&validate_buf[0x28], &message[RSA_2048_BYTES - 0x20 - 0x20 - 1], 0x20);
|
||||
flush_dcache_range(validate_buf, validate_buf + sizeof(validate_buf));
|
||||
se_calculate_sha256(validate_hash, validate_buf, sizeof(validate_buf));
|
||||
return memcmp(h_buf, validate_hash, 0x20) == 0;
|
||||
}
|
||||
|
||||
static void package2_crypt_ctr(unsigned int master_key_rev, void *dst, size_t dst_size, const void *src, size_t src_size, const void *ctr, size_t ctr_size) {
|
||||
/* Derive package2 key. */
|
||||
const uint8_t package2_key_source[0x10] = {0xFB, 0x8B, 0x6A, 0x9C, 0x79, 0x00, 0xC8, 0x49, 0xEF, 0xD2, 0x4D, 0x85, 0x4D, 0x30, 0xA0, 0xC7};
|
||||
flush_dcache_range((uint8_t *)dst, (uint8_t *)dst + dst_size);
|
||||
flush_dcache_range((uint8_t *)src, (uint8_t *)src + src_size);
|
||||
unsigned int keyslot = mkey_get_keyslot(master_key_rev);
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_PACKAGE2KEY, keyslot, package2_key_source, 0x10);
|
||||
|
||||
/* Perform Encryption. */
|
||||
se_aes_ctr_crypt(KEYSLOT_SWITCH_PACKAGE2KEY, dst, dst_size, src, src_size, ctr, ctr_size);
|
||||
}
|
||||
|
||||
|
||||
static void verify_header_signature(package2_header_t *header) {
|
||||
const uint8_t *modulus;
|
||||
|
||||
if (configitem_is_retail()) {
|
||||
const uint8_t package2_modulus_retail[0x100] = {
|
||||
0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59,
|
||||
0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE,
|
||||
0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5,
|
||||
0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74,
|
||||
0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48,
|
||||
0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE,
|
||||
0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32,
|
||||
0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A,
|
||||
0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B,
|
||||
0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3,
|
||||
0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9,
|
||||
0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47,
|
||||
0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D,
|
||||
0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2,
|
||||
0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF,
|
||||
0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F
|
||||
};
|
||||
modulus = package2_modulus_retail;
|
||||
} else {
|
||||
const uint8_t package2_modulus_dev[0x100] = {
|
||||
0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99,
|
||||
0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7,
|
||||
0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6,
|
||||
0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38,
|
||||
0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83,
|
||||
0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0,
|
||||
0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6,
|
||||
0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50,
|
||||
0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7,
|
||||
0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42,
|
||||
0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E,
|
||||
0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8,
|
||||
0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7,
|
||||
0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4,
|
||||
0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46,
|
||||
0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B
|
||||
};
|
||||
modulus = package2_modulus_dev;
|
||||
}
|
||||
|
||||
/* This is normally only allowed on dev units, but we'll allow it anywhere. */
|
||||
if (bootconfig_is_package2_unsigned() == 0 && rsa2048_pss_verify(header->signature, 0x100, modulus, 0x100, header->encrypted_header, 0x100) == 0) {
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
|
||||
static bool validate_package2_metadata(package2_meta_t *metadata) {
|
||||
if (metadata->magic != MAGIC_PK21) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Package2 size, version number is stored XORed in header CTR. */
|
||||
/* Nintendo, what the fuck? */
|
||||
uint32_t package_size = metadata->ctr_dwords[0] ^ metadata->ctr_dwords[2] ^ metadata->ctr_dwords[3];
|
||||
uint8_t header_version = (uint8_t)((metadata->ctr_dwords[1] ^ (metadata->ctr_dwords[1] >> 16) ^ (metadata->ctr_dwords[1] >> 24)) & 0xFF);
|
||||
|
||||
/* Ensure package isn't too big or too small. */
|
||||
if (package_size <= sizeof(package2_header_t) || package_size > PACKAGE2_SIZE_MAX - sizeof(package2_header_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Validate that we're working with a header we know how to handle. */
|
||||
if (header_version > MASTERKEY_REVISION_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Require aligned entrypoint. */
|
||||
if (metadata->entrypoint & 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Validate section size sanity. */
|
||||
if (metadata->section_sizes[0] + metadata->section_sizes[1] + metadata->section_sizes[2] + sizeof(package2_header_t) != package_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool entrypoint_found = false;
|
||||
|
||||
/* Header has space for 4 sections, but only 3 are validated/potentially loaded on hardware. */
|
||||
for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) {
|
||||
/* Validate section size alignment. */
|
||||
if (metadata->section_sizes[section] & 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Validate section does not overflow. */
|
||||
if (check_32bit_additive_overflow(metadata->section_offsets[section], metadata->section_sizes[section])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check for entrypoint presence. */
|
||||
uint32_t section_end = metadata->section_offsets[section] + metadata->section_sizes[section];
|
||||
if (metadata->section_offsets[section] <= metadata->entrypoint && metadata->entrypoint < section_end) {
|
||||
entrypoint_found = true;
|
||||
}
|
||||
|
||||
/* Ensure no overlap with later sections. */
|
||||
for (unsigned int later_section = section + 1; later_section < PACKAGE2_SECTION_MAX; later_section++) {
|
||||
uint32_t later_section_end = metadata->section_offsets[later_section] + metadata->section_sizes[later_section];
|
||||
if (overlaps(metadata->section_offsets[section], section_end, metadata->section_offsets[later_section], later_section_end)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate section hashes. */
|
||||
void *section_data = (void *)((uint8_t *)NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS + sizeof(package2_header_t) + metadata->section_offsets[section]);
|
||||
uint8_t calculated_hash[0x20];
|
||||
se_calculate_sha256(calculated_hash, section_data, metadata->section_sizes[section]);
|
||||
if (memcmp(calculated_hash, metadata->section_hashes[section], sizeof(metadata->section_hashes[section])) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure that entrypoint is present in one of our sections. */
|
||||
if (!entrypoint_found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Perform version checks. */
|
||||
/* We will be compatible with all package2s released before current, but not newer ones. */
|
||||
if (metadata->version_max >= PACKAGE2_MINVER_THEORETICAL && metadata->version_min < PACKAGE2_MAXVER_400_CURRENT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Decrypts package2 header, and returns the master key revision required. */
|
||||
static uint32_t decrypt_and_validate_header(package2_header_t *header) {
|
||||
package2_meta_t metadata;
|
||||
|
||||
if (bootconfig_is_package2_plaintext() == 0) {
|
||||
uint32_t mkey_rev;
|
||||
|
||||
/* Try to decrypt for all possible master keys. */
|
||||
for (mkey_rev = 0; mkey_rev < MASTERKEY_REVISION_MAX; mkey_rev++) {
|
||||
package2_crypt_ctr(mkey_rev, &metadata, sizeof(package2_meta_t), &header->metadata, sizeof(package2_meta_t), header->metadata.ctr, sizeof(header->metadata.ctr));
|
||||
/* Copy the ctr (which stores information) into the decrypted metadata. */
|
||||
memcpy(metadata.ctr, header->metadata.ctr, sizeof(header->metadata.ctr));
|
||||
/* See if this is the correct key. */
|
||||
if (validate_package2_metadata(&metadata)) {
|
||||
header->metadata = metadata;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure we successfully decrypted the header. */
|
||||
generic_panic();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void load_package2_sections(package2_meta_t *metadata, uint32_t master_key_rev) {
|
||||
/* By default, copy data directly from where NX_BOOTLOADER puts it. */
|
||||
void *load_buf = NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS;
|
||||
|
||||
/* Check whether any of our sections overlap this region. If they do, we must relocate and copy from elsewhere. */
|
||||
bool needs_relocation = false;
|
||||
for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) {
|
||||
uint64_t section_start = DRAM_BASE_PHYSICAL + (uint64_t)metadata->section_offsets[section];
|
||||
uint64_t section_end = section_start + (uint64_t)metadata->section_sizes[section];
|
||||
if (overlaps(section_start, section_end, (uint64_t)(NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS), (uint64_t)(NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS) + PACKAGE2_SIZE_MAX)) {
|
||||
needs_relocation = true;
|
||||
}
|
||||
}
|
||||
if (needs_relocation) {
|
||||
/* This code should *always* succeed in finding a carveout within seven loops, */
|
||||
/* due to the section size limit, and section number limit. */
|
||||
/* However, Nintendo tries panics after 8 loops if a safe section is not found. */
|
||||
/* This should never be the case, mathematically. */
|
||||
/* We will replicate this behavior. */
|
||||
bool found_safe_carveout = false;
|
||||
uint64_t potential_base_start = DRAM_BASE_PHYSICAL;
|
||||
uint64_t potential_base_end = potential_base_start + PACKAGE2_SIZE_MAX;
|
||||
for (unsigned int i = 0; i < 8; i++) {
|
||||
int is_safe = 1;
|
||||
for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) {
|
||||
uint64_t section_start = DRAM_BASE_PHYSICAL + (uint64_t)metadata->section_offsets[section];
|
||||
uint64_t section_end = section_start + (uint64_t)metadata->section_sizes[section];
|
||||
if (overlaps(section_start, section_end, potential_base_start, potential_base_end)) {
|
||||
is_safe = 0;
|
||||
}
|
||||
}
|
||||
found_safe_carveout |= is_safe;
|
||||
if (found_safe_carveout) {
|
||||
break;
|
||||
}
|
||||
potential_base_start += PACKAGE2_SIZE_MAX;
|
||||
potential_base_end += PACKAGE2_SIZE_MAX;
|
||||
}
|
||||
if (!found_safe_carveout) {
|
||||
generic_panic();
|
||||
}
|
||||
/* Relocate to new carveout. */
|
||||
memcpy((void *)potential_base_start, load_buf, PACKAGE2_SIZE_MAX);
|
||||
memset(load_buf, 0, PACKAGE2_SIZE_MAX);
|
||||
load_buf = (void *)potential_base_start;
|
||||
}
|
||||
|
||||
/* Copy each section to its appropriate location, decrypting if necessary. */
|
||||
for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) {
|
||||
if (metadata->section_sizes[section] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
void *dst_start = (void *)(DRAM_BASE_PHYSICAL + (uint64_t)metadata->section_offsets[section]);
|
||||
void *src_start = load_buf + sizeof(package2_header_t) + metadata->section_offsets[section];
|
||||
size_t size = (size_t)metadata->section_sizes[section];
|
||||
|
||||
if (bootconfig_is_package2_plaintext()) {
|
||||
memcpy(dst_start, src_start, size);
|
||||
} else {
|
||||
package2_crypt_ctr(master_key_rev, dst_start, size, src_start, size, metadata->section_ctrs[section], 0x10);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear the encrypted package2 from memory. */
|
||||
memset(load_buf, 0, PACKAGE2_SIZE_MAX);
|
||||
}
|
||||
|
||||
uintptr_t get_pk2ldr_stack_address(void) {
|
||||
return tzram_get_segment_address(TZRAM_SEGMENT_ID_PK2LDR) + 0x2000;
|
||||
}
|
||||
|
||||
/* This function is called during coldboot init, and validates a package2. */
|
||||
/* This package2 is read into memory by a concurrent BPMP bootloader. */
|
||||
void load_package2(void) {
|
||||
/* Setup the Security Engine. */
|
||||
setup_se();
|
||||
|
||||
/* TODO: bootup_misc_mmio(). */
|
||||
/* This func will also be called on warmboot. */
|
||||
/* And will verify stored SE Test Vector, clear keyslots, */
|
||||
/* Generate an SRK, set the warmboot firmware location, */
|
||||
/* Configure the GPU uCode carveout, configure the Kernel default carveouts, */
|
||||
/* Initialize the PMC secure scratch registers, initialize MISC registers, */
|
||||
/* And assign "se_operation_completed" to Interrupt 0x5A. */
|
||||
|
||||
/* TODO: Read and save BOOTREASON stored by NX_BOOTLOADER at 0x1F009FE00 */
|
||||
|
||||
/* Initialize cache'd random bytes for kernel. */
|
||||
randomcache_init();
|
||||
|
||||
/* TODO: memclear the initial copy of Exosphere running in IRAM (relocated to TZRAM by earlier code). */
|
||||
|
||||
/* Let NX Bootloader know that we're running. */
|
||||
MAILBOX_NX_BOOTLOADER_IS_SECMON_AWAKE = 1;
|
||||
|
||||
/* Synchronize with NX BOOTLOADER. */
|
||||
if (MAILBOX_NX_BOOTLOADER_SETUP_STATE == NX_BOOTLOADER_STATE_INIT) {
|
||||
while (MAILBOX_NX_BOOTLOADER_SETUP_STATE < NX_BOOTLOADER_STATE_MOVED_BOOTCONFIG) {
|
||||
wait(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load Boot Config into global. */
|
||||
setup_boot_config();
|
||||
|
||||
/* Synchronize with NX BOOTLOADER. */
|
||||
if (MAILBOX_NX_BOOTLOADER_SETUP_STATE == NX_BOOTLOADER_STATE_MOVED_BOOTCONFIG) {
|
||||
while (MAILBOX_NX_BOOTLOADER_SETUP_STATE < NX_BOOTLOADER_STATE_LOADED_PACKAGE2) {
|
||||
wait(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load header from NX_BOOTLOADER-initialized DRAM. */
|
||||
package2_header_t header;
|
||||
flush_dcache_range((uint8_t *)NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS, (uint8_t *)NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS + sizeof(header));
|
||||
memcpy(&header, NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS, sizeof(header));
|
||||
flush_dcache_range((uint8_t *)&header, (uint8_t *)&header + sizeof(header));
|
||||
|
||||
/* Perform signature checks. */
|
||||
verify_header_signature(&header);
|
||||
|
||||
/* Decrypt header, get key revision required. */
|
||||
uint32_t package2_mkey_rev = decrypt_and_validate_header(&header);
|
||||
|
||||
/* Load Package2 Sections. */
|
||||
load_package2_sections(&header.metadata, package2_mkey_rev);
|
||||
|
||||
/* Clean up cache. */
|
||||
flush_dcache_all();
|
||||
invalidate_icache_inner_shareable();
|
||||
|
||||
/* Set CORE0 entrypoint for Package2. */
|
||||
set_core_entrypoint_and_context_id(0, DRAM_BASE_PHYSICAL + header.metadata.entrypoint, 0);
|
||||
|
||||
/* Synchronize with NX BOOTLOADER. */
|
||||
if (MAILBOX_NX_BOOTLOADER_SETUP_STATE == NX_BOOTLOADER_STATE_LOADED_PACKAGE2) {
|
||||
while (MAILBOX_NX_BOOTLOADER_SETUP_STATE < NX_BOOTLOADER_STATE_FINISHED) {
|
||||
wait(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: MISC register 0x1F0098C00 |= 0x2000; */
|
||||
|
||||
/* TODO: Update SCR_EL3 depending on value in Bootconfig. */
|
||||
}
|
||||
78
exosphere/src/package2.h
Normal file
78
exosphere/src/package2.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef EXOSPHERE_PACKAGE2_H
|
||||
#define EXOSPHERE_PACKAGE2_H
|
||||
|
||||
/* This is code responsible for validating a package2. Actual file reading is done by bootloader. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include "bootconfig.h"
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Physaddr 0x40002EF8 */
|
||||
#define MAILBOX_NX_BOOTLOADER_BASE (mmio_get_device_address(MMIO_DEVID_NXBOOTLOADER_MAILBOX))
|
||||
|
||||
#define MAILBOX_NX_BOOTLOADER_SETUP_STATE (*((volatile uint32_t *)(MAILBOX_NX_BOOTLOADER_BASE + 0xEF8ULL)))
|
||||
|
||||
#define NX_BOOTLOADER_STATE_INIT 0
|
||||
#define NX_BOOTLOADER_STATE_MOVED_BOOTCONFIG 1
|
||||
#define NX_BOOTLOADER_STATE_LOADED_PACKAGE2 2
|
||||
#define NX_BOOTLOADER_STATE_FINISHED 3
|
||||
|
||||
/* Physaddr 0x40002EFC */
|
||||
#define MAILBOX_NX_BOOTLOADER_IS_SECMON_AWAKE (*((volatile uint32_t *)(MAILBOX_NX_BOOTLOADER_BASE + 0xEFCULL)))
|
||||
|
||||
#define NX_BOOTLOADER_BOOTCONFIG_POINTER ((void *)(0x4003D000ULL))
|
||||
|
||||
#define NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS ((void *)(0xA9800000ULL))
|
||||
|
||||
#define DRAM_BASE_PHYSICAL (0x80000000ULL)
|
||||
|
||||
#define MAGIC_PK21 (0x31324B50)
|
||||
#define PACKAGE2_SIZE_MAX 0x7FC000
|
||||
#define PACKAGE2_SECTION_MAX 0x3
|
||||
|
||||
#define PACKAGE2_MINVER_THEORETICAL 0x0
|
||||
#define PACKAGE2_MAXVER_100 0x2
|
||||
#define PACKAGE2_MAXVER_200 0x3
|
||||
#define PACKAGE2_MAXVER_300 0x4
|
||||
#define PACKAGE2_MAXVER_302 0x5
|
||||
#define PACKAGE2_MAXVER_400_CURRENT 0x6
|
||||
|
||||
#define PACKAGE2_MINVER_100 0x3
|
||||
#define PACKAGE2_MINVER_200 0x4
|
||||
#define PACKAGE2_MINVER_300 0x5
|
||||
#define PACKAGE2_MINVER_302 0x6
|
||||
#define PACKAGE2_MINVER_400_CURRENT 0x7
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
union {
|
||||
uint8_t ctr[0x10];
|
||||
uint32_t ctr_dwords[0x4];
|
||||
};
|
||||
uint8_t section_ctrs[4][0x10];
|
||||
uint32_t magic;
|
||||
uint32_t entrypoint;
|
||||
uint32_t _0x58;
|
||||
uint8_t version_max; /* Must be > TZ value. */
|
||||
uint8_t version_min; /* Must be < TZ value. */
|
||||
uint16_t _0x5E;
|
||||
uint32_t section_sizes[4];
|
||||
uint32_t section_offsets[4];
|
||||
uint8_t section_hashes[4][0x20];
|
||||
} package2_meta_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint8_t signature[0x100];
|
||||
union {
|
||||
package2_meta_t metadata;
|
||||
uint8_t encrypted_header[0x100];
|
||||
};
|
||||
} package2_header_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
void load_package2(void);
|
||||
|
||||
#endif
|
||||
14
exosphere/src/pmc.h
Normal file
14
exosphere/src/pmc.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef EXOSPHERE_PMC_H
|
||||
#define EXOSPHERE_PMC_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Exosphere register definitions for the Tegra X1 PMC. */
|
||||
|
||||
#define PMC_BASE (mmio_get_device_address(MMIO_DEVID_RTC_PMC) + 0x400ULL)
|
||||
|
||||
#define APBDEV_PMC_PWRGATE_TOGGLE_0 (*((volatile uint32_t *)(PMC_BASE + 0x30)))
|
||||
#define APBDEV_PMC_PWRGATE_STATUS_0 (*((volatile uint32_t *)(PMC_BASE + 0x38)))
|
||||
|
||||
#endif
|
||||
73
exosphere/src/randomcache.c
Normal file
73
exosphere/src/randomcache.c
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "randomcache.h"
|
||||
#include "se.h"
|
||||
#include "cache.h"
|
||||
|
||||
/* TrustZone maintains a cache of random for the kernel. */
|
||||
/* So that requests can still be serviced even when a */
|
||||
/* usermode SMC is in progress. */
|
||||
|
||||
static uint8_t g_random_cache[0x400];
|
||||
static unsigned int g_random_cache_low = 0;
|
||||
static unsigned int g_random_cache_high = 0x3FF;
|
||||
|
||||
|
||||
void randomcache_refill_segment(unsigned int offset, unsigned int size) {
|
||||
if (offset + size >= 0x400) {
|
||||
size = 0x400 - offset;
|
||||
}
|
||||
|
||||
flush_dcache_range(&g_random_cache[offset], &g_random_cache[offset + size]);
|
||||
se_generate_random(KEYSLOT_SWITCH_RNGKEY, &g_random_cache[offset], size);
|
||||
flush_dcache_range(&g_random_cache[offset], &g_random_cache[offset + size]);
|
||||
|
||||
}
|
||||
|
||||
void randomcache_init(void) {
|
||||
randomcache_refill_segment(0, 0x400);
|
||||
g_random_cache_low = 0;
|
||||
g_random_cache_high = 0x3FF;
|
||||
}
|
||||
|
||||
void randomcache_refill(void) {
|
||||
unsigned int high_plus_one = (g_random_cache_high + 1) & 0x3FF;
|
||||
if (g_random_cache_low != high_plus_one) {
|
||||
/* Only refill if there's data to refill. */
|
||||
if (g_random_cache_low < high_plus_one) {
|
||||
/* NOTE: There is a bug in official code that causes this to not work properly. */
|
||||
/* In particular, official code checks whether high_plus_one == 0x400. */
|
||||
/* However, because high_plus_one is &= 0x3FF'd, this can never be the case. */
|
||||
/* We will implement according to Nintendo's intention, and not include their bug. */
|
||||
/* This should have no impact on actual observable results, anyway, since this data is random anyway... */
|
||||
|
||||
if (g_random_cache_high != 0x3FF) { /* This is if (true) in Nintendo's code due to the above bug. */
|
||||
randomcache_refill_segment(high_plus_one, 0x400 - high_plus_one);
|
||||
g_random_cache_high = (g_random_cache_high + 0x400 - high_plus_one) & 0x3FF;
|
||||
}
|
||||
|
||||
if (g_random_cache_low > 0) {
|
||||
randomcache_refill_segment(0, g_random_cache_low);
|
||||
g_random_cache_high = (g_random_cache_high + g_random_cache_low) & 0x3FF;
|
||||
}
|
||||
} else { /* g_random_cache_low > high_plus_one */
|
||||
randomcache_refill_segment(high_plus_one, g_random_cache_low - high_plus_one);
|
||||
g_random_cache_high = g_random_cache_low - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void randomcache_getbytes(void *dst, size_t num_bytes) {
|
||||
unsigned int low = g_random_cache_low;
|
||||
|
||||
memcpy(dst, &g_random_cache[low], num_bytes);
|
||||
|
||||
unsigned int new_low = low + num_bytes;
|
||||
if (new_low + 0x38 > 0x3FF) {
|
||||
new_low = 0;
|
||||
}
|
||||
|
||||
g_random_cache_low = new_low;
|
||||
}
|
||||
13
exosphere/src/randomcache.h
Normal file
13
exosphere/src/randomcache.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef EXOSPHERE_RANDOM_CACHE_H
|
||||
#define EXOSPHERE_RANDOM_CACHE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* This method must be called on startup. */
|
||||
void randomcache_init(void);
|
||||
void randomcache_refill(void);
|
||||
|
||||
void randomcache_getbytes(void *dst, size_t num_bytes);
|
||||
|
||||
|
||||
#endif
|
||||
596
exosphere/src/se.c
Normal file
596
exosphere/src/se.c
Normal file
@@ -0,0 +1,596 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "interrupt.h"
|
||||
#include "se.h"
|
||||
#include "memory_map.h"
|
||||
#include "cache.h"
|
||||
#include "se.h"
|
||||
|
||||
/* Macro for the SE registers. */
|
||||
#define SECURITY_ENGINE ((volatile security_engine_t *)(mmio_get_device_address(MMIO_DEVID_SE)))
|
||||
|
||||
void trigger_se_rsa_op(void *buf, size_t size);
|
||||
void trigger_se_blocking_op(unsigned int op, void *dst, size_t dst_size, const void *src, size_t src_size);
|
||||
|
||||
/* Globals for driver. */
|
||||
unsigned int (*g_se_callback)(void);
|
||||
|
||||
unsigned int g_se_modulus_sizes[KEYSLOT_RSA_MAX];
|
||||
unsigned int g_se_exp_sizes[KEYSLOT_RSA_MAX];
|
||||
|
||||
|
||||
/* Initialize a SE linked list. */
|
||||
void ll_init(se_ll_t *ll, void *buffer, size_t size) {
|
||||
ll->num_entries = 0; /* 1 Entry. */
|
||||
|
||||
if (buffer != NULL) {
|
||||
ll->addr_info.address = (uint32_t) get_physical_address(buffer);
|
||||
ll->addr_info.size = (uint32_t) size;
|
||||
} else {
|
||||
ll->addr_info.address = 0;
|
||||
ll->addr_info.size = 0;
|
||||
}
|
||||
|
||||
flush_dcache_range((uint8_t *)ll, (uint8_t *)ll + sizeof(*ll));
|
||||
}
|
||||
|
||||
void set_security_engine_callback(unsigned int (*callback)(void)) {
|
||||
if (callback == NULL || g_se_callback != NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
g_se_callback = callback;
|
||||
}
|
||||
|
||||
/* Fires on Security Engine operation completion. */
|
||||
void se_operation_completed(void) {
|
||||
SECURITY_ENGINE->INT_ENABLE_REG = 0;
|
||||
if (g_se_callback != NULL) {
|
||||
g_se_callback();
|
||||
g_se_callback = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void se_check_for_error(void) {
|
||||
if (SECURITY_ENGINE->INT_STATUS_REG & 0x10000 || SECURITY_ENGINE->FLAGS_REG & 3 || SECURITY_ENGINE->ERR_STATUS_REG) {
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
|
||||
void se_trigger_interrupt(void) {
|
||||
intr_set_pending(INTERRUPT_ID_USER_SECURITY_ENGINE);
|
||||
}
|
||||
|
||||
void se_verify_flags_cleared(void) {
|
||||
if (SECURITY_ENGINE->FLAGS_REG & 3) {
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
|
||||
void se_clear_interrupts(void) {
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
/* Set the flags for an AES keyslot. */
|
||||
void set_aes_keyslot_flags(unsigned int keyslot, unsigned int flags) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Misc flags. */
|
||||
if (flags & ~0x80) {
|
||||
SECURITY_ENGINE->AES_KEYSLOT_FLAGS[keyslot] = ~flags;
|
||||
}
|
||||
|
||||
/* Disable keyslot reads. */
|
||||
if (flags & 0x80) {
|
||||
SECURITY_ENGINE->AES_KEY_READ_DISABLE_REG &= ~(1 << keyslot);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the flags for an RSA keyslot. */
|
||||
void set_rsa_keyslot_flags(unsigned int keyslot, unsigned int flags) {
|
||||
if (keyslot >= KEYSLOT_RSA_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Misc flags. */
|
||||
if (flags & ~0x80) {
|
||||
/* TODO: Why are flags assigned this way? */
|
||||
SECURITY_ENGINE->RSA_KEYSLOT_FLAGS[keyslot] = (((flags >> 4) & 4) | (flags & 3)) ^ 7;
|
||||
}
|
||||
|
||||
/* Disable keyslot reads. */
|
||||
if (flags & 0x80) {
|
||||
SECURITY_ENGINE->RSA_KEY_READ_DISABLE_REG &= ~(1 << keyslot);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_aes_keyslot(unsigned int keyslot) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Zero out the whole keyslot and IV. */
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
SECURITY_ENGINE->AES_KEYTABLE_ADDR = (keyslot << 4) | i;
|
||||
SECURITY_ENGINE->AES_KEYTABLE_DATA = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_rsa_keyslot(unsigned int keyslot) {
|
||||
if (keyslot >= KEYSLOT_RSA_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Zero out the whole keyslot. */
|
||||
for (unsigned int i = 0; i < 0x40; i++) {
|
||||
/* Select Keyslot Modulus[i] */
|
||||
SECURITY_ENGINE->RSA_KEYTABLE_ADDR = (keyslot << 7) | i | 0x40;
|
||||
SECURITY_ENGINE->RSA_KEYTABLE_DATA = 0;
|
||||
}
|
||||
for (unsigned int i = 0; i < 0x40; i++) {
|
||||
/* Select Keyslot Expontent[i] */
|
||||
SECURITY_ENGINE->RSA_KEYTABLE_ADDR = (keyslot << 7) | i;
|
||||
SECURITY_ENGINE->RSA_KEYTABLE_DATA = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void set_aes_keyslot(unsigned int keyslot, const void *key, size_t key_size) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX || key_size > KEYSIZE_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (key_size >> 2); i++) {
|
||||
SECURITY_ENGINE->AES_KEYTABLE_ADDR = (keyslot << 4) | i;
|
||||
SECURITY_ENGINE->AES_KEYTABLE_DATA = read32le(key, 4 * i);
|
||||
}
|
||||
}
|
||||
|
||||
void set_rsa_keyslot(unsigned int keyslot, const void *modulus, size_t modulus_size, const void *exponent, size_t exp_size) {
|
||||
if (keyslot >= KEYSLOT_RSA_MAX || modulus_size > KEYSIZE_RSA_MAX || exp_size > KEYSIZE_RSA_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (modulus_size >> 2); i++) {
|
||||
SECURITY_ENGINE->RSA_KEYTABLE_ADDR = (keyslot << 7) | 0x40 | i;
|
||||
SECURITY_ENGINE->RSA_KEYTABLE_DATA = read32be(modulus, 4 * i);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (exp_size >> 2); i++) {
|
||||
SECURITY_ENGINE->RSA_KEYTABLE_ADDR = (keyslot << 7) | i;
|
||||
SECURITY_ENGINE->RSA_KEYTABLE_DATA = read32be(exponent, 4 * i);
|
||||
}
|
||||
|
||||
g_se_modulus_sizes[keyslot] = modulus_size;
|
||||
g_se_exp_sizes[keyslot] = exp_size;
|
||||
}
|
||||
|
||||
void set_aes_keyslot_iv(unsigned int keyslot, const void *iv, size_t iv_size) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX || iv_size > 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (iv_size >> 2); i++) {
|
||||
SECURITY_ENGINE->AES_KEYTABLE_ADDR = (keyslot << 4) | 8 | i;
|
||||
SECURITY_ENGINE->AES_KEYTABLE_DATA = read32le(iv, 4 * i);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_aes_keyslot_iv(unsigned int keyslot) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < (0x10 >> 2); i++) {
|
||||
SECURITY_ENGINE->AES_KEYTABLE_ADDR = (keyslot << 4) | 8;
|
||||
SECURITY_ENGINE->AES_KEYTABLE_DATA = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void set_se_ctr(const void *ctr) {
|
||||
for (unsigned int i = 0; i < 4; i++) {
|
||||
SECURITY_ENGINE->CRYPTO_CTR_REG[i] = read32le(ctr, i * 4);
|
||||
}
|
||||
}
|
||||
|
||||
void decrypt_data_into_keyslot(unsigned int keyslot_dst, unsigned int keyslot_src, const void *wrapped_key, size_t wrapped_key_size) {
|
||||
if (keyslot_dst >= KEYSLOT_AES_MAX || keyslot_src >= KEYSIZE_AES_MAX || wrapped_key_size > KEYSIZE_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_DEC | DST_KEYTAB);
|
||||
SECURITY_ENGINE->CRYPTO_REG = keyslot_src << 24;
|
||||
SECURITY_ENGINE->BLOCK_COUNT_REG = 0;
|
||||
SECURITY_ENGINE->CRYPTO_KEYTABLE_DST_REG = keyslot_dst << 8;
|
||||
|
||||
flush_dcache_range(wrapped_key, (const uint8_t *)wrapped_key + wrapped_key_size);
|
||||
/* TODO: trigger_se_aes_op(OP_START, NULL, 0, wrapped_key, wrapped_key_size); */
|
||||
}
|
||||
|
||||
void se_aes_crypt_insecure_internal(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, unsigned int crypt_config, bool encrypt, unsigned int (*callback)(void)) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Setup Config register. */
|
||||
if (encrypt) {
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_ENC | DST_MEMORY);
|
||||
} else {
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_DEC | DST_MEMORY);
|
||||
}
|
||||
|
||||
/* Setup Crypto register. */
|
||||
SECURITY_ENGINE->CRYPTO_REG = crypt_config | (keyslot << 24) | (encrypt << 8);
|
||||
|
||||
/* Mark this encryption as insecure -- this makes the SE not a secure busmaster. */
|
||||
SECURITY_ENGINE->CRYPTO_REG |= 0x80000000;
|
||||
|
||||
/* Appropriate number of blocks. */
|
||||
SECURITY_ENGINE->BLOCK_COUNT_REG = (size >> 4) - 1;
|
||||
|
||||
/* Set the callback, for after the async operation. */
|
||||
set_security_engine_callback(callback);
|
||||
|
||||
/* Enable SE Interrupt firing for async op. */
|
||||
SECURITY_ENGINE->INT_ENABLE_REG = 0x10;
|
||||
|
||||
/* Setup Input/Output lists */
|
||||
SECURITY_ENGINE->IN_LL_ADDR_REG = in_ll_paddr;
|
||||
SECURITY_ENGINE->OUT_LL_ADDR_REG = out_ll_paddr;
|
||||
|
||||
/* Set registers for operation. */
|
||||
SECURITY_ENGINE->ERR_STATUS_REG = SECURITY_ENGINE->ERR_STATUS_REG;
|
||||
SECURITY_ENGINE->INT_STATUS_REG = SECURITY_ENGINE->INT_STATUS_REG;
|
||||
SECURITY_ENGINE->OPERATION_REG = 1;
|
||||
|
||||
/* Ensure writes go through. */
|
||||
__asm__ __volatile__ ("dsb ish" : : : "memory");
|
||||
}
|
||||
|
||||
void se_aes_ctr_crypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *ctr, unsigned int (*callback)(void)) {
|
||||
/* Unknown what this write does, but official code writes it for CTR mode. */
|
||||
SECURITY_ENGINE->_0x80C = 1;
|
||||
set_se_ctr(ctr);
|
||||
se_aes_crypt_insecure_internal(keyslot, out_ll_paddr, in_ll_paddr, size, 0x81E, true, callback);
|
||||
}
|
||||
|
||||
void se_aes_cbc_encrypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *iv, unsigned int (*callback)(void)) {
|
||||
set_aes_keyslot_iv(keyslot, iv, 0x10);
|
||||
se_aes_crypt_insecure_internal(keyslot, out_ll_paddr, in_ll_paddr, size, 0x44, true, callback);
|
||||
}
|
||||
|
||||
void se_aes_cbc_decrypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *iv, unsigned int (*callback)(void)) {
|
||||
set_aes_keyslot_iv(keyslot, iv, 0x10);
|
||||
se_aes_crypt_insecure_internal(keyslot, out_ll_paddr, in_ll_paddr, size, 0x66, false, callback);
|
||||
}
|
||||
|
||||
|
||||
void se_exp_mod(unsigned int keyslot, void *buf, size_t size, unsigned int (*callback)(void)) {
|
||||
uint8_t stack_buf[KEYSIZE_RSA_MAX];
|
||||
|
||||
if (keyslot >= KEYSLOT_RSA_MAX || size > KEYSIZE_RSA_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Endian swap the input. */
|
||||
for (size_t i = size; i > 0; i--) {
|
||||
stack_buf[i] = *((uint8_t *)buf + size - i);
|
||||
}
|
||||
|
||||
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_RSA | DST_RSAREG);
|
||||
SECURITY_ENGINE->RSA_CONFIG = keyslot << 24;
|
||||
SECURITY_ENGINE->RSA_KEY_SIZE_REG = (g_se_modulus_sizes[keyslot] >> 6) - 1;
|
||||
SECURITY_ENGINE->RSA_EXP_SIZE_REG = g_se_exp_sizes[keyslot] >> 2;
|
||||
|
||||
set_security_engine_callback(callback);
|
||||
|
||||
/* Enable SE Interrupt firing for async op. */
|
||||
SECURITY_ENGINE->INT_ENABLE_REG = 0x10;
|
||||
|
||||
flush_dcache_range(stack_buf, stack_buf + KEYSIZE_RSA_MAX);
|
||||
trigger_se_rsa_op(stack_buf, size);
|
||||
|
||||
while (!(SECURITY_ENGINE->INT_STATUS_REG & 2)) { /* Wait a while */ }
|
||||
}
|
||||
|
||||
void se_synchronous_exp_mod(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
uint8_t stack_buf[KEYSIZE_RSA_MAX];
|
||||
|
||||
if (keyslot >= KEYSLOT_RSA_MAX || src_size > KEYSIZE_RSA_MAX || dst_size > KEYSIZE_RSA_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Endian swap the input. */
|
||||
for (size_t i = src_size; i > 0; i--) {
|
||||
stack_buf[i] = *((uint8_t *)src + src_size - i);
|
||||
}
|
||||
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_RSA | DST_RSAREG);
|
||||
SECURITY_ENGINE->RSA_CONFIG = keyslot << 24;
|
||||
SECURITY_ENGINE->RSA_KEY_SIZE_REG = (g_se_modulus_sizes[keyslot] >> 6) - 1;
|
||||
SECURITY_ENGINE->RSA_EXP_SIZE_REG = g_se_exp_sizes[keyslot] >> 2;
|
||||
|
||||
|
||||
flush_dcache_range(stack_buf, stack_buf + KEYSIZE_RSA_MAX);
|
||||
trigger_se_blocking_op(1, NULL, 0, stack_buf, src_size);
|
||||
se_get_exp_mod_output(dst, dst_size);
|
||||
}
|
||||
|
||||
void se_get_exp_mod_output(void *buf, size_t size) {
|
||||
size_t num_dwords = (size >> 2);
|
||||
if (num_dwords < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t *p_out = ((uint32_t *)buf) + num_dwords - 1;
|
||||
uint32_t offset = 0;
|
||||
|
||||
/* Copy endian swapped output. */
|
||||
while (num_dwords) {
|
||||
*p_out = read32be(SECURITY_ENGINE->RSA_OUTPUT, offset);
|
||||
offset += 4;
|
||||
p_out--;
|
||||
num_dwords--;
|
||||
}
|
||||
}
|
||||
|
||||
void trigger_se_rsa_op(void *buf, size_t size) {
|
||||
se_ll_t in_ll;
|
||||
ll_init(&in_ll, (void *)buf, size);
|
||||
|
||||
/* Set the input LL. */
|
||||
SECURITY_ENGINE->IN_LL_ADDR_REG = get_physical_address(&in_ll);
|
||||
|
||||
/* Set registers for operation. */
|
||||
SECURITY_ENGINE->ERR_STATUS_REG = SECURITY_ENGINE->ERR_STATUS_REG;
|
||||
SECURITY_ENGINE->INT_STATUS_REG = SECURITY_ENGINE->INT_STATUS_REG;
|
||||
SECURITY_ENGINE->OPERATION_REG = 1;
|
||||
|
||||
/* Ensure writes go through. */
|
||||
__asm__ __volatile__ ("dsb ish" : : : "memory");
|
||||
}
|
||||
|
||||
void trigger_se_blocking_op(unsigned int op, void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
se_ll_t in_ll;
|
||||
se_ll_t out_ll;
|
||||
|
||||
ll_init(&in_ll, (void *)src, src_size);
|
||||
ll_init(&out_ll, dst, dst_size);
|
||||
|
||||
/* Set the LLs. */
|
||||
SECURITY_ENGINE->IN_LL_ADDR_REG = get_physical_address(&in_ll);
|
||||
SECURITY_ENGINE->OUT_LL_ADDR_REG = get_physical_address(&out_ll);
|
||||
|
||||
/* Set registers for operation. */
|
||||
SECURITY_ENGINE->ERR_STATUS_REG = SECURITY_ENGINE->ERR_STATUS_REG;
|
||||
SECURITY_ENGINE->INT_STATUS_REG = SECURITY_ENGINE->INT_STATUS_REG;
|
||||
SECURITY_ENGINE->OPERATION_REG = op;
|
||||
|
||||
while (!(SECURITY_ENGINE->INT_STATUS_REG & 0x10)) { /* Wait a while */ }
|
||||
se_check_for_error();
|
||||
}
|
||||
|
||||
|
||||
/* Secure AES Functionality. */
|
||||
void se_perform_aes_block_operation(void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
uint8_t block[0x10];
|
||||
|
||||
if (src_size > sizeof(block) || dst_size > sizeof(block)) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Load src data into block. */
|
||||
memset(block, 0, sizeof(block));
|
||||
memcpy(block, src, src_size);
|
||||
flush_dcache_range(block, block + sizeof(block));
|
||||
|
||||
/* Trigger AES operation. */
|
||||
SECURITY_ENGINE->BLOCK_COUNT_REG = 0;
|
||||
trigger_se_blocking_op(1, block, sizeof(block), block, sizeof(block));
|
||||
|
||||
/* Copy output data into dst. */
|
||||
flush_dcache_range(block, block + sizeof(block));
|
||||
memcpy(dst, block, dst_size);
|
||||
}
|
||||
|
||||
void se_aes_ctr_crypt(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size, const void *ctr, size_t ctr_size) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX || ctr_size != 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
unsigned int num_blocks = src_size >> 4;
|
||||
|
||||
/* Unknown what this write does, but official code writes it for CTR mode. */
|
||||
SECURITY_ENGINE->_0x80C = 1;
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_ENC | DST_MEMORY);
|
||||
SECURITY_ENGINE->CRYPTO_REG = (keyslot << 24) | 0x91E;
|
||||
set_se_ctr(ctr);
|
||||
|
||||
/* Handle any aligned blocks. */
|
||||
size_t aligned_size = (size_t)num_blocks << 4;
|
||||
if (aligned_size) {
|
||||
SECURITY_ENGINE->BLOCK_COUNT_REG = num_blocks - 1;
|
||||
trigger_se_blocking_op(1, dst, dst_size, src, aligned_size);
|
||||
}
|
||||
|
||||
/* Handle final, unaligned block. */
|
||||
if (aligned_size < dst_size && aligned_size < src_size) {
|
||||
size_t last_block_size = dst_size - aligned_size;
|
||||
if (src_size < dst_size) {
|
||||
last_block_size = src_size - aligned_size;
|
||||
}
|
||||
se_perform_aes_block_operation(dst + aligned_size, last_block_size, (uint8_t *)src + aligned_size, src_size - aligned_size);
|
||||
}
|
||||
}
|
||||
|
||||
void se_aes_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size, unsigned int config_high) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX || dst_size != 0x10 || src_size != 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Set configuration high (256-bit vs 128-bit) based on parameter. */
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_ENC | DST_MEMORY) | (config_high << 16);
|
||||
SECURITY_ENGINE->CRYPTO_REG = keyslot << 24;
|
||||
se_perform_aes_block_operation(dst, 0x10, src, 0x10);
|
||||
|
||||
}
|
||||
|
||||
void se_aes_128_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
se_aes_ecb_encrypt_block(keyslot, dst, dst_size, src, src_size, 0);
|
||||
}
|
||||
|
||||
void se_aes_256_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
se_aes_ecb_encrypt_block(keyslot, dst, dst_size, src, src_size, 0x202);
|
||||
}
|
||||
|
||||
|
||||
void se_aes_ecb_decrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX || dst_size != 0x10 || src_size != 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_DEC | DST_MEMORY);
|
||||
SECURITY_ENGINE->CRYPTO_REG = keyslot << 24;
|
||||
se_perform_aes_block_operation(dst, 0x10, src, 0x10);
|
||||
}
|
||||
|
||||
void shift_left_xor_rb(uint8_t *key) {
|
||||
uint8_t prev_high_bit = 0;
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
uint8_t cur_byte = key[0xF - i];
|
||||
key[0xF - i] = (cur_byte << 1) | (prev_high_bit);
|
||||
prev_high_bit = cur_byte >> 7;
|
||||
}
|
||||
if (prev_high_bit) {
|
||||
key[0xF] ^= 0x87;
|
||||
}
|
||||
}
|
||||
|
||||
void se_compute_aes_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size, unsigned int config_high) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Generate the derived key, to be XOR'd with final output block. */
|
||||
uint8_t derived_key[0x10];
|
||||
memset(derived_key, 0, sizeof(derived_key));
|
||||
se_aes_128_ecb_encrypt_block(keyslot, derived_key, sizeof(derived_key), derived_key, sizeof(derived_key));
|
||||
shift_left_xor_rb(derived_key);
|
||||
if (data_size & 0xF) {
|
||||
shift_left_xor_rb(derived_key);
|
||||
}
|
||||
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_AES_ENC | DST_HASHREG) | (config_high << 16);
|
||||
SECURITY_ENGINE->CRYPTO_REG = (keyslot << 24) | (0x145);
|
||||
clear_aes_keyslot_iv(keyslot);
|
||||
|
||||
unsigned int num_blocks = (data_size + 0xF) >> 4;
|
||||
/* Handle aligned blocks. */
|
||||
if (num_blocks > 1) {
|
||||
SECURITY_ENGINE->BLOCK_COUNT_REG = num_blocks - 2;
|
||||
trigger_se_blocking_op(1, NULL, 0, data, data_size);
|
||||
SECURITY_ENGINE->CRYPTO_REG |= 0x80;
|
||||
}
|
||||
|
||||
/* Create final block. */
|
||||
uint8_t last_block[0x10];
|
||||
memset(last_block, 0, sizeof(last_block));
|
||||
if (data_size & 0xF) {
|
||||
|
||||
memcpy(last_block, data + (data_size & ~0xF), data_size & 0xF);
|
||||
last_block[data_size & 0xF] = 0x80; /* Last block = data || 100...0 */
|
||||
} else if (data_size >= 0x10) {
|
||||
memcpy(last_block, data + data_size - 0x10, 0x10);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
last_block[i] ^= derived_key[i];
|
||||
}
|
||||
|
||||
/* Perform last operation. */
|
||||
flush_dcache_range(last_block, last_block + sizeof(last_block));
|
||||
trigger_se_blocking_op(1, NULL, 0, last_block, sizeof(last_block));
|
||||
|
||||
/* Copy output CMAC. */
|
||||
for (unsigned int i = 0; i < (cmac_size >> 2); i++) {
|
||||
((uint32_t *)cmac)[i] = read32le(SECURITY_ENGINE->HASH_RESULT_REG, i << 2);
|
||||
}
|
||||
}
|
||||
|
||||
void se_compute_aes_128_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size) {
|
||||
se_compute_aes_cmac(keyslot, cmac, cmac_size, data, data_size, 0);
|
||||
}
|
||||
void se_compute_aes_256_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size) {
|
||||
se_compute_aes_cmac(keyslot, cmac, cmac_size, data, data_size, 0x202);
|
||||
}
|
||||
|
||||
/* SHA256 Implementation. */
|
||||
void se_calculate_sha256(void *dst, const void *src, size_t src_size) {
|
||||
/* Setup config for SHA256, size = BITS(src_size) */
|
||||
SECURITY_ENGINE->CONFIG_REG = (ENCMODE_SHA256 | ALG_SHA | DST_HASHREG);
|
||||
SECURITY_ENGINE->SHA_CONFIG_REG = 1;
|
||||
SECURITY_ENGINE->SHA_MSG_LENGTH_REG = (unsigned int)(src_size << 3);
|
||||
SECURITY_ENGINE->_0x20C = 0;
|
||||
SECURITY_ENGINE->_0x210 = 0;
|
||||
SECURITY_ENGINE->SHA_MSG_LEFT_REG = 0;
|
||||
SECURITY_ENGINE->_0x218 = (unsigned int)(src_size << 3);
|
||||
SECURITY_ENGINE->_0x21C = 0;
|
||||
SECURITY_ENGINE->_0x220 = 0;
|
||||
SECURITY_ENGINE->_0x224 = 0;
|
||||
|
||||
/* Trigger the operation. */
|
||||
trigger_se_blocking_op(1, NULL, 0, src, src_size);
|
||||
|
||||
/* Copy output hash. */
|
||||
for (unsigned int i = 0; i < (0x20 >> 2); i++) {
|
||||
((uint32_t *)dst)[i] = read32be(SECURITY_ENGINE->HASH_RESULT_REG, i << 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* RNG API */
|
||||
void se_initialize_rng(unsigned int keyslot) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* To initialize the RNG, we'll perform an RNG operation into an output buffer. */
|
||||
/* This will be discarded, when done. */
|
||||
uint8_t output_buf[0x10];
|
||||
|
||||
SECURITY_ENGINE->RNG_SRC_CONFIG_REG = 3; /* Entropy enable + Entropy lock enable */
|
||||
SECURITY_ENGINE->RNG_RESEED_INTERVAL_REG = 70001;
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_RNG | DST_MEMORY);
|
||||
SECURITY_ENGINE->CRYPTO_REG = (keyslot << 24) | 0x108;
|
||||
SECURITY_ENGINE->RNG_CONFIG_REG = 5;
|
||||
SECURITY_ENGINE->BLOCK_COUNT_REG = 0;
|
||||
trigger_se_blocking_op(1, output_buf, 0x10, NULL, 0);
|
||||
}
|
||||
|
||||
void se_generate_random(unsigned int keyslot, void *dst, size_t size) {
|
||||
if (keyslot >= KEYSLOT_AES_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
uint32_t num_blocks = size >> 4;
|
||||
size_t aligned_size = num_blocks << 4;
|
||||
SECURITY_ENGINE->CONFIG_REG = (ALG_RNG | DST_MEMORY);
|
||||
SECURITY_ENGINE->CRYPTO_REG = (keyslot << 24) | 0x108;
|
||||
SECURITY_ENGINE->RNG_CONFIG_REG = 4;
|
||||
|
||||
if (num_blocks >= 1) {
|
||||
SECURITY_ENGINE->BLOCK_COUNT_REG = num_blocks - 1;
|
||||
trigger_se_blocking_op(1, dst, aligned_size, NULL, 0);
|
||||
}
|
||||
if (size > aligned_size) {
|
||||
se_perform_aes_block_operation(dst + aligned_size, size - aligned_size, NULL, 0);
|
||||
}
|
||||
|
||||
}
|
||||
193
exosphere/src/se.h
Normal file
193
exosphere/src/se.h
Normal file
@@ -0,0 +1,193 @@
|
||||
#ifndef EXOSPHERE_SE_H
|
||||
#define EXOSPHERE_SE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Exosphere driver for the Tegra X1 security engine. */
|
||||
|
||||
#define KEYSLOT_SWITCH_PACKAGE2KEY 0x8
|
||||
#define KEYSLOT_SWITCH_TEMPKEY 0x9
|
||||
#define KEYSLOT_SWITCH_SESSIONKEY 0xA
|
||||
#define KEYSLOT_SWITCH_RNGKEY 0xB
|
||||
#define KEYSLOT_SWITCH_MASTERKEY 0xC
|
||||
#define KEYSLOT_SWITCH_DEVICEKEY 0xD
|
||||
|
||||
/* This keyslot was added in 4.0.0. */
|
||||
#define KEYSLOT_SWITCH_4XNEWDEVICEKEYGENKEY 0xD
|
||||
#define KEYSLOT_SWITCH_4XNEWCONSOLEKEYGENKEY 0xE
|
||||
#define KEYSLOT_SWITCH_4XOLDDEVICEKEY 0xF
|
||||
|
||||
#define KEYSLOT_AES_MAX 0x10
|
||||
#define KEYSLOT_RSA_MAX 0x2
|
||||
|
||||
#define KEYSIZE_AES_MAX 0x20
|
||||
#define KEYSIZE_RSA_MAX 0x100
|
||||
|
||||
#define ALG_SHIFT (12)
|
||||
#define ALG_DEC_SHIFT (8)
|
||||
#define ALG_NOP (0 << ALG_SHIFT)
|
||||
#define ALG_AES_ENC (1 << ALG_SHIFT)
|
||||
#define ALG_AES_DEC ((1 << ALG_DEC_SHIFT) | ALG_NOP)
|
||||
#define ALG_RNG (2 << ALG_SHIFT)
|
||||
#define ALG_SHA (3 << ALG_SHIFT)
|
||||
#define ALG_RSA (4 << ALG_SHIFT)
|
||||
|
||||
#define DST_SHIFT (2)
|
||||
#define DST_MEMORY (0 << DST_SHIFT)
|
||||
#define DST_HASHREG (1 << DST_SHIFT)
|
||||
#define DST_KEYTAB (2 << DST_SHIFT)
|
||||
#define DST_SRK (3 << DST_SHIFT)
|
||||
#define DST_RSAREG (4 << DST_SHIFT)
|
||||
|
||||
#define ENCMODE_SHIFT (24)
|
||||
#define DECMODE_SHIFT (16)
|
||||
#define ENCMODE_SHA256 (5 << ENCMODE_SHIFT)
|
||||
|
||||
#define HASH_DISABLE (0x0)
|
||||
#define HASH_ENABLE (0x1)
|
||||
|
||||
#define OP_ABORT 0
|
||||
#define OP_START 1
|
||||
#define OP_RESTART 2
|
||||
#define OP_CTX_SAVE 3
|
||||
#define OP_RESTART_IN 4
|
||||
|
||||
#define RSA_2048_BYTES 0x100
|
||||
|
||||
typedef struct security_engine {
|
||||
unsigned int _0x0;
|
||||
unsigned int _0x4;
|
||||
unsigned int OPERATION_REG;
|
||||
unsigned int INT_ENABLE_REG;
|
||||
unsigned int INT_STATUS_REG;
|
||||
unsigned int CONFIG_REG;
|
||||
unsigned int IN_LL_ADDR_REG;
|
||||
unsigned int _0x1C;
|
||||
unsigned int _0x20;
|
||||
unsigned int OUT_LL_ADDR_REG;
|
||||
unsigned int _0x28;
|
||||
unsigned int _0x2C;
|
||||
unsigned char HASH_RESULT_REG[0x20];
|
||||
unsigned char _0x50[0x1B0];
|
||||
unsigned int SHA_CONFIG_REG;
|
||||
unsigned int SHA_MSG_LENGTH_REG;
|
||||
unsigned int _0x20C;
|
||||
unsigned int _0x210;
|
||||
unsigned int SHA_MSG_LEFT_REG;
|
||||
unsigned int _0x218;
|
||||
unsigned int _0x21C;
|
||||
unsigned int _0x220;
|
||||
unsigned int _0x224;
|
||||
unsigned char _0x228[0x5C];
|
||||
unsigned int AES_KEY_READ_DISABLE_REG;
|
||||
unsigned int AES_KEYSLOT_FLAGS[0x10];
|
||||
unsigned char _0x2C4[0x3C];
|
||||
unsigned int _0x300;
|
||||
unsigned int CRYPTO_REG;
|
||||
unsigned int CRYPTO_CTR_REG[4];
|
||||
unsigned int BLOCK_COUNT_REG;
|
||||
unsigned int AES_KEYTABLE_ADDR;
|
||||
unsigned int AES_KEYTABLE_DATA;
|
||||
unsigned int _0x324;
|
||||
unsigned int _0x328;
|
||||
unsigned int _0x32C;
|
||||
unsigned int CRYPTO_KEYTABLE_DST_REG;
|
||||
unsigned char _0x334[0xC];
|
||||
unsigned int RNG_CONFIG_REG;
|
||||
unsigned int RNG_SRC_CONFIG_REG;
|
||||
unsigned int RNG_RESEED_INTERVAL_REG;
|
||||
unsigned char _0x34C[0xB4];
|
||||
unsigned int RSA_CONFIG;
|
||||
unsigned int RSA_KEY_SIZE_REG;
|
||||
unsigned int RSA_EXP_SIZE_REG;
|
||||
unsigned int RSA_KEY_READ_DISABLE_REG;
|
||||
unsigned int RSA_KEYSLOT_FLAGS[2];
|
||||
unsigned int _0x418;
|
||||
unsigned int _0x41C;
|
||||
unsigned int RSA_KEYTABLE_ADDR;
|
||||
unsigned int RSA_KEYTABLE_DATA;
|
||||
unsigned char RSA_OUTPUT[0x100];
|
||||
unsigned char _0x528[0x2D8];
|
||||
unsigned int FLAGS_REG;
|
||||
unsigned int ERR_STATUS_REG;
|
||||
unsigned int _0x808;
|
||||
unsigned int _0x80C;
|
||||
unsigned int _0x810;
|
||||
unsigned int _0x814;
|
||||
unsigned int _0x818;
|
||||
unsigned int _0x81C;
|
||||
unsigned char _0x820[0x17E0];
|
||||
} security_engine_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t address;
|
||||
uint32_t size;
|
||||
} se_addr_info_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t num_entries; /* Set to total entries - 1 */
|
||||
se_addr_info_t addr_info; /* This should really be an array...but for our use case it works. */
|
||||
} se_ll_t;
|
||||
|
||||
/* TODO: Define constants for the C driver. */
|
||||
|
||||
|
||||
/* WIP, API subject to change. */
|
||||
|
||||
|
||||
static inline volatile security_engine_t *get_security_engine_address(void) {
|
||||
return (volatile security_engine_t *)(mmio_get_device_address(MMIO_DEVID_SE));
|
||||
}
|
||||
|
||||
/* This function MUST be registered to fire on the appropriate interrupt. */
|
||||
void se_operation_completed(void);
|
||||
|
||||
void se_check_for_error(void);
|
||||
void se_trigger_interrupt(void);
|
||||
void se_clear_interrupts(void); /* TODO */
|
||||
|
||||
void se_verify_flags_cleared(void);
|
||||
|
||||
void set_aes_keyslot_flags(unsigned int keyslot, unsigned int flags);
|
||||
void set_rsa_keyslot_flags(unsigned int keyslot, unsigned int flags);
|
||||
void clear_aes_keyslot(unsigned int keyslot);
|
||||
void clear_rsa_keyslot(unsigned int keyslot);
|
||||
|
||||
void set_aes_keyslot(unsigned int keyslot, const void *key, size_t key_size);
|
||||
void decrypt_data_into_keyslot(unsigned int keyslot_dst, unsigned int keyslot_src, const void *wrapped_key, size_t wrapped_key_size);
|
||||
void set_rsa_keyslot(unsigned int keyslot, const void *modulus, size_t modulus_size, const void *exponent, size_t exp_size);
|
||||
void set_aes_keyslot_iv(unsigned int keyslot, const void *iv, size_t iv_size);
|
||||
void set_se_ctr(const void *ctr);
|
||||
|
||||
|
||||
/* Insecure AES API */
|
||||
void se_aes_ctr_crypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *ctr, unsigned int (*callback)(void));
|
||||
void se_aes_cbc_encrypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *iv, unsigned int (*callback)(void));
|
||||
void se_aes_cbc_decrypt_insecure(unsigned int keyslot, uint32_t out_ll_paddr, uint32_t in_ll_paddr, size_t size, const void *iv, unsigned int (*callback)(void));
|
||||
|
||||
/* Secure AES API */
|
||||
void se_compute_aes_128_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size);
|
||||
void se_compute_aes_256_cmac(unsigned int keyslot, void *cmac, size_t cmac_size, const void *data, size_t data_size);
|
||||
void se_aes_128_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size);
|
||||
void se_aes_256_ecb_encrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size);
|
||||
void se_aes_ctr_crypt(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size, const void *ctr, size_t ctr_size);
|
||||
void se_aes_ecb_decrypt_block(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size);
|
||||
|
||||
/* Hash API */
|
||||
void se_calculate_sha256(void *dst, const void *src, size_t src_size);
|
||||
|
||||
/* RSA API */
|
||||
void se_exp_mod(unsigned int keyslot, void *buf, size_t size, unsigned int (*callback)(void));
|
||||
void se_get_exp_mod_output(void *buf, size_t size);
|
||||
void se_synchronous_exp_mod(unsigned int keyslot, void *dst, size_t dst_size, const void *src, size_t src_size);
|
||||
|
||||
/* RNG API */
|
||||
void se_initialize_rng(unsigned int keyslot);
|
||||
void se_generate_random(unsigned int keyslot, void *dst, size_t size);
|
||||
|
||||
/* TODO: SE context save API. */
|
||||
|
||||
#endif /* EXOSPHERE_SE_H */
|
||||
63
exosphere/src/sealedkeys.c
Normal file
63
exosphere/src/sealedkeys.c
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "sealedkeys.h"
|
||||
#include "se.h"
|
||||
|
||||
const uint8_t g_titlekey_seal_key_source[0x10] = {
|
||||
0xCB, 0xB7, 0x6E, 0x38, 0xA1, 0xCB, 0x77, 0x0F, 0xB2, 0xA5, 0xB2, 0x9D, 0xD8, 0x56, 0x9F, 0x76
|
||||
};
|
||||
|
||||
|
||||
const uint8_t g_seal_key_sources[CRYPTOUSECASE_MAX][0x10] = {
|
||||
{0xF4, 0x0C, 0x16, 0x26, 0x0D, 0x46, 0x3B, 0xE0, 0x8C, 0x6A, 0x56, 0xE5, 0x82, 0xD4, 0x1B, 0xF6},
|
||||
{0x7F, 0x54, 0x2C, 0x98, 0x1E, 0x54, 0x18, 0x3B, 0xBA, 0x63, 0xBD, 0x4C, 0x13, 0x5B, 0xF1, 0x06},
|
||||
{0xC7, 0x3F, 0x73, 0x60, 0xB7, 0xB9, 0x9D, 0x74, 0x0A, 0xF8, 0x35, 0x60, 0x1A, 0x18, 0x74, 0x63},
|
||||
{0x0E, 0xE0, 0xC4, 0x33, 0x82, 0x66, 0xE8, 0x08, 0x39, 0x13, 0x41, 0x7D, 0x04, 0x64, 0x2B, 0x6D}
|
||||
};
|
||||
|
||||
void seal_key_internal(void *dst, const void *src, const uint8_t *seal_key_source) {
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, KEYSLOT_SWITCH_SESSIONKEY, seal_key_source, 0x10);
|
||||
se_aes_128_ecb_encrypt_block(KEYSLOT_SWITCH_TEMPKEY, dst, 0x10, src, 0x10);
|
||||
}
|
||||
|
||||
void unseal_key_internal(unsigned int keyslot, const void *src, const uint8_t *seal_key_source) {
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, KEYSLOT_SWITCH_SESSIONKEY, seal_key_source, 0x10);
|
||||
decrypt_data_into_keyslot(keyslot, KEYSLOT_SWITCH_TEMPKEY, src, 0x10);
|
||||
}
|
||||
|
||||
|
||||
void seal_titlekey(void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
if (dst_size != 0x10 || src_size != 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
seal_key_internal(dst, src, g_titlekey_seal_key_source);
|
||||
|
||||
}
|
||||
|
||||
void unseal_titlekey(unsigned int keyslot, const void *src, size_t src_size) {
|
||||
if (src_size != 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
unseal_key_internal(keyslot, src, g_titlekey_seal_key_source);
|
||||
}
|
||||
|
||||
|
||||
void seal_key(void *dst, size_t dst_size, const void *src, size_t src_size, unsigned int usecase) {
|
||||
if (usecase >= CRYPTOUSECASE_MAX || dst_size != 0x10 || src_size != 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
|
||||
seal_key_internal(dst, src, g_seal_key_sources[usecase]);
|
||||
}
|
||||
|
||||
void unseal_key(unsigned int keyslot, const void *src, size_t src_size, unsigned int usecase) {
|
||||
if (usecase >= CRYPTOUSECASE_MAX || src_size != 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
unseal_key_internal(keyslot, src, g_seal_key_sources[usecase]);
|
||||
}
|
||||
21
exosphere/src/sealedkeys.h
Normal file
21
exosphere/src/sealedkeys.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef EXOSPHERE_SEALED_KEYS_H
|
||||
#define EXOSPHERE_SEALED_KEYS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Key sealing/unsealing functionality. */
|
||||
|
||||
#define CRYPTOUSECASE_AES 0
|
||||
#define CRYPTOUSECASE_RSAPRIVATE 1
|
||||
#define CRYPTOUSECASE_SECUREEXPMOD 2
|
||||
#define CRYPTOUSECASE_RSAOAEP 3
|
||||
|
||||
#define CRYPTOUSECASE_MAX 4
|
||||
|
||||
void seal_titlekey(void *dst, size_t dst_size, const void *src, size_t src_size);
|
||||
void unseal_titlekey(unsigned int keyslot, const void *src, size_t src_size);
|
||||
|
||||
void seal_key(void *dst, size_t dst_size, const void *src, size_t src_size, unsigned int usecase);
|
||||
void unseal_key(unsigned int keyslot, const void *src, size_t src_size, unsigned int usecase);
|
||||
|
||||
#endif
|
||||
562
exosphere/src/smc_api.c
Normal file
562
exosphere/src/smc_api.c
Normal file
@@ -0,0 +1,562 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "memory_map.h"
|
||||
|
||||
#include "configitem.h"
|
||||
#include "cpu_context.h"
|
||||
#include "lock.h"
|
||||
#include "masterkey.h"
|
||||
#include "mc.h"
|
||||
#include "memory_map.h"
|
||||
#include "pmc.h"
|
||||
#include "randomcache.h"
|
||||
#include "sealedkeys.h"
|
||||
#include "smc_api.h"
|
||||
#include "smc_user.h"
|
||||
#include "se.h"
|
||||
#include "userpage.h"
|
||||
#include "titlekey.h"
|
||||
|
||||
#define SMC_USER_HANDLERS 0x13
|
||||
#define SMC_PRIV_HANDLERS 0x9
|
||||
|
||||
/* User SMC prototypes */
|
||||
uint32_t smc_set_config(smc_args_t *args);
|
||||
uint32_t smc_get_config(smc_args_t *args);
|
||||
uint32_t smc_check_status(smc_args_t *args);
|
||||
uint32_t smc_get_result(smc_args_t *args);
|
||||
uint32_t smc_exp_mod(smc_args_t *args);
|
||||
uint32_t smc_get_random_bytes_for_user(smc_args_t *args);
|
||||
uint32_t smc_generate_aes_kek(smc_args_t *args);
|
||||
uint32_t smc_load_aes_key(smc_args_t *args);
|
||||
uint32_t smc_crypt_aes(smc_args_t *args);
|
||||
uint32_t smc_generate_specific_aes_key(smc_args_t *args);
|
||||
uint32_t smc_compute_cmac(smc_args_t *args);
|
||||
uint32_t smc_load_rsa_oaep_key(smc_args_t *args);
|
||||
uint32_t smc_decrypt_rsa_private_key(smc_args_t *args);
|
||||
uint32_t smc_load_secure_exp_mod_key(smc_args_t *args);
|
||||
uint32_t smc_secure_exp_mod(smc_args_t *args);
|
||||
uint32_t smc_unwrap_rsa_oaep_wrapped_titlekey(smc_args_t *args);
|
||||
uint32_t smc_load_titlekey(smc_args_t *args);
|
||||
uint32_t smc_unwrap_aes_wrapped_titlekey(smc_args_t *args);
|
||||
|
||||
/* Privileged SMC prototypes */
|
||||
uint32_t smc_cpu_suspend(smc_args_t *args);
|
||||
uint32_t smc_cpu_off(smc_args_t *args);
|
||||
uint32_t smc_cpu_on(smc_args_t *args);
|
||||
/* uint32_t smc_get_config(smc_args_t *args); */
|
||||
uint32_t smc_get_random_bytes_for_priv(smc_args_t *args);
|
||||
uint32_t smc_panic(smc_args_t *args);
|
||||
uint32_t smc_configure_carveout(smc_args_t *args);
|
||||
uint32_t smc_read_write_register(smc_args_t *args);
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
uint32_t (*handler)(smc_args_t *args);
|
||||
} smc_table_entry_t;
|
||||
|
||||
typedef struct {
|
||||
smc_table_entry_t *handlers;
|
||||
uint32_t num_handlers;
|
||||
} smc_table_t;
|
||||
|
||||
smc_table_entry_t g_smc_user_table[SMC_USER_HANDLERS] = {
|
||||
{0, NULL},
|
||||
{0xC3000401, smc_set_config},
|
||||
{0xC3000002, smc_get_config},
|
||||
{0xC3000003, smc_check_status},
|
||||
{0xC3000404, smc_get_result},
|
||||
{0xC3000E05, smc_exp_mod},
|
||||
{0xC3000006, smc_get_random_bytes_for_user},
|
||||
{0xC3000007, smc_generate_aes_kek},
|
||||
{0xC3000008, smc_load_aes_key},
|
||||
{0xC3000009, smc_crypt_aes},
|
||||
{0xC300000A, smc_generate_specific_aes_key},
|
||||
{0xC300040B, smc_compute_cmac},
|
||||
{0xC300100C, smc_load_rsa_oaep_key},
|
||||
{0xC300100D, smc_decrypt_rsa_private_key},
|
||||
{0xC300100E, smc_load_secure_exp_mod_key},
|
||||
{0xC300060F, smc_secure_exp_mod},
|
||||
{0xC3000610, smc_unwrap_rsa_oaep_wrapped_titlekey},
|
||||
{0xC3000011, smc_load_titlekey},
|
||||
{0xC3000012, smc_unwrap_aes_wrapped_titlekey}
|
||||
};
|
||||
|
||||
smc_table_entry_t g_smc_priv_table[SMC_PRIV_HANDLERS] = {
|
||||
{0, NULL},
|
||||
{0xC4000001, smc_cpu_suspend},
|
||||
{0x84000002, smc_cpu_off},
|
||||
{0xC4000003, smc_cpu_on},
|
||||
{0xC3000004, smc_get_config}, /* NOTE: Same function as for USER */
|
||||
{0xC3000005, smc_get_random_bytes_for_priv},
|
||||
{0xC3000006, smc_panic},
|
||||
{0xC3000007, smc_configure_carveout},
|
||||
{0xC3000008, smc_read_write_register}
|
||||
};
|
||||
|
||||
smc_table_t g_smc_tables[2] = {
|
||||
{ /* SMC_HANDLER_USER */
|
||||
g_smc_user_table,
|
||||
SMC_USER_HANDLERS
|
||||
},
|
||||
{ /* SMC_HANDLER_PRIV */
|
||||
g_smc_priv_table,
|
||||
SMC_PRIV_HANDLERS
|
||||
}
|
||||
};
|
||||
|
||||
bool g_is_user_smc_in_progress = false;
|
||||
bool g_is_priv_smc_in_progress = false;
|
||||
|
||||
uintptr_t get_smc_core012_stack_address(void) {
|
||||
return tzram_get_segment_address(TZRAM_SEGMENT_ID_CORE012_STACK) + 0x1000;
|
||||
}
|
||||
|
||||
uintptr_t get_exception_entry_stack_address(unsigned int core_id) {
|
||||
/* For core3, this is also the smc stack */
|
||||
if (core_id == 3) {
|
||||
return tzram_get_segment_address(TZRAM_SEGMENT_ID_CORE3_STACK) + 0x1000;
|
||||
}
|
||||
else {
|
||||
return tzram_get_segment_address(TZRAM_SEGEMENT_ID_SECMON_EVT) + 0x80 * (core_id + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Privileged SMC lock must be available to exceptions.s. */
|
||||
void set_priv_smc_in_progress(void) {
|
||||
lock_acquire(&g_is_priv_smc_in_progress);
|
||||
}
|
||||
void clear_priv_smc_in_progress(void) {
|
||||
lock_release(&g_is_priv_smc_in_progress);
|
||||
}
|
||||
|
||||
uint32_t (*g_smc_callback)(void *, uint64_t) = NULL;
|
||||
uint64_t g_smc_callback_key = 0;
|
||||
|
||||
uint64_t try_set_smc_callback(uint32_t (*callback)(void *, uint64_t)) {
|
||||
uint64_t key;
|
||||
/* TODO: Atomics... */
|
||||
if (g_smc_callback_key) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
se_generate_random(KEYSLOT_SWITCH_RNGKEY, &key, sizeof(uint64_t));
|
||||
g_smc_callback_key = key;
|
||||
g_smc_callback = callback;
|
||||
return key;
|
||||
}
|
||||
|
||||
void clear_smc_callback(uint64_t key) {
|
||||
/* TODO: Atomics... */
|
||||
if (g_smc_callback_key == key) {
|
||||
g_smc_callback_key = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void call_smc_handler(uint32_t handler_id, smc_args_t *args) {
|
||||
unsigned char smc_id;
|
||||
unsigned int result;
|
||||
unsigned int (*smc_handler)(smc_args_t *args);
|
||||
|
||||
/* Validate top-level handler. */
|
||||
if (handler_id != SMC_HANDLER_USER && handler_id != SMC_HANDLER_PRIV) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Validate core is appropriate for handler. */
|
||||
if (handler_id == SMC_HANDLER_USER && get_core_id() != 3) {
|
||||
/* USER SMCs must be called via svcCallSecureMonitor on core 3 (where spl runs) */
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Validate sub-handler index */
|
||||
if ((smc_id = (unsigned char)args->X[0]) >= g_smc_tables[handler_id].num_handlers) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Validate sub-handler */
|
||||
if (g_smc_tables[handler_id].handlers[smc_id].id != args->X[0]) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Validate handler. */
|
||||
if ((smc_handler = g_smc_tables[handler_id].handlers[smc_id].handler) == NULL) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* Call function. */
|
||||
args->X[0] = smc_handler(args);
|
||||
(void)result; /* FIXME: result unused */
|
||||
}
|
||||
|
||||
uint32_t smc_wrapper_sync(smc_args_t *args, uint32_t (*handler)(smc_args_t *)) {
|
||||
uint32_t result;
|
||||
if (!lock_try_acquire(&g_is_user_smc_in_progress)) {
|
||||
return 3;
|
||||
}
|
||||
result = handler(args);
|
||||
lock_release(&g_is_user_smc_in_progress);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t smc_wrapper_async(smc_args_t *args, uint32_t (*handler)(smc_args_t *), uint32_t (*callback)(void *, uint64_t)) {
|
||||
uint32_t result;
|
||||
uint64_t key;
|
||||
if (!lock_try_acquire(&g_is_user_smc_in_progress)) {
|
||||
return 3;
|
||||
}
|
||||
if ((key = try_set_smc_callback(callback)) != 0) {
|
||||
result = handler(args);
|
||||
if (result == 0) {
|
||||
/* Pass the status check key back to userland. */
|
||||
args->X[1] = key;
|
||||
/* Early return, leaving g_is_user_smc_in_progress locked */
|
||||
return result;
|
||||
} else {
|
||||
/* No status to check. */
|
||||
clear_smc_callback(key);
|
||||
}
|
||||
} else {
|
||||
/* smcCheckStatus needs to be called. */
|
||||
result = 3;
|
||||
}
|
||||
lock_release(&g_is_user_smc_in_progress);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t smc_set_config(smc_args_t *args) {
|
||||
/* Actual value presumed in X3 on hardware. */
|
||||
return configitem_set((enum ConfigItem)args->X[1], args->X[3]);
|
||||
}
|
||||
|
||||
uint32_t smc_get_config(smc_args_t *args) {
|
||||
uint64_t out_item = 0;
|
||||
uint32_t result;
|
||||
result = configitem_get((enum ConfigItem)args->X[1], &out_item);
|
||||
args->X[1] = out_item;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t smc_check_status(smc_args_t *args) {
|
||||
if (g_smc_callback_key == 0) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (args->X[1] != g_smc_callback_key) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
args->X[1] = g_smc_callback(NULL, 0);
|
||||
|
||||
g_smc_callback_key = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t smc_get_result(smc_args_t *args) {
|
||||
uint32_t status;
|
||||
unsigned char result_buf[0x400];
|
||||
upage_ref_t page_ref;
|
||||
|
||||
void *user_address = (void *)args->X[2];
|
||||
|
||||
if (g_smc_callback_key == 0) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (args->X[1] != g_smc_callback_key) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/* Check result size */
|
||||
if (args->X[3] > 0x400) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
args->X[1] = g_smc_callback(result_buf, args->X[3]);
|
||||
g_smc_callback_key = 0;
|
||||
|
||||
/* Initialize page reference. */
|
||||
if (upage_init(&page_ref, user_address) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Copy result output back to user. */
|
||||
if (secure_copy_to_user(&page_ref, user_address, result_buf, (size_t)args->X[3]) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
(void)status; /* FIXME: status unused */
|
||||
}
|
||||
|
||||
uint32_t smc_exp_mod_get_result(void *buf, uint64_t size) {
|
||||
if (get_exp_mod_done() != 1) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (size != 0x100) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
se_get_exp_mod_output(buf, 0x100);
|
||||
|
||||
/* smc_exp_mod is done now. */
|
||||
lock_release(&g_is_user_smc_in_progress);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t smc_exp_mod(smc_args_t *args) {
|
||||
return smc_wrapper_async(args, user_exp_mod, smc_exp_mod_get_result);
|
||||
}
|
||||
|
||||
uint32_t smc_get_random_bytes_for_user(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_get_random_bytes);
|
||||
}
|
||||
|
||||
uint32_t smc_generate_aes_kek(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_generate_aes_kek);
|
||||
}
|
||||
|
||||
uint32_t smc_load_aes_key(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_load_aes_key);
|
||||
}
|
||||
|
||||
uint32_t smc_crypt_aes_status_check(void *buf, uint64_t size) {
|
||||
/* Buf and size are unused. */
|
||||
if (get_crypt_aes_done() != 1) {
|
||||
return 3;
|
||||
}
|
||||
/* smc_crypt_aes is done now. */
|
||||
lock_release(&g_is_user_smc_in_progress);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t smc_crypt_aes(smc_args_t *args) {
|
||||
return smc_wrapper_async(args, user_crypt_aes, smc_crypt_aes_status_check);
|
||||
}
|
||||
|
||||
uint32_t smc_generate_specific_aes_key(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_generate_specific_aes_key);
|
||||
}
|
||||
|
||||
uint32_t smc_compute_cmac(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_compute_cmac);
|
||||
}
|
||||
|
||||
uint32_t smc_load_rsa_oaep_key(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_load_rsa_oaep_key);
|
||||
}
|
||||
|
||||
uint32_t smc_decrypt_rsa_private_key(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_decrypt_rsa_private_key);
|
||||
}
|
||||
|
||||
uint32_t smc_load_secure_exp_mod_key(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_load_secure_exp_mod_key);
|
||||
}
|
||||
|
||||
uint32_t smc_secure_exp_mod(smc_args_t *args) {
|
||||
return smc_wrapper_async(args, user_secure_exp_mod, smc_exp_mod_get_result);
|
||||
}
|
||||
|
||||
uint32_t smc_unwrap_rsa_oaep_wrapped_titlekey_get_result(void *buf, uint64_t size) {
|
||||
uint64_t *p_sealed_key = (uint64_t *)buf;
|
||||
uint8_t rsa_wrapped_titlekey[0x100];
|
||||
uint8_t aes_wrapped_titlekey[0x10];
|
||||
uint8_t titlekey[0x10];
|
||||
uint64_t sealed_titlekey[2];
|
||||
if (get_exp_mod_done() != 1) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (size != 0x10) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
se_get_exp_mod_output(rsa_wrapped_titlekey, 0x100);
|
||||
if (tkey_rsa_oaep_unwrap(aes_wrapped_titlekey, 0x10, rsa_wrapped_titlekey, 0x100) != 0x10) {
|
||||
/* Failed to extract RSA OAEP wrapped key. */
|
||||
lock_release(&g_is_user_smc_in_progress);
|
||||
return 2;
|
||||
}
|
||||
|
||||
tkey_aes_unwrap(titlekey, 0x10, aes_wrapped_titlekey, 0x10);
|
||||
seal_titlekey(sealed_titlekey, 0x10, titlekey, 0x10);
|
||||
|
||||
p_sealed_key[0] = sealed_titlekey[0];
|
||||
p_sealed_key[1] = sealed_titlekey[1];
|
||||
|
||||
/* smc_unwrap_rsa_oaep_wrapped_titlekey is done now. */
|
||||
lock_release(&g_is_user_smc_in_progress);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t smc_unwrap_rsa_oaep_wrapped_titlekey(smc_args_t *args) {
|
||||
return smc_wrapper_async(args, user_unwrap_rsa_oaep_wrapped_titlekey, smc_unwrap_rsa_oaep_wrapped_titlekey_get_result);
|
||||
}
|
||||
|
||||
uint32_t smc_load_titlekey(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_load_titlekey);
|
||||
}
|
||||
|
||||
uint32_t smc_unwrap_aes_wrapped_titlekey(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, user_unwrap_aes_wrapped_titlekey);
|
||||
}
|
||||
|
||||
uint32_t smc_cpu_on(smc_args_t *args) {
|
||||
return cpu_on((uint32_t)args->X[1], args->X[2], args->X[3]);
|
||||
}
|
||||
|
||||
uint32_t smc_cpu_off(smc_args_t *args) {
|
||||
return cpu_off();
|
||||
}
|
||||
|
||||
/* Wrapper for cpu_suspend */
|
||||
uint32_t cpu_suspend_wrapper(smc_args_t *args) {
|
||||
return cpu_suspend(args->X[1], args->X[2], args->X[3]);
|
||||
}
|
||||
|
||||
uint32_t smc_cpu_suspend(smc_args_t *args) {
|
||||
return smc_wrapper_sync(args, cpu_suspend_wrapper);
|
||||
}
|
||||
|
||||
uint32_t smc_get_random_bytes_for_priv(smc_args_t *args) {
|
||||
/* This is an interesting SMC. */
|
||||
/* The kernel must NEVER be unable to get random bytes, if it needs them */
|
||||
/* As such: */
|
||||
|
||||
uint32_t result;
|
||||
|
||||
if (!lock_try_acquire(&g_is_user_smc_in_progress)) {
|
||||
if (args->X[1] > 0x38) {
|
||||
return 2;
|
||||
}
|
||||
/* Retrieve bytes from the cache. */
|
||||
size_t num_bytes = (size_t)args->X[1];
|
||||
randomcache_getbytes(&args->X[1], num_bytes);
|
||||
result = 0;
|
||||
} else {
|
||||
/* If the kernel isn't denied service by a usermode SMC, generate fresh random bytes. */
|
||||
result = user_get_random_bytes(args);
|
||||
/* Also, refill our cache while we have the chance in case we get denied later. */
|
||||
randomcache_refill();
|
||||
lock_release(&g_is_user_smc_in_progress);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t smc_read_write_register(smc_args_t *args) {
|
||||
uint64_t address = args->X[1];
|
||||
uint32_t mask = (uint32_t)(args->X[2]);
|
||||
uint32_t value = (uint32_t)(args->X[3]);
|
||||
volatile uint32_t *p_mmio = NULL;
|
||||
/* Address must be aligned. */
|
||||
if (address & 3) {
|
||||
return 2;
|
||||
}
|
||||
/* Check for PMC registers. */
|
||||
if (0x7000E400 <= address && address <= 0x7000EFFF) {
|
||||
const uint8_t pmc_whitelist[0x28] = {
|
||||
0xB9, 0xF9, 0x07, 0x00, 0x00, 0x00, 0x80, 0x03,
|
||||
0x00, 0x00, 0x00, 0x17, 0x00, 0xC4, 0x07, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00
|
||||
};
|
||||
/* Offset = Address - PMC_BASE */
|
||||
uint32_t offset = (uint32_t)(address - 0x7000E400);
|
||||
uint32_t wl_ind = (offset >> 5);
|
||||
/* If address is whitelisted, allow write. */
|
||||
if (wl_ind < sizeof(pmc_whitelist) && (pmc_whitelist[wl_ind] & (1 << ((offset >> 2) & 0x7)))) {
|
||||
p_mmio = (volatile uint32_t *)(PMC_BASE + offset);
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
} else if (mkey_get_revision() >= MASTERKEY_REVISION_400_CURRENT && mmio_get_device_pa(MMIO_DEVID_MC) <= address &&
|
||||
address < mmio_get_device_pa(MMIO_DEVID_MC) + 0x1000) {
|
||||
/* Memory Controller RW supported only on 4.0.0+ */
|
||||
const uint8_t mc_whitelist[0x68] = {
|
||||
0x9F, 0x31, 0x30, 0x00, 0xF0, 0xFF, 0xF7, 0x01,
|
||||
0xCD, 0xFE, 0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0x40, 0x73, 0x3E, 0x2F, 0x00, 0x00, 0x6E,
|
||||
0x30, 0x05, 0x06, 0xB0, 0x71, 0xC8, 0x43, 0x04,
|
||||
0x80, 0x1F, 0x08, 0x80, 0x03, 0x00, 0x0E, 0x00,
|
||||
0x08, 0x00, 0xE0, 0x00, 0x0E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x30, 0xF0, 0x03, 0x03, 0x30,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x31, 0x00, 0x40, 0x00, 0x00,
|
||||
0x00, 0x03, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xFE, 0x0F,
|
||||
0x01, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00
|
||||
};
|
||||
uint32_t offset = (uint32_t)(address - 0x70019000);
|
||||
uint32_t wl_ind = (offset >> 5);
|
||||
/* If address is whitelisted, allow write. */
|
||||
if (wl_ind < sizeof(mc_whitelist) && (mc_whitelist[wl_ind] & (1 << ((offset >> 2) & 0x7)))) {
|
||||
p_mmio = (volatile uint32_t *)(mmio_get_device_address(MMIO_DEVID_MC) + offset);
|
||||
} else {
|
||||
/* These addresses are not allowed by the whitelist. */
|
||||
/* They correspond to SMMU DISABLE for the BPMP, and for APB-DMA. */
|
||||
/* However, smcReadWriteRegister returns 0 for these addresses despite not actually performing the write. */
|
||||
/* This is "probably" to fuck with hackers who got access to smcReadWriteRegister and are trying to get */
|
||||
/* control of the BPMP for jamais vu etc., since there's no other reason to return 0 despite failure. */
|
||||
if (address == 0x7001923C || address == 0x70019298) {
|
||||
return 0;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform actual write. */
|
||||
if (p_mmio != NULL) {
|
||||
uint32_t old_value;
|
||||
/* Write whole value. */
|
||||
if (mask == 0xFFFFFFFF) {
|
||||
old_value = 0;
|
||||
} else {
|
||||
old_value = *p_mmio;
|
||||
}
|
||||
if (mask) {
|
||||
*p_mmio = (old_value & ~mask) | (value & mask);
|
||||
}
|
||||
/* Return old value. */
|
||||
args->X[1] = old_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
|
||||
uint32_t smc_configure_carveout(smc_args_t *args) {
|
||||
if (args->X[0] > 1) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
unsigned int carveout_id = (unsigned int)args->X[1];
|
||||
uint64_t address = args->X[2];
|
||||
uint64_t size = args->X[3];
|
||||
|
||||
/* Ensure carveout isn't too big. */
|
||||
if (size > KERNEL_CARVEOUT_SIZE_MAX) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Configuration is one-shot, and cannot be done multiple times. */
|
||||
static bool configured_carveouts[2] = {false, false};
|
||||
if (configured_carveouts[carveout_id]) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
configure_kernel_carveout(carveout_id + 4, address, size);
|
||||
configured_carveouts[carveout_id] = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t smc_panic(smc_args_t *args) {
|
||||
(void)args;
|
||||
return 0;
|
||||
/* TODO */
|
||||
}
|
||||
21
exosphere/src/smc_api.h
Normal file
21
exosphere/src/smc_api.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef EXOSPHERE_SMC_API_H
|
||||
#define EXOSPHERE_SMC_API_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SMC_HANDLER_USER 0
|
||||
#define SMC_HANDLER_PRIV 1
|
||||
|
||||
typedef struct {
|
||||
uint64_t X[8];
|
||||
} smc_args_t;
|
||||
|
||||
void set_priv_smc_in_progress(void);
|
||||
void clear_priv_smc_in_progress(void);
|
||||
|
||||
uintptr_t get_smc_core012_stack_address(void);
|
||||
uintptr_t get_exception_entry_stack_address(unsigned int core_id);
|
||||
|
||||
void call_smc_handler(unsigned int handler_id, smc_args_t *args);
|
||||
|
||||
#endif
|
||||
617
exosphere/src/smc_user.c
Normal file
617
exosphere/src/smc_user.c
Normal file
@@ -0,0 +1,617 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "cache.h"
|
||||
#include "configitem.h"
|
||||
#include "gcm.h"
|
||||
#include "masterkey.h"
|
||||
#include "smc_api.h"
|
||||
#include "smc_user.h"
|
||||
#include "se.h"
|
||||
#include "fuse.h"
|
||||
#include "sealedkeys.h"
|
||||
#include "userpage.h"
|
||||
#include "titlekey.h"
|
||||
|
||||
/* Globals. */
|
||||
static bool g_crypt_aes_done = false;
|
||||
static bool g_exp_mod_done = false;
|
||||
|
||||
static uint8_t g_secure_exp_mod_exponent[0x100];
|
||||
static uint8_t g_rsa_oaep_exponent[0x100];
|
||||
|
||||
|
||||
void set_exp_mod_done(bool done) {
|
||||
g_exp_mod_done = done;
|
||||
}
|
||||
|
||||
bool get_exp_mod_done(void) {
|
||||
return g_exp_mod_done;
|
||||
}
|
||||
|
||||
uint32_t exp_mod_done_handler(void) {
|
||||
set_exp_mod_done(true);
|
||||
|
||||
se_trigger_interrupt();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_exp_mod(smc_args_t *args) {
|
||||
uint8_t modulus[0x100];
|
||||
uint8_t exponent[0x100];
|
||||
uint8_t input[0x100];
|
||||
|
||||
upage_ref_t page_ref;
|
||||
|
||||
/* Validate size. */
|
||||
if (args->X[4] == 0 || args->X[4] > 0x100 || (args->X[4] & 3) != 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
size_t exponent_size = (size_t)args->X[4];
|
||||
|
||||
void *user_input = (void *)args->X[1];
|
||||
void *user_exponent = (void *)args->X[2];
|
||||
void *user_modulus = (void *)args->X[3];
|
||||
|
||||
/* Copy user data into secure memory. */
|
||||
if (upage_init(&page_ref, user_input) == 0) {
|
||||
return 2;
|
||||
}
|
||||
if (user_copy_to_secure(&page_ref, input, user_input, 0x100) == 0) {
|
||||
return 2;
|
||||
}
|
||||
if (user_copy_to_secure(&page_ref, exponent, user_exponent, exponent_size) == 0) {
|
||||
return 2;
|
||||
}
|
||||
if (user_copy_to_secure(&page_ref, modulus, user_modulus, 0x100) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
set_exp_mod_done(false);
|
||||
/* Hardcode RSA keyslot 0. */
|
||||
set_rsa_keyslot(0, modulus, 0x100, exponent, exponent_size);
|
||||
se_exp_mod(0, input, 0x100, exp_mod_done_handler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_get_random_bytes(smc_args_t *args) {
|
||||
uint8_t random_bytes[0x40];
|
||||
if (args->X[1] > 0x38) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
size_t size = (size_t)args->X[1];
|
||||
|
||||
flush_dcache_range(random_bytes, random_bytes + size);
|
||||
se_generate_random(KEYSLOT_SWITCH_RNGKEY, random_bytes, size);
|
||||
flush_dcache_range(random_bytes, random_bytes + size);
|
||||
|
||||
memcpy(&args->X[1], random_bytes, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_generate_aes_kek(smc_args_t *args) {
|
||||
uint64_t wrapped_kek[2];
|
||||
uint8_t kek_source[0x10];
|
||||
uint64_t kek[2];
|
||||
uint64_t sealed_kek[2];
|
||||
|
||||
wrapped_kek[0] = args->X[1];
|
||||
wrapped_kek[1] = args->X[2];
|
||||
|
||||
unsigned int master_key_rev = (unsigned int)args->X[3];
|
||||
|
||||
if (master_key_rev > 0) {
|
||||
master_key_rev -= 1; /* GenerateAesKek offsets by one. */
|
||||
}
|
||||
|
||||
if (master_key_rev >= MASTERKEY_REVISION_MAX) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
uint64_t packed_options = args->X[4];
|
||||
if (packed_options > 0xFF) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Switched the output based on how the system was booted. */
|
||||
uint8_t mask_id = (uint8_t)((packed_options >> 1) & 3);
|
||||
|
||||
/* Switches the output based on how it will be used. */
|
||||
uint8_t usecase = (uint8_t)((packed_options >> 5) & 3);
|
||||
|
||||
/* Switched the output based on whether it should be console unique. */
|
||||
bool is_personalized = (int)(packed_options & 1);
|
||||
|
||||
bool is_recovery_boot = configitem_is_recovery_boot();
|
||||
|
||||
/* Mask 2 is only allowed when booted from recovery. */
|
||||
if (mask_id == 2 && !is_recovery_boot) {
|
||||
return 2;
|
||||
}
|
||||
/* Mask 1 is only allowed when booted normally. */
|
||||
if (mask_id == 1 && is_recovery_boot) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Masks 0, 3 are allowed all the time. */
|
||||
|
||||
const uint8_t kek_seeds[4][0x10] = {
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0xA2, 0xAB, 0xBF, 0x9C, 0x92, 0x2F, 0xBB, 0xE3, 0x78, 0x79, 0x9B, 0xC0, 0xCC, 0xEA, 0xA5, 0x74},
|
||||
{0x57, 0xE2, 0xD9, 0x45, 0xE4, 0x92, 0xF4, 0xFD, 0xC3, 0xF9, 0x86, 0x38, 0x89, 0x78, 0x9F, 0x3C},
|
||||
{0xE5, 0x4D, 0x9A, 0x02, 0xF0, 0x4F, 0x5F, 0xA8, 0xAD, 0x76, 0x0A, 0xF6, 0x32, 0x95, 0x59, 0xBB}
|
||||
};
|
||||
const uint8_t kek_masks[4][0x10] = {
|
||||
{0x4D, 0x87, 0x09, 0x86, 0xC4, 0x5D, 0x20, 0x72, 0x2F, 0xBA, 0x10, 0x53, 0xDA, 0x92, 0xE8, 0xA9},
|
||||
{0x25, 0x03, 0x31, 0xFB, 0x25, 0x26, 0x0B, 0x79, 0x8C, 0x80, 0xD2, 0x69, 0x98, 0xE2, 0x22, 0x77},
|
||||
{0x76, 0x14, 0x1D, 0x34, 0x93, 0x2D, 0xE1, 0x84, 0x24, 0x7B, 0x66, 0x65, 0x55, 0x04, 0x65, 0x81},
|
||||
{0xAF, 0x3D, 0xB7, 0xF3, 0x08, 0xA2, 0xD8, 0xA2, 0x08, 0xCA, 0x18, 0xA8, 0x69, 0x46, 0xC9, 0x0B}
|
||||
};
|
||||
|
||||
/* Create kek source. */
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
kek_source[i] = kek_seeds[usecase][i] ^ kek_masks[mask_id][i];
|
||||
}
|
||||
|
||||
unsigned int keyslot;
|
||||
if (is_personalized) {
|
||||
/* Behavior changed in 4.0.0. */
|
||||
if (mkey_get_revision() >= MASTERKEY_REVISION_400_CURRENT) {
|
||||
if (master_key_rev >= 1) {
|
||||
keyslot = KEYSLOT_SWITCH_DEVICEKEY; /* New device key, 4.x. */
|
||||
} else {
|
||||
keyslot = KEYSLOT_SWITCH_4XOLDDEVICEKEY; /* Old device key, 4.x. */
|
||||
}
|
||||
} else {
|
||||
keyslot = KEYSLOT_SWITCH_DEVICEKEY;
|
||||
}
|
||||
} else {
|
||||
keyslot = mkey_get_keyslot(master_key_rev);
|
||||
}
|
||||
|
||||
/* Derive kek. */
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, keyslot, kek_source, 0x10);
|
||||
se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_TEMPKEY, kek, 0x10, wrapped_kek, 0x10);
|
||||
|
||||
|
||||
/* Seal kek. */
|
||||
seal_key(sealed_kek, 0x10, kek, 0x10, usecase);
|
||||
|
||||
args->X[1] = sealed_kek[0];
|
||||
args->X[2] = sealed_kek[1];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_load_aes_key(smc_args_t *args) {
|
||||
uint64_t sealed_kek[2];
|
||||
uint64_t wrapped_key[2];
|
||||
|
||||
uint32_t keyslot = (uint32_t)args->X[1];
|
||||
if (keyslot > 3) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Copy keydata */
|
||||
sealed_kek[0] = args->X[2];
|
||||
sealed_kek[1] = args->X[3];
|
||||
wrapped_key[0] = args->X[4];
|
||||
wrapped_key[1] = args->X[5];
|
||||
|
||||
/* Unseal the kek. */
|
||||
unseal_key(KEYSLOT_SWITCH_TEMPKEY, sealed_kek, 0x10, CRYPTOUSECASE_AES);
|
||||
|
||||
/* Unwrap the key. */
|
||||
decrypt_data_into_keyslot(keyslot, KEYSLOT_SWITCH_TEMPKEY, wrapped_key, 0x10);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void set_crypt_aes_done(bool done) {
|
||||
g_crypt_aes_done = done;
|
||||
}
|
||||
|
||||
bool get_crypt_aes_done(void) {
|
||||
return g_crypt_aes_done;
|
||||
}
|
||||
|
||||
uint32_t crypt_aes_done_handler(void) {
|
||||
se_check_for_error();
|
||||
|
||||
set_crypt_aes_done(true);
|
||||
|
||||
se_trigger_interrupt();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_crypt_aes(smc_args_t *args) {
|
||||
uint32_t keyslot = args->X[1] & 3;
|
||||
uint32_t mode = (args->X[1] >> 4) & 3;
|
||||
|
||||
uint64_t iv_ctr[2];
|
||||
iv_ctr[0] = args->X[2];
|
||||
iv_ctr[1] = args->X[3];
|
||||
|
||||
uint32_t in_ll_paddr = (uint32_t)(args->X[4]);
|
||||
uint32_t out_ll_paddr = (uint32_t)(args->X[5]);
|
||||
|
||||
size_t size = args->X[6];
|
||||
if (size & 0xF) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
set_crypt_aes_done(false);
|
||||
|
||||
uint64_t result = 0;
|
||||
switch (mode) {
|
||||
case 0: /* CBC Encryption */
|
||||
se_aes_cbc_encrypt_insecure(keyslot, out_ll_paddr, in_ll_paddr, size, iv_ctr, crypt_aes_done_handler);
|
||||
result = 0;
|
||||
break;
|
||||
case 1: /* CBC Decryption */
|
||||
se_aes_cbc_decrypt_insecure(keyslot, out_ll_paddr, in_ll_paddr, size, iv_ctr, crypt_aes_done_handler);
|
||||
result = 0;
|
||||
break;
|
||||
case 2: /* CTR "Encryption" */
|
||||
se_aes_ctr_crypt_insecure(keyslot, out_ll_paddr, in_ll_paddr, size, iv_ctr, crypt_aes_done_handler);
|
||||
result = 0;
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
result = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t user_generate_specific_aes_key(smc_args_t *args) {
|
||||
uint64_t wrapped_key[2];
|
||||
uint8_t key[0x10];
|
||||
unsigned int master_key_rev;
|
||||
bool should_mask;
|
||||
|
||||
wrapped_key[0] = args->X[1];
|
||||
wrapped_key[1] = args->X[2];
|
||||
if (args->X[4] > MASTERKEY_REVISION_MAX) {
|
||||
return 2;
|
||||
}
|
||||
master_key_rev = (unsigned int)(args->X[4]);
|
||||
if (args->X[3] > 1) {
|
||||
return 2;
|
||||
}
|
||||
should_mask = args->X[3] != 0;
|
||||
|
||||
unsigned int keyslot;
|
||||
|
||||
/* Behavior changed in 4.0.0. */
|
||||
if (mkey_get_revision() >= MASTERKEY_REVISION_400_CURRENT) {
|
||||
if (master_key_rev >= 2) {
|
||||
keyslot = KEYSLOT_SWITCH_DEVICEKEY; /* New device key, 4.x. */
|
||||
} else {
|
||||
keyslot = KEYSLOT_SWITCH_4XOLDDEVICEKEY; /* Old device key, 4.x. */
|
||||
}
|
||||
} else {
|
||||
keyslot = KEYSLOT_SWITCH_DEVICEKEY;
|
||||
}
|
||||
|
||||
if (fuse_get_bootrom_patch_version() < 0x7F) {
|
||||
/* On dev units, use a fixed "all-zeroes" seed. */
|
||||
/* Yes, this data really is all-zero in actual TrustZone .rodata. */
|
||||
uint8_t dev_specific_aes_key_source[0x10] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t dev_specific_aes_key_ctr[0x10] = {0x3C, 0xD5, 0x92, 0xEC, 0x68, 0x31, 0x4A, 0x06, 0xD4, 0x1B, 0x0C, 0xD9, 0xF6, 0x2E, 0xD9, 0xE9};
|
||||
uint8_t dev_specific_aes_key_mask[0x10] = {0xAC, 0xCA, 0x9A, 0xCA, 0xFF, 0x2E, 0xB9, 0x22, 0xCC, 0x1F, 0x4F, 0xAD, 0xDD, 0x77, 0x21, 0x1E};
|
||||
|
||||
flush_dcache_range(key, key + 0x10);
|
||||
se_aes_ctr_crypt(keyslot, key, 0x10, dev_specific_aes_key_source, 0x10, dev_specific_aes_key_ctr, 0x10);
|
||||
flush_dcache_range(key, key + 0x10);
|
||||
|
||||
if (should_mask) {
|
||||
for (unsigned int i = 0; i < 0x10; i++) {
|
||||
key[i] ^= dev_specific_aes_key_mask[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* On retail, standard kek->key decryption. */
|
||||
uint8_t retail_specific_aes_key_source[0x10] = {0xE2, 0xD6, 0xB8, 0x7A, 0x11, 0x9C, 0xB8, 0x80, 0xE8, 0x22, 0x88, 0x8A, 0x46, 0xFB, 0xA1, 0x95};
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, keyslot, retail_specific_aes_key_source, 0x10);
|
||||
se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_TEMPKEY, key, 0x10, wrapped_key, 0x10);
|
||||
}
|
||||
|
||||
|
||||
args->X[1] = key[0];
|
||||
args->X[2] = key[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_compute_cmac(smc_args_t *args) {
|
||||
uint32_t keyslot = (uint32_t)args->X[1];
|
||||
void *user_address = (void *)args->X[2];
|
||||
size_t size = (size_t)args->X[3];
|
||||
|
||||
uint8_t user_data[0x400];
|
||||
uint64_t result_cmac[2];
|
||||
upage_ref_t page_ref;
|
||||
|
||||
/* Validate keyslot and size. */
|
||||
if (keyslot > 3 || args->X[3] > 0x400) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (upage_init(&page_ref, user_address) == 0 || user_copy_to_secure(&page_ref, user_data, user_address, size) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
flush_dcache_range(user_data, user_data + size);
|
||||
se_compute_aes_128_cmac(keyslot, result_cmac, 0x10, user_data, size);
|
||||
|
||||
/* Copy CMAC out. */
|
||||
args->X[1] = result_cmac[0];
|
||||
args->X[2] = result_cmac[1];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_load_rsa_oaep_key(smc_args_t *args) {
|
||||
uint64_t sealed_kek[2];
|
||||
uint64_t wrapped_key[2];
|
||||
bool is_personalized;
|
||||
|
||||
uint8_t user_data[0x400];
|
||||
void *user_address;
|
||||
size_t size;
|
||||
upage_ref_t page_ref;
|
||||
|
||||
|
||||
/* Copy keydata */
|
||||
sealed_kek[0] = args->X[1];
|
||||
sealed_kek[1] = args->X[2];
|
||||
if (args->X[3] > 1) {
|
||||
return 2;
|
||||
}
|
||||
is_personalized = args->X[3] != 0;
|
||||
user_address = (void *)args->X[4];
|
||||
size = (size_t)args->X[5];
|
||||
wrapped_key[0] = args->X[6];
|
||||
wrapped_key[1] = args->X[7];
|
||||
|
||||
if (is_personalized && size != 0x240) {
|
||||
return 2;
|
||||
}
|
||||
if (!is_personalized && (size != 0x220 || fuse_get_bootrom_patch_version() >= 0x7F)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (upage_init(&page_ref, user_address) == 0 || user_copy_to_secure(&page_ref, user_data, user_address, size) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Ensure that our private key is 0x100 bytes. */
|
||||
if (gcm_decrypt_key(user_data, size, user_data, size, sealed_kek, 0x10, wrapped_key, 0x10, CRYPTOUSECASE_RSAOAEP, is_personalized) < 0x100) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
memcpy(g_rsa_oaep_exponent, user_data, 0x100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_decrypt_rsa_private_key(smc_args_t *args) {
|
||||
uint64_t sealed_kek[2];
|
||||
uint64_t wrapped_key[2];
|
||||
bool is_personalized;
|
||||
|
||||
uint8_t user_data[0x400];
|
||||
void *user_address;
|
||||
size_t size;
|
||||
upage_ref_t page_ref;
|
||||
|
||||
|
||||
/* Copy keydata */
|
||||
sealed_kek[0] = args->X[1];
|
||||
sealed_kek[1] = args->X[2];
|
||||
if (args->X[3] > 1) {
|
||||
return 2;
|
||||
}
|
||||
is_personalized = args->X[3] != 0;
|
||||
user_address = (void *)args->X[4];
|
||||
size = (size_t)args->X[5];
|
||||
wrapped_key[0] = args->X[6];
|
||||
wrapped_key[1] = args->X[7];
|
||||
|
||||
if (size > 0x240) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (is_personalized && size < 0x31) {
|
||||
return 2;
|
||||
}
|
||||
if (!is_personalized && (size < 0x11 || fuse_get_bootrom_patch_version() >= 0x7F)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (upage_init(&page_ref, user_address) == 0 || user_copy_to_secure(&page_ref, user_data, user_address, size) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
size_t out_size;
|
||||
|
||||
if ((out_size = gcm_decrypt_key(user_data, size, user_data, size, sealed_kek, 0x10, wrapped_key, 0x10, CRYPTOUSECASE_RSAPRIVATE, is_personalized)) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (secure_copy_to_user(&page_ref, user_address, user_data, size) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
args->X[1] = out_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_load_secure_exp_mod_key(smc_args_t *args) {
|
||||
uint64_t sealed_kek[2];
|
||||
uint64_t wrapped_key[2];
|
||||
bool is_personalized;
|
||||
|
||||
uint8_t user_data[0x400];
|
||||
void *user_address;
|
||||
size_t size;
|
||||
upage_ref_t page_ref;
|
||||
|
||||
|
||||
/* Copy keydata */
|
||||
sealed_kek[0] = args->X[1];
|
||||
sealed_kek[1] = args->X[2];
|
||||
if (args->X[3] > 1) {
|
||||
return 2;
|
||||
}
|
||||
is_personalized = args->X[3] != 0;
|
||||
user_address = (void *)args->X[4];
|
||||
size = (size_t)args->X[5];
|
||||
wrapped_key[0] = args->X[6];
|
||||
wrapped_key[1] = args->X[7];
|
||||
|
||||
if (is_personalized && size != 0x130) {
|
||||
return 2;
|
||||
}
|
||||
if (!is_personalized && (size != 0x110 || fuse_get_bootrom_patch_version() >= 0x7F)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (upage_init(&page_ref, user_address) == 0 || user_copy_to_secure(&page_ref, user_data, user_address, size) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
size_t out_size;
|
||||
|
||||
/* Ensure that our key is non-zero bytes. */
|
||||
if ((out_size = gcm_decrypt_key(user_data, size, user_data, size, sealed_kek, 0x10, wrapped_key, 0x10, CRYPTOUSECASE_SECUREEXPMOD, is_personalized)) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Copy key to global. */
|
||||
if (out_size <= 0x100) {
|
||||
memcpy(g_secure_exp_mod_exponent, user_data, out_size);
|
||||
} else {
|
||||
memcpy(g_secure_exp_mod_exponent, user_data, 0x100);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_secure_exp_mod(smc_args_t *args) {
|
||||
uint8_t modulus[0x100];
|
||||
uint8_t input[0x100];
|
||||
|
||||
upage_ref_t page_ref;
|
||||
|
||||
void *user_input = (void *)args->X[1];
|
||||
void *user_modulus = (void *)args->X[2];
|
||||
|
||||
/* Copy user data into secure memory. */
|
||||
if (upage_init(&page_ref, user_input) == 0) {
|
||||
return 2;
|
||||
}
|
||||
if (user_copy_to_secure(&page_ref, input, user_input, 0x100) == 0) {
|
||||
return 2;
|
||||
}
|
||||
if (user_copy_to_secure(&page_ref, modulus, user_modulus, 0x100) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
set_exp_mod_done(false);
|
||||
/* Hardcode RSA keyslot 0. */
|
||||
set_rsa_keyslot(0, modulus, 0x100, g_secure_exp_mod_exponent, 0x100);
|
||||
se_exp_mod(0, input, 0x100, exp_mod_done_handler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t user_unwrap_rsa_oaep_wrapped_titlekey(smc_args_t *args) {
|
||||
uint8_t modulus[0x100];
|
||||
uint8_t wrapped_key[0x100];
|
||||
|
||||
upage_ref_t page_ref;
|
||||
|
||||
void *user_wrapped_key = (void *)args->X[1];
|
||||
void *user_modulus = (void *)args->X[2];
|
||||
unsigned int master_key_rev = (unsigned int)args->X[7];
|
||||
|
||||
if (master_key_rev >= MASTERKEY_REVISION_MAX) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Copy user data into secure memory. */
|
||||
if (upage_init(&page_ref, user_wrapped_key) == 0) {
|
||||
return 2;
|
||||
}
|
||||
if (user_copy_to_secure(&page_ref, wrapped_key, user_wrapped_key, 0x100) == 0) {
|
||||
return 2;
|
||||
}
|
||||
if (user_copy_to_secure(&page_ref, modulus, user_modulus, 0x100) == 0) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
set_exp_mod_done(false);
|
||||
|
||||
/* Expected label_hash occupies args->X[3] to args->X[6]. */
|
||||
tkey_set_expected_label_hash(&args->X[3]);
|
||||
|
||||
tkey_set_master_key_rev(master_key_rev);
|
||||
|
||||
/* Hardcode RSA keyslot 0. */
|
||||
set_rsa_keyslot(0, modulus, 0x100, g_rsa_oaep_exponent, 0x100);
|
||||
se_exp_mod(0, wrapped_key, 0x100, exp_mod_done_handler);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
uint32_t user_load_titlekey(smc_args_t *args) {
|
||||
uint64_t sealed_titlekey[2];
|
||||
|
||||
uint32_t keyslot = (uint32_t)args->X[1];
|
||||
if (keyslot > 3) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Copy keydata */
|
||||
sealed_titlekey[0] = args->X[2];
|
||||
sealed_titlekey[1] = args->X[3];
|
||||
|
||||
/* Unseal the key. */
|
||||
unseal_titlekey(keyslot, sealed_titlekey, 0x10);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
uint32_t user_unwrap_aes_wrapped_titlekey(smc_args_t *args) {
|
||||
uint64_t aes_wrapped_titlekey[2];
|
||||
uint8_t titlekey[0x10];
|
||||
uint64_t sealed_titlekey[2];
|
||||
|
||||
aes_wrapped_titlekey[0] = args->X[1];
|
||||
aes_wrapped_titlekey[1] = args->X[2];
|
||||
unsigned int master_key_rev = (unsigned int)args->X[3];
|
||||
|
||||
|
||||
if (master_key_rev >= MASTERKEY_REVISION_MAX) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
tkey_set_master_key_rev(master_key_rev);
|
||||
|
||||
tkey_aes_unwrap(titlekey, 0x10, aes_wrapped_titlekey, 0x10);
|
||||
seal_titlekey(sealed_titlekey, 0x10, titlekey, 0x10);
|
||||
|
||||
args->X[1] = sealed_titlekey[0];
|
||||
args->X[2] = sealed_titlekey[1];
|
||||
|
||||
return 0; /* FIXME: what should we return there */
|
||||
}
|
||||
28
exosphere/src/smc_user.h
Normal file
28
exosphere/src/smc_user.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef EXOSPHERE_SMC_USER_H
|
||||
#define EXOSPHERE_SMC_USER_H
|
||||
|
||||
#include "smc_api.h"
|
||||
|
||||
uint32_t user_exp_mod(smc_args_t *args);
|
||||
uint32_t user_get_random_bytes(smc_args_t *args);
|
||||
uint32_t user_generate_aes_kek(smc_args_t *args);
|
||||
uint32_t user_load_aes_key(smc_args_t *args);
|
||||
uint32_t user_crypt_aes(smc_args_t *args);
|
||||
uint32_t user_generate_specific_aes_key(smc_args_t *args);
|
||||
uint32_t user_compute_cmac(smc_args_t *args);
|
||||
uint32_t user_load_rsa_oaep_key(smc_args_t *args);
|
||||
uint32_t user_decrypt_rsa_private_key(smc_args_t *args);
|
||||
uint32_t user_load_secure_exp_mod_key(smc_args_t *args);
|
||||
uint32_t user_secure_exp_mod(smc_args_t *args);
|
||||
uint32_t user_unwrap_rsa_oaep_wrapped_titlekey(smc_args_t *args);
|
||||
uint32_t user_load_titlekey(smc_args_t *args);
|
||||
uint32_t user_unwrap_aes_wrapped_titlekey(smc_args_t *args);
|
||||
|
||||
|
||||
void set_crypt_aes_done(bool done);
|
||||
bool get_crypt_aes_done(void);
|
||||
|
||||
void set_exp_mod_done(bool done);
|
||||
bool get_exp_mod_done(void);
|
||||
|
||||
#endif
|
||||
150
exosphere/src/start.s
Normal file
150
exosphere/src/start.s
Normal file
@@ -0,0 +1,150 @@
|
||||
/* For some reason GAS doesn't know about it, even with .cpu cortex-a57 */
|
||||
#define cpuactlr_el1 s3_1_c15_c2_0
|
||||
.macro ERRATUM_INVALIDATE_BTB_AT_BOOT
|
||||
/* Nintendo copy-pasted https://github.com/ARM-software/arm-trusted-firmware/blob/master/plat/nvidia/tegra/common/aarch64/tegra_helpers.S#L312 */
|
||||
/*
|
||||
* Copyright (c) 2015-2017, ARM Limited and Contributors. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
/* The following comments are mine. */
|
||||
/* mask all interrupts */
|
||||
msr daifset, 0b1111
|
||||
|
||||
/*
|
||||
Enable invalidates of branch target buffer, then flush
|
||||
the entire instruction cache at the local level, and
|
||||
with the reg change, the branch target buffer, then disable
|
||||
invalidates of the branch target buffer again.
|
||||
*/
|
||||
mrs x0, cpuactlr_el1
|
||||
orr x0, x0, #1
|
||||
msr cpuactlr_el1, x0
|
||||
|
||||
dsb sy
|
||||
isb
|
||||
ic iallu
|
||||
dsb sy
|
||||
isb
|
||||
|
||||
mrs x0, cpuactlr_el1
|
||||
bic x0, x0, #1
|
||||
msr cpuactlr_el1, x0
|
||||
|
||||
.rept 7
|
||||
nop /* wait long enough for the write to cpuactlr_el1 to have completed */
|
||||
.endr
|
||||
|
||||
/* if the OS lock is set, disable it and request a warm reset */
|
||||
mrs x0, oslsr_el1
|
||||
ands x0, x0, #2
|
||||
b.eq 2f
|
||||
mov x0, xzr
|
||||
msr oslar_el1, x0
|
||||
|
||||
mov x0, #(1 << 63)
|
||||
msr cpuactlr_el1, x0 /* disable regional clock gating */
|
||||
isb
|
||||
mov x0, #3
|
||||
msr rmr_el3, x0
|
||||
isb
|
||||
dsb sy
|
||||
/* Nintendo forgot to copy-paste the branch instruction below. */
|
||||
1:
|
||||
wfi
|
||||
b 1b
|
||||
.rept 65
|
||||
nop /* guard against speculative excecution */
|
||||
.endr
|
||||
|
||||
2:
|
||||
/* set the OS lock */
|
||||
mov x0, #1
|
||||
msr oslar_el1, x0
|
||||
.endm
|
||||
|
||||
.align 6
|
||||
.section .text.cold.start, "ax", %progbits
|
||||
.global __start_cold
|
||||
__start_cold:
|
||||
ERRATUM_INVALIDATE_BTB_AT_BOOT
|
||||
|
||||
msr spsel, #0
|
||||
bl get_coldboot_crt0_stack_address /* should be optimized so it doesn't make function calls */
|
||||
mov sp, x0
|
||||
bl coldboot_init
|
||||
ldr x16, =__jump_to_main_cold
|
||||
br x16
|
||||
|
||||
.align 6
|
||||
.section .text.warm.start, "ax", %progbits
|
||||
.global __start_warm
|
||||
__start_warm:
|
||||
ERRATUM_INVALIDATE_BTB_AT_BOOT
|
||||
|
||||
/* For some reasons, Nintendo uses spsel, #1 here, causing issues if an exception occurs */
|
||||
msr spsel, #0
|
||||
bl get_warmboot_crt0_stack_address /* should be optimized so it doesn't make function calls */
|
||||
mov sp, x0
|
||||
bl warmboot_init
|
||||
ldr x16, =__jump_to_main_warm
|
||||
br x16
|
||||
|
||||
.section .text.__jump_to_main_cold, "ax", %progbits
|
||||
__jump_to_main_cold:
|
||||
bl __set_exception_entry_stack_pointer
|
||||
|
||||
bl get_pk2ldr_stack_address
|
||||
mov sp, x0
|
||||
bl load_package2
|
||||
|
||||
mov w0, #3 /* use core3 stack temporarily */
|
||||
bl get_exception_entry_stack_address
|
||||
mov sp, x0
|
||||
b coldboot_main
|
||||
|
||||
.section .text.__jump_to_main_warm, "ax", %progbits
|
||||
__jump_to_main_warm:
|
||||
/* Nintendo doesn't do that here, causing issues if an exception occurs */
|
||||
bl __set_exception_entry_stack_pointer
|
||||
|
||||
bl get_pk2ldr_stack_address
|
||||
mov sp, x0
|
||||
bl load_package2
|
||||
|
||||
mov w0, #3 /* use core0,1,2 stack bottom + 0x800 (VA of warmboot crt0 sp) temporarily */
|
||||
bl get_exception_entry_stack_address
|
||||
add sp, x0, #0x800
|
||||
b warmboot_main
|
||||
|
||||
.section .text.__set_exception_entry_stack, "ax", %progbits
|
||||
.type __set_exception_entry_stack, %function
|
||||
.global __set_exception_entry_stack
|
||||
__set_exception_entry_stack_pointer:
|
||||
/* If SPSel == 1 on entry, make sure your function doesn't use stack variables! */
|
||||
mov x16, lr
|
||||
mrs x17, spsel
|
||||
mrs x0, mpidr_el1
|
||||
and w0, w0, #3
|
||||
bl get_exception_entry_stack_address /* should be optimized so it doesn't make function calls */
|
||||
msr spsel, #1
|
||||
mov sp, x0
|
||||
msr spsel, x17
|
||||
mov lr, x16
|
||||
ret
|
||||
|
||||
.section .text.__jump_to_lower_el, "ax", %progbits
|
||||
.global __jump_to_lower_el
|
||||
.type __jump_to_lower_el, %function
|
||||
__jump_to_lower_el:
|
||||
/* x0: arg (context ID), x1: entrypoint, w2: exception level */
|
||||
msr elr_el3, x1
|
||||
|
||||
mov w1, #((0b1111 << 6) | 1) /* DAIF set and SP = SP_ELx*/
|
||||
orr w1, w2, w2, lsl#2
|
||||
msr spsr_el3, x1
|
||||
|
||||
bl __set_exception_entry_stack_pointer
|
||||
|
||||
isb
|
||||
eret
|
||||
8
exosphere/src/timers.c
Normal file
8
exosphere/src/timers.c
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "timers.h"
|
||||
|
||||
void wait(uint32_t microseconds) {
|
||||
uint32_t old_time = TIMERUS_CNTR_1US_0;
|
||||
while (TIMERUS_CNTR_1US_0 - old_time <= microseconds) {
|
||||
/* Spin-lock. */
|
||||
}
|
||||
}
|
||||
15
exosphere/src/timers.h
Normal file
15
exosphere/src/timers.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef EXOSPHERE_TIMERS_H
|
||||
#define EXOSPHERE_TIMERS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Exosphere driver for the Tegra X1 Timers. */
|
||||
|
||||
#define TIMERS_BASE (mmio_get_device_address(MMIO_DEVID_TMRs_WDTs))
|
||||
|
||||
#define TIMERUS_CNTR_1US_0 (*((volatile uint32_t *)(TIMERS_BASE + 0x10)))
|
||||
|
||||
void wait(uint32_t microseconds);
|
||||
|
||||
#endif
|
||||
156
exosphere/src/titlekey.c
Normal file
156
exosphere/src/titlekey.c
Normal file
@@ -0,0 +1,156 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "cache.h"
|
||||
|
||||
#include "titlekey.h"
|
||||
#include "masterkey.h"
|
||||
#include "se.h"
|
||||
|
||||
uint64_t g_tkey_expected_label_hash[4];
|
||||
unsigned int g_tkey_master_key_rev = MASTERKEY_REVISION_MAX;
|
||||
|
||||
/* Set the expected db prefix. */
|
||||
void tkey_set_expected_label_hash(uint64_t *label_hash) {
|
||||
for (unsigned int i = 0; i < 4; i++) {
|
||||
g_tkey_expected_label_hash[i] = label_hash[i];
|
||||
}
|
||||
}
|
||||
|
||||
void tkey_set_master_key_rev(unsigned int master_key_rev) {
|
||||
if (master_key_rev >= MASTERKEY_REVISION_MAX) {
|
||||
generic_panic();
|
||||
}
|
||||
}
|
||||
|
||||
/* Reference for MGF1 can be found here: https://en.wikipedia.org/wiki/Mask_generation_function#MGF1 */
|
||||
void calculate_mgf1_and_xor(void *masked, size_t masked_size, const void *seed, size_t seed_size) {
|
||||
uint8_t cur_hash[0x20];
|
||||
uint8_t hash_buf[0xE4];
|
||||
if (seed_size >= 0xE0) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
size_t hash_buf_size = seed_size + 4;
|
||||
memcpy(hash_buf, seed, seed_size);
|
||||
|
||||
uint32_t round = 0;
|
||||
|
||||
uint8_t *p_out = (uint8_t *)masked;
|
||||
|
||||
while (masked_size) {
|
||||
size_t cur_size = masked_size;
|
||||
if (cur_size > 0x20) {
|
||||
cur_size = 0x20;
|
||||
}
|
||||
|
||||
hash_buf[seed_size + 0] = (uint8_t)((round >> 24) & 0xFF);
|
||||
hash_buf[seed_size + 1] = (uint8_t)((round >> 16) & 0xFF);
|
||||
hash_buf[seed_size + 2] = (uint8_t)((round >> 8) & 0xFF);
|
||||
hash_buf[seed_size + 3] = (uint8_t)((round >> 0) & 0xFF);
|
||||
round++;
|
||||
|
||||
flush_dcache_range(hash_buf, hash_buf + hash_buf_size);
|
||||
se_calculate_sha256(cur_hash, hash_buf, hash_buf_size);
|
||||
|
||||
for (unsigned int i = 0; i < cur_size; i++) {
|
||||
*p_out ^= cur_hash[i];
|
||||
p_out++;
|
||||
}
|
||||
|
||||
masked_size -= cur_size;
|
||||
}
|
||||
}
|
||||
|
||||
size_t tkey_rsa_oaep_unwrap(void *dst, size_t dst_size, void *src, size_t src_size) {
|
||||
if (src_size != 0x100) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
/* RSA Wrapped titlekeys use RSA-OAEP. */
|
||||
/* Message is of the form prefix || maskedSalt || maskedDB. */
|
||||
/* maskedSalt = salt ^ MGF1(maskedDB) */
|
||||
/* maskedDB = DB ^ MGF1(salt) */
|
||||
/* Salt is random and not validated in any way. */
|
||||
/* DB is of the form label_hash || 00....01 || wrapped_titlekey. */
|
||||
/* label_hash is, in practice, a constant in es .rodata. */
|
||||
/* I have no idea why Nintendo did this, it should be either nonconstant (in tik) or in tz .rodata. */
|
||||
|
||||
uint8_t *message = (uint8_t *)src;
|
||||
|
||||
/* Prefix should always be zero. */
|
||||
if (*message != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
uint8_t *salt = message + 1;
|
||||
uint8_t *db = message + 0x21;
|
||||
|
||||
/* This will be passed to smc_unwrap_rsa_oaep_wrapped_titlekey. */
|
||||
uint8_t *expected_label_hash = (uint8_t *)(&g_tkey_expected_label_hash[0]);
|
||||
|
||||
/* Unmask the salt. */
|
||||
calculate_mgf1_and_xor(salt, 0x20, db, 0xDF);
|
||||
/* Unmask the DB. */
|
||||
calculate_mgf1_and_xor(db, 0xDF, salt, 0x20);
|
||||
|
||||
/* Validate expected salt. */
|
||||
for (unsigned int i = 0; i < 0x20; i++) {
|
||||
if (expected_label_hash[i] != db[i]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't validate salt from message[1:0x21] at all. */
|
||||
|
||||
/* Advance pointer to DB, since we've validated the salt prefix. */
|
||||
db += 0x20;
|
||||
|
||||
/* DB must be of the form 0000...01 || wrapped_titlekey */
|
||||
if (*db != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Locate wrapped_titlekey inside DB. */
|
||||
size_t wrapped_key_offset_in_db = 0;
|
||||
while (wrapped_key_offset_in_db < 0xBF) {
|
||||
if (db[wrapped_key_offset_in_db] == 0) {
|
||||
wrapped_key_offset_in_db++;
|
||||
} else if (db[wrapped_key_offset_in_db] == 1) {
|
||||
wrapped_key_offset_in_db++;
|
||||
break;
|
||||
} else {
|
||||
/* Invalid wrapped titlekey prefix. */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate size... */
|
||||
size_t wrapped_titlekey_size = 0xBF - wrapped_key_offset_in_db;
|
||||
if (wrapped_titlekey_size > dst_size || wrapped_titlekey_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Extract the wrapped key. */
|
||||
memcpy(dst, &db[wrapped_key_offset_in_db], wrapped_titlekey_size);
|
||||
return wrapped_key_offset_in_db;
|
||||
}
|
||||
|
||||
void tkey_aes_unwrap(void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
if (g_tkey_master_key_rev >= MASTERKEY_REVISION_MAX || dst_size != 0x10 || src_size != 0x10) {
|
||||
generic_panic();
|
||||
}
|
||||
|
||||
const uint8_t titlekek_source[0x10] = {
|
||||
0x1E, 0xDC, 0x7B, 0x3B, 0x60, 0xE6, 0xB4, 0xD8, 0x78, 0xB8, 0x17, 0x15, 0x98, 0x5E, 0x62, 0x9B
|
||||
};
|
||||
|
||||
/* Generate the appropriate titlekek into keyslot 9. */
|
||||
unsigned int master_keyslot = mkey_get_keyslot(g_tkey_master_key_rev);
|
||||
decrypt_data_into_keyslot(KEYSLOT_SWITCH_TEMPKEY, master_keyslot, titlekek_source, 0x10);
|
||||
|
||||
/* Unwrap the titlekey using the titlekek. */
|
||||
se_aes_ecb_decrypt_block(KEYSLOT_SWITCH_TEMPKEY, dst, 0x10, src, 0x10);
|
||||
}
|
||||
13
exosphere/src/titlekey.h
Normal file
13
exosphere/src/titlekey.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef EXOSPHERE_TITLEKEY_H
|
||||
#define EXOSPHERE_TITLEKEY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void tkey_set_expected_label_hash(uint64_t *label_hash);
|
||||
void tkey_set_master_key_rev(unsigned int master_key_rev);
|
||||
|
||||
size_t tkey_rsa_oaep_unwrap(void *dst, size_t dst_size, void *src, size_t src_size);
|
||||
|
||||
void tkey_aes_unwrap(void *dst, size_t dst_size, const void *src, size_t src_size);
|
||||
|
||||
#endif
|
||||
36
exosphere/src/uart.c
Normal file
36
exosphere/src/uart.c
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "uart.h"
|
||||
|
||||
void uart_initialize(uint16_t divider) {
|
||||
/* Setup UART in 16450 mode. We assume the relevant UART clock has been enabled. */
|
||||
|
||||
/* Disable FIFO */
|
||||
UART_IIR_FCR_0 = 0x00;
|
||||
|
||||
/* Set DLAB */
|
||||
UART_LCR_0 = 0x80;
|
||||
UART_THR_DLAB_0_0 = (uint8_t)divider;
|
||||
UART_IER_DLAB_0_0 = (uint8_t)(divider >> 8);
|
||||
|
||||
/* 8N1 mode */
|
||||
UART_LCR_0 = 0x03;
|
||||
}
|
||||
|
||||
void uart_transmit_char(char ch) {
|
||||
/* Wait for THR to be empty */
|
||||
while (!(UART_LSR_0 & 0x20)) {}
|
||||
|
||||
UART_THR_DLAB_0_0 = ch;
|
||||
}
|
||||
|
||||
void uart_transmit_str(const char *str) {
|
||||
while (*str) {
|
||||
uart_transmit_char(*str++);
|
||||
}
|
||||
}
|
||||
|
||||
void uart_transmit_hex(uint32_t value) {
|
||||
for (unsigned int i = 0; i < 8; i++) {
|
||||
uint32_t nibble = (value >> (28 - i * 4)) & 0xF;
|
||||
uart_transmit_char("0123456789ABCDEF"[nibble]);
|
||||
}
|
||||
}
|
||||
24
exosphere/src/uart.h
Normal file
24
exosphere/src/uart.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef EXOSPHERE_UART_H
|
||||
#define EXOSPHERE_UART_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "memory_map.h"
|
||||
|
||||
/* Exosphere driver for the Tegra X1 UARTs. */
|
||||
|
||||
/* TODO: Should we bother with support UARTB-D? */
|
||||
|
||||
#define UARTA_BASE (mmio_get_device_address(MMIO_DEVID_UART_A))
|
||||
|
||||
#define UART_THR_DLAB_0_0 (*((volatile uint32_t *)(UARTA_BASE + 0x0)))
|
||||
#define UART_IER_DLAB_0_0 (*((volatile uint32_t *)(UARTA_BASE + 0x4)))
|
||||
#define UART_IIR_FCR_0 (*((volatile uint32_t *)(UARTA_BASE+ 0x8)))
|
||||
#define UART_LCR_0 (*((volatile uint32_t *)(UARTA_BASE + 0xC)))
|
||||
#define UART_LSR_0 (*((volatile uint32_t *)(UARTA_BASE + 0x14)))
|
||||
|
||||
void uart_initialize(uint16_t divider);
|
||||
void uart_transmit_char(char ch);
|
||||
void uart_transmit_str(const char *str);
|
||||
void uart_transmit_hex(uint32_t value);
|
||||
|
||||
#endif
|
||||
71
exosphere/src/userpage.c
Normal file
71
exosphere/src/userpage.c
Normal file
@@ -0,0 +1,71 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "userpage.h"
|
||||
#include "memory_map.h"
|
||||
#include "cache.h"
|
||||
|
||||
static uintptr_t g_user_page_user_address = 0ULL;
|
||||
|
||||
static inline uintptr_t get_page_for_address(void *address) {
|
||||
return ((uintptr_t)(address)) & ~0xFFFULL;
|
||||
}
|
||||
|
||||
/* Create a user page reference for the desired address. */
|
||||
/* Returns 1 on success, 0 on failure. */
|
||||
bool upage_init(upage_ref_t *upage, void *user_address) {
|
||||
upage->user_address = get_page_for_address(user_address);
|
||||
upage->secure_monitor_address = 0ULL;
|
||||
|
||||
if (g_user_page_user_address != 0ULL) {
|
||||
/* Different physical address indicate SPL was rebooted, or another process got access to svcCallSecureMonitor. Panic. */
|
||||
if (g_user_page_user_address != upage->user_address) {
|
||||
generic_panic();
|
||||
}
|
||||
upage->secure_monitor_address = USER_PAGE_SECURE_MONITOR_ADDR;
|
||||
} else {
|
||||
/* Weakly validate SPL's physically random address is in DRAM. */
|
||||
if (upage->user_address >> 31) {
|
||||
static const uint64_t userpage_attributes = MMU_PTE_BLOCK_XN | MMU_PTE_BLOCK_INNER_SHAREBLE | MMU_PTE_BLOCK_NS | ATTRIB_MEMTYPE_NORMAL;
|
||||
uintptr_t *mmu_l3_tbl = (uintptr_t *)tzram_get_segment_address(TZRAM_SEGMENT_ID_L3_TRANSLATION_TABLE);
|
||||
g_user_page_user_address = upage->user_address;
|
||||
mmu_map_page(mmu_l3_tbl, USER_PAGE_SECURE_MONITOR_ADDR, upage->user_address, userpage_attributes);
|
||||
tlb_invalidate_page_inner_shareable((void *)USER_PAGE_SECURE_MONITOR_ADDR);
|
||||
upage->secure_monitor_address = USER_PAGE_SECURE_MONITOR_ADDR;
|
||||
}
|
||||
}
|
||||
|
||||
return upage->secure_monitor_address != 0ULL;
|
||||
}
|
||||
|
||||
bool user_copy_to_secure(upage_ref_t *upage, void *secure_dst, void *user_src, size_t size) {
|
||||
/* Fail if the page doesn't match. */
|
||||
if (get_page_for_address(user_src) != upage->user_address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fail if we go past the page boundary. */
|
||||
if (size != 0 && get_page_for_address(user_src + size - 1) != upage->user_address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void *secure_src = (void *)(upage->secure_monitor_address + ((uintptr_t)user_src - upage->user_address));
|
||||
memcpy(secure_dst, secure_src, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool secure_copy_to_user(upage_ref_t *upage, void *user_dst, void *secure_src, size_t size) {
|
||||
/* Fail if the page doesn't match. */
|
||||
if (get_page_for_address(user_dst) != upage->user_address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Fail if we go past the page boundary. */
|
||||
if (size != 0 && get_page_for_address(user_dst + size - 1) != upage->user_address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void *secure_dst = (void *)(upage->secure_monitor_address + ((uintptr_t)user_dst - upage->user_address));
|
||||
memcpy(secure_dst, secure_src, size);
|
||||
return true;
|
||||
}
|
||||
19
exosphere/src/userpage.h
Normal file
19
exosphere/src/userpage.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef EXOSPHERE_USERPAGE_H
|
||||
#define EXOSPHERE_USERPAGE_H
|
||||
|
||||
#include "utils.h"
|
||||
#include "memory_map.h"
|
||||
|
||||
#define USER_PAGE_SECURE_MONITOR_ADDR (tzram_get_segment_address(TZRAM_SEGMENT_ID_USERPAGE))
|
||||
|
||||
typedef struct {
|
||||
uintptr_t user_address;
|
||||
uintptr_t secure_monitor_address;
|
||||
} upage_ref_t;
|
||||
|
||||
bool upage_init(upage_ref_t *user_page, void *user_address);
|
||||
|
||||
bool user_copy_to_secure(upage_ref_t *user_page, void *secure_dst, void *user_src, size_t size);
|
||||
bool secure_copy_to_user(upage_ref_t *user_page, void *user_dst, void *secure_src, size_t size);
|
||||
|
||||
#endif
|
||||
18
exosphere/src/utils.c
Normal file
18
exosphere/src/utils.c
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "utils.h"
|
||||
|
||||
void panic(uint32_t code) {
|
||||
(void)code; /* TODO */
|
||||
}
|
||||
|
||||
void generic_panic(void) {
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
__attribute__((noinline)) bool overlaps(uint64_t as, uint64_t ae, uint64_t bs, uint64_t be)
|
||||
{
|
||||
if(as <= bs && bs <= ae)
|
||||
return true;
|
||||
if(bs <= as && as <= be)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
44
exosphere/src/utils.h
Normal file
44
exosphere/src/utils.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef EXOSPHERE_UTILS_H
|
||||
#define EXOSPHERE_UTILS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define BIT(x) (1u << (x))
|
||||
#define BITL(x) (1ull << (x))
|
||||
|
||||
void panic(uint32_t code);
|
||||
void generic_panic(void);
|
||||
bool overlaps(uint64_t as, uint64_t ae, uint64_t bs, uint64_t be);
|
||||
|
||||
static inline uintptr_t get_physical_address(const void *vaddr) {
|
||||
uintptr_t PAR;
|
||||
__asm__ __volatile__ ("at s1e3r, %0" :: "r"(vaddr));
|
||||
__asm__ __volatile__ ("mrs %0, par_el1" : "=r"(PAR));
|
||||
return (PAR & 1) ? 0ULL : (PAR & 0x00000FFFFFFFF000ULL) | ((uintptr_t)vaddr & 0xFFF);
|
||||
}
|
||||
|
||||
static inline uint32_t read32le(const volatile void *dword, size_t offset) {
|
||||
return *(uint32_t *)((uintptr_t)dword + offset);
|
||||
}
|
||||
|
||||
static inline uint32_t read32be(const volatile void *dword, size_t offset) {
|
||||
return __builtin_bswap32(read32le(dword, offset));
|
||||
}
|
||||
|
||||
static inline uint64_t read64le(const volatile void *qword, size_t offset) {
|
||||
return *(uint64_t *)((uintptr_t)qword + offset);
|
||||
}
|
||||
|
||||
static inline unsigned int get_core_id(void) {
|
||||
uint64_t core_id;
|
||||
__asm__ __volatile__ ("mrs %0, mpidr_el1" : "=r"(core_id));
|
||||
return (unsigned int)core_id & 3;
|
||||
}
|
||||
|
||||
static inline bool check_32bit_additive_overflow(uint32_t a, uint32_t b) {
|
||||
return __builtin_add_overflow_p(a, b, (uint32_t)0);
|
||||
}
|
||||
|
||||
#endif
|
||||
16
exosphere/src/warmboot_init.c
Normal file
16
exosphere/src/warmboot_init.c
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "utils.h"
|
||||
#include "memory_map.h"
|
||||
|
||||
uintptr_t get_warmboot_crt0_stack_address(void);
|
||||
|
||||
void flush_dcache_all_tzram_pa(void) {
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
void invalidate_icache_all_tzram_pa(void) {
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
uintptr_t get_warmboot_crt0_stack_address(void) {
|
||||
return tzram_get_segment_pa(TZRAM_SEGMENT_ID_CORE3_STACK) + 0x800;
|
||||
}
|
||||
11
exosphere/src/warmboot_main.c
Normal file
11
exosphere/src/warmboot_main.c
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "utils.h"
|
||||
#include "mmu.h"
|
||||
#include "memory_map.h"
|
||||
|
||||
extern void __jump_to_lower_el(uint64_t arg, uintptr_t ep, unsigned int el);
|
||||
|
||||
void warmboot_main(void);
|
||||
|
||||
void warmboot_main(void) {
|
||||
/* TODO */
|
||||
}
|
||||
Reference in New Issue
Block a user