Skip to content

Instantly share code, notes, and snippets.

@hexkyz
Last active August 15, 2022 17:28
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 hexkyz/98c28e292597d8fc7bef7a2200e792d7 to your computer and use it in GitHub Desktop.
Save hexkyz/98c28e292597d8fc7bef7a2200e792d7 to your computer and use it in GitHub Desktop.
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#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
#define FUSE_SIZE_IN_BITS (6 * 1024)
#define FUSE_MAX_SIZE_IN_BITS (8 * 1024)
#define FUSE_PATCH_START_ADDRESS ((FUSE_SIZE_IN_BITS - 32)/32)
#define FUSE_MAX_PATCH_START_ADDRESS ((FUSE_MAX_SIZE_IN_BITS - 32)/32)
#define FUSE_MAX_PATCH_PAYLOAD (2560 >> 5)
#define FUSE_PATCH_C_MASK 0x000F0000
#define FUSE_PATCH_C_SHIFT 16
#define FUSE_NEXT_PATCH_SHIFT 25
#define FUSE_CAM_ADDR_MASK 0xFFFF0000
#define FUSE_CAM_ADDR_SHIFT 16
#define FUSE_CAM_DATA_MASK 0x0000FFFF
#define UINT_BITS 32
#define LOG2_UINT_BITS 5
#define INSIDE_UINT_OFFSET_MASK 0x1F
#define H16_BITS 16
#define H16_START_OFFSET (1 << (16 - 2))
#define H16_ECC_MASK 0xFFF0FFFF
#define H16_PARITY_MASK 0x00008000
#define H16_H_MASK 0x00007FFF
#define H5_CODEWORD_SIZE 12
#define H5_BIT_OFFSET 20
#define H5_CODEWORD_MASK 0xFFF00000
#define H5_PARITY_MASK 0x01000000
#define H_NO_ERROR 0
#define H_CORRECTED_ERROR 1
#define H_UNCORRECTED_ERROR_ODD 2
#define H_UNCORRECTED_ERROR_EVEN 3
// "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 uint32_t calc_parity(uint32_t *data, uint32_t size) {
uint32_t parity_word, i;
parity_word = data[0];
for (i = 1; i < size; i++) {
parity_word ^= data[i];
}
parity_word = (parity_word & 0x0000FFFF) ^ (parity_word >> 16);
parity_word = (parity_word & 0x000000FF) ^ (parity_word >> 8);
parity_word = (parity_word & 0x0000000F) ^ (parity_word >> 4);
parity_word = (parity_word & 0x00000003) ^ (parity_word >> 2);
parity_word = (parity_word & 0x00000001) ^ (parity_word >> 1);
return parity_word;
}
static uint32_t hamming16_syndrome(uint32_t *data, uint32_t size) {
uint32_t i, j, syndrome;
syndrome = 0;
for (i = 0; i < size; i++) {
if (data[i] != 0) {
for (j = 0; j < UINT_BITS; j++) {
if ((data[i] >> j) & 0x1) {
syndrome ^= (H16_START_OFFSET + (i * UINT_BITS) + j);
}
}
}
}
return syndrome;
}
static uint32_t hamming16_decode(uint32_t *data, uint32_t size) {
uint32_t calculated_parity, stored_syndrome, stored_parity;
uint32_t syndrome, offset, i, j, offset_bits_set;
calculated_parity = calc_parity(data, size);
stored_syndrome = (data[0] & H16_H_MASK);
stored_parity = (data[0] & H16_PARITY_MASK);
data[0] &= ~H16_ECC_MASK;
syndrome = (hamming16_syndrome(data, size) ^ stored_syndrome);
data[0] ^= stored_parity;
data[0] ^= stored_syndrome;
if (syndrome != 0) {
if (!calculated_parity) {
return H_UNCORRECTED_ERROR_EVEN;
}
offset = syndrome - H16_START_OFFSET;
i = (offset >> LOG2_UINT_BITS);
if ((offset < H16_BITS) || (offset >= UINT_BITS * size)) {
offset_bits_set = 0;
for (j = 0; j < H16_BITS-1; j++) {
if ((syndrome >> j) & 1) {
offset_bits_set++;
}
}
if (offset_bits_set == 1) {
data[0] ^= syndrome;
return H_CORRECTED_ERROR;
} else {
return H_UNCORRECTED_ERROR_ODD;
}
}
data[i] ^= (1 << (offset & INSIDE_UINT_OFFSET_MASK));
return H_CORRECTED_ERROR;
}
if (calculated_parity) {
data[0] ^= H16_PARITY_MASK;
return H_CORRECTED_ERROR;
}
return H_NO_ERROR;
}
static const uint32_t h5syndrome_table[] = {
0x1, 0x2, 0x4, 0x8,
0x0, 0x3, 0x5, 0x6,
0x7, 0x9, 0xA, 0xB };
static uint32_t hamming5_syndrome(uint32_t *data) {
uint32_t i, syndrome;
syndrome = 0;
for (i = 0; i < H5_CODEWORD_SIZE; i++) {
if (data[0] & (1 << (H5_BIT_OFFSET + i))) {
syndrome ^= h5syndrome_table[i];
}
}
return (syndrome << H5_BIT_OFFSET);
}
static uint32_t hamming5_decode(uint32_t *data) {
uint32_t calculated_parity, syndrome, i, storednonh5;
storednonh5 = data[0] & ~H5_CODEWORD_MASK;
data[0] &= H5_CODEWORD_MASK;
calculated_parity = calc_parity(data, 1);
syndrome = (hamming5_syndrome(data) >> H5_BIT_OFFSET);
data[0] |= storednonh5;
if (syndrome != 0) {
if (!calculated_parity) {
return H_UNCORRECTED_ERROR_EVEN;
}
for (i = 0; i < H5_CODEWORD_SIZE; i++) {
if (syndrome == h5syndrome_table[i]) {
data[0] ^= (1 << (i + H5_BIT_OFFSET));
return H_CORRECTED_ERROR;
}
}
return H_UNCORRECTED_ERROR_ODD;
}
if (calculated_parity) {
data[0] ^= H5_PARITY_MASK;
return H_CORRECTED_ERROR;
}
return H_NO_ERROR;
}
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++;
}
}
static void ipatch_process(uint32_t *patch_buff, uint32_t num_entries) {
uint32_t cam_entry, cam_addr, cam_data;
int count;
for (count = 0; count < num_entries; count++) {
cam_entry = patch_buff[count];
cam_addr = (((cam_entry & FUSE_CAM_ADDR_MASK) >> FUSE_CAM_ADDR_SHIFT) * 2);
cam_data = (cam_entry & FUSE_CAM_DATA_MASK);
printf("%2d: 0x%08x 0x%08x 0x%08x", count, cam_entry, ROM_BASE + cam_addr, cam_data);
switch (cam_data >> 8) {
case 0xDF:
printf(" : svc #0x%02x (offset 0x%02lx)\n", (cam_data & 0xFF), svc_handler_thunk_len + (cam_data & 0xFF) * 2);
break;
case 0x20:
printf(" : movs r0, #0x%02x\n", (cam_data & 0xFF));
break;
case 0x21:
printf(" : movs r1, #0x%02x\n", (cam_data & 0xFF));
break;
case 0x47:
if ((cam_data & 0xFF) == 0x70) {
printf(" : bx lr\n");
}
break;
default:
printf("\n");
break;
}
}
}
static int decode_ipatches(uint32_t *fuse_image, int tegra_version) {
uint32_t iram_svc_handlers[0x200] = {0};
bool svc_handler_thunk_written = false;
void *svc_handler_thunk_dst_addr = 0;
uint32_t fuse_record_buff[FUSE_MAX_PATCH_PAYLOAD] = {0};
uint32_t next_fuse_record_buff[FUSE_MAX_PATCH_PAYLOAD] = {0};
uint32_t first_patch_record_size, patch_record_size, patch_record_total_size, patch_start_addr;
uint32_t patch_header, num_cam_entries, num_insn_entries;
int count, ret;
if (tegra_version == 210) {
first_patch_record_size = fuse_image[FUSE_BOOTROM_PATCH_SIZE_T210_PRI_ALIAS];
first_patch_record_size &= (0x7F << FUSE_BOOTROM_PATCH_SIZE_T210_START_BIT);
first_patch_record_size >>= FUSE_BOOTROM_PATCH_SIZE_T210_START_BIT;
} else if (tegra_version == 214) {
first_patch_record_size = fuse_image[FUSE_BOOTROM_PATCH_SIZE_T214_PRI_ALIAS];
first_patch_record_size &= (0x7F << FUSE_BOOTROM_PATCH_SIZE_T214_START_BIT);
first_patch_record_size >>= FUSE_BOOTROM_PATCH_SIZE_T214_START_BIT;
} else {
printf("Unsupported Tegra version!\n");
return -1;
}
printf("FUSE_FIRST_BOOTROM_PATCH_SIZE_REG: 0x%08x\n", first_patch_record_size);
patch_record_size = first_patch_record_size;
if (patch_record_size > FUSE_MAX_PATCH_PAYLOAD) {
printf("Invalid patch record size!\n");
return -1;
}
patch_start_addr = FUSE_PATCH_START_ADDRESS;
while (patch_record_size) {
printf("Total size: %d, Current size: %d\n", patch_record_total_size, patch_record_size);
patch_record_total_size += patch_record_size;
// We've read all patch words.
if (patch_record_total_size >= FUSE_MAX_PATCH_PAYLOAD) {
printf("Reached maximum word count!\n");
return -1;
}
// Read the first bootrom patch's words.
for (count = 0; count < patch_record_size; count++) {
if ((patch_start_addr == (FUSE_PATCH_START_ADDRESS - 0x10)) && (tegra_version == 214)) {
// Emulate the t214 fuse patch expansion.
patch_start_addr = FUSE_MAX_PATCH_START_ADDRESS;
}
fuse_record_buff[count] = fuse_image[patch_start_addr];
patch_start_addr--;
}
patch_header = fuse_record_buff[0];
ret = hamming16_decode(fuse_record_buff, patch_record_size);
if (ret < H_UNCORRECTED_ERROR_ODD) {
num_cam_entries = ((fuse_record_buff[0] & FUSE_PATCH_C_MASK) >> FUSE_PATCH_C_SHIFT);
num_insn_entries = (patch_record_size - num_cam_entries - 1);
if (num_cam_entries) {
// Process the CAM entries.
printf("IPATCH map:\n");
ipatch_process(&fuse_record_buff[1], num_cam_entries);
}
if (num_insn_entries) {
// Process the instruction data entries.
printf("IPATCH data:\n");
for (count = 0; count < num_insn_entries; count++) {
uint32_t data = fuse_record_buff[num_cam_entries + 1 + count];
printf("%2d: 0x%08x\n", count, 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 = (num_insn_entries * sizeof(uint32_t));
ipatch_memcpy(svc_handler_thunk_dst_addr, &fuse_record_buff[num_cam_entries + 1], thunk_patch_len);
svc_handler_thunk_dst_addr += thunk_patch_len;
}
fuse_record_buff[0] = patch_header;
if (fuse_record_buff[0] >> FUSE_NEXT_PATCH_SHIFT) {
ret = hamming5_decode(&patch_header);
if (ret >= H_UNCORRECTED_ERROR_ODD) {
printf("Unrecoverable patch record!\n");
return ret;
} else {
patch_record_size = (patch_header >> FUSE_NEXT_PATCH_SHIFT);
}
} else {
patch_record_size = 0;
}
} else {
printf("Unrecoverable patch record!\n");
return ret;
}
}
// 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);
}
}
return 0;
}
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);
// Validate fuse image's size.
if (fuse_size != FUSE_IMAGE_SIZE) {
printf("Fuse image must be 0x400 bytes!\n");
return -1;
}
// Read in the fuse image.
uint32_t fuse_image[FUSE_IMAGE_SIZE/4];
fread((void *)&fuse_image, FUSE_IMAGE_SIZE, 1, f_fuse);
fclose(f_fuse);
// Decode the ipatch map.
decode_ipatches(fuse_image, tegra_version);
return 0;
}
@DumbDonaldDump
Copy link

What does this mean. We can finally hacked the ipatched switches

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment