123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- // SPDX-License-Identifier: GPL-2.0+
- #include <linux/kernel.h>
- #include <linux/uaccess.h>
- #include <linux/sched.h>
- #include <asm/hw_breakpoint.h>
- #include <asm/sstep.h>
- #include <asm/cache.h>
- static bool dar_in_user_range(unsigned long dar, struct arch_hw_breakpoint *info)
- {
- return ((info->address <= dar) && (dar - info->address < info->len));
- }
- static bool ea_user_range_overlaps(unsigned long ea, int size,
- struct arch_hw_breakpoint *info)
- {
- return ((ea < info->address + info->len) &&
- (ea + size > info->address));
- }
- static bool dar_in_hw_range(unsigned long dar, struct arch_hw_breakpoint *info)
- {
- unsigned long hw_start_addr, hw_end_addr;
- hw_start_addr = ALIGN_DOWN(info->address, HW_BREAKPOINT_SIZE);
- hw_end_addr = ALIGN(info->address + info->len, HW_BREAKPOINT_SIZE);
- return ((hw_start_addr <= dar) && (hw_end_addr > dar));
- }
- static bool ea_hw_range_overlaps(unsigned long ea, int size,
- struct arch_hw_breakpoint *info)
- {
- unsigned long hw_start_addr, hw_end_addr;
- unsigned long align_size = HW_BREAKPOINT_SIZE;
- /*
- * On p10 predecessors, quadword is handle differently then
- * other instructions.
- */
- if (!cpu_has_feature(CPU_FTR_ARCH_31) && size == 16)
- align_size = HW_BREAKPOINT_SIZE_QUADWORD;
- hw_start_addr = ALIGN_DOWN(info->address, align_size);
- hw_end_addr = ALIGN(info->address + info->len, align_size);
- return ((ea < hw_end_addr) && (ea + size > hw_start_addr));
- }
- /*
- * If hw has multiple DAWR registers, we also need to check all
- * dawrx constraint bits to confirm this is _really_ a valid event.
- * If type is UNKNOWN, but privilege level matches, consider it as
- * a positive match.
- */
- static bool check_dawrx_constraints(struct pt_regs *regs, int type,
- struct arch_hw_breakpoint *info)
- {
- if (OP_IS_LOAD(type) && !(info->type & HW_BRK_TYPE_READ))
- return false;
- /*
- * The Cache Management instructions other than dcbz never
- * cause a match. i.e. if type is CACHEOP, the instruction
- * is dcbz, and dcbz is treated as Store.
- */
- if ((OP_IS_STORE(type) || type == CACHEOP) && !(info->type & HW_BRK_TYPE_WRITE))
- return false;
- if (is_kernel_addr(regs->nip) && !(info->type & HW_BRK_TYPE_KERNEL))
- return false;
- if (user_mode(regs) && !(info->type & HW_BRK_TYPE_USER))
- return false;
- return true;
- }
- /*
- * Return true if the event is valid wrt dawr configuration,
- * including extraneous exception. Otherwise return false.
- */
- bool wp_check_constraints(struct pt_regs *regs, ppc_inst_t instr,
- unsigned long ea, int type, int size,
- struct arch_hw_breakpoint *info)
- {
- bool in_user_range = dar_in_user_range(regs->dar, info);
- bool dawrx_constraints;
- /*
- * 8xx supports only one breakpoint and thus we can
- * unconditionally return true.
- */
- if (IS_ENABLED(CONFIG_PPC_8xx)) {
- if (!in_user_range)
- info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
- return true;
- }
- if (unlikely(ppc_inst_equal(instr, ppc_inst(0)))) {
- if (cpu_has_feature(CPU_FTR_ARCH_31) &&
- !dar_in_hw_range(regs->dar, info))
- return false;
- return true;
- }
- dawrx_constraints = check_dawrx_constraints(regs, type, info);
- if (type == UNKNOWN) {
- if (cpu_has_feature(CPU_FTR_ARCH_31) &&
- !dar_in_hw_range(regs->dar, info))
- return false;
- return dawrx_constraints;
- }
- if (ea_user_range_overlaps(ea, size, info))
- return dawrx_constraints;
- if (ea_hw_range_overlaps(ea, size, info)) {
- if (dawrx_constraints) {
- info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
- return true;
- }
- }
- return false;
- }
- void wp_get_instr_detail(struct pt_regs *regs, ppc_inst_t *instr,
- int *type, int *size, unsigned long *ea)
- {
- struct instruction_op op;
- int err;
- pagefault_disable();
- err = __get_user_instr(*instr, (void __user *)regs->nip);
- pagefault_enable();
- if (err)
- return;
- analyse_instr(&op, regs, *instr);
- *type = GETTYPE(op.type);
- *ea = op.ea;
- if (!(regs->msr & MSR_64BIT))
- *ea &= 0xffffffffUL;
- *size = GETSIZE(op.type);
- if (*type == CACHEOP) {
- *size = l1_dcache_bytes();
- *ea &= ~(*size - 1);
- } else if (*type == LOAD_VMX || *type == STORE_VMX) {
- *ea &= ~(*size - 1);
- }
- }
|