/** * This installs a data abort handler in the MMIO region * (a subset of C0000000 - CFFFFFFF) and logs all reads/writes from this range. * * These logs are very useful for emulation. * * Based on mem_prot. */ #include #include #define ASM_VAR __attribute__((section(".text"))) __attribute__((used)) /* select the MMIO range to be logged: */ #ifdef CONFIG_DIGIC_VI /* DIGIC 6 (Cortex R4): * DRBAR, mask 0xFFFFFFE0: base address * DRSR, mask 0x0000003E: region size (min. 4096, power of 2) * DRSR, mask 0x00000001: enabled (must be 1) * DRSR, mask 0x0000FF00: sub-region disable bits * Cortex R4 TRM, 4.3.20 c6, MPU memory region programming registers */ #define REGION_BASE(base) (base & 0xFFFFFFE0) #define REGION_SIZE(size) ((LOG2(size/2) << 1) | 1) #else /* DIGIC V and earlier (ARM946E-S): * PRBSn, mask FFFFF000: base address * 0000001E: region size (min. 4096, power of 2) * 00000001: enabled (must be 1) * ARM946E-S TRM, 2.3.9 Register 6, Protection Region Base and Size Registers */ #define REGION(base, size) ((base & 0xFFFFF000) | (LOG2(size/2) << 1) | 1) #endif /* address ranges can be found at magiclantern.wikia.com/wiki/Register_Map * or in QEMU source: contrib/qemu/eos/eos.c, eos_handlers * examples: * REGION(0xC0220000, 0x010000): GPIO (LEDs, chip select signals etc) * REGION(0xC0820000, 0x001000): SIO communication (SPI) * REGION(0xC0210000, 0x001000): timers (activity visible only with early startup logging, as in da607f7 or !1dd2792) * REGION(0xC0243000, 0x001000): HPTimer (high resolution timers) * REGION(0xC0200000, 0x002000): interrupt controller (it works! interrupts are interrupted!) * REGION(0xC0F00000, 0x010000): EDMAC #0-15 and many others * REGION(0xC0F00000, 0x100000): EDMAC (all), display (C0F14) and many others (more likely to crash) * REGION(0xC0C00000, 0x100000): SDIO/SFIO (also paired with SDDMA/SFDMA, unsure whether they can be logged at the same time) * REGION(0xC0500000, 0x100000): SDDMA/SFDMA/CFDMA * REGION(0xC0E20000, 0x010000): JPCORE (JP57, lossless) * REGION(0xC0000000, 0x1000000): nearly everything except EEKO? (DIGIC <= 5) * REGION(0xC0000000, 0x20000000): everything including EEKO? (DIGIC 5) * REGION(0xE0000000, 0x1000): DFE (5D2, 50D); untested */ #ifdef CONFIG_DIGIC_VI //static ASM_VAR uint32_t protected_region_base = REGION_BASE(0xC0000000); //static ASM_VAR uint32_t protected_region_size = REGION_SIZE(0x20000000); /* This logs the entire MMIO activity, from B0000000 to DFFFFFFF: * BFE00000 - BFEFFFFF: used for communicating with Omar * BFF00000 - BFFFFFFF: used for communicating with Zico (MRZM) * C0000000 - DFFFFFFF: regular MMIO range */ static ASM_VAR uint32_t protected_region_base = REGION_BASE(0x80000000); static ASM_VAR uint32_t protected_region_size = REGION_SIZE(0x80000000) | 0xC700; /* How it works: on DIGIC 6, each memory protection region can be divided * in 8 equal subregions. You may enable/disable any of them (bit set = subregion disabled). * Subregion 80000000 - 8FFFFFFF: disabled (covers BTCM) * Subregion 90000000 - 9FFFFFFF: disabled (unused?) * Subregion A0000000 - AFFFFFFF: disabled (unused?) * Subregion B0000000 - BFFFFFFF: enabled (Omar/Zico communication) * Subregion C0000000 - CFFFFFFF: enabled (regular MMIO range - used in earlier models) * Subregion D0000000 - DFFFFFFF: enabled (regular MMIO range - also used, to a lesser extent, in DIGIC 5) * Subregion E0000000 - EFFFFFFF: disabled (unused? there is a memory region configured as 0xEE000000 - EFFFFFFF) * Subregion F0000000 - FFFFFFFF: disabled (ROM) */ #else /* DIGIC V and earlier */ static ASM_VAR uint32_t protected_region = REGION(0xC0000000, 0x20000000); #endif /* number of 32-bit integers recorded for one MMIO event (power of 2) */ #define RECORD_SIZE 8 static uint32_t trap_orig = 0; /* buffer size must be power of 2 and multiple of RECORD_SIZE, to simplify bounds checking */ static ASM_VAR uint32_t * buffer = 0; static ASM_VAR uint32_t buffer_index = 0; static uint32_t buffer_count = 0; /* how many uint32_t's we have allocated */ /* * TCM usage in main firmware * ========================== * * checked 60D, 500D, 5D2, 50D, 1300D, 5D3, 70D, 700D, 100D, EOSM in QEMU * procedure: * - fill both TCMs with 0xBADCAFFE when PC reaches F8010000 or F80C0000 * (doable from either QEMU dbi/logging.c or GDB breakpoint) * - confirm that it still boots the GUI and you can navigate Canon menus * - examine TCM contents with 'x/1024 0' and 'x/1024 0x40000000' in GDB * * DIGIC 4: * 400006F8-40000AF7 ISR handler table (256 entries?) * 40000AF8-40000EF7 ISR argument table * 40000000-400006F7 possibly unused (no code reads/writes it in QEMU) * 40000EF8-40000FFF possibly unused * * DIGIC 5 and 1300D: * 40000000-400007FF ISR handler table (512 entries, some unused?) * 40000800-40000FFF ISR argument table * 40000E00-40000FFF possibly unused ISR entries? probably best not to rely on it * * DIGIC 4 and 5: * 00000000-00000020 exception vectors (0x18 = IRQ, 0x10 = Data Abort etc) * 00000020-00000040 exception routines (used with LDR PC, [PC, #20]) * 00000120-000001BF some executable code (60D: copied from FF055CA8; 1300D,100D: 00000100-000001BF; 70D: not present) * 000004B0-000006C0 interrupt handler (code jumps there from 0x18; end address may vary slightly) * 000006C4-00001000 interrupt stack (start address may vary slightly; used until about 0xf00) * * other exception stacks are at UND:400007fc FIQ:4000077c ABT:400007bc SYS/USR:4000057c (IRQ was 4000067c) * these were set up from bootloader, but they are no longer valid in main firmware * pick SP at the bottom of the interrupt stack, roughly 0x700-0x740 * * 80D: * 00000000-00002BFF ATCM, used by Canon code * 00002C00-00003FFF ATCM, unused * 80000000-8000A4BF BTCM, used by Canon code * 8000A4C0-8000FFFF BTCM, unused * * 5D4: * 00000000-0000338F ATCM, used by Canon code * 00003390-00003FFF ATCM, unused * 80000000-80009C7F BTCM, used by Canon code * 80009C80-8000FFFF BTCM, unused * * 750D/760D: * 00000000-00003D4F ATCM, used by Canon code * 00003D50-00003FFF ATCM, unused * 80000000-8000923F BTCM, used by Canon code * 80009240-8000FFFF BTCM, unused * */ static void __attribute__ ((naked)) trap() { /* data abort exception occurred. switch stacks, log the access, * enable permissions and re-execute trapping instruction */ asm( /* set up a stack in some unused area in the TCM (we need 64 bytes) */ #ifdef CONFIG_DIGIC_VI "MOV SP, #0x0000FF00\n" "ORR SP, SP, #0x80000000\n" #else /* DIGIC V and earlier */ "MOV SP, #0x740\n" #endif /* save context, including flags */ "STMFD SP!, {LR}\n" /* LR_ABT (where the exception happened) */ "STMFD SP!, {R0-R12, LR}\n" /* R0-R12,LR of the interrupted mode (LR will be updated later) */ "MRS R0, CPSR\n" /* read condition flags (must be done after saving R0) */ "STMFD SP!, {R0}\n" /* store condition flags on the stack */ /* save registers from the interrupted mode */ "MRS R8, SPSR\n" /* CPSR of the interrupted mode */ "AND R1, R8, #0x1F\n" /* what was the interrupted CPU mode? */ "ORR R1, #0xC0\n" /* keep the interrupts disabled and stay in ARM mode */ "MOV R12, SP\n" /* keep the SP_ABT before switching */ "MSR CPSR_c, R1\n" /* switch to the interrupted mode (it won't be user) */ "STR LR, [R12, #0x38]\n" /* store LR of the interrupted mode */ "MSR CPSR_c, R0\n" /* back to Data Abort mode */ /* prepare to save information about trapping code */ "LDR R4, buffer\n" /* load buffer address */ "LDR R2, buffer_index\n" /* load buffer index from memory */ "TST R2, #0x3FC00000\n" /* check for buffer overflow (16MB buffer) */ "MOVNE R2, #0x00400000\n" /* we have allocated 0x400000+8 words (0x80000+1 records) */ /* interrupted code was ARM or Thumb? */ "TST R8, #0x20\n" /* R8 contains SPSR */ /* store the program counter */ "SUB R5, LR, #8\n" /* retrieve PC where the exception happened */ "MOVEQ R6, R5\n" /* if we have interrupted ARM code, store just the PC */ "ORRNE R6, R5, #1\n" /* otherwise, store the Thumb bit as well */ "STR R6, [R4, R2, LSL#2]\n" /* store PC at index [0], possibly with the Thumb bit set */ "ADD R2, #1\n" /* increment index */ /* get and store DryOS task name and interrupt ID */ "LDR R0, =current_task\n" "LDR R1, [R0, #4]\n" /* 1 if running in interrupt, 0 otherwise; other values? */ "LDR R0, [R0]\n" "LDR R0, [R0, #0x24]\n" "STR R0, [R4, R2, LSL#2]\n" /* store task name at index [1] */ "ADD R2, #1\n" /* increment index */ "LDR R0, =current_interrupt\n" "LDR R0, [R0]\n" /* interrupt ID is shifted by 2 on DIGIC 5 and earlier, but not on DIGIC 6 */ "ORR R0, R1, LSL#31\n" /* store whether the interrupt ID is valid, in the MSB */ "STR R0, [R4, R2, LSL#2]\n" /* store interrupt ID at index [2] */ "ADD R2, #1\n" /* increment index */ /* prepare to re-execute the old instruction */ /* copy it into this routine (cacheable memory) */ "SUB R5, LR, #8\n" /* retrieve PC where the exception happened */ "LDR R6, [R5]\n" /* read the old instruction */ #ifdef CONFIG_DIGIC_VI /* we have 3 cases: * - ARM instruction (4 bytes wide) * - Thumb instruction (2 bytes wide) * - Thumb instruction (4 bytes wide) * * - ARM vs Thumb: bit 5 in SPSR is set in Thumb mode * - Thumb instruction: wide if bits [15:11] of current halfword * are 0b11101/0b11110/0b11111 (A6.1 in ARMv7-AR) */ /* last check was: TST R8, #0x20; R8 contains SPSR */ "BNE interrupted_thumb\n" #endif /* CONFIG_DIGIC_VI */ /* ARM code interrupted */ "interrupted_arm:\n" /* where are we going to copy the old instruction? */ "ADR R1, trapped_instruction_arm\n" /* store the old instruction */ "STR R6, [R1]\n" /* clean the cache for this address (without touching the cache hacks), * then disable our memory protection region temporarily for re-execution */ "MOV R0, #0x00\n" "MCR p15, 0, R1, c7, c10, 1\n" /* first clean that address in dcache */ "MCR p15, 0, R0, c7, c10, 4\n" /* then drain write buffer */ "MCR p15, 0, R1, c7, c5, 1\n" /* flush icache line for that address */ #ifdef CONFIG_DIGIC_VI "MOV R7, #0x07\n" "MCR p15, 0, R7, c6, c2, 0\n" /* write RGNR (adjust memory region #7) */ "MCR p15, 0, R0, c6, c1, 2\n" /* enable full access to memory (disable region #7) */ #else /* DIGIC V and earlier */ "MCR p15, 0, R0, c6, c7, 0\n" /* enable full access to memory */ #endif #ifdef CONFIG_QEMU /* disassemble the instruction */ "LDR R3, =0xCF123010\n" "STR R5, [R3]\n" #endif /* find the source and destination registers */ "MOV R1, R6, LSR#12\n" /* extract destination register */ "AND R1, #0xF\n" "STR R1, destination\n" /* store it to memory; will use it later */ #ifdef CONFIG_DIGIC_VI "MRC p15, 0, R0, c5, c0, 0\n" /* read DFSR */ "STR R0, [R4, R2, LSL#2]\n" /* store DFSR (fault status) at index [3] */ "ADD R2, #1\n" /* increment index */ "MRC p15, 0, R0, c6, c0, 0\n" /* read DFAR */ "STR R0, [R4, R2, LSL#2]\n" /* store DFAR (fault address) at index [4] */ "ADD R2, #1\n" /* increment index */ #else /* DIGIC V and earlier */ /* no DFAR; we need to manually decode the instruction to figure it out */ "MOV R1, R6, LSR#16\n" /* extract source register */ "AND R1, #0xF\n" "LDR R0, [SP, R1, LSL#2]\n" /* load source register contents from stack */ "STR R0, [R4, R2, LSL#2]\n" /* store source register at index [3] */ "ADD R2, #1\n" /* increment index */ "AND R1, R6, #0xF\n" /* extract index register (only valid in "register offset" addressing modes; will decode later) */ "LDR R0, [SP, R1, LSL#2]\n" /* load index register contents from stack */ "STR R0, [R4, R2, LSL#2]\n" /* store index register at index [4] */ "ADD R2, #1\n" /* increment index */ #endif "STR R2, buffer_index\n" /* store buffer index to memory */ /* restore context, with LR of interuped mode */ "LDMFD SP!, {R0}\n" "MSR CPSR_f, R0\n" "LDMFD SP!, {R0-R12, LR}\n" /* placeholder for executing the old instruction (as ARM) */ "trapped_instruction_arm:\n" ".word 0x00000000\n" /* save context once again (sans flags) */ "STMFD SP!, {R0-R12, LR}\n" #ifdef CONFIG_QEMU /* print the register and value loaded from MMIO */ "LDR R3, =0xCF123000\n" "LDR R0, destination\n" "LDR R0, [SP, R0, LSL#2]\n" "STR R0, [R3,#0xC]\n" "MOV R0, #10\n" "STR R0, [R3]\n" #endif /* store the result (value read from / written to MMIO) */ "LDR R4, buffer\n" /* load buffer address */ "LDR R2, buffer_index\n" /* load buffer index from memory */ "LDR R0, destination\n" /* load destination register index from memory */ "LDR R0, [SP, R0, LSL#2]\n" /* load destination register contents from stack */ "STR R0, [R4, R2, LSL#2]\n" /* store destination register at index [5] */ "ADD R2, #1\n" /* increment index */ /* timestamp the event (requires MMIO access; do this before re-enabling memory protection) */ #ifdef CONFIG_DIGIC_VI "LDR R0, =0xD400000C\n" /* 32-bit microsecond timer */ #else /* DIGIC V and earlier */ "LDR R0, =0xC0242014\n" /* 20-bit microsecond timer */ #endif "LDR R0, [R0]\n" "STR R0, [R4, R2, LSL#2]\n" /* store timestamp at index[6]; will be unwrapped in post */ "ADD R2, #1\n" /* increment index */ "STR LR, [R4, R2, LSL#2]\n" /* store LR of the interrupted mode at index [7] */ "ADD R2, #1\n" /* increment index (total 8 = RECORD_SIZE) */ "STR R2, buffer_index\n" /* store buffer index to memory */ /* re-enable memory protection */ #ifdef CONFIG_DIGIC_VI "LDR R0, protected_region_size\n" "MCR p15, 0, R0, c6, c1, 2\n" #else /* DIGIC V and earlier */ "LDR R0, protected_region\n" "MCR p15, 0, R0, c6, c7, 0\n" #endif /* restore context */ "LDMFD SP!, {R0-R12, LR}\n" /* restore LR_ABT (where we should continue the execution) */ "LDMFD SP!, {LR}\n" /* continue the execution after the trapped instruction */ "SUBS PC, LR, #4\n" /* ------------------------------------------ */ "destination:\n" ".word 0x00000000\n" /* ARM code path finished */ /* FIXME: reduce amount of duplicate code */ /* ----------------------------------------------------- */ #ifdef CONFIG_DIGIC_VI /* Thumb code interrupted */ "interrupted_thumb:\n" /* where are we going to copy the old instruction? */ "ADR R1, trapped_instruction_thumb\n" /* for "narrow" Thumb instructions, replace the upper 16 bits with a NOP */ /* the old instruction is in R6 */ "AND R7, R6, #0xF800\n" "CMP R7, #0xF800\n" "CMPNE R7, #0xF000\n" "CMPNE R7, #0xE800\n" "BICNE R6, #0xFF000000\n" "BICNE R6, #0x00FF0000\n" "ORRNE R6, #0xBF000000\n" /* store the old instruction */ "STR R6, [R1]\n" /* adjust LR_ABT for next instruction, if we have interrupted a "narrow" one */ "SUBNE LR, LR, #2\n" "STRNE LR, [SP, #0x3C]\n" /* clean the cache for this address (without touching the cache hacks), * then disable our memory protection region temporarily for re-execution */ "MOV R0, #0x00\n" "MCR p15, 0, R1, c7, c10, 1\n" /* first clean that address in dcache */ "MCR p15, 0, R0, c7, c10, 4\n" /* then drain write buffer */ "MCR p15, 0, R1, c7, c5, 1\n" /* flush icache line for that address */ "MOV R7, #0x07\n" "MCR p15, 0, R7, c6, c2, 0\n" /* write RGNR (adjust memory region #7) */ "MCR p15, 0, R0, c6, c1, 2\n" /* enable full access to memory (disable region #7) */ #ifdef CONFIG_QEMU /* disassemble the instruction (as Thumb) */ "LDR R3, =0xCF123010\n" "ORR R0, R5, #1\n" "STR R0, [R3]\n" #endif /* find the destination register */ "ANDNE R1, R6, #0x7\n" /* Rt, for "narrow" instruction (T1 encoding) */ "MOVEQ R1, R6, LSR#28\n" /* Rt, for "wide" instruction (T2 encoding) */ "STR R1, destination\n" /* store it to memory; will use it later */ "MRC p15, 0, R0, c5, c0, 0\n" /* read DFSR */ "STR R0, [R4, R2, LSL#2]\n" /* store DFSR (fault status) at index [3] */ "ADD R2, #1\n" /* increment index */ "MRC p15, 0, R0, c6, c0, 0\n" /* read DFAR */ "STR R0, [R4, R2, LSL#2]\n" /* store DFAR (fault address) at index [4] */ "ADD R2, #1\n" /* increment index */ "STR R2, buffer_index\n" /* store buffer index to memory */ /* switch to Thumb mode */ "MOV R12, PC\n" "ADD R12, #5\n" "BX R12\n" ".code 16\n" ".syntax unified\n" /* restore context, with LR of interrupted mode */ "NOP\n" "LDMFD SP!, {R0}\n" "MSR CPSR_f, R0\n" "LDMFD SP!, {R0-R12, LR}\n" /* placeholder for executing the old instruction (as Thumb) */ "trapped_instruction_thumb:\n" ".word 0x00000000\n" /* back to ARM mode */ /* careful with alignment */ "BX PC\n" ".code 32\n" /* save context once again (sans flags) */ "STMFD SP!, {R0-R12, LR}\n" #ifdef CONFIG_QEMU /* print the register and value loaded from MMIO */ "LDR R3, =0xCF123000\n" "LDR R0, destination\n" "LDR R0, [SP, R0, LSL#2]\n" "STR R0, [R3,#0xC]\n" "MOV R0, #10\n" "STR R0, [R3]\n" #endif /* store the result (value read from / written to MMIO) */ "LDR R4, buffer\n" /* load buffer address */ "LDR R2, buffer_index\n" /* load buffer index from memory */ "LDR R0, destination\n" /* load destination register index from memory */ "LDR R0, [SP, R0, LSL#2]\n" /* load destination register contents from stack */ "STR R0, [R4, R2, LSL#2]\n" /* store destination register at index [5] */ "ADD R2, #1\n" /* increment index */ /* timestamp the event (requires MMIO access; do this before re-enabling memory protection) */ "LDR R0, =0xD400000C\n" /* 32-bit microsecond timer */ "LDR R0, [R0]\n" "STR R0, [R4, R2, LSL#2]\n" /* store timestamp at index[6]; will be unwrapped in post */ "ADD R2, #1\n" /* increment index */ "STR LR, [R4, R2, LSL#2]\n" /* store LR of the interrupted mode at index [7] */ "ADD R2, #1\n" /* increment index (total 8 = RECORD_SIZE) */ "STR R2, buffer_index\n" /* store buffer index to memory (this requires ARM mode) */ /* re-enable memory protection */ "LDR R0, protected_region_size\n" "MCR p15, 0, R0, c6, c1, 2\n" /* restore context */ "LDMFD SP!, {R0-R12, LR}\n" /* restore LR_ABT (where we should continue the execution) */ "LDMFD SP!, {LR}\n" /* continue the execution after the trapped instruction */ /* note: LR was adjusted earlier for "narrow" instructions */ "SUBS PC, LR, #4\n" /* Thumb code path finished */ /* ----------------------------------------------------- */ #endif /* CONFIG_DIGIC_VI */ ); } #define TRAP_INSTALLED (MEM(0x0000002C) == (uint32_t) &trap) void io_trace_uninstall() { ASSERT(buffer); ASSERT(trap_orig); ASSERT(TRAP_INSTALLED); if (!TRAP_INSTALLED) { /* not installed, nothing to do */ return; } uint32_t int_status = cli(); /* remove our trap handler */ MEM(0x0000002C) = (uint32_t)trap_orig; sync_caches(); asm( /* enable full access to memory */ #ifdef CONFIG_DIGIC_VI "MOV R0, #0x07\n" "MCR p15, 0, R0, c6, c2, 0\n" /* write RGNR (adjust memory region #7) */ "MOV R0, #0x00\n" "MCR p15, 0, R0, c6, c1, 2\n" /* enable full access to memory (disable region #7) */ #else /* DIGIC V and earlier */ "MOV R0, #0x00\n" "MCR p15, 0, r0, c6, c7, 0\n" #endif ::: "r0" ); sync_caches(); sei(int_status); } /* to be called after uninstallation, to free the buffer */ void io_trace_cleanup() { ASSERT(buffer); ASSERT(!TRAP_INSTALLED); if (TRAP_INSTALLED) { /* called at the wrong moment; don't do more damage */ return; } #if 0 free(buffer); buffer = 0; #endif } void io_trace_prepare() { #if 0 extern int ml_started; if (!ml_started) { qprintf("[io_trace] FIXME: large allocators not available\n"); return; } #endif qprintf("[io_trace] allocating memory...\n"); /* allocate RAM */ buffer_index = 0; buffer_count = 4*1024*1024; int alloc_size = (buffer_count + RECORD_SIZE) * sizeof(buffer[0]); ASSERT(!buffer); #if 0 /* FIXME: no large allocators yet */ buffer = malloc(alloc_size); #else #ifdef CONFIG_80D /* hardcoded address, model-specific, see log_start() for details */ buffer = (void *) 0x28000000; #endif #ifdef CONFIG_5D4 /* let's hope it's OK; appears to be used during bursts */ buffer = (void *) 0x20B00000; #endif #endif if (!buffer) return; memset(buffer, 0, alloc_size); } void io_trace_install() { if (!buffer) { qprintf("[io_trace] no buffer allocated\n"); return; } qprintf("[io_trace] installing...\n"); uint32_t int_status = cli(); /* install data abort handler */ trap_orig = MEM(0x0000002C); MEM(0x0000002C) = (uint32_t) &trap; /* also needed before? */ sync_caches(); /* set buffer/cache bits for the logged region */ asm( #ifdef CONFIG_DIGIC_VI /* enable memory protection */ "MOV R7, #0x07\n" "MCR p15, 0, R7, c6, c2, 0\n" /* write RGNR (adjust memory region #7) */ "LDR R0, protected_region_base\n" "MCR p15, 0, R0, c6, c1, 0\n" /* write DRBAR (base address) */ "LDR R0, protected_region_size\n" "MCR p15, 0, R0, c6, c1, 2\n" /* write DRSR (size and enable register) */ "MOV R0, #0x005\n" /* like 0xC0000000, but with AP bits disabled */ "MCR p15, 0, R0, c6, c1, 4\n" /* write DRACR (access control register) */ : : : "r7" #else /* DIGIC V and earlier */ /* set area uncacheable (already set up that way, but...) */ "mrc p15, 0, R4, c2, c0, 0\n" "bic r4, #0x80\n" "mcr p15, 0, R4, c2, c0, 0\n" "mrc p15, 0, R4, c2, c0, 1\n" "bic r4, #0x80\n" "mcr p15, 0, R4, c2, c0, 1\n" /* set area non-bufferable (already set up that way, but...) */ "mrc p15, 0, R4, c3, c0, 0\n" "bic r4, #0x80\n" "mcr p15, 0, R4, c3, c0, 0\n" /* set access permissions (disable data access in the protected area) */ "mrc p15, 0, R4, c5, c0, 2\n" "bic R4, #0xF0000000\n" "mcr p15, 0, R4, c5, c0, 2\n" /* enable memory protection */ "ldr r0, protected_region\n" "mcr p15, 0, r0, c6, c7, 0\n" : : : "r4" #endif ); sync_caches(); sei(int_status); qprintf("[io_trace] installed.\n"); } static const char * interrupt_name(int i) { static char name[] = "INT-00h"; int i0 = (i & 0xF); int i1 = (i >> 4) & 0xF; int i2 = (i >> 8) & 0xF; name[3] = i2 ? '0' + i2 : '-'; name[4] = i1 < 10 ? '0' + i1 : 'A' + i1 - 10; name[5] = i0 < 10 ? '0' + i0 : 'A' + i0 - 10; return name; } uint32_t io_trace_log_get_index() { return buffer_index / RECORD_SIZE; } uint32_t io_trace_log_get_nmax() { return buffer_count / RECORD_SIZE; } static uint32_t ror(uint32_t word, uint32_t count) { return word >> count | word << (32 - count); } int io_trace_log_message(uint32_t msg_index, char * msg_buffer, int msg_size) { uint32_t i = msg_index * RECORD_SIZE; if (i >= buffer_index) return 0; #ifdef CONFIG_DIGIC_VI /* we have enough metadata, no need to decode the instructions manually */ uint32_t pc = buffer[i]; uint32_t dfsr = buffer[i+3]; uint32_t dfar = buffer[i+4]; uint32_t val = buffer[i+5]; uint32_t us = buffer[i+6]; uint32_t lr = buffer[i+7]; const char * task_name = (const char *) buffer[i+1]; uint32_t interrupt = buffer[i+2]; if (interrupt & 0x80000000) { task_name = interrupt_name(interrupt & 0xFFF); } char task_name_padded[11] = " "; int spaces = 10 - strlen(task_name); if (spaces < 0) spaces = 0; snprintf(task_name_padded + spaces, 11 - spaces, "%s", task_name); int len = snprintf( msg_buffer, msg_size, "%d.%06d %s:%08x:%08x:MMIO : ", us/1000000, us%1000000, task_name_padded, pc, lr); int is_ldr = (dfsr & 0x800) ? 0 : 1; len += snprintf(msg_buffer + len, msg_size - len, "[0x%08X] %s 0x%08X\n", dfar, /* DFAR - MMIO register address */ is_ldr ? "->" : "<-", /* direction (read or write), from DFSR */ val /* MMIO register value */ ); #else /* assuming ARM code only */ uint32_t pc = buffer[i]; uint32_t insn = MEM(pc); uint32_t Rn = buffer[i+3]; uint32_t Rm = buffer[i+4]; uint32_t Rd = buffer[i+5]; char * task_name = (char *) buffer[i+1]; uint32_t interrupt = buffer[i+2]; uint32_t us_timer = buffer[i+6]; uint32_t is_ldr = insn & (1 << 20); uint32_t offset = 0; char raw[48] = ""; if ((insn & 0x0F000000) == 0x05000000) { /* ARM ARM: * A5.2.2 Load and Store Word or Unsigned Byte - Immediate offset * A5.2.5 Load and Store Word or Unsigned Byte - Immediate pre-indexed */ offset = (insn & 0xFFF); } else if ((insn & 0x0D200000) == 0x04000000) { /* A5.2.8 Load and Store Word or Unsigned Byte - Immediate post-indexed * A5.2.9 Load and Store Word or Unsigned Byte - Register post-indexed * A5.2.10 Load and Store Word or Unsigned Byte - Scaled register post-indexed */ offset = 0; } else if ((insn & 0x0F000000) == 0x07000000) { /* A5.2.3 Load and Store Word or Unsigned Byte - Register offset * A5.2.4 Load and Store Word or Unsigned Byte - Scaled register offset * A5.2.6 Load and Store Word or Unsigned Byte - Register pre-indexed * A5.2.7 Load and Store Word or Unsigned Byte - Scaled register pre-indexed */ uint32_t shift = (insn >> 5) & 0x3; uint32_t shift_imm = (insn >> 7) & 0x1F; /* index == offset */ switch (shift) { case 0b00: /* LSL */ offset = Rm << shift_imm; qprintf("%x: %x [Rn, Rm, LSL#%d] => %x\n", pc, insn, shift_imm, offset); break; case 0b01: /* LSR */ offset = Rm >> (shift_imm ? shift_imm : 32); qprintf("%x: %x [Rn, Rm, LSR#%d] => %x\n", pc, insn, shift_imm, offset); break; case 0b10: /* ASR */ offset = (int32_t) Rm >> (shift_imm ? shift_imm : 32); qprintf("%x: %x [Rn, Rm, ASR#%d] => %x\n", pc, insn, shift_imm, offset); break; case 0b11: /* ROR or RRX */ offset = (shift_imm) ? ror(Rm, shift_imm) : 0 /* FIXME: C not saved; important? */; qprintf("%x: %x [Rn, Rm, ROR#%d] => %x\n", pc, insn, shift_imm, offset); ASSERT(shift_imm); break; } } else if ((insn & 0x0F600F90) == 0x01000090) { /* A5.3.3 Miscellaneous Loads and Stores - Register offset */ offset = Rm; } else { /* unhandled case; print raw values to assist troubleshooting */ snprintf(raw, sizeof(raw), " (%08X, Rn=%08X, Rm=%08X)", insn, Rn, Rm); qprintf("%x: %x ???\n", pc, insn); } /* all of the above may use the sign bit */ if (!(insn & (1 << 23))) { offset = -offset; } char msg[128]; snprintf(msg, sizeof(msg), "[0x%08X] %s 0x%08X%s", Rn + offset, /* Rn - MMIO register (to be interpreted after disassembling the instruction) */ is_ldr ? "->" : "<-", /* assume LDR or STR; can you find a counterexample? */ Rd, /* Rd - destination register (MMIO register value) */ raw /* optional raw values */ ); /* we don't store this in the "blockchain", so we don't really need block_size */ struct debug_msg dm = { .msg = msg, .class_name = "MMIO", .us_timer = us_timer, .pc = pc, .task_name = task_name, .interrupt = interrupt, }; int len = debug_format_msg(&dm, msg_buffer, msg_size); if (buffer[buffer_count - RECORD_SIZE]) { /* index wrapped around? */ printf("[MMIO] warning: buffer full\n"); len += snprintf(msg_buffer + len, msg_size - len, "[MMIO] warning: buffer full\n"); buffer[buffer_count - RECORD_SIZE] = 0; } #endif return len; } static inline void local_io_trace_pause() { asm volatile ( /* enable full access to memory */ #ifdef CONFIG_DIGIC_VI "MOV R0, #0x07\n" "MCR p15, 0, R0, c6, c2, 0\n" /* write RGNR (adjust memory region #7) */ "MOV R0, #0x00\n" "MCR p15, 0, R0, c6, c1, 2\n" /* enable full access to memory (disable region #7) */ #else /* DIGIC V and earlier */ "MOV R0, #0x00\n" "MCR p15, 0, R0, c6, c7, 0\n" #endif ::: "r0" ); } static inline void local_io_trace_resume() { asm volatile ( /* re-enable memory protection */ #ifdef CONFIG_DIGIC_VI "MOV R0, #0x07\n" "MCR p15, 0, R0, c6, c2, 0\n" /* write RGNR (adjust memory region #7) */ "LDR R0, protected_region_size\n" "MCR p15, 0, R0, c6, c1, 2\n" /* enable region #7 */ #else /* DIGIC V and earlier */ "LDR R0, protected_region\n" "MCR p15, 0, R0, c6, c7, 0\n" #endif ::: "r0" ); } /* public wrappers */ void io_trace_pause() { uint32_t old = cli(); if (TRAP_INSTALLED) { local_io_trace_pause(); } sei(old); } void io_trace_resume() { uint32_t old = cli(); if (TRAP_INSTALLED) { local_io_trace_resume(); } sei(old); } /* get timer value without logging it as MMIO access */ uint32_t io_trace_get_timer() { uint32_t old = cli(); if (!TRAP_INSTALLED) { #ifdef CONFIG_DIGIC_VI uint32_t timer = MEM(0xD400000C); #else uint32_t timer = MEM(0xC0242014); #endif sei(old); return timer; } local_io_trace_pause(); #ifdef CONFIG_DIGIC_VI uint32_t timer = MEM(0xD400000C); #else uint32_t timer = MEM(0xC0242014); #endif local_io_trace_resume(); sei(old); return timer; } void io_trace_dump() { #ifdef CONFIG_80D /* hardcoded address, model-specific, see log_start() for details */ char * msg_buffer = (void *) 0x2AB00000; int buffer_size = 32 * 1024 * 1024; #endif #ifdef CONFIG_5D4 char * msg_buffer = (void *) 0x3C500000; int buffer_size = 12 * 1024 * 1024; #endif int msg_len = 0; uint32_t n = io_trace_log_get_index(); msg_len += snprintf(msg_buffer + msg_len, buffer_size - msg_len, "[MMIO] Saving %d events...\n", n); for (uint32_t i = 0; i < n; i++) { char * msg = msg_buffer + msg_len; msg_len += io_trace_log_message(i, msg_buffer + msg_len, buffer_size - msg_len); qprintf(msg); } qprintf("Saving MMIO log %X size %X...\n", msg_buffer, msg_len); extern void dump_file(char* name, uint32_t addr, uint32_t size); dump_file("MMIO.LOG", (uint32_t) msg_buffer, msg_len); }