blob: d3ba783a55f81693f453a9b45a7aa0e1e73e3bdc [file] [log] [blame]
Robert Zieba3f01cd12022-04-14 10:36:15 -06001/* SPDX-License-Identifier: GPL-2.0-only */
2#include <arch/registers.h>
3#include <arch/breakpoint.h>
4#include <console/console.h>
Robert Zieba3f01cd12022-04-14 10:36:15 -06005#include <types.h>
6
7#define DEBUG_REGISTER_COUNT 4
8
9/* Each enable field is 2 bits and starts at bit 0 */
10#define DEBUG_CTRL_ENABLE_SHIFT(index) (2 * (index))
11#define DEBUG_CTRL_ENABLE_MASK(index) (0x3 << DEBUG_CTRL_ENABLE_SHIFT(index))
12#define DEBUG_CTRL_ENABLE(index, enable) ((enable) << DEBUG_CTRL_ENABLE_SHIFT(index))
13
14/* Each breakpoint has a length and type, each is two bits and start at bit 16 */
15#define DEBUG_CTRL_LT_SHIFT(index) (4 * (index) + 16)
16#define DEBUG_CTRL_LT_MASK(index) (0xf << DEBUG_CTRL_LT_SHIFT(index))
17#define DEBUG_CTRL_LT(index, len, type) ((((len) << 2 | (type))) << DEBUG_CTRL_LT_SHIFT(index))
18
19/* Each field is one bit, starting at bit 0 */
20#define DEBUG_STATUS_BP_HIT_MASK(index) (1 << (index))
21#define DEBUG_STATUS_GET_BP_HIT(index, value) \
22 (((value) & DEBUG_STATUS_BP_HIT_MASK(index)) >> (index))
23
24/* Breakpoint lengths values */
25#define DEBUG_CTRL_LEN_1 0x0
26#define DEBUG_CTRL_LEN_2 0x1
27#define DEBUG_CTRL_LEN_8 0x2
28#define DEBUG_CTRL_LEN_4 0x3
29
30/* Breakpoint enable values */
31#define DEBUG_CTRL_ENABLE_LOCAL 0x1
32#define DEBUG_CTRL_ENABLE_GLOBAL 0x2
33
34/* eflags/rflags bit to continue execution after hitting an instruction breakpoint */
35#define FLAGS_RESUME (1 << 16)
36
37struct breakpoint {
38 bool allocated;
39 enum breakpoint_type type;
40 breakpoint_handler handler;
41};
42
43static struct breakpoint breakpoints[DEBUG_REGISTER_COUNT];
44
45static inline bool debug_write_addr_reg(int index, uintptr_t value)
46{
47 switch (index) {
48 case 0:
49 asm("mov %0, %%dr0" ::"r"(value));
50 break;
51
52 case 1:
53 asm("mov %0, %%dr1" ::"r"(value));
54 break;
55
56 case 2:
57 asm("mov %0, %%dr2" ::"r"(value));
58 break;
59
60 case 3:
61 asm("mov %0, %%dr3" ::"r"(value));
62 break;
63
64 default:
65 return false;
66 }
67
68 return true;
69}
70
71static inline uintptr_t debug_read_status(void)
72{
73 uintptr_t ret = 0;
74
75 asm("mov %%dr6, %0" : "=r"(ret));
76 return ret;
77}
78
79static inline void debug_write_status(uintptr_t value)
80{
81 asm("mov %0, %%dr6" ::"r"(value));
82}
83
84static inline uintptr_t debug_read_control(void)
85{
86 uintptr_t ret = 0;
87
88 asm("mov %%dr7, %0" : "=r"(ret));
89 return ret;
90}
91
92static inline void debug_write_control(uintptr_t value)
93{
94 asm("mov %0, %%dr7" ::"r"(value));
95}
96
97static enum breakpoint_result allocate_breakpoint(struct breakpoint_handle *out_handle,
98 enum breakpoint_type type)
99{
100 for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
101 if (breakpoints[i].allocated)
102 continue;
103
104 breakpoints[i].allocated = true;
105 breakpoints[i].handler = NULL;
106 breakpoints[i].type = type;
107 out_handle->bp = i;
108 return BREAKPOINT_RES_OK;
109 }
110
111 return BREAKPOINT_RES_NONE_AVAILABLE;
112}
113
114static enum breakpoint_result validate_handle(struct breakpoint_handle handle)
115{
116 int bp = handle.bp;
117
118 if (bp < 0 || bp >= DEBUG_REGISTER_COUNT || !breakpoints[bp].allocated)
119 return BREAKPOINT_RES_INVALID_HANDLE;
120
121 return BREAKPOINT_RES_OK;
122}
123
124enum breakpoint_result breakpoint_create_instruction(struct breakpoint_handle *out_handle,
125 void *virt_addr)
126{
127 enum breakpoint_result res =
128 allocate_breakpoint(out_handle, BREAKPOINT_TYPE_INSTRUCTION);
129
130 if (res != BREAKPOINT_RES_OK)
131 return res;
132
133 int bp = out_handle->bp;
134 if (!debug_write_addr_reg(bp, (uintptr_t)virt_addr))
135 return BREAKPOINT_RES_INVALID_HANDLE;
136
137 uintptr_t control = debug_read_control();
138 control &= ~DEBUG_CTRL_LT_MASK(bp);
139 control |= DEBUG_CTRL_LT(bp, DEBUG_CTRL_LEN_1, BREAKPOINT_TYPE_INSTRUCTION);
140 debug_write_control(control);
141 return BREAKPOINT_RES_OK;
142}
143
144enum breakpoint_result breakpoint_create_data(struct breakpoint_handle *out_handle,
145 void *virt_addr, size_t len, bool write_only)
146{
147 uintptr_t len_value = 0;
148
149 switch (len) {
150 case 1:
151 len_value = DEBUG_CTRL_LEN_1;
152 break;
153
154 case 2:
155 len_value = DEBUG_CTRL_LEN_2;
156 break;
157
158 case 4:
159 len_value = DEBUG_CTRL_LEN_4;
160 break;
161
162 case 8:
163 /* Only supported on 64-bit CPUs */
164 if (!ENV_X86_64)
165 return BREAKPOINT_RES_INVALID_LENGTH;
166 len_value = DEBUG_CTRL_LEN_8;
167 break;
168
169 default:
170 return BREAKPOINT_RES_INVALID_LENGTH;
171 }
172
173 enum breakpoint_type type =
174 write_only ? BREAKPOINT_TYPE_DATA_WRITE : BREAKPOINT_TYPE_DATA_RW;
175 enum breakpoint_result res = allocate_breakpoint(out_handle, type);
176 if (res != BREAKPOINT_RES_OK)
177 return res;
178
179 int bp = out_handle->bp;
180 if (!debug_write_addr_reg(bp, (uintptr_t)virt_addr))
181 return BREAKPOINT_RES_INVALID_HANDLE;
182
183 uintptr_t control = debug_read_control();
184 control &= ~DEBUG_CTRL_LT_MASK(bp);
185 control |= DEBUG_CTRL_LT(bp, len_value, type);
186 debug_write_control(control);
187 return BREAKPOINT_RES_OK;
188}
189
190enum breakpoint_result breakpoint_remove(struct breakpoint_handle handle)
191{
192 enum breakpoint_result res = validate_handle(handle);
193
194 if (res != BREAKPOINT_RES_OK)
195 return res;
196 breakpoint_enable(handle, false);
197
198 int bp = handle.bp;
199 breakpoints[bp].allocated = false;
200 return BREAKPOINT_RES_OK;
201}
202
203enum breakpoint_result breakpoint_enable(struct breakpoint_handle handle, bool enabled)
204{
205 enum breakpoint_result res = validate_handle(handle);
206
207 if (res != BREAKPOINT_RES_OK)
208 return res;
209
210 uintptr_t control = debug_read_control();
211 int bp = handle.bp;
212 control &= ~DEBUG_CTRL_ENABLE_MASK(bp);
213 if (enabled)
214 control |= DEBUG_CTRL_ENABLE(bp, DEBUG_CTRL_ENABLE_GLOBAL);
215 debug_write_control(control);
216 return BREAKPOINT_RES_OK;
217}
218
219enum breakpoint_result breakpoint_get_type(struct breakpoint_handle handle,
220 enum breakpoint_type *type)
221{
222 enum breakpoint_result res = validate_handle(handle);
223
224 if (res != BREAKPOINT_RES_OK)
225 return res;
226
227 *type = breakpoints[handle.bp].type;
228 return BREAKPOINT_RES_OK;
229}
230
231enum breakpoint_result breakpoint_set_handler(struct breakpoint_handle handle,
232 breakpoint_handler handler)
233{
234 enum breakpoint_result res = validate_handle(handle);
235
236 if (res != BREAKPOINT_RES_OK)
237 return res;
238
239 breakpoints[handle.bp].handler = handler;
240 return BREAKPOINT_RES_OK;
241}
242
243static enum breakpoint_result is_breakpoint_hit(struct breakpoint_handle handle, bool *out_hit)
244{
245 enum breakpoint_result res = validate_handle(handle);
246
247 if (res != BREAKPOINT_RES_OK)
248 return res;
249
250 uintptr_t status = debug_read_status();
251 *out_hit = DEBUG_STATUS_GET_BP_HIT(handle.bp, status);
252
253 return BREAKPOINT_RES_OK;
254}
255
256int breakpoint_dispatch_handler(struct eregs *info)
257{
258 bool instr_bp_hit = 0;
259
260 for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
261 struct breakpoint_handle handle = { i };
262 bool hit = false;
263 enum breakpoint_type type;
264
265 if (is_breakpoint_hit(handle, &hit) != BREAKPOINT_RES_OK || !hit)
266 continue;
267
268 if (breakpoint_get_type(handle, &type) != BREAKPOINT_RES_OK)
269 continue;
270
271 instr_bp_hit |= type == BREAKPOINT_TYPE_INSTRUCTION;
272
273 /* Call the breakpoint handler. */
274 if (breakpoints[handle.bp].handler) {
275 int ret = breakpoints[handle.bp].handler(handle, info);
276 /* A non-zero return value indicates a fatal error. */
277 if (ret)
278 return ret;
279 }
280 }
281
282 /* Clear hit breakpoints. */
283 uintptr_t status = debug_read_status();
284 for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
285 status &= ~DEBUG_STATUS_BP_HIT_MASK(i);
286 }
287 debug_write_status(status);
288
289 if (instr_bp_hit) {
290 /* Set the resume flag so the same breakpoint won't be hit immediately. */
291#if ENV_X86_64
292 info->rflags |= FLAGS_RESUME;
293#else
294 info->eflags |= FLAGS_RESUME;
295#endif
296 }
297
298 return 0;
299}