Commit f6780ce6 authored by Ravi Bangoria's avatar Ravi Bangoria Committed by Michael Ellerman

powerpc/watchpoint: Fix DAWR exception constraint

Pedro Miraglia Franco de Carvalho noticed that on p8/p9, DAR value is
inconsistent with different type of load/store. Like for byte,word
etc. load/stores, DAR is set to the address of the first byte of
overlap between watch range and real access. But for quadword load/
store it's sometime set to the address of the first byte of real
access whereas sometime set to the address of the first byte of
overlap. This issue has been fixed in p10. In p10(ISA 3.1), DAR is
always set to the address of the first byte of overlap. Commit 27985b2a
("powerpc/watchpoint: Don't ignore extraneous exceptions blindly")
wrongly assumes that DAR is set to the address of the first byte of
overlap for all load/stores on p8/p9 as well. Fix that. With the fix,
we now rely on 'ea' provided by analyse_instr(). If analyse_instr()
fails, generate event unconditionally on p8/p9, and on p10 generate
event only if DAR is within a DAWR range.

Note: 8xx is not affected.

Fixes: 27985b2a ("powerpc/watchpoint: Don't ignore extraneous exceptions blindly")
Fixes: 74c68810 ("powerpc/watchpoint: Prepare handler to handle more than one watchpoint")
Reported-by: default avatarPedro Miraglia Franco de Carvalho <pedromfc@br.ibm.com>
Signed-off-by: default avatarRavi Bangoria <ravi.bangoria@linux.ibm.com>
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20200723090813.303838-3-ravi.bangoria@linux.ibm.com
parent 3190ecbf
...@@ -498,11 +498,11 @@ static bool dar_in_user_range(unsigned long dar, struct arch_hw_breakpoint *info ...@@ -498,11 +498,11 @@ static bool dar_in_user_range(unsigned long dar, struct arch_hw_breakpoint *info
return ((info->address <= dar) && (dar - info->address < info->len)); return ((info->address <= dar) && (dar - info->address < info->len));
} }
static bool dar_user_range_overlaps(unsigned long dar, int size, static bool ea_user_range_overlaps(unsigned long ea, int size,
struct arch_hw_breakpoint *info) struct arch_hw_breakpoint *info)
{ {
return ((dar < info->address + info->len) && return ((ea < info->address + info->len) &&
(dar + size > info->address)); (ea + size > info->address));
} }
static bool dar_in_hw_range(unsigned long dar, struct arch_hw_breakpoint *info) static bool dar_in_hw_range(unsigned long dar, struct arch_hw_breakpoint *info)
...@@ -515,20 +515,22 @@ static bool dar_in_hw_range(unsigned long dar, struct arch_hw_breakpoint *info) ...@@ -515,20 +515,22 @@ static bool dar_in_hw_range(unsigned long dar, struct arch_hw_breakpoint *info)
return ((hw_start_addr <= dar) && (hw_end_addr > dar)); return ((hw_start_addr <= dar) && (hw_end_addr > dar));
} }
static bool dar_hw_range_overlaps(unsigned long dar, int size, static bool ea_hw_range_overlaps(unsigned long ea, int size,
struct arch_hw_breakpoint *info) struct arch_hw_breakpoint *info)
{ {
unsigned long hw_start_addr, hw_end_addr; unsigned long hw_start_addr, hw_end_addr;
hw_start_addr = ALIGN_DOWN(info->address, HW_BREAKPOINT_SIZE); hw_start_addr = ALIGN_DOWN(info->address, HW_BREAKPOINT_SIZE);
hw_end_addr = ALIGN(info->address + info->len, HW_BREAKPOINT_SIZE); hw_end_addr = ALIGN(info->address + info->len, HW_BREAKPOINT_SIZE);
return ((dar < hw_end_addr) && (dar + size > hw_start_addr)); return ((ea < hw_end_addr) && (ea + size > hw_start_addr));
} }
/* /*
* If hw has multiple DAWR registers, we also need to check all * If hw has multiple DAWR registers, we also need to check all
* dawrx constraint bits to confirm this is _really_ a valid event. * 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, static bool check_dawrx_constraints(struct pt_regs *regs, int type,
struct arch_hw_breakpoint *info) struct arch_hw_breakpoint *info)
...@@ -553,7 +555,8 @@ static bool check_dawrx_constraints(struct pt_regs *regs, int type, ...@@ -553,7 +555,8 @@ static bool check_dawrx_constraints(struct pt_regs *regs, int type,
* including extraneous exception. Otherwise return false. * including extraneous exception. Otherwise return false.
*/ */
static bool check_constraints(struct pt_regs *regs, struct ppc_inst instr, static bool check_constraints(struct pt_regs *regs, struct ppc_inst instr,
int type, int size, struct arch_hw_breakpoint *info) unsigned long ea, int type, int size,
struct arch_hw_breakpoint *info)
{ {
bool in_user_range = dar_in_user_range(regs->dar, info); bool in_user_range = dar_in_user_range(regs->dar, info);
bool dawrx_constraints; bool dawrx_constraints;
...@@ -569,22 +572,27 @@ static bool check_constraints(struct pt_regs *regs, struct ppc_inst instr, ...@@ -569,22 +572,27 @@ static bool check_constraints(struct pt_regs *regs, struct ppc_inst instr,
} }
if (unlikely(ppc_inst_equal(instr, ppc_inst(0)))) { if (unlikely(ppc_inst_equal(instr, ppc_inst(0)))) {
if (in_user_range) if (cpu_has_feature(CPU_FTR_ARCH_31) &&
return true; !dar_in_hw_range(regs->dar, info))
return false;
if (dar_in_hw_range(regs->dar, info)) { return true;
info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
return true;
}
return false;
} }
dawrx_constraints = check_dawrx_constraints(regs, type, info); dawrx_constraints = check_dawrx_constraints(regs, type, info);
if (dar_user_range_overlaps(regs->dar, size, info)) if (type == UNKNOWN) {
if (cpu_has_feature(CPU_FTR_ARCH_31) &&
!dar_in_hw_range(regs->dar, info))
return false;
return dawrx_constraints; return dawrx_constraints;
}
if (dar_hw_range_overlaps(regs->dar, size, info)) { if (ea_user_range_overlaps(ea, size, info))
return dawrx_constraints;
if (ea_hw_range_overlaps(ea, size, info)) {
if (dawrx_constraints) { if (dawrx_constraints) {
info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ; info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ;
return true; return true;
...@@ -594,7 +602,7 @@ static bool check_constraints(struct pt_regs *regs, struct ppc_inst instr, ...@@ -594,7 +602,7 @@ static bool check_constraints(struct pt_regs *regs, struct ppc_inst instr,
} }
static void get_instr_detail(struct pt_regs *regs, struct ppc_inst *instr, static void get_instr_detail(struct pt_regs *regs, struct ppc_inst *instr,
int *type, int *size, bool *larx_stcx) int *type, int *size, unsigned long *ea)
{ {
struct instruction_op op; struct instruction_op op;
...@@ -602,16 +610,18 @@ static void get_instr_detail(struct pt_regs *regs, struct ppc_inst *instr, ...@@ -602,16 +610,18 @@ static void get_instr_detail(struct pt_regs *regs, struct ppc_inst *instr,
return; return;
analyse_instr(&op, regs, *instr); analyse_instr(&op, regs, *instr);
/*
* Set size = 8 if analyse_instr() fails. If it's a userspace
* watchpoint(valid or extraneous), we can notify user about it.
* If it's a kernel watchpoint, instruction emulation will fail
* in stepping_handler() and watchpoint will be disabled.
*/
*type = GETTYPE(op.type); *type = GETTYPE(op.type);
*size = !(*type == UNKNOWN) ? GETSIZE(op.type) : 8; *ea = op.ea;
*larx_stcx = (*type == LARX || *type == STCX); #ifdef __powerpc64__
if (!(regs->msr & MSR_64BIT))
*ea &= 0xffffffffUL;
#endif
*size = GETSIZE(op.type);
}
static bool is_larx_stcx_instr(int type)
{
return type == LARX || type == STCX;
} }
/* /*
...@@ -678,7 +688,7 @@ int hw_breakpoint_handler(struct die_args *args) ...@@ -678,7 +688,7 @@ int hw_breakpoint_handler(struct die_args *args)
struct ppc_inst instr = ppc_inst(0); struct ppc_inst instr = ppc_inst(0);
int type = 0; int type = 0;
int size = 0; int size = 0;
bool larx_stcx = false; unsigned long ea;
/* Disable breakpoints during exception handling */ /* Disable breakpoints during exception handling */
hw_breakpoint_disable(); hw_breakpoint_disable();
...@@ -692,7 +702,7 @@ int hw_breakpoint_handler(struct die_args *args) ...@@ -692,7 +702,7 @@ int hw_breakpoint_handler(struct die_args *args)
rcu_read_lock(); rcu_read_lock();
if (!IS_ENABLED(CONFIG_PPC_8xx)) if (!IS_ENABLED(CONFIG_PPC_8xx))
get_instr_detail(regs, &instr, &type, &size, &larx_stcx); get_instr_detail(regs, &instr, &type, &size, &ea);
for (i = 0; i < nr_wp_slots(); i++) { for (i = 0; i < nr_wp_slots(); i++) {
bp[i] = __this_cpu_read(bp_per_reg[i]); bp[i] = __this_cpu_read(bp_per_reg[i]);
...@@ -702,7 +712,7 @@ int hw_breakpoint_handler(struct die_args *args) ...@@ -702,7 +712,7 @@ int hw_breakpoint_handler(struct die_args *args)
info[i] = counter_arch_bp(bp[i]); info[i] = counter_arch_bp(bp[i]);
info[i]->type &= ~HW_BRK_TYPE_EXTRANEOUS_IRQ; info[i]->type &= ~HW_BRK_TYPE_EXTRANEOUS_IRQ;
if (check_constraints(regs, instr, type, size, info[i])) { if (check_constraints(regs, instr, ea, type, size, info[i])) {
if (!IS_ENABLED(CONFIG_PPC_8xx) && if (!IS_ENABLED(CONFIG_PPC_8xx) &&
ppc_inst_equal(instr, ppc_inst(0))) { ppc_inst_equal(instr, ppc_inst(0))) {
handler_error(bp[i], info[i]); handler_error(bp[i], info[i]);
...@@ -744,7 +754,7 @@ int hw_breakpoint_handler(struct die_args *args) ...@@ -744,7 +754,7 @@ int hw_breakpoint_handler(struct die_args *args)
} }
if (!IS_ENABLED(CONFIG_PPC_8xx)) { if (!IS_ENABLED(CONFIG_PPC_8xx)) {
if (larx_stcx) { if (is_larx_stcx_instr(type)) {
for (i = 0; i < nr_wp_slots(); i++) { for (i = 0; i < nr_wp_slots(); i++) {
if (!hit[i]) if (!hit[i])
continue; continue;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment