target-arm: Implement handling of fired watchpoints
Implement the ARM debug exception handler for dealing with fired watchpoints. Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
		
							parent
							
								
									73c5211ba9
								
							
						
					
					
						commit
						3ff6fc9148
					
				| @ -1065,6 +1065,7 @@ static void arm_cpu_class_init(ObjectClass *oc, void *data) | ||||
| #endif | ||||
|     cc->gdb_num_core_regs = 26; | ||||
|     cc->gdb_core_xml_file = "arm-core.xml"; | ||||
|     cc->debug_excp_handler = arm_debug_excp_handler; | ||||
| } | ||||
| 
 | ||||
| static void cpu_register(const ARMCPUInfo *info) | ||||
|  | ||||
| @ -2399,14 +2399,18 @@ static void define_debug_regs(ARMCPU *cpu) | ||||
|      * These are just dummy implementations for now. | ||||
|      */ | ||||
|     int i; | ||||
|     int wrps, brps; | ||||
|     int wrps, brps, ctx_cmps; | ||||
|     ARMCPRegInfo dbgdidr = { | ||||
|         .name = "DBGDIDR", .cp = 14, .crn = 0, .crm = 0, .opc1 = 0, .opc2 = 0, | ||||
|         .access = PL0_R, .type = ARM_CP_CONST, .resetvalue = cpu->dbgdidr, | ||||
|     }; | ||||
| 
 | ||||
|     /* Note that all these register fields hold "number of Xs minus 1". */ | ||||
|     brps = extract32(cpu->dbgdidr, 24, 4); | ||||
|     wrps = extract32(cpu->dbgdidr, 28, 4); | ||||
|     ctx_cmps = extract32(cpu->dbgdidr, 20, 4); | ||||
| 
 | ||||
|     assert(ctx_cmps <= brps); | ||||
| 
 | ||||
|     /* The DBGDIDR and ID_AA64DFR0_EL1 define various properties
 | ||||
|      * of the debug registers such as number of breakpoints; | ||||
| @ -2415,6 +2419,7 @@ static void define_debug_regs(ARMCPU *cpu) | ||||
|     if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) { | ||||
|         assert(extract32(cpu->id_aa64dfr0, 12, 4) == brps); | ||||
|         assert(extract32(cpu->id_aa64dfr0, 20, 4) == wrps); | ||||
|         assert(extract32(cpu->id_aa64dfr0, 28, 4) == ctx_cmps); | ||||
|     } | ||||
| 
 | ||||
|     define_one_arm_cp_reg(cpu, &dbgdidr); | ||||
|  | ||||
| @ -307,6 +307,12 @@ static inline uint32_t syn_swstep(int same_el, int isv, int ex) | ||||
|         | (isv << 24) | (ex << 6) | 0x22; | ||||
| } | ||||
| 
 | ||||
| static inline uint32_t syn_watchpoint(int same_el, int cm, int wnr) | ||||
| { | ||||
|     return (EC_WATCHPOINT << ARM_EL_EC_SHIFT) | (same_el << ARM_EL_EC_SHIFT) | ||||
|         | (cm << 8) | (wnr << 6) | 0x22; | ||||
| } | ||||
| 
 | ||||
| /* Update a QEMU watchpoint based on the information the guest has set in the
 | ||||
|  * DBGWCR<n>_EL1 and DBGWVR<n>_EL1 registers. | ||||
|  */ | ||||
| @ -317,4 +323,7 @@ void hw_watchpoint_update(ARMCPU *cpu, int n); | ||||
|  */ | ||||
| void hw_watchpoint_update_all(ARMCPU *cpu); | ||||
| 
 | ||||
| /* Callback function for when a watchpoint or breakpoint triggers. */ | ||||
| void arm_debug_excp_handler(CPUState *cs); | ||||
| 
 | ||||
| #endif | ||||
|  | ||||
| @ -456,6 +456,194 @@ illegal_return: | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* Return true if the linked breakpoint entry lbn passes its checks */ | ||||
| static bool linked_bp_matches(ARMCPU *cpu, int lbn) | ||||
| { | ||||
|     CPUARMState *env = &cpu->env; | ||||
|     uint64_t bcr = env->cp15.dbgbcr[lbn]; | ||||
|     int brps = extract32(cpu->dbgdidr, 24, 4); | ||||
|     int ctx_cmps = extract32(cpu->dbgdidr, 20, 4); | ||||
|     int bt; | ||||
|     uint32_t contextidr; | ||||
| 
 | ||||
|     /* Links to unimplemented or non-context aware breakpoints are
 | ||||
|      * CONSTRAINED UNPREDICTABLE: either behave as if disabled, or | ||||
|      * as if linked to an UNKNOWN context-aware breakpoint (in which | ||||
|      * case DBGWCR<n>_EL1.LBN must indicate that breakpoint). | ||||
|      * We choose the former. | ||||
|      */ | ||||
|     if (lbn > brps || lbn < (brps - ctx_cmps)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bcr = env->cp15.dbgbcr[lbn]; | ||||
| 
 | ||||
|     if (extract64(bcr, 0, 1) == 0) { | ||||
|         /* Linked breakpoint disabled : generate no events */ | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bt = extract64(bcr, 20, 4); | ||||
| 
 | ||||
|     /* We match the whole register even if this is AArch32 using the
 | ||||
|      * short descriptor format (in which case it holds both PROCID and ASID), | ||||
|      * since we don't implement the optional v7 context ID masking. | ||||
|      */ | ||||
|     contextidr = extract64(env->cp15.contextidr_el1, 0, 32); | ||||
| 
 | ||||
|     switch (bt) { | ||||
|     case 3: /* linked context ID match */ | ||||
|         if (arm_current_pl(env) > 1) { | ||||
|             /* Context matches never fire in EL2 or (AArch64) EL3 */ | ||||
|             return false; | ||||
|         } | ||||
|         return (contextidr == extract64(env->cp15.dbgbvr[lbn], 0, 32)); | ||||
|     case 5: /* linked address mismatch (reserved in AArch64) */ | ||||
|     case 9: /* linked VMID match (reserved if no EL2) */ | ||||
|     case 11: /* linked context ID and VMID match (reserved if no EL2) */ | ||||
|     default: | ||||
|         /* Links to Unlinked context breakpoints must generate no
 | ||||
|          * events; we choose to do the same for reserved values too. | ||||
|          */ | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| static bool wp_matches(ARMCPU *cpu, int n) | ||||
| { | ||||
|     CPUARMState *env = &cpu->env; | ||||
|     uint64_t wcr = env->cp15.dbgwcr[n]; | ||||
|     int pac, hmc, ssc, wt, lbn; | ||||
|     /* TODO: check against CPU security state when we implement TrustZone */ | ||||
|     bool is_secure = false; | ||||
| 
 | ||||
|     if (!env->cpu_watchpoint[n] | ||||
|         || !(env->cpu_watchpoint[n]->flags & BP_WATCHPOINT_HIT)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /* The WATCHPOINT_HIT flag guarantees us that the watchpoint is
 | ||||
|      * enabled and that the address and access type match; check the | ||||
|      * remaining fields, including linked breakpoints. | ||||
|      * Note that some combinations of {PAC, HMC SSC} are reserved and | ||||
|      * must act either like some valid combination or as if the watchpoint | ||||
|      * were disabled. We choose the former, and use this together with | ||||
|      * the fact that EL3 must always be Secure and EL2 must always be | ||||
|      * Non-Secure to simplify the code slightly compared to the full | ||||
|      * table in the ARM ARM. | ||||
|      */ | ||||
|     pac = extract64(wcr, 1, 2); | ||||
|     hmc = extract64(wcr, 13, 1); | ||||
|     ssc = extract64(wcr, 14, 2); | ||||
| 
 | ||||
|     switch (ssc) { | ||||
|     case 0: | ||||
|         break; | ||||
|     case 1: | ||||
|     case 3: | ||||
|         if (is_secure) { | ||||
|             return false; | ||||
|         } | ||||
|         break; | ||||
|     case 2: | ||||
|         if (!is_secure) { | ||||
|             return false; | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     /* TODO: this is not strictly correct because the LDRT/STRT/LDT/STT
 | ||||
|      * "unprivileged access" instructions should match watchpoints as if | ||||
|      * they were accesses done at EL0, even if the CPU is at EL1 or higher. | ||||
|      * Implementing this would require reworking the core watchpoint code | ||||
|      * to plumb the mmu_idx through to this point. Luckily Linux does not | ||||
|      * rely on this behaviour currently. | ||||
|      */ | ||||
|     switch (arm_current_pl(env)) { | ||||
|     case 3: | ||||
|     case 2: | ||||
|         if (!hmc) { | ||||
|             return false; | ||||
|         } | ||||
|         break; | ||||
|     case 1: | ||||
|         if (extract32(pac, 0, 1) == 0) { | ||||
|             return false; | ||||
|         } | ||||
|         break; | ||||
|     case 0: | ||||
|         if (extract32(pac, 1, 1) == 0) { | ||||
|             return false; | ||||
|         } | ||||
|         break; | ||||
|     default: | ||||
|         g_assert_not_reached(); | ||||
|     } | ||||
| 
 | ||||
|     wt = extract64(wcr, 20, 1); | ||||
|     lbn = extract64(wcr, 16, 4); | ||||
| 
 | ||||
|     if (wt && !linked_bp_matches(cpu, lbn)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static bool check_watchpoints(ARMCPU *cpu) | ||||
| { | ||||
|     CPUARMState *env = &cpu->env; | ||||
|     int n; | ||||
| 
 | ||||
|     /* If watchpoints are disabled globally or we can't take debug
 | ||||
|      * exceptions here then watchpoint firings are ignored. | ||||
|      */ | ||||
|     if (extract32(env->cp15.mdscr_el1, 15, 1) == 0 | ||||
|         || !arm_generate_debug_exceptions(env)) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     for (n = 0; n < ARRAY_SIZE(env->cpu_watchpoint); n++) { | ||||
|         if (wp_matches(cpu, n)) { | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void arm_debug_excp_handler(CPUState *cs) | ||||
| { | ||||
|     /* Called by core code when a watchpoint or breakpoint fires;
 | ||||
|      * need to check which one and raise the appropriate exception. | ||||
|      */ | ||||
|     ARMCPU *cpu = ARM_CPU(cs); | ||||
|     CPUARMState *env = &cpu->env; | ||||
|     CPUWatchpoint *wp_hit = cs->watchpoint_hit; | ||||
| 
 | ||||
|     if (wp_hit) { | ||||
|         if (wp_hit->flags & BP_CPU) { | ||||
|             cs->watchpoint_hit = NULL; | ||||
|             if (check_watchpoints(cpu)) { | ||||
|                 bool wnr = (wp_hit->flags & BP_WATCHPOINT_HIT_WRITE) != 0; | ||||
|                 bool same_el = arm_debug_target_el(env) == arm_current_pl(env); | ||||
| 
 | ||||
|                 env->exception.syndrome = syn_watchpoint(same_el, 0, wnr); | ||||
|                 if (extended_addresses_enabled(env)) { | ||||
|                     env->exception.fsr = (1 << 9) | 0x22; | ||||
|                 } else { | ||||
|                     env->exception.fsr = 0x2; | ||||
|                 } | ||||
|                 env->exception.vaddress = wp_hit->hitaddr; | ||||
|                 raise_exception(env, EXCP_DATA_ABORT); | ||||
|             } else { | ||||
|                 cpu_resume_from_signal(cs, NULL); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* ??? Flag setting arithmetic is awkward because we need to do comparisons.
 | ||||
|    The only way to do that in TCG is a conditional branch, which clobbers | ||||
|    all our temporaries.  For now implement these as helper functions.  */ | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Peter Maydell
						Peter Maydell