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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
//! WDT_A watchdog timer — the countdown that resets the chip unless software
//! keeps proving it is alive.
//!
//! # Why every binary must deal with this first
//!
//! The watchdog powers up **running**: after any reset, `WDTCTL` = `0x6904`
//! (SMCLK source, /32768 divider). At the ~1 MHz default SMCLK that is a
//! **~32 ms** fuse — burn through it without either feeding the counter or
//! stopping it, and the WDT issues a PUC and the chip reboots, forever, in a
//! loop. 32 ms is not much: `msp430-rt`'s startup (`.data` copy, `.bss` zero)
//! plus `Peripherals::take()` (which enters a critical section) can flirt with
//! it. The front door is [`crate::init`], which fuses the two so the ordering
//! is guaranteed by construction — make it the first statement in `main`:
//!
//! ```ignore
//! #[entry]
//! fn main() -> ! {
//! let p = hal::init(hal::watchdog::WdtMode::Hold).unwrap();
//! // ...
//! }
//! ```
//!
//! ([`disable`] remains the underlying primitive: a free function callable
//! before `Peripherals::take()`, for code that doesn't go through `init`.)
//!
//! # The password mechanism (and why the PAC field API is a trap)
//!
//! `WDTCTL` is key-protected so runaway code can't silently reconfigure it.
//! The upper byte is asymmetric:
//!
//! - every **write** must carry `0x5A` in bits 15:8, or the violation itself
//! triggers an immediate PUC (this is also the idiomatic MSP430 *software
//! reset* — see [`force_reset`]);
//! - every **read** returns `0x69` there, so a read-modify-write that echoes
//! the read-back high byte *also* resets the chip.
//!
//! The SVD (and therefore the PAC) models only the low-byte fields — there is
//! no `WDTPW` field, and the PAC's declared reset value is 0. So the obvious
//! PAC idioms are all landmines: `wdtctl().write(|w| w.wdthold().set_bit())`
//! writes password `0x00` → PUC; `wdtctl().modify(...)` writes back the `0x69`
//! it read → PUC. This module therefore does only **whole-register writes**
//! with the password composed in (`w.bits(0x5A00 | cfg)`), and masks the high
//! byte off anything it reads. That's the esoteric knowledge, captured once.
//!
//! # Re-arming it
//!
//! A held watchdog costs nothing, but production firmware usually wants it
//! back on: [`Watchdog::start`] picks a clock source and divider and starts
//! the countdown, and the main loop must then call [`Watchdog::feed`] more
//! often than the timeout or the chip resets. Timeout = 2^N cycles of the
//! chosen clock:
//!
//! | [`Interval`] | ACLK = 32 768 Hz | SMCLK = 8 MHz |
//! |---------------|------------------|---------------|
//! | `Cycles2G` | 18.2 h | 268 s |
//! | `Cycles128M` | 68 min | 16.8 s |
//! | `Cycles8192K` | 256 s | 1.05 s |
//! | `Cycles512K` | 16 s | 65.5 ms |
//! | `Cycles32K` | **1 s** | 4.1 ms |
//! | `Cycles8192` | 250 ms | 1.02 ms |
//! | `Cycles512` | 15.6 ms | 64 µs |
//! | `Cycles64` | ~2 ms | 8 µs |
//!
//! Feeding sources: SMCLK stops in LPM3+, which *holds* (not resets) the WDT
//! count — a watchdog meant to guard a sleepy application should run from
//! ACLK or VLOCLK so it keeps counting through sleep.
//!
//! To guard **boot itself** (everything between reset and the main loop),
//! skip the disable and arm a sane timeout up front with
//! `hal::init(WdtMode::Arm { source, interval })` — see [`WdtMode::Arm`] for
//! the boot-clock caveat.
//!
//! ```ignore
//! let mut wdt = Watchdog::new(p.watchdog_timer);
//! wdt.start(ClockSource::Aclk, Interval::Cycles32K); // 1 s @ 32.768 kHz
//! loop {
//! do_work();
//! wdt.feed(); // must land within every 1 s window
//! }
//! ```
//!
//! # Interval-timer mode
//!
//! WDT_A has a second personality: `WDTTMSEL = 1` turns the reset into a plain
//! periodic interrupt — the counter still counts 2^N cycles and still sets
//! `WDTIFG` on expiry, but instead of a PUC the chip gets the `WDT` vector
//! (once `SFRIE1.WDTIE` and GIE are set). [`Watchdog::start_interval`] arms
//! it; the same [`Interval`] table above gives the tick period. It's a
//! serviceable coarse periodic tick when the Timer_A/B blocks are busy doing
//! real work — but note it *replaces* the guard role: one WDT_A, one mode.
//!
//! ## The shared-`SFRIE1` rule
//!
//! `WDTIE` does not live in a watchdog register — it lives in `SFRIE1`, the
//! chip-global special-function interrupt-enable register, next to unrelated
//! bits like `NMIIE` and `VMAIE` that other code may someday own. So no
//! driver *owns* `pac::Sfr`; the convention (this module is its first user)
//! is: touch `SFRIE1`/`SFRIFG1` only via `pac::Sfr::steal()`, only with
//! bit-field `modify`, and only inside `critical_section::with` from thread
//! mode. The critical section closes the read-modify-write race between two
//! thread-mode users and against ISRs; ISRs themselves must not modify
//! `SFRIE1` under this convention, so the CS suffices.
use cratepac;
/// The write key for `WDTCTL` bits 15:8. Reads return `0x6900` instead; any
/// write without this key causes a PUC.
const PASSWORD: u16 = 0x5A00;
/// `WDTHOLD` (bit 7): 1 = watchdog stopped.
const HOLD: u16 = 0x0080;
/// `WDTTMSEL` (bit 4): 1 = interval-timer mode (expiry sets `WDTIFG` and
/// fires the `WDT` vector instead of resetting the chip).
const TMSEL: u16 = 0x0010;
/// `WDTCNTCL` (bit 3): writing 1 zeroes the count (self-clearing).
const CNTCL: u16 = 0x0008;
/// Boot-time watchdog policy for [`crate::init`].
///
/// `WDTCTL` cannot be partially updated — every access is a whole-register,
/// password-keyed write — so there are exactly three things `init` can do to
/// the watchdog: write hold, write a fresh configuration, or not write at
/// all. One variant per possibility.
/// Clock feeding the watchdog counter (`WDTSSEL`).
///
/// The discriminants are the raw 2-bit field values (bits 6:5 of `WDTCTL`);
/// `0b11` is reserved on this part and deliberately unrepresentable here.
/// Countdown length in clock cycles, 2^N (`WDTIS`).
///
/// The discriminants are the raw 3-bit field values (bits 2:0 of `WDTCTL`).
/// See the module docs for the resulting timeouts at common clock rates.
/// Whole-register `WDTCTL` write with the password composed in — the only
/// safe way to write it (see module docs). `low` is the configuration byte;
/// anything above bit 7 is discarded so a smuggled bad key is impossible.
///
/// Uses `steal()` rather than an owned peripheral: sound because a single
/// whole-register volatile store has no read-modify-write window, and every
/// caller either runs before `Peripherals::take()` ([`disable`]/[`arm`] via
/// [`crate::init`]) or holds the peripheral exclusively ([`Watchdog`]).
/// The low-byte watchdog-mode configuration for `source`/`interval`
/// (`WDTSSEL` bits 6:5, `WDTIS` bits 2:0).
/// Stop the watchdog. **Call this first in `main`, before
/// `Peripherals::take()`** — or let [`crate::init`] do it for you.
///
/// This is a free function rather than a method on [`Watchdog`] precisely so
/// it needs no `Peripherals` — the whole point is to run before `take()`'s
/// critical section, while the ~32 ms power-up fuse is still burning. It
/// compiles to a single 16-bit store (no read-modify-write), so there is
/// nothing for an interrupt to race with, and at this point in boot nothing
/// else can be touching the register anyway.
/// Arm the watchdog with a fresh `source`/`interval` and a zeroed count, in
/// one write. Backs [`crate::init`]'s [`WdtMode::Arm`]; the owned-peripheral
/// path is [`Watchdog::start`], which does the identical write.
// Sole caller is `init`, which is gated on `critical-section` — without that
// feature this is (correctly) unreachable, not a bug.
pub
/// Reboot the chip *now* via a deliberate watchdog password violation.
///
/// Writing `WDTCTL` without the `0x5A` key triggers an immediate PUC — the
/// idiomatic MSP430 software reset (there is no ARM-style `SYSRESETREQ`).
/// After the reboot, `SYSRSTIV` reads `0x18` (WDT password violation) so
/// startup code can tell this reset apart from a power-on or a genuine
/// watchdog timeout (`0x16`) — see [`crate::sys::ResetReasons`] for the
/// decoded view.
!
/// The watchdog in its guard role: owns the PAC peripheral so exactly one
/// place in the program can arm, feed, or stop it.
///
/// Construction does not touch the hardware — after the boot-time
/// [`disable`], the watchdog stays held until [`start`](Watchdog::start).
/// Has an interval expired (`SFRIFG1.WDTIFG` latched)? For polling the
/// interval tick without enabling the interrupt at all.
/// ISR-side: clear `SFRIFG1.WDTIFG`.
///
/// Servicing the dedicated `WDT` vector already auto-resets `WDTIFG` in
/// hardware (SLAU367), so a `#[interrupt] fn WDT()` handler normally has no
/// flag work at all — this exists for the *polling* consumer pairing with
/// [`interval_pending`], and as belt-and-braces if a handler wants the flag
/// provably down. Bit-field `modify` under `critical_section::with`, per the
/// shared-`SFRIE1`/`SFRIFG1` rule in the module docs.