Commit | Line | Data |
---|---|---|
b2441318 | 1 | /* SPDX-License-Identifier: GPL-2.0 */ |
1965aae3 PA |
2 | #ifndef _ASM_X86_DEBUGREG_H |
3 | #define _ASM_X86_DEBUGREG_H | |
21ebddd3 | 4 | |
f649e938 | 5 | #include <linux/bug.h> |
2d08a893 | 6 | #include <linux/percpu.h> |
af170c50 | 7 | #include <uapi/asm/debugreg.h> |
f649e938 | 8 | |
28b4e0d8 | 9 | DECLARE_PER_CPU(unsigned long, cpu_dr7); |
b332828c | 10 | |
9bad5658 | 11 | #ifndef CONFIG_PARAVIRT_XXL |
f649e938 PG |
12 | /* |
13 | * These special macros can be used to get or set a debugging register | |
14 | */ | |
15 | #define get_debugreg(var, register) \ | |
16 | (var) = native_get_debugreg(register) | |
17 | #define set_debugreg(value, register) \ | |
18 | native_set_debugreg(register, value) | |
19 | #endif | |
20 | ||
28eaf871 | 21 | static __always_inline unsigned long native_get_debugreg(int regno) |
f649e938 PG |
22 | { |
23 | unsigned long val = 0; /* Damn you, gcc! */ | |
24 | ||
25 | switch (regno) { | |
26 | case 0: | |
27 | asm("mov %%db0, %0" :"=r" (val)); | |
28 | break; | |
29 | case 1: | |
30 | asm("mov %%db1, %0" :"=r" (val)); | |
31 | break; | |
32 | case 2: | |
33 | asm("mov %%db2, %0" :"=r" (val)); | |
34 | break; | |
35 | case 3: | |
36 | asm("mov %%db3, %0" :"=r" (val)); | |
37 | break; | |
38 | case 6: | |
39 | asm("mov %%db6, %0" :"=r" (val)); | |
40 | break; | |
41 | case 7: | |
9d2c7203 JR |
42 | /* |
43 | * Apply __FORCE_ORDER to DR7 reads to forbid re-ordering them | |
44 | * with other code. | |
45 | * | |
46 | * This is needed because a DR7 access can cause a #VC exception | |
47 | * when running under SEV-ES. Taking a #VC exception is not a | |
48 | * safe thing to do just anywhere in the entry code and | |
49 | * re-ordering might place the access into an unsafe location. | |
50 | * | |
51 | * This happened in the NMI handler, where the DR7 read was | |
52 | * re-ordered to happen before the call to sev_es_ist_enter(), | |
53 | * causing stack recursion. | |
54 | */ | |
55 | asm volatile("mov %%db7, %0" : "=r" (val) : __FORCE_ORDER); | |
f649e938 PG |
56 | break; |
57 | default: | |
58 | BUG(); | |
59 | } | |
60 | return val; | |
61 | } | |
62 | ||
28eaf871 | 63 | static __always_inline void native_set_debugreg(int regno, unsigned long value) |
f649e938 PG |
64 | { |
65 | switch (regno) { | |
66 | case 0: | |
67 | asm("mov %0, %%db0" ::"r" (value)); | |
68 | break; | |
69 | case 1: | |
70 | asm("mov %0, %%db1" ::"r" (value)); | |
71 | break; | |
72 | case 2: | |
73 | asm("mov %0, %%db2" ::"r" (value)); | |
74 | break; | |
75 | case 3: | |
76 | asm("mov %0, %%db3" ::"r" (value)); | |
77 | break; | |
78 | case 6: | |
79 | asm("mov %0, %%db6" ::"r" (value)); | |
80 | break; | |
81 | case 7: | |
9d2c7203 JR |
82 | /* |
83 | * Apply __FORCE_ORDER to DR7 writes to forbid re-ordering them | |
84 | * with other code. | |
85 | * | |
86 | * While is didn't happen with a DR7 write (see the DR7 read | |
87 | * comment above which explains where it happened), add the | |
88 | * __FORCE_ORDER here too to avoid similar problems in the | |
89 | * future. | |
90 | */ | |
91 | asm volatile("mov %0, %%db7" ::"r" (value), __FORCE_ORDER); | |
f649e938 PG |
92 | break; |
93 | default: | |
94 | BUG(); | |
95 | } | |
96 | } | |
97 | ||
b332828c P |
98 | static inline void hw_breakpoint_disable(void) |
99 | { | |
100 | /* Zero the control register for HW Breakpoint */ | |
101 | set_debugreg(0UL, 7); | |
102 | ||
103 | /* Zero-out the individual HW breakpoint address registers */ | |
104 | set_debugreg(0UL, 0); | |
105 | set_debugreg(0UL, 1); | |
106 | set_debugreg(0UL, 2); | |
107 | set_debugreg(0UL, 3); | |
108 | } | |
109 | ||
28eaf871 | 110 | static __always_inline bool hw_breakpoint_active(void) |
59d8eb53 | 111 | { |
0a3aee0d | 112 | return __this_cpu_read(cpu_dr7) & DR_GLOBAL_ENABLE_MASK; |
59d8eb53 FW |
113 | } |
114 | ||
24f1e32c | 115 | extern void hw_breakpoint_restore(void); |
24f1e32c | 116 | |
e1de11d4 PZ |
117 | static __always_inline unsigned long local_db_save(void) |
118 | { | |
119 | unsigned long dr7; | |
120 | ||
84b6a349 PZ |
121 | if (static_cpu_has(X86_FEATURE_HYPERVISOR) && !hw_breakpoint_active()) |
122 | return 0; | |
123 | ||
e1de11d4 PZ |
124 | get_debugreg(dr7, 7); |
125 | dr7 &= ~0x400; /* architecturally set bit */ | |
126 | if (dr7) | |
127 | set_debugreg(0, 7); | |
128 | /* | |
129 | * Ensure the compiler doesn't lower the above statements into | |
130 | * the critical section; disabling breakpoints late would not | |
131 | * be good. | |
132 | */ | |
133 | barrier(); | |
134 | ||
135 | return dr7; | |
136 | } | |
137 | ||
138 | static __always_inline void local_db_restore(unsigned long dr7) | |
139 | { | |
140 | /* | |
141 | * Ensure the compiler doesn't raise this statement into | |
142 | * the critical section; enabling breakpoints early would | |
143 | * not be good. | |
144 | */ | |
145 | barrier(); | |
146 | if (dr7) | |
147 | set_debugreg(dr7, 7); | |
148 | } | |
149 | ||
d6d55f0b JS |
150 | #ifdef CONFIG_CPU_SUP_AMD |
151 | extern void set_dr_addr_mask(unsigned long mask, int dr); | |
152 | #else | |
153 | static inline void set_dr_addr_mask(unsigned long mask, int dr) { } | |
154 | #endif | |
42181186 | 155 | |
1965aae3 | 156 | #endif /* _ASM_X86_DEBUGREG_H */ |