| #include <libcflat.h> | |
| #include <migrate.h> | |
| #include <errata.h> | |
| #include <asm/setup.h> | |
| #include <asm/processor.h> | |
| #include <asm/delay.h> | |
| #include <asm/smp.h> | |
| #include <asm/barrier.h> | |
| #include <asm/io.h> | |
| #define MDSCR_KDE (1 << 13) | |
| #define MDSCR_MDE (1 << 15) | |
| #define MDSCR_SS (1 << 0) | |
| #define DBGBCR_LEN8 (0xff << 5) | |
| #define DBGBCR_EXEC (0x0 << 3) | |
| #define DBGBCR_EL1 (0x1 << 1) | |
| #define DBGBCR_E (0x1 << 0) | |
| #define DBGWCR_LEN8 (0xff << 5) | |
| #define DBGWCR_RD (0x1 << 3) | |
| #define DBGWCR_WR (0x2 << 3) | |
| #define DBGWCR_EL1 (0x1 << 1) | |
| #define DBGWCR_E (0x1 << 0) | |
| #define SPSR_D (1 << 9) | |
| #define SPSR_SS (1 << 21) | |
| #define ESR_EC_HW_BP_CURRENT 0x31 | |
| #define ESR_EC_SSTEP_CURRENT 0x33 | |
| #define ESR_EC_WP_CURRENT 0x35 | |
| #define ID_AA64DFR0_BRPS_SHIFT 12 | |
| #define ID_AA64DFR0_BRPS_MASK 0xf | |
| #define ID_AA64DFR0_WRPS_SHIFT 20 | |
| #define ID_AA64DFR0_WRPS_MASK 0xf | |
| static volatile uint64_t hw_bp_idx, hw_bp_addr[16]; | |
| static volatile uint64_t wp_idx, wp_data_addr[16]; | |
| static volatile uint64_t ss_addr[4], ss_idx; | |
| static void hw_bp_handler(struct pt_regs *regs, unsigned int esr) | |
| { | |
| hw_bp_addr[hw_bp_idx++] = regs->pc; | |
| regs->pstate |= SPSR_D; | |
| } | |
| static void wp_handler(struct pt_regs *regs, unsigned int esr) | |
| { | |
| wp_data_addr[wp_idx++] = read_sysreg(far_el1); | |
| regs->pstate |= SPSR_D; | |
| } | |
| static void ss_handler(struct pt_regs *regs, unsigned int esr) | |
| { | |
| ss_addr[ss_idx++] = regs->pc; | |
| regs->pstate |= SPSR_SS; | |
| } | |
| static int get_num_hw_bp(void) | |
| { | |
| uint64_t reg = read_sysreg(id_aa64dfr0_el1); | |
| /* Number of breakpoints, minus 1 */ | |
| uint8_t brps = (reg >> ID_AA64DFR0_BRPS_SHIFT) & ID_AA64DFR0_BRPS_MASK; | |
| return brps + 1; | |
| } | |
| static int get_num_wp(void) | |
| { | |
| uint64_t reg = read_sysreg(id_aa64dfr0_el1); | |
| /* Number of watchpoints, minus 1 */ | |
| uint8_t wrps = (reg >> ID_AA64DFR0_WRPS_SHIFT) & ID_AA64DFR0_WRPS_MASK; | |
| return wrps + 1; | |
| } | |
| static void write_dbgbcr(int n, uint32_t bcr) | |
| { | |
| switch (n) { | |
| case 0: | |
| write_sysreg(bcr, dbgbcr0_el1); break; | |
| case 1: | |
| write_sysreg(bcr, dbgbcr1_el1); break; | |
| case 2: | |
| write_sysreg(bcr, dbgbcr2_el1); break; | |
| case 3: | |
| write_sysreg(bcr, dbgbcr3_el1); break; | |
| case 4: | |
| write_sysreg(bcr, dbgbcr4_el1); break; | |
| case 5: | |
| write_sysreg(bcr, dbgbcr5_el1); break; | |
| case 6: | |
| write_sysreg(bcr, dbgbcr6_el1); break; | |
| case 7: | |
| write_sysreg(bcr, dbgbcr7_el1); break; | |
| case 8: | |
| write_sysreg(bcr, dbgbcr8_el1); break; | |
| case 9: | |
| write_sysreg(bcr, dbgbcr9_el1); break; | |
| case 10: | |
| write_sysreg(bcr, dbgbcr10_el1); break; | |
| case 11: | |
| write_sysreg(bcr, dbgbcr11_el1); break; | |
| case 12: | |
| write_sysreg(bcr, dbgbcr12_el1); break; | |
| case 13: | |
| write_sysreg(bcr, dbgbcr13_el1); break; | |
| case 14: | |
| write_sysreg(bcr, dbgbcr14_el1); break; | |
| case 15: | |
| write_sysreg(bcr, dbgbcr15_el1); break; | |
| default: | |
| report_abort("Invalid bcr"); | |
| } | |
| } | |
| static void write_dbgbvr(int n, uint64_t bvr) | |
| { | |
| switch (n) { | |
| case 0: | |
| write_sysreg(bvr, dbgbvr0_el1); break; | |
| case 1: | |
| write_sysreg(bvr, dbgbvr1_el1); break; | |
| case 2: | |
| write_sysreg(bvr, dbgbvr2_el1); break; | |
| case 3: | |
| write_sysreg(bvr, dbgbvr3_el1); break; | |
| case 4: | |
| write_sysreg(bvr, dbgbvr4_el1); break; | |
| case 5: | |
| write_sysreg(bvr, dbgbvr5_el1); break; | |
| case 6: | |
| write_sysreg(bvr, dbgbvr6_el1); break; | |
| case 7: | |
| write_sysreg(bvr, dbgbvr7_el1); break; | |
| case 8: | |
| write_sysreg(bvr, dbgbvr8_el1); break; | |
| case 9: | |
| write_sysreg(bvr, dbgbvr9_el1); break; | |
| case 10: | |
| write_sysreg(bvr, dbgbvr10_el1); break; | |
| case 11: | |
| write_sysreg(bvr, dbgbvr11_el1); break; | |
| case 12: | |
| write_sysreg(bvr, dbgbvr12_el1); break; | |
| case 13: | |
| write_sysreg(bvr, dbgbvr13_el1); break; | |
| case 14: | |
| write_sysreg(bvr, dbgbvr14_el1); break; | |
| case 15: | |
| write_sysreg(bvr, dbgbvr15_el1); break; | |
| default: | |
| report_abort("invalid bvr"); | |
| } | |
| } | |
| static void write_dbgwcr(int n, uint32_t wcr) | |
| { | |
| switch (n) { | |
| case 0: | |
| write_sysreg(wcr, dbgwcr0_el1); break; | |
| case 1: | |
| write_sysreg(wcr, dbgwcr1_el1); break; | |
| case 2: | |
| write_sysreg(wcr, dbgwcr2_el1); break; | |
| case 3: | |
| write_sysreg(wcr, dbgwcr3_el1); break; | |
| case 4: | |
| write_sysreg(wcr, dbgwcr4_el1); break; | |
| case 5: | |
| write_sysreg(wcr, dbgwcr5_el1); break; | |
| case 6: | |
| write_sysreg(wcr, dbgwcr6_el1); break; | |
| case 7: | |
| write_sysreg(wcr, dbgwcr7_el1); break; | |
| case 8: | |
| write_sysreg(wcr, dbgwcr8_el1); break; | |
| case 9: | |
| write_sysreg(wcr, dbgwcr9_el1); break; | |
| case 10: | |
| write_sysreg(wcr, dbgwcr10_el1); break; | |
| case 11: | |
| write_sysreg(wcr, dbgwcr11_el1); break; | |
| case 12: | |
| write_sysreg(wcr, dbgwcr12_el1); break; | |
| case 13: | |
| write_sysreg(wcr, dbgwcr13_el1); break; | |
| case 14: | |
| write_sysreg(wcr, dbgwcr14_el1); break; | |
| case 15: | |
| write_sysreg(wcr, dbgwcr15_el1); break; | |
| default: | |
| report_abort("Invalid wcr"); | |
| } | |
| } | |
| static void write_dbgwvr(int n, uint64_t wvr) | |
| { | |
| switch (n) { | |
| case 0: | |
| write_sysreg(wvr, dbgwvr0_el1); break; | |
| case 1: | |
| write_sysreg(wvr, dbgwvr1_el1); break; | |
| case 2: | |
| write_sysreg(wvr, dbgwvr2_el1); break; | |
| case 3: | |
| write_sysreg(wvr, dbgwvr3_el1); break; | |
| case 4: | |
| write_sysreg(wvr, dbgwvr4_el1); break; | |
| case 5: | |
| write_sysreg(wvr, dbgwvr5_el1); break; | |
| case 6: | |
| write_sysreg(wvr, dbgwvr6_el1); break; | |
| case 7: | |
| write_sysreg(wvr, dbgwvr7_el1); break; | |
| case 8: | |
| write_sysreg(wvr, dbgwvr8_el1); break; | |
| case 9: | |
| write_sysreg(wvr, dbgwvr9_el1); break; | |
| case 10: | |
| write_sysreg(wvr, dbgwvr10_el1); break; | |
| case 11: | |
| write_sysreg(wvr, dbgwvr11_el1); break; | |
| case 12: | |
| write_sysreg(wvr, dbgwvr12_el1); break; | |
| case 13: | |
| write_sysreg(wvr, dbgwvr13_el1); break; | |
| case 14: | |
| write_sysreg(wvr, dbgwvr14_el1); break; | |
| case 15: | |
| write_sysreg(wvr, dbgwvr15_el1); break; | |
| default: | |
| report_abort("invalid wvr"); | |
| } | |
| } | |
| static void reset_debug_state(void) | |
| { | |
| int i, num_bp = get_num_hw_bp(); | |
| int num_wp = get_num_wp(); | |
| asm volatile("msr daifset, #8"); | |
| write_sysreg(0, osdlr_el1); | |
| write_sysreg(0, oslar_el1); | |
| isb(); | |
| write_sysreg(0, mdscr_el1); | |
| for (i = 0; i < num_bp; i++) { | |
| write_dbgbvr(i, 0); | |
| write_dbgbcr(i, 0); | |
| } | |
| for (i = 0; i < num_wp; i++) { | |
| write_dbgwvr(i, 0); | |
| write_dbgwcr(i, 0); | |
| } | |
| isb(); | |
| } | |
| static noinline void test_hw_bp(bool migrate) | |
| { | |
| extern unsigned char hw_bp0; | |
| uint32_t bcr; | |
| uint32_t mdscr; | |
| uint64_t addr; | |
| int num_bp = get_num_hw_bp(); | |
| int i; | |
| if (current_level() == CurrentEL_EL2) { | |
| report_skip("test broken for EL2"); | |
| return; | |
| } | |
| install_exception_handler(EL1H_SYNC, ESR_EC_HW_BP_CURRENT, hw_bp_handler); | |
| reset_debug_state(); | |
| bcr = DBGBCR_LEN8 | DBGBCR_EXEC | DBGBCR_EL1 | DBGBCR_E; | |
| for (i = 0, addr = (uint64_t)&hw_bp0; i < num_bp; i++, addr += 4) { | |
| write_dbgbcr(i, bcr); | |
| write_dbgbvr(i, addr); | |
| } | |
| isb(); | |
| asm volatile("msr daifclr, #8"); | |
| mdscr = read_sysreg(mdscr_el1) | MDSCR_KDE | MDSCR_MDE; | |
| write_sysreg(mdscr, mdscr_el1); | |
| isb(); | |
| if (migrate) { | |
| migrate_once(); | |
| report(num_bp == get_num_hw_bp(), "brps match after migrate"); | |
| } | |
| hw_bp_idx = 0; | |
| /* Trap on up to 16 debug exception unmask instructions. */ | |
| asm volatile( | |
| ".globl hw_bp0\n" | |
| "hw_bp0:\n" | |
| "msr daifclr, #8; msr daifclr, #8; msr daifclr, #8; msr daifclr, #8\n" | |
| "msr daifclr, #8; msr daifclr, #8; msr daifclr, #8; msr daifclr, #8\n" | |
| "msr daifclr, #8; msr daifclr, #8; msr daifclr, #8; msr daifclr, #8\n" | |
| "msr daifclr, #8; msr daifclr, #8; msr daifclr, #8; msr daifclr, #8\n" | |
| ); | |
| for (i = 0, addr = (uint64_t)&hw_bp0; i < num_bp; i++, addr += 4) | |
| report(hw_bp_addr[i] == addr, "hw breakpoint: %d", i); | |
| } | |
| static volatile char write_data[16]; | |
| static noinline void test_wp(bool migrate) | |
| { | |
| uint32_t wcr; | |
| uint32_t mdscr; | |
| int num_wp = get_num_wp(); | |
| int i; | |
| if (current_level() == CurrentEL_EL2) { | |
| report_skip("test broken for EL2"); | |
| return; | |
| } | |
| install_exception_handler(EL1H_SYNC, ESR_EC_WP_CURRENT, wp_handler); | |
| reset_debug_state(); | |
| wcr = DBGWCR_LEN8 | DBGWCR_RD | DBGWCR_WR | DBGWCR_EL1 | DBGWCR_E; | |
| for (i = 0; i < num_wp; i++) { | |
| write_dbgwcr(i, wcr); | |
| write_dbgwvr(i, (uint64_t)&write_data[i]); | |
| } | |
| isb(); | |
| asm volatile("msr daifclr, #8"); | |
| mdscr = read_sysreg(mdscr_el1) | MDSCR_KDE | MDSCR_MDE; | |
| write_sysreg(mdscr, mdscr_el1); | |
| isb(); | |
| if (migrate) { | |
| migrate_once(); | |
| report(num_wp == get_num_wp(), "wrps match after migrate"); | |
| } | |
| wp_idx = 0; | |
| for (i = 0; i < num_wp; i++) { | |
| write_data[i] = i; | |
| asm volatile("msr daifclr, #8"); | |
| } | |
| for (i = 0; i < num_wp; i++) { | |
| report(wp_data_addr[i] == (uint64_t)&write_data[i], | |
| "watchpoint received: %d", i); | |
| report(write_data[i] == i, "watchpoint data: %d", i); | |
| } | |
| } | |
| static noinline void test_ss(bool migrate) | |
| { | |
| extern unsigned char ss_start; | |
| uint32_t mdscr; | |
| install_exception_handler(EL1H_SYNC, ESR_EC_SSTEP_CURRENT, ss_handler); | |
| reset_debug_state(); | |
| ss_idx = 0; | |
| mdscr = read_sysreg(mdscr_el1) | MDSCR_KDE | MDSCR_SS; | |
| write_sysreg(mdscr, mdscr_el1); | |
| isb(); | |
| if (migrate) | |
| migrate_once(); | |
| asm volatile("msr daifclr, #8"); | |
| asm volatile( | |
| ".globl ss_start\n" | |
| "ss_start:\n" | |
| "mrs x0, esr_el1\n" | |
| "add x0, x0, #1\n" | |
| "msr daifset, #8\n" | |
| : : : "x0" | |
| ); | |
| report(ss_addr[0] == (uint64_t)&ss_start, "single step"); | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| if (argc < 2) | |
| report_abort("no test specified"); | |
| if (strcmp(argv[1], "bp") == 0) { | |
| report_prefix_push(argv[1]); | |
| test_hw_bp(false); | |
| report_prefix_pop(); | |
| } else if (strcmp(argv[1], "bp-migration") == 0) { | |
| report_prefix_push(argv[1]); | |
| test_hw_bp(true); | |
| report_prefix_pop(); | |
| } else if (strcmp(argv[1], "wp") == 0) { | |
| report_prefix_push(argv[1]); | |
| test_wp(false); | |
| report_prefix_pop(); | |
| } else if (strcmp(argv[1], "wp-migration") == 0) { | |
| report_prefix_push(argv[1]); | |
| test_wp(true); | |
| report_prefix_pop(); | |
| } else if (strcmp(argv[1], "ss") == 0) { | |
| report_prefix_push(argv[1]); | |
| test_ss(false); | |
| report_prefix_pop(); | |
| } else if (strcmp(argv[1], "ss-migration") == 0) { | |
| report_prefix_push(argv[1]); | |
| test_ss(true); | |
| report_prefix_pop(); | |
| } else { | |
| report_abort("Unknown subtest '%s'", argv[1]); | |
| } | |
| return report_summary(); | |
| } |