Skip to content

Instantly share code, notes, and snippets.

@shuffle2
Created October 17, 2017 23:26
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save shuffle2/f8728159da100e9df2606d43925de0af to your computer and use it in GitHub Desktop.
Save shuffle2/f8728159da100e9df2606d43925de0af to your computer and use it in GitHub Desktop.
dump + decode tegra t210 ipatches
#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