Commit | Line | Data |
---|---|---|
edc8dd99 RB |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | #include <linux/kernel.h> | |
3 | #include <linux/uaccess.h> | |
4 | #include <linux/sched.h> | |
5 | #include <asm/hw_breakpoint.h> | |
6 | #include <asm/sstep.h> | |
7 | #include <asm/cache.h> | |
8 | ||
9 | static bool dar_in_user_range(unsigned long dar, struct arch_hw_breakpoint *info) | |
10 | { | |
11 | return ((info->address <= dar) && (dar - info->address < info->len)); | |
12 | } | |
13 | ||
14 | static bool ea_user_range_overlaps(unsigned long ea, int size, | |
15 | struct arch_hw_breakpoint *info) | |
16 | { | |
17 | return ((ea < info->address + info->len) && | |
18 | (ea + size > info->address)); | |
19 | } | |
20 | ||
21 | static bool dar_in_hw_range(unsigned long dar, struct arch_hw_breakpoint *info) | |
22 | { | |
23 | unsigned long hw_start_addr, hw_end_addr; | |
24 | ||
25 | hw_start_addr = ALIGN_DOWN(info->address, HW_BREAKPOINT_SIZE); | |
26 | hw_end_addr = ALIGN(info->address + info->len, HW_BREAKPOINT_SIZE); | |
27 | ||
28 | return ((hw_start_addr <= dar) && (hw_end_addr > dar)); | |
29 | } | |
30 | ||
31 | static bool ea_hw_range_overlaps(unsigned long ea, int size, | |
32 | struct arch_hw_breakpoint *info) | |
33 | { | |
34 | unsigned long hw_start_addr, hw_end_addr; | |
35 | unsigned long align_size = HW_BREAKPOINT_SIZE; | |
36 | ||
37 | /* | |
38 | * On p10 predecessors, quadword is handle differently then | |
39 | * other instructions. | |
40 | */ | |
41 | if (!cpu_has_feature(CPU_FTR_ARCH_31) && size == 16) | |
42 | align_size = HW_BREAKPOINT_SIZE_QUADWORD; | |
43 | ||
44 | hw_start_addr = ALIGN_DOWN(info->address, align_size); | |
45 | hw_end_addr = ALIGN(info->address + info->len, align_size); | |
46 | ||
47 | return ((ea < hw_end_addr) && (ea + size > hw_start_addr)); | |
48 | } | |
49 | ||
50 | /* | |
51 | * If hw has multiple DAWR registers, we also need to check all | |
52 | * dawrx constraint bits to confirm this is _really_ a valid event. | |
53 | * If type is UNKNOWN, but privilege level matches, consider it as | |
54 | * a positive match. | |
55 | */ | |
56 | static bool check_dawrx_constraints(struct pt_regs *regs, int type, | |
57 | struct arch_hw_breakpoint *info) | |
58 | { | |
59 | if (OP_IS_LOAD(type) && !(info->type & HW_BRK_TYPE_READ)) | |
60 | return false; | |
61 | ||
62 | /* | |
63 | * The Cache Management instructions other than dcbz never | |
64 | * cause a match. i.e. if type is CACHEOP, the instruction | |
65 | * is dcbz, and dcbz is treated as Store. | |
66 | */ | |
67 | if ((OP_IS_STORE(type) || type == CACHEOP) && !(info->type & HW_BRK_TYPE_WRITE)) | |
68 | return false; | |
69 | ||
70 | if (is_kernel_addr(regs->nip) && !(info->type & HW_BRK_TYPE_KERNEL)) | |
71 | return false; | |
72 | ||
73 | if (user_mode(regs) && !(info->type & HW_BRK_TYPE_USER)) | |
74 | return false; | |
75 | ||
76 | return true; | |
77 | } | |
78 | ||
79 | /* | |
80 | * Return true if the event is valid wrt dawr configuration, | |
81 | * including extraneous exception. Otherwise return false. | |
82 | */ | |
c545b9f0 | 83 | bool wp_check_constraints(struct pt_regs *regs, ppc_inst_t instr, |
edc8dd99 RB |
84 | unsigned long ea, int type, int size, |
85 | struct arch_hw_breakpoint *info) | |
86 | { | |
87 | bool in_user_range = dar_in_user_range(regs->dar, info); | |
88 | bool dawrx_constraints; | |
89 | ||
90 | /* | |
91 | * 8xx supports only one breakpoint and thus we can | |
92 | * unconditionally return true. | |
93 | */ | |
94 | if (IS_ENABLED(CONFIG_PPC_8xx)) { | |
95 | if (!in_user_range) | |
96 | info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ; | |
97 | return true; | |
98 | } | |
99 | ||
100 | if (unlikely(ppc_inst_equal(instr, ppc_inst(0)))) { | |
101 | if (cpu_has_feature(CPU_FTR_ARCH_31) && | |
102 | !dar_in_hw_range(regs->dar, info)) | |
103 | return false; | |
104 | ||
105 | return true; | |
106 | } | |
107 | ||
108 | dawrx_constraints = check_dawrx_constraints(regs, type, info); | |
109 | ||
110 | if (type == UNKNOWN) { | |
111 | if (cpu_has_feature(CPU_FTR_ARCH_31) && | |
112 | !dar_in_hw_range(regs->dar, info)) | |
113 | return false; | |
114 | ||
115 | return dawrx_constraints; | |
116 | } | |
117 | ||
118 | if (ea_user_range_overlaps(ea, size, info)) | |
119 | return dawrx_constraints; | |
120 | ||
121 | if (ea_hw_range_overlaps(ea, size, info)) { | |
122 | if (dawrx_constraints) { | |
123 | info->type |= HW_BRK_TYPE_EXTRANEOUS_IRQ; | |
124 | return true; | |
125 | } | |
126 | } | |
127 | return false; | |
128 | } | |
129 | ||
c545b9f0 | 130 | void wp_get_instr_detail(struct pt_regs *regs, ppc_inst_t *instr, |
edc8dd99 RB |
131 | int *type, int *size, unsigned long *ea) |
132 | { | |
133 | struct instruction_op op; | |
3241f260 | 134 | int err; |
edc8dd99 | 135 | |
3241f260 BG |
136 | pagefault_disable(); |
137 | err = __get_user_instr(*instr, (void __user *)regs->nip); | |
138 | pagefault_enable(); | |
139 | ||
140 | if (err) | |
edc8dd99 RB |
141 | return; |
142 | ||
143 | analyse_instr(&op, regs, *instr); | |
144 | *type = GETTYPE(op.type); | |
145 | *ea = op.ea; | |
a61ec782 | 146 | |
edc8dd99 RB |
147 | if (!(regs->msr & MSR_64BIT)) |
148 | *ea &= 0xffffffffUL; | |
a61ec782 | 149 | |
edc8dd99 RB |
150 | |
151 | *size = GETSIZE(op.type); | |
152 | if (*type == CACHEOP) { | |
a61ec782 | 153 | *size = l1_dcache_bytes(); |
edc8dd99 RB |
154 | *ea &= ~(*size - 1); |
155 | } else if (*type == LOAD_VMX || *type == STORE_VMX) { | |
156 | *ea &= ~(*size - 1); | |
157 | } | |
158 | } |