1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
//! Debugger utilities
/// Checks if a debugger is connected.
pub fn debugger_connected() -> bool {
cfg_if::cfg_if! {
if #[cfg(xtensa)] {
xtensa_lx::is_debugger_attached()
} else if #[cfg(all(riscv, soc_has_assist_debug))] {
crate::peripherals::ASSIST_DEBUG::regs()
.cpu(0)
.debug_mode()
.read()
.debug_module_active()
.bit_is_set()
} else {
false
}
}
}
/// Set a word-sized data breakpoint at the given address.
/// No breakpoint will be set when a debugger is currently attached if
/// the `stack_guard_monitoring_with_debugger_connected` option is false.
///
/// Breakpoint 0 is used.
///
/// # Safety
/// The address must be word aligned.
pub unsafe fn set_stack_watchpoint(addr: usize) {
assert!(addr.is_multiple_of(4));
if cfg!(stack_guard_monitoring_with_debugger_connected)
|| !crate::debugger::debugger_connected()
{
cfg_if::cfg_if! {
if #[cfg(xtensa)] {
let addr = addr & !0b11;
let dbreakc = 0b1111100 | (1 << 31); // bit 31 = STORE
unsafe {
core::arch::asm!(
"
wsr {addr}, 144 // 144 = dbreaka0
wsr {dbreakc}, 160 // 160 = dbreakc0
",
addr = in(reg) addr,
dbreakc = in(reg) dbreakc,
);
}
} else {
unsafe { set_watchpoint(0, addr, 4); }
}
}
}
}
#[cfg(riscv)]
pub(crate) static DEBUGGER_LOCK: esp_sync::RawMutex = esp_sync::RawMutex::new();
#[cfg(riscv)]
const NAPOT_MATCH: u8 = 1;
#[cfg(riscv)]
bitfield::bitfield! {
/// Only match type (0x2) triggers are supported.
#[derive(Clone, Copy, Default)]
pub(crate) struct Tdata1(u32);
/// Set this for configuring the selected trigger to fire right before a load operation with matching
/// data address is executed by the CPU.
pub bool, load, set_load: 0;
/// Set this for configuring the selected trigger to fire right before a store operation with matching
/// data address is executed by the CPU.
pub bool, store, set_store: 1;
/// Set this for configuring the selected trigger to fire right before an instruction with matching
/// virtual address is executed by the CPU.
pub bool, execute, set_execute: 2;
/// Set this for enabling selected trigger to operate in user mode.
pub bool, u, set_u: 3;
/// Set this for enabling selected trigger to operate in machine mode.
pub bool, m, set_m: 6;
/// Configures the selected trigger to perform one of the available matching operations on a
/// data/instruction address. Valid options are:
/// 0x0: exact byte match, i.e. address corresponding to one of the bytes in an access must match
/// the value of maddress exactly.
/// 0x1: NAPOT match, i.e. at least one of the bytes of an access must lie in the NAPOT region
/// specified in maddress.
/// Note: Writing a larger value will clip it to the largest possible value 0x1.
pub u8, _match, set_match: 10, 7;
/// Configures the selected trigger to perform one of the available actions when firing. Valid
/// options are:
/// 0x0: cause breakpoint exception.
/// 0x1: enter debug mode (only valid when dmode = 1)
/// Note: Writing an invalid value will set this to the default value 0x0.
pub u8, action, set_action: 15, 12;
/// This is found to be 1 if the selected trigger had fired previously. This bit is to be cleared manually.
pub bool, hit, set_hit: 20;
/// 0: Both Debug and M mode can write the tdata1 and tdata2 registers at the selected tselect.
/// 1: Only Debug Mode can write the tdata1 and tdata2 registers at the selected tselect. Writes from
/// other modes are ignored.
/// Note: Only writable from debug mode.
pub bool, dmode, set_dmode: 27;
}
#[cfg(riscv)]
bitfield::bitfield! {
/// Only match type (0x2) triggers are supported.
#[derive(Clone, Copy, Default)]
pub(crate) struct Tcontrol(u32);
/// Current M mode trigger enable bit
pub bool, mte, set_mte: 3;
/// Previous M mode trigger enable bit
pub bool, mpte, set_mpte: 7;
}
#[cfg(riscv)]
pub(crate) struct WatchPoint {
tdata1: u32,
tdata2: u32,
}
/// Clear the watchpoint
#[cfg(riscv)]
pub(crate) unsafe fn clear_watchpoint(id: u8) -> WatchPoint {
assert!(id < 4);
// tdata1 is a WARL(write any read legal) register. We can just write 0 to it.
let mut tdata1 = 0;
let mut tdata2 = 0;
DEBUGGER_LOCK.lock(|| unsafe {
core::arch::asm!(
"
csrw 0x7a0, {id} // tselect
csrrw {tdata1}, 0x7a1, {tdata2} // tdata1
csrr {tdata2}, 0x7a2 // tdata2
", id = in(reg) id,
tdata1 = inout(reg) tdata1,
tdata2 = out(reg) tdata2,
);
});
WatchPoint { tdata1, tdata2 }
}
/// Clear the watchpoint
#[cfg(riscv)]
pub(crate) unsafe fn restore_watchpoint(id: u8, watchpoint: WatchPoint) {
DEBUGGER_LOCK.lock(|| unsafe {
core::arch::asm!(
"
csrw 0x7a0, {id} // tselect
csrw 0x7a1, {tdata1} // tdata1
csrw 0x7a2, {tdata2} // tdata2
", id = in(reg) id,
tdata1 = in(reg) watchpoint.tdata1,
tdata2 = in(reg) watchpoint.tdata2,
);
});
}
/// Clear the watchpoint
#[cfg(all(riscv, feature = "exception-handler"))]
pub(crate) unsafe fn watchpoint_hit(id: u8) -> bool {
assert!(id < 4);
let mut tdata = Tdata1::default();
DEBUGGER_LOCK.lock(|| unsafe {
core::arch::asm!(
"
csrw 0x7a0, {id} // tselect
csrr {tdata}, 0x7a1 // tdata1
", id = in(reg) id,
tdata = out(reg) tdata.0,
);
});
tdata.hit()
}
/// Set watchpoint and enable triggers.
#[cfg(riscv)]
pub(crate) unsafe fn set_watchpoint(id: u8, addr: usize, len: usize) {
assert!(id < 4);
assert!(len.is_power_of_two());
assert!(addr.is_multiple_of(len));
let z = len.trailing_zeros();
let mask = {
let mut mask: usize = 0;
for i in 0..z {
mask |= 1 << i;
}
mask
};
let napot_encoding = { mask & !(1 << (z - 1)) };
let addr = (addr & !mask) | napot_encoding;
let mut tdata = Tdata1::default();
tdata.set_m(true);
tdata.set_store(true);
tdata.set_match(NAPOT_MATCH);
let tdata: u32 = tdata.0;
let mut tcontrol = Tcontrol::default();
tcontrol.set_mte(true);
let tcontrol: u32 = tcontrol.0;
DEBUGGER_LOCK.lock(|| unsafe {
core::arch::asm!(
"
csrw 0x7a0, {id} // tselect
csrw 0x7a5, {tcontrol} // tcontrol
csrw 0x7a1, {tdata} // tdata1
csrw 0x7a2, {addr} // tdata2
", id = in(reg) id,
addr = in(reg) addr,
tdata = in(reg) tdata,
tcontrol = in(reg) tcontrol,
);
});
}