Created
October 17, 2017 23:26
-
-
Save shuffle2/f8728159da100e9df2606d43925de0af to your computer and use it in GitHub Desktop.
dump + decode tegra t210 ipatches
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/mman.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <stdio.h> | |
#include <stdint.h> | |
#include <string.h> | |
#define ARRAYSIZE(x) (sizeof(x) / sizeof(*x)) | |
typedef uint8_t u8; | |
typedef uint16_t u16; | |
typedef uint32_t u32; | |
typedef uint64_t u64; | |
typedef int8_t s8; | |
typedef int16_t s16; | |
typedef int32_t s32; | |
typedef int64_t s64; | |
typedef volatile u8 vu8; | |
typedef volatile u16 vu16; | |
typedef volatile u32 vu32; | |
typedef volatile u64 vu64; | |
typedef volatile s8 vs8; | |
typedef volatile s16 vs16; | |
typedef volatile s32 vs32; | |
typedef volatile s64 vs64; | |
int open_fd(int *fd, const char *path, int flags) | |
{ | |
*fd = open(path, flags); | |
if (*fd < 0) | |
{ | |
printf("open %s: %s\n", path, strerror(errno)); | |
return 0; | |
} | |
return 1; | |
} | |
int mmap_fd_rw(void **ptr, int fd, size_t offset, size_t len) | |
{ | |
*ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, | |
offset); | |
if (*ptr == (void *)-1) | |
{ | |
printf("mmap %d %zx %zx: %s\n", fd, offset, len, strerror(errno)); | |
return 0; | |
} | |
return 1; | |
} | |
u32 read32(void *src) | |
{ | |
return *(vu32 *)src; | |
} | |
void write32(void *dst, u32 val) | |
{ | |
*(vu32 *)dst = val; | |
} | |
#define PAGE_SIZE 0x1000 | |
#define PAGE_MASK (PAGE_SIZE - 1) | |
#define TEGRA_FUSE_BASE 0x7000F800 | |
#define TEGRA_FUSE_SIZE 0x400 | |
#define FUSE_CTRL 0x000 | |
#define FUSE_REG_ADDR 0x004 | |
#define FUSE_REG_READ 0x008 | |
#define FUSE_FIRST_BOOTROM_PATCH_SIZE_REG 0x19c | |
#define FUSE_READ 0x1 | |
#define FUSE_WRITE 0x2 | |
#define FUSE_SENSE 0x3 | |
#define FUSE_CMD_MASK 0x3 | |
#define TEGRA_CLK_RESET_BASE 0x60006000 | |
#define TEGRA_CLK_RESET_SIZE 0x1000 | |
#define MISC_CLK_ENB 0x48 | |
#define TEGRA_EXCEPTION_VECTORS_BASE 0x6000f000 | |
#define ROM_BASE 0x100000 | |
//#define REAL_FUSES | |
#ifdef REAL_FUSES | |
static int mem_fd; | |
static void *fuse_base; | |
static void *clk_base; | |
int init() | |
{ | |
if (!open_fd(&mem_fd, "/dev/mem", O_RDWR | O_SYNC)) | |
{ | |
return 0; | |
} | |
size_t addr = TEGRA_FUSE_BASE; | |
size_t slack = addr - (addr & ~PAGE_MASK); | |
if (!mmap_fd_rw(&fuse_base, mem_fd, addr - slack, TEGRA_FUSE_SIZE + slack)) | |
{ | |
return 0; | |
} | |
fuse_base += slack; | |
if (!mmap_fd_rw(&clk_base, mem_fd, TEGRA_CLK_RESET_BASE, TEGRA_CLK_RESET_SIZE)) | |
{ | |
return 0; | |
} | |
return 1; | |
} | |
u32 fuse_read32(u32 offset) | |
{ | |
return read32(fuse_base + offset); | |
} | |
void fuse_write32(u32 offset, u32 val) | |
{ | |
write32(fuse_base + offset, val); | |
} | |
void clk_enable_fuse(u32 enable) | |
{ | |
void *addr = clk_base + MISC_CLK_ENB; | |
u32 val = read32(addr); | |
val |= (enable & 1) << 28; | |
write32(addr, val); | |
} | |
void fuse_wait_idle() | |
{ | |
u32 ctrl; | |
do | |
{ | |
ctrl = fuse_read32(FUSE_CTRL); | |
} while (((ctrl >> 16) & 0x1f) != 4); | |
} | |
#else | |
static u32 fuse_reg_addr = 0; | |
int init() { return 1; } | |
static u32 fuse_image[0x100] = { | |
/* put a fuse dump here */ | |
}; | |
u32 fuse_read32(u32 offset) | |
{ | |
switch (offset) { | |
case FUSE_FIRST_BOOTROM_PATCH_SIZE_REG: | |
// you may need to change this | |
return 77; | |
case FUSE_REG_READ: | |
return fuse_image[fuse_reg_addr]; | |
default: | |
return 0; | |
} | |
} | |
void fuse_write32(u32 offset, u32 val) | |
{ | |
switch (offset) { | |
case FUSE_REG_ADDR: | |
fuse_reg_addr = val; | |
break; | |
} | |
} | |
void clk_enable_fuse(u32 enable) {} | |
void fuse_wait_idle() {} | |
#endif // REAL_FUSES | |
// would be at 40004C30 | |
static u32 iram_evp_thunks[0x200]; | |
static const u32 evp_thunk_template[] = { | |
0xe92d0007, // STMFD SP!, {R0-R2} | |
0xe1a0200e, // MOV R2, LR | |
0xe2422002, // SUB R2, R2, #2 | |
0xe5922000, // LDR R2, [R2] | |
0xe20220ff, // AND R2, R2, #0xFF | |
0xe1a02082, // MOV R2, R2,LSL#1 | |
0xe59f001c, // LDR R0, =evp_thunk_template | |
0xe59f101c, // LDR R1, =thunk_end | |
0xe0411000, // SUB R1, R1, R0 | |
0xe59f0018, // LDR R0, =iram_evp_thunks | |
0xe0800001, // ADD R0, R0, R1 | |
0xe0822000, // ADD R2, R2, R0 | |
0xe3822001, // ORR R2, R2, #1 | |
0xe8bd0003, // LDMFD SP!, {R0,R1} | |
0xe12fff12, // BX R2 | |
0x001007b0, // off_1007EC DCD evp_thunk_template | |
0x001007f8, // off_1007F0 DCD thunk_end | |
0x40004c30, // off_1007F4 DCD iram_evp_thunks | |
// thunk_end is here | |
}; | |
static const size_t evp_thunk_template_len = sizeof(evp_thunk_template); | |
void memcpy32(void *dst, void *src, u32 len) | |
{ | |
u32 *s = src; | |
u32 *d = dst; | |
for (u32 i = 0; i < len / sizeof(u32); i++) | |
{ | |
*d++ = *s++; | |
} | |
} | |
// treated as 12bit values | |
static const u32 hash_vals[] = { | |
1, 2, 4, 8, 0, 3, 5, 6, 7, 9, 10, 11}; | |
u32 parity32_even(u32 *words, u32 count) | |
{ | |
u32 acc = words[0]; | |
for (u32 i = 1; i < count; i++) | |
{ | |
acc ^= words[i]; | |
} | |
u32 lo = ((acc & 0xffff) ^ (acc >> 16)) & 0xff; | |
u32 hi = ((acc & 0xffff) ^ (acc >> 16)) >> 8; | |
u32 x = hi ^ lo; | |
lo = ((x & 0xf) ^ (x >> 4)) & 3; | |
hi = ((x & 0xf) ^ (x >> 4)) >> 2; | |
x = hi ^ lo; | |
return (x & 1) ^ (x >> 1); | |
} | |
int patch_hash_one(u32 *word) | |
{ | |
u32 bits20_31 = *word & 0xfff00000; | |
u32 parity_bit = parity32_even(&bits20_31, 1); | |
u32 hash = 0; | |
for (u32 i = 0; i < 12; i++) | |
{ | |
if (*word & (1 << (20 + i))) | |
{ | |
hash ^= hash_vals[i]; | |
} | |
} | |
if (hash == 0) | |
{ | |
if (parity_bit == 0) | |
{ | |
return 0; | |
} | |
*word ^= 1 << 24; | |
return 1; | |
} | |
if (parity_bit == 0) | |
{ | |
return 3; | |
} | |
for (size_t i = 0; i < ARRAYSIZE(hash_vals); i++) | |
{ | |
if (hash_vals[i] == hash) | |
{ | |
*word ^= 1 << (20 + i); | |
return 1; | |
} | |
} | |
return 2; | |
} | |
int patch_hash_multi(u32 *words, u32 count) | |
{ | |
u32 parity_bit = parity32_even(words, count); | |
u32 bits0_14 = words[0] & 0x7fff; | |
u32 bit15 = words[0] & 0x8000; | |
u32 bits16_19 = words[0] & 0xf0000; | |
u32 hash = 0; | |
words[0] = bits16_19; | |
for (u32 i = 0; i < count; i++) | |
{ | |
u32 w = words[i]; | |
if (w) | |
{ | |
for (u32 bitpos = 0; bitpos < 32; bitpos++) | |
{ | |
if ((w >> bitpos) & 1) | |
{ | |
hash ^= 0x4000 + i * 32 + bitpos; | |
} | |
} | |
} | |
} | |
hash ^= bits0_14; | |
// stupid but this is what original code does. | |
// equivalent to original words[0] &= 0xfff00000 | |
words[0] = bits16_19 ^ bit15 ^ bits0_14; | |
if (hash == 0) | |
{ | |
if (parity_bit == 0) | |
{ | |
return 0; | |
} | |
words[0] ^= 0x8000; | |
return 1; | |
} | |
if (parity_bit == 0) | |
{ | |
return 3; | |
} | |
u32 bitcount = hash - 0x4000; | |
if (bitcount < 16 || bitcount >= count * 32) | |
{ | |
u32 num_set = 0; | |
for (u32 bitpos = 0; bitpos < 15; bitpos++) | |
{ | |
if ((hash >> bitpos) & 1) | |
{ | |
num_set++; | |
} | |
} | |
if (num_set != 1) | |
{ | |
return 2; | |
} | |
words[0] ^= hash; | |
return 1; | |
} | |
words[bitcount / 32] ^= 1 << (hash & 0x1f); | |
return 1; | |
} | |
void ipatch_process(u32 *entries, u32 count) | |
{ | |
puts("-- ipatch data:"); | |
for (u32 i = 0; i < count; i++) | |
{ | |
u32 entry = entries[i]; | |
u32 addr = (entry >> 16) * 2; | |
u32 data = entry & 0xffff; | |
printf("%2d %8x %8x %8x", i, entry, ROM_BASE + addr, data); | |
u8 lo = data & 0xff; | |
switch (data >> 8) | |
{ | |
case 0xdf: | |
printf(" : svc #0x%02x (offset 0x%02lx)", lo, evp_thunk_template_len + lo * 2); | |
break; | |
case 0x20: | |
printf(" : movs r0, #0x%02x", lo); | |
break; | |
} | |
puts(""); | |
} | |
} | |
void read_fused_patches() | |
{ | |
u32 words[80]; | |
u32 word_count; | |
u32 word_addr; | |
u32 word0 = 0; | |
u32 total_read = 0; | |
int evp_thunk_written = 0; | |
void *evp_thunk_dst_addr = 0; | |
word_count = fuse_read32(FUSE_FIRST_BOOTROM_PATCH_SIZE_REG); | |
printf("FUSE_FIRST_BOOTROM_PATCH_SIZE_REG: %d\n", word_count); | |
word_count &= 0x7f; | |
word_addr = 191; | |
while (word_count) | |
{ | |
puts("----------------------------------------"); | |
printf("total %d current %d\n", total_read, word_count); | |
total_read += word_count; | |
if (total_read >= ARRAYSIZE(words)) | |
{ | |
puts("done (max count)"); | |
break; | |
} | |
clk_enable_fuse(1); | |
for (u32 i = 0; i < word_count; i++) | |
{ | |
fuse_write32(FUSE_REG_ADDR, word_addr); | |
fuse_write32(FUSE_CTRL, (fuse_read32(FUSE_REG_ADDR) & ~FUSE_CMD_MASK) | FUSE_READ); | |
word_addr--; | |
fuse_wait_idle(); | |
words[i] = fuse_read32(FUSE_REG_READ); | |
} | |
clk_enable_fuse(0); | |
printf("control word %08x\n", words[0]); | |
word0 = words[0]; | |
if (patch_hash_multi(words, word_count) >= 2) | |
{ | |
puts("done (integrity error: 1)"); | |
break; | |
} | |
u32 ipatch_count = (words[0] >> 16) & 0xf; | |
u32 insn_count = word_count - ipatch_count - 1; | |
if (ipatch_count) | |
{ | |
ipatch_process(&words[1], ipatch_count); | |
} | |
if (insn_count) | |
{ | |
puts("-- insn data:"); | |
for (u32 i = 0; i < insn_count; i++) | |
{ | |
u32 data = words[ipatch_count + 1 + i]; | |
printf("%2d %8x\n", i, data); | |
} | |
if (!evp_thunk_written) | |
{ | |
evp_thunk_dst_addr = (void *)iram_evp_thunks; | |
memcpy32(evp_thunk_dst_addr, (void *)evp_thunk_template, evp_thunk_template_len); | |
evp_thunk_dst_addr += evp_thunk_template_len; | |
evp_thunk_written = 1; | |
size_t thunk_patch_len = insn_count * sizeof(u32); | |
memcpy32(evp_thunk_dst_addr, &words[ipatch_count + 1], thunk_patch_len); | |
evp_thunk_dst_addr += thunk_patch_len; | |
//write32(TEGRA_EXCEPTION_VECTORS_BASE + 0x208, iram_evp_thunks); | |
} | |
else | |
{ | |
size_t thunk_patch_len = insn_count * sizeof(u32); | |
memcpy32(evp_thunk_dst_addr, &words[ipatch_count + 1], thunk_patch_len); | |
evp_thunk_dst_addr += thunk_patch_len; | |
} | |
} | |
words[0] = word0; | |
if ((word0 >> 25) == 0) | |
{ | |
puts("done (reached end: 1)"); | |
break; | |
} | |
if (patch_hash_one(&word0) >= 2) | |
{ | |
puts("done (integrity error: 2)"); | |
break; | |
} | |
word_count = word0 >> 25; | |
if (word_count == 0) | |
{ | |
puts("done (reached end: 2)"); | |
} | |
} | |
size_t svc_handlers_len = evp_thunk_dst_addr - (void *)iram_evp_thunks; | |
if (svc_handlers_len) | |
{ | |
FILE *f = fopen("./svc_handlers", "wb"); | |
if (f) | |
{ | |
fwrite(iram_evp_thunks, svc_handlers_len, 1, f); | |
fclose(f); | |
} | |
} | |
} | |
void dump_fuses() { | |
u32 words[0x100]; | |
clk_enable_fuse(1); | |
for (u32 i = 0; i < ARRAYSIZE(words); i++) | |
{ | |
fuse_write32(FUSE_REG_ADDR, i); | |
fuse_write32(FUSE_CTRL, (fuse_read32(FUSE_REG_ADDR) & ~FUSE_CMD_MASK) | FUSE_READ); | |
fuse_wait_idle(); | |
words[i] = fuse_read32(FUSE_REG_READ); | |
} | |
clk_enable_fuse(0); | |
FILE *f = fopen("./fuse_dump", "wb"); | |
if (f) | |
{ | |
fwrite(words, sizeof(words), 1, f); | |
fclose(f); | |
} | |
puts("fuse block (mmio):"); | |
for (u32 i = 0; i < TEGRA_FUSE_SIZE; i += sizeof(u32)) { | |
printf("%3x: %8x\n", i, fuse_read32(i)); | |
} | |
} | |
int main() | |
{ | |
if (!init()) | |
{ | |
return 1; | |
} | |
memset(iram_evp_thunks, 0, sizeof(iram_evp_thunks)); | |
read_fused_patches(); | |
//dump_fuses(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment