Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#define ARRAYSIZE(x) (sizeof(x) / sizeof(*x))
#define FUSE_IMAGE_SIZE 0x400
#define ROM_BASE 0x100000
#define FUSE_BOOTROM_PATCH_SIZE_T210_START_BIT 13
#define FUSE_BOOTROM_PATCH_SIZE_T210_END_BIT 19
#define FUSE_BOOTROM_PATCH_SIZE_T210_PRI_ALIAS 0x00000022
#define FUSE_BOOTROM_PATCH_SIZE_T210_RED_ALIAS 0x00000023
#define FUSE_BOOTROM_PATCH_SIZE_T214_START_BIT 23
#define FUSE_BOOTROM_PATCH_SIZE_T214_END_BIT 29
#define FUSE_BOOTROM_PATCH_SIZE_T214_PRI_ALIAS 0x00000056
#define FUSE_BOOTROM_PATCH_SIZE_T214_RED_ALIAS 0x00000057
// "NV Boot T210 WXYZ.HIJK"
static const uint32_t svc_handler_thunk_t210[] = {
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, =svc_handler_thunk
0xe59f101c, // LDR R1, =svc_handler_thunk_end
0xe0411000, // SUB R1, R1, R0
0xe59f0018, // LDR R0, =iram_svc_handlers
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 svc_handler_thunk
0x001007f8, // off_1007F0 DCD svc_handler_thunk_end
0x40004c30, // off_1007F4 DCD iram_svc_handlers
// svc_handler_thunk_end is here
};
// "NV Boot T214 WXYZ.HIJK"
static const uint32_t svc_handler_thunk_t214[] = {
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, =svc_handler_thunk
0xe59f101c, // LDR R1, =svc_handler_thunk_end
0xe0411000, // SUB R1, R1, R0
0xe59f0018, // LDR R0, =iram_svc_handlers
0xe0800001, // ADD R0, R0, R1
0xe0822000, // ADD R2, R2, R0
0xe3822001, // ORR R2, R2, #1
0xe8bd0003, // LDMFD SP!, {R0,R1}
0xe12fff12, // BX R2
0x0010022c, // off_100268 DCD svc_handler_thunk
0x00100174, // off_10026C DCD svc_handler_thunk_end
0x40004164, // off_100270 DCD iram_svc_handlers
// svc_handler_thunk_end is here
};
// T210 and T214 SVC handler thunks have the same size
static const size_t svc_handler_thunk_len = sizeof(svc_handler_thunk_t210);
static const uint32_t ipatch_hamming_single_syndrome_table[] = {
1, 2, 4, 8, 0, 3, 5, 6, 7, 9, 10, 11
};
void ipatch_memcpy(void *dst, void *src, uint32_t len) {
uint32_t *s = src;
uint32_t *d = dst;
for (uint32_t i = 0; i < len / sizeof(uint32_t); i++) {
*d++ = *s++;
}
}
uint32_t ipatch_hamming_parity(uint32_t *words, uint32_t count) {
uint32_t acc = words[0];
for (uint32_t i = 1; i < count; i++) {
acc ^= words[i];
}
uint32_t lo = ((acc & 0xffff) ^ (acc >> 16)) & 0xff;
uint32_t hi = ((acc & 0xffff) ^ (acc >> 16)) >> 8;
uint32_t x = hi ^ lo;
lo = ((x & 0xf) ^ (x >> 4)) & 3;
hi = ((x & 0xf) ^ (x >> 4)) >> 2;
x = hi ^ lo;
return (x & 1) ^ (x >> 1);
}
int ipatch_hamming_decode_single(uint32_t *word) {
uint32_t bits20_31 = *word & 0xfff00000;
uint32_t parity_bit = ipatch_hamming_parity(&bits20_31, 1);
uint32_t hash = 0;
for (uint32_t i = 0; i < 12; i++) {
if (*word & (1 << (20 + i))) {
hash ^= ipatch_hamming_single_syndrome_table[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(ipatch_hamming_single_syndrome_table); i++) {
if (ipatch_hamming_single_syndrome_table[i] == hash) {
*word ^= 1 << (20 + i);
return 1;
}
}
return 2;
}
int ipatch_hamming_decode_multi(uint32_t *words, uint32_t count) {
uint32_t parity_bit = ipatch_hamming_parity(words, count);
uint32_t bits0_14 = words[0] & 0x7fff;
uint32_t bit15 = words[0] & 0x8000;
uint32_t bits16_19 = words[0] & 0xf0000;
uint32_t hash = 0;
words[0] = bits16_19;
for (uint32_t i = 0; i < count; i++) {
uint32_t w = words[i];
if (w) {
for (uint32_t bitpos = 0; bitpos < 32; bitpos++) {
if ((w >> bitpos) & 1) {
hash ^= 0x4000 + i * 32 + bitpos;
}
}
}
}
hash ^= bits0_14;
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;
}
uint32_t bitcount = hash - 0x4000;
if (bitcount < 16 || bitcount >= count * 32) {
uint32_t num_set = 0;
for (uint32_t 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(uint32_t *entries, uint32_t count) {
for (uint32_t i = 0; i < count; i++) {
uint32_t entry = entries[i];
uint32_t addr = (entry >> 16) * 2;
uint32_t data = entry & 0xffff;
printf("%2d: 0x%08x 0x%08x 0x%08x", i, entry, ROM_BASE + addr, data);
uint8_t lo = data & 0xff;
switch (data >> 8) {
case 0xDF:
printf(" : svc #0x%02x (offset 0x%02lx)\n", lo, svc_handler_thunk_len + lo * 2);
break;
case 0x20:
printf(" : movs r0, #0x%02x\n", lo);
break;
default:
printf("\n");
break;
}
}
}
void decode_ipatches(uint8_t *fuse_image, int tegra_version) {
static uint32_t iram_svc_handlers[0x200];
bool svc_handler_thunk_written = false;
void *svc_handler_thunk_dst_addr = 0;
uint32_t first_bootrom_patch_words[80];
uint32_t first_bootrom_patch_size = 0;
uint32_t first_bootrom_patch_addr = 0;
uint32_t bootrom_patch_words[80];
uint32_t bootrom_patch_size = 0;
uint32_t bootrom_patch_addr = 0;
uint32_t bootrom_patch_total_read = 0;
uint32_t bootrom_patch_control_word = 0;
if (tegra_version == 210) {
first_bootrom_patch_size = *(uint32_t *)(fuse_image + FUSE_BOOTROM_PATCH_SIZE_T210_PRI_ALIAS * 0x04);
first_bootrom_patch_size &= (0x7F << FUSE_BOOTROM_PATCH_SIZE_T210_START_BIT);
first_bootrom_patch_size >>= FUSE_BOOTROM_PATCH_SIZE_T210_START_BIT;
} else if (tegra_version == 214) {
first_bootrom_patch_size = *(uint32_t *)(fuse_image + FUSE_BOOTROM_PATCH_SIZE_T214_PRI_ALIAS * 0x04);
first_bootrom_patch_size &= (0x7F << FUSE_BOOTROM_PATCH_SIZE_T214_START_BIT);
first_bootrom_patch_size >>= FUSE_BOOTROM_PATCH_SIZE_T214_START_BIT;
}
printf("FUSE_FIRST_BOOTROM_PATCH_SIZE_REG: 0x%08x\n", first_bootrom_patch_size);
first_bootrom_patch_addr = 0xBF;
bootrom_patch_size = first_bootrom_patch_size;
bootrom_patch_addr = 0xFF;
memset(iram_svc_handlers, 0, sizeof(iram_svc_handlers));
while (bootrom_patch_size) {
printf("Total size: %d, Current size: %d\n", bootrom_patch_total_read, bootrom_patch_size);
bootrom_patch_total_read += bootrom_patch_size;
// We've read all patch words.
if (bootrom_patch_total_read >= ARRAYSIZE(bootrom_patch_words)) {
printf("Done (reached maximum word count)!\n");
break;
}
// Read the first bootrom patch's words.
for (uint32_t i = 0; i < bootrom_patch_size; i++) {
first_bootrom_patch_words[i] = *(uint32_t *)(fuse_image + first_bootrom_patch_addr * 0x04);
first_bootrom_patch_addr--;
}
bootrom_patch_control_word = first_bootrom_patch_words[0];
printf("Control word: 0x%08x\n", bootrom_patch_control_word);
if (ipatch_hamming_decode_multi(first_bootrom_patch_words, bootrom_patch_size) >= 2) {
printf("Hamming decode multi failed!\n");
break;
}
uint32_t ipatch_map_count = ((first_bootrom_patch_words[0] >> 16) & 0xF);
uint32_t ipatch_data_count = (bootrom_patch_size - ipatch_map_count - 1);
// Translate the patch.
if (ipatch_map_count) {
printf("IPATCH map:\n");
ipatch_process(&first_bootrom_patch_words[1], ipatch_map_count);
}
if (ipatch_data_count) {
printf("IPATCH data:\n");
if (bootrom_patch_size != first_bootrom_patch_size) {
// We are not reading the first patch map.
for (uint32_t i = 0; i < ipatch_data_count; i++) {
bootrom_patch_words[i] = *(uint32_t *)(fuse_image + bootrom_patch_addr * 0x04);
bootrom_patch_addr--;
printf("%2d: 0x%08x\n", i, bootrom_patch_words[i]);
}
} else {
// We are reading the first patch map.
for (uint32_t i = 0; i < ipatch_data_count; i++) {
uint32_t data = first_bootrom_patch_words[ipatch_map_count + 1 + i];
printf("%2d: 0x%08x\n", i, data);
}
}
// Write the SVC handler thunk if necessary.
if (!svc_handler_thunk_written) {
svc_handler_thunk_dst_addr = (void *)iram_svc_handlers;
if (tegra_version == 210) {
ipatch_memcpy(svc_handler_thunk_dst_addr, (void *)svc_handler_thunk_t210, svc_handler_thunk_len);
} else if (tegra_version == 214) {
ipatch_memcpy(svc_handler_thunk_dst_addr, (void *)svc_handler_thunk_t214, svc_handler_thunk_len);
} else {
printf("Invalid Tegra version!\n");
break;
}
svc_handler_thunk_dst_addr += svc_handler_thunk_len;
svc_handler_thunk_written = true;
}
size_t thunk_patch_len = (ipatch_data_count * sizeof(uint32_t));
if (bootrom_patch_size != first_bootrom_patch_size)
ipatch_memcpy(svc_handler_thunk_dst_addr, &bootrom_patch_words[0], thunk_patch_len);
else
ipatch_memcpy(svc_handler_thunk_dst_addr, &first_bootrom_patch_words[ipatch_map_count + 1], thunk_patch_len);
svc_handler_thunk_dst_addr += thunk_patch_len;
}
first_bootrom_patch_words[0] = bootrom_patch_control_word;
if ((bootrom_patch_control_word >> 25) == 0) {
printf("Done (no more patches)!\n");
break;
}
if (ipatch_hamming_decode_single(&bootrom_patch_control_word) >= 2) {
printf("Hamming decode single failed!\n");
break;
}
bootrom_patch_size = (bootrom_patch_control_word >> 25);
if (bootrom_patch_size == 0) {
printf("Done (next patch has size 0)!\n");
break;
}
}
// Output a file with the ipatch SVC handlers' data.
size_t svc_handlers_len = (svc_handler_thunk_dst_addr - (void *)iram_svc_handlers);
if (svc_handlers_len) {
FILE *f_svc = fopen("./svc_handlers.bin", "wb");
if (f_svc) {
fwrite(iram_svc_handlers, svc_handlers_len, 1, f_svc);
fclose(f_svc);
}
}
}
int main(int argc, char **argv) {
if (argc != 3) {
printf("Usage: %s fuse_image tegra_version\n", argv[0]);
return -1;
}
// Try to open input file.
FILE *f_fuse = fopen(argv[1], "rb");
if (f_fuse == NULL) {
printf("Failed to open fuse image!\n");
return -1;
}
// Check the version.
int tegra_version = atoi(argv[2]);
if ((tegra_version != 210) && (tegra_version != 214)) {
printf("Unsupported Tegra version!\n");
return -1;
}
// Get input file's size.
size_t fuse_size = 0;
fseek(f_fuse, 0L, SEEK_END);
fuse_size = ftell(f_fuse);
fseek(f_fuse, 0L, SEEK_SET);
if (fuse_size != FUSE_IMAGE_SIZE) {
printf("Fuse image must be 0x400 bytes!\n");
return -1;
}
void *fuse_image = malloc(FUSE_IMAGE_SIZE);
// Read in the fuse image.
fread(fuse_image, FUSE_IMAGE_SIZE, 1, f_fuse);
fclose(f_fuse);
// Decode the ipatch map.
decode_ipatches(fuse_image, tegra_version);
free(fuse_image);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.