rigless 0.0.4

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model, and unit of virtual time in Rust; needs the rigless-sidecar package from rigless-labs/qemu
Documentation
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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: Apache-2.0
//! Reset and clock control of RM0090 rev 22 chapter 6, written against the `rcc` layout.
//!
//! Chapter 6 is the RCC of the STM32F42xxx and STM32F43xxx, which is this SoC. Chapter 7 is the same
//! peripheral for the STM32F405/407/415/417: its sections are numbered alike and read almost the same, and
//! it is the wrong part. Every citation here is chapter 6.
//!
//! | Register or section | Behaviour | Source | Test |
//! |---|---|---|---|
//! | Reset state | HSI on and ready, everything else off, HSI the system clock | 6.2.6, 6.3.1 | `reset_runs_on_the_ready_hsi` |
//! | `CR` oscillators | Each enable raises its ready flag after that oscillator's startup, and clearing the enable drops it | 6.3.1 | `an_oscillator_becomes_ready_after_its_startup`, `clearing_an_enable_drops_its_ready_flag` |
//! | `CR` `HSEBYP` | Writable only while the HSE is off | 6.3.1 | `hsebyp_is_writable_only_while_the_hse_is_off` |
//! | `CR` in-use rule | The enable of an oscillator that clocks the system, directly or through the PLL, cannot be cleared | 6.3.1, 6.2.6 | `the_running_system_clock_cannot_be_switched_off` |
//! | `CR` ready flags | Hardware's: a write cannot set one, so a firmware cannot report an oscillator locked that never started | 6.3.1 | `software_cannot_set_a_flag_hardware_owns` |
//! | `CR` startup, cancelled | An enable cleared before its startup fires leaves no stale event behind | 6.3.1 | `a_startup_cancelled_before_it_fires_leaves_nothing_behind` |
//! | `PLLCFGR` | `f(VCO) = f(input) x PLLN / PLLM`, `f(PLL) = f(VCO) / PLLP`, `f(48 MHz) = f(VCO) / PLLQ` | 6.3.2 | `the_pll_multiplies_and_divides_as_the_formula_says`, `a_factor_of_zero_yields_no_clock_rather_than_a_division_by_zero` |
//! | `PLLCFGR` while locked | A write is ignored while `PLLON` is set, and `PLLM` and `PLLSRC`, which the PLLI2S shares, while either is on | 6.2.3, 6.3.2 | `a_locked_pll_ignores_its_configuration`, `the_shared_pll_input_is_frozen_by_either_pll` |
//! | `CR` in-use rule through the PLL | Neither the PLL nor the oscillator feeding it can be stopped while the PLL clocks the system, and a PLL whose oscillator does stop is no longer ready | 6.3.1, 6.2.6 | `the_pll_and_its_source_cannot_be_stopped_while_they_clock_the_system`, `a_pll_whose_oscillator_stops_is_no_longer_ready` |
//! | `CFGR` `SW`, `SWS` | The switch happens only when the target is ready, and is deferred until it becomes ready | 6.2.6, 6.3.3 | `the_switch_waits_for_the_target_to_be_ready` |
//! | `CFGR` `HPRE` | The AHB prescaler, whose encoding has no divide by 32 | 6.3.3 | `the_ahb_prescaler_skips_thirty_two` |
//! | `CFGR` `PPRE1`, `PPRE2` | The two APB prescalers | 6.3.3 | `the_apb_prescalers_divide_the_ahb_clock` |
//! | `CFGR` `RTCPRE` | The HSE division for the RTC; 0 and 1 are no clock | 6.3.3 | `the_rtc_clock_is_the_hse_divided_or_nothing`, `rtcpre_of_zero_is_no_clock` |
//! | `CFGR` `MCO1`, `MCO2` | Both outputs, each of the four sources of either, and their prescalers | 6.3.3 | `the_clock_outputs_carry_their_source_divided`, `every_clock_output_source_is_what_the_encoding_says` |
//! | Timer clocks | `TIMPRE` clear: `PCLKx` at a prescaler of 1, else twice it. `TIMPRE` set: `HCLK` at 1, 2, or 4, else four times `PCLKx` | 6.2, 6.3.25 | `the_timer_clock_follows_the_doubling_rule`, `timpre_gives_the_timers_hclk` |
//! | `CIR` | A ready flag rises only when that source's `RDYIE` already stands, which is then what raises the interrupt, and `RDYC` clears it; each source has its own triplet | 6.3.4 | `a_ready_flag_rises_only_for_a_source_whose_interrupt_is_enabled`, `a_ready_event_sets_its_flag_and_an_enabled_one_interrupts`, `the_clear_bits_clear_and_read_zero`, `every_source_has_its_own_flag_enable_and_clear` |
//! | `CIR` flags | Bits 7:0 are hardware's; a write cannot raise one, bit 6 included, which ST's file omits | 6.3.4 | `software_cannot_set_a_flag_hardware_owns` |
//! | `BDCR` LSE | Its ready flag, and a bypass writable only while the LSE is off | 6.3.20 | `the_lse_becomes_ready_and_clocks_the_rtc`, `lsebyp_is_writable_only_while_the_lse_is_off` |
//! | `BDCR` RTC | `RTCEN` gates `RTCCLK`, and `RTCSEL` cannot be changed once chosen unless the domain is reset | 6.2.8, 6.3.20 | `the_rtc_source_is_chosen_once_and_the_enable_gates_it` |
//! | `BDCR` `BDRST` | Set and cleared by software: it stands until the firmware clears it and holds the domain at its reset value while it does | 6.3.20 | `the_backup_reset_holds_the_domain_until_it_is_cleared` |
//! | `CSR` | The LSI with its ready flag, and the reset flags, which only `RMVF` clears and a plain write leaves alone | 6.3.21 | `the_lsi_becomes_ready`, `rmvf_clears_the_reset_flags`, `a_plain_write_to_csr_keeps_the_reset_flags` |
//! | `DCKCFGR` | `TIMPRE`, and the SAI1-A source and divisors, which [`Rcc::sai_clock`] answers. `SAI1BSRC` is stored: no model consumes it | 6.3.25 | `the_sai_clock_follows_its_source_and_divisor` |
//! | `PLLI2SCFGR`, `PLLSAICFGR` | `N` on the VCO, `R` for the PLLI2S output that MCO2 and the I2S see, `Q` for the SAI; ignored while the PLL they configure is on | 6.2.3, 6.3.23, 6.3.24 | `the_audio_plls_multiply_and_divide`, `an_audio_pll_with_a_factor_of_zero_gives_no_clock`, `a_locked_audio_pll_ignores_its_configuration` |
//! | Clock security | With `CSSON` set and the HSE running, a failure disables it, sets `CSSF`, and, only where the HSE clocked the system, forces the HSI and stops the PLL and PLLI2S | 6.2.7 | `a_clock_failure_forces_the_hsi_and_flags_it`, `a_clock_failure_leaves_a_system_it_does_not_clock_alone`, `a_clock_failure_starts_the_oscillator_it_switches_to`, `a_detector_that_is_off_or_has_nothing_to_watch_reports_nothing` |
//! | `AHBxENR`, `APBxENR`, `AHBxRSTR`, `APBxRSTR` | Stored and readable, and nothing acts on them | 6.3.5 to 6.3.14 | `an_enable_bit_is_stored`, `a_reset_bit_is_stored` |
//! | `AHBxLPENR`, `APBxLPENR` | Stored; the low-power modes they qualify are not modeled, and all three registers come out of reset with every bit set, which ST's file gets wrong | 6.3.15 to 6.3.19 | `a_low_power_enable_is_stored`, `the_low_power_enables_reset_with_every_peripheral_set` |
//! | `SSCGR` | Stored. Spread spectrum modulates the PLL to spread its emissions; it has no observable effect on a clock a deterministic emulator counts in whole nanoseconds | 6.3.22 | `spread_spectrum_is_stored_and_changes_nothing` |
//! | 6.1.1, 6.1.2, 6.1.3 | The three resets, as the flags they leave in `CSR` and the domain `BDRST` clears | 6.1, 6.3.20, 6.3.21 | `rmvf_clears_the_reset_flags`, `the_backup_reset_holds_the_domain_until_it_is_cleared` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behaviour | Source | Reason |
//! |---|---|---|
//! | Any relation between a startup and the number of instructions a firmware retires while it waits | 6.2, DS9405 rev 13 | These are virtual times, and the emulated core retires an instruction every four nanoseconds whatever the clock tree says, where the part would be running at 16 MHz on the HSI. A firmware whose wait is a counted loop rather than a tick timeout therefore spins about fifteen times as long as it would on the part, and one with a tight bound gives up: the 2 ms HSE is about 62 000 turns of an eight-instruction loop, and the 2 s LSE about 62 million. Both figures are the datasheet's, and a machine that wants a shorter one has no way to say so yet |
//! | A cleared enable drops its ready flag at once, where the part takes 6 oscillator cycles: 250 ns on a 24 MHz HSE, 183 us on the LSE | 6.3.1, 6.3.20 | A simplification, and the model's own. A firmware polls a ready flag to see it rise, not to watch it fall, and the direction this errs in is the one that cannot hang a wait loop |
//! | The NMI a clock failure raises | 6.2.7 | A core exception, which the interrupt bitmap cannot reach. A firmware sees `CSSF` and the forced switch |
//! | The failure event sent to the break inputs of TIM1 and TIM8 | 6.2.7 | A wire between two models, RL-112. It also puts ES0206 rev 24 2.7.1, the system break the timer does not latch, out of reach |
//! | An HSE that stops while `CSSON` is clear | 6.2.7 | The detector is what makes a failure observable; with it off, nothing in this chapter reports one |
//! | The remaining branches of figure 16: the I2S clock, whose `I2SSRC` is stored and not answered, SAI1-B, LTDC, the Ethernet and USB taps, `IWDGCLK`, and the core timer's `HCLK/8` | 6.2 | Nothing consumes a clock yet, so the model answers the taps a firmware configures and leaves the rest; the core's timer is the core's own model |
//! | Measuring the HSI or the LSE against TIM5 or TIM11 | 6.2.11 | The wiring between a clock and a timer's input is not built |
//! | The backup domain's bits being writable only once `PWR_CR`'s `DBP` is set | 6.3.20 | `DBP` is the power controller's bit, and no model can read another's register yet, RL-112. The power controller does hold the half of that rule which lives inside itself, `DBP` gating `BRE` |
//! | The independent watchdog forcing the LSI on, so that `LSION` cannot be cleared once it has started | 6.2.9 | The same shape as the in-use rule the model does implement for `CR`, and blocked on the same wire: `IWDG_KR` belongs to another instance, RL-112 |
//! | Section 6.2.3's broader reading of two rules the model takes from the register sections instead | 6.2.3, 6.2.7, 6.3.2 | Chapter 6 states each twice and not alike. Section 6.2.3 says `PLLM` and `PLLSRC` are "common to both PLLs" and names all three, where section 6.3.2's own bit text says they "can be written only when the PLL and PLLI2S are disabled"; and it says the PLLs are disabled "when HSE or PLL (clocked by HSE) are used as system clock", where section 6.2.7 says "the HSE oscillator clock was the clock source of PLL used as the system clock". The model follows the register section in the first case and section 6.2.7 in the second, because each is the narrower rule and refusing a write or stopping a PLL that the part would have allowed is the failure a firmware cannot work around |
//! | Clock gating: holding a peripheral in reset or off its clock so it stops answering the bus | 6.3.5 to 6.3.14 | The bus does not consult another model, RL-112 |
//! | ES0206 rev 24 2.2.7: a peripheral does not answer for two AHB cycles, or `1 + (AHB/APB prescaler)` APB cycles, after its clock is enabled | ES0206 rev 24 | Pending on the same wiring, RL-112; bear-bones already applies the dummy-read workaround |
//! | ES0206 rev 24 2.2.9: the backup domain's protection takes effect some APB1 cycles after `DBP` is programmed | ES0206 rev 24 | Pending on the same wiring as the `DBP` rule itself, RL-112 |
//! | ES0206 rev 24 2.2.10: PC13 transitions disturb the LSE crystal | ES0206 rev 24 | An analogue effect with no register behaviour |
//! | ES0206 rev 24 2.2.11: a backup domain reset missed after its supply drops into a window a few mV wide | ES0206 rev 24 | The model has no VBAT rail and no power cycle, so the domain always reaches its reset value |
//! | ES0206 rev 24 2.2.12: over-drive and under-drive unavailable on some revisions | ES0206 rev 24 | A power mode of `PWR`, which owns the row that settles it. What belongs here is the ceiling it would impose: the model applies no upper bound to any frequency, so a firmware may configure a tree the part could not run |
//!
//! **Where this model departs from the vendor's file.** ST's SVD splits `SW`, `SWS`, and the `PLLCFGR`
//! factors one field per bit and omits `PLLI2SQ` and `CIR`'s three `PLLSAI` bits, so the model declares
//! them as sections 6.3.2, 6.3.3, 6.3.4, and 6.3.23 give them. It types three flags read-write that those
//! sections type `r` — `CR` bit 29, `CIR` bit 6, and `CSR` bits 31:25 — and the model puts those back on
//! every write; the register file enforces the rest from the table, which stays the vendor's.
//!
use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::soc::{Access, FieldSpec, ReadAction, WriteModify};
use crate::time::{EventId, VirtualNanos};

use crate::soc::stm32f427::generated::rcc::{
    Register, bdcr, cfgr, cir, cr, csr, dckcfgr, pllcfgr, plli2scfgr, pllsaicfgr,
};

/// The RCC's one interrupt, position zero of its instance's table; RM0090 rev 22 table 63 gives it line 5.
const GLOBAL: usize = 0;

/// The internal oscillator that clocks the SoC out of reset, 16 MHz; RM0090 rev 22 section 6.2.2.
const HSI_HZ: u64 = 16_000_000;
/// The internal low-speed oscillator. RM0090 rev 22 sections 6.2.5 and 6.3.21 disagree, saying around
/// 32 kHz and 40 kHz; DS9405 rev 13 table 42's typical is 32 kHz, which is the figure taken.
const LSI_HZ: u64 = 32_000;

const fn field(name: &'static str, bit: u8, width: u8, access: Access) -> FieldSpec {
    FieldSpec {
        name,
        bit,
        width,
        access,
        read: ReadAction::None,
        write: WriteModify::None,
    }
}

/// `SW`, the system clock switch, RM0090 rev 22 section 6.3.3; ST's file splits it into `SW0` and `SW1`.
pub static SW: FieldSpec = field("SW", 0, 2, Access::ReadWrite);
/// `SWS`, the switch status, RM0090 rev 22 section 6.3.3; read-only, and ST's file splits it.
pub static SWS: FieldSpec = field("SWS", 2, 2, Access::ReadOnly);
/// `PLLM`, RM0090 rev 22 section 6.3.2; ST's file splits it into `PLLM0` to `PLLM5`.
pub static PLLM: FieldSpec = field("PLLM", 0, 6, Access::ReadWrite);
/// `PLLN`, RM0090 rev 22 section 6.3.2; ST's file splits it into `PLLN0` to `PLLN8`.
pub static PLLN: FieldSpec = field("PLLN", 6, 9, Access::ReadWrite);
/// `PLLP`, RM0090 rev 22 section 6.3.2; ST's file splits it into `PLLP0` and `PLLP1`.
pub static PLLP: FieldSpec = field("PLLP", 16, 2, Access::ReadWrite);
/// `PLLQ`, RM0090 rev 22 section 6.3.2; ST's file splits it into `PLLQ0` to `PLLQ3`.
pub static PLLQ: FieldSpec = field("PLLQ", 24, 4, Access::ReadWrite);
/// `PLLI2SQ`, RM0090 rev 22 section 6.3.23 bits 27:24, the PLLI2S divider for the SAI1 clock; ST's file
/// omits it, where it carries `PLLSAIQ` of the sibling register.
pub static PLLI2SQ: FieldSpec = field("PLLI2SQ", 24, 4, Access::ReadWrite);
/// `PLLSAIRDYF`, RM0090 rev 22 section 6.3.4 bit 6; ST's file omits it.
pub static PLLSAIRDYF: FieldSpec = field("PLLSAIRDYF", 6, 1, Access::ReadOnly);
/// `PLLSAIRDYIE`, RM0090 rev 22 section 6.3.4 bit 14; ST's file omits it.
pub static PLLSAIRDYIE: FieldSpec = field("PLLSAIRDYIE", 14, 1, Access::ReadWrite);
/// `PLLSAIRDYC`, RM0090 rev 22 section 6.3.4 bit 22; ST's file omits it.
pub static PLLSAIRDYC: FieldSpec = field("PLLSAIRDYC", 22, 1, Access::WriteOnly);

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Source {
    Hsi,
    Hse,
    Pll,
    PllI2s,
    PllSai,
    Lse,
    Lsi,
}

impl Source {
    /// Every source, in the order their flags sit in `CIR`; RM0090 rev 22 section 6.3.4.
    const ALL: [Self; 7] = [
        Self::Lsi,
        Self::Lse,
        Self::Hsi,
        Self::Hse,
        Self::Pll,
        Self::PllI2s,
        Self::PllSai,
    ];

    fn control(self) -> (Register, &'static FieldSpec, &'static FieldSpec) {
        match self {
            Self::Hsi => (Register::Cr, cr::HSION, cr::HSIRDY),
            Self::Hse => (Register::Cr, cr::HSEON, cr::HSERDY),
            Self::Pll => (Register::Cr, cr::PLLON, cr::PLLRDY),
            Self::PllI2s => (Register::Cr, cr::PLLI2SON, cr::PLLI2SRDY),
            Self::PllSai => (Register::Cr, cr::PLLSAION, cr::PLLSAIRDY),
            Self::Lse => (Register::Bdcr, bdcr::LSEON, bdcr::LSERDY),
            Self::Lsi => (Register::Csr, csr::LSION, csr::LSIRDY),
        }
    }

    fn is_pll(self) -> bool {
        matches!(self, Self::Pll | Self::PllI2s | Self::PllSai)
    }

    /// The `CIR` flag, enable, and clear bits of this source; RM0090 rev 22 section 6.3.4.
    fn interrupt(self) -> (&'static FieldSpec, &'static FieldSpec, &'static FieldSpec) {
        match self {
            Self::Hsi => (cir::HSIRDYF, cir::HSIRDYIE, cir::HSIRDYC),
            Self::Hse => (cir::HSERDYF, cir::HSERDYIE, cir::HSERDYC),
            Self::Pll => (cir::PLLRDYF, cir::PLLRDYIE, cir::PLLRDYC),
            Self::PllI2s => (cir::PLLI2SRDYF, cir::PLLI2SRDYIE, cir::PLLI2SRDYC),
            Self::PllSai => (&PLLSAIRDYF, &PLLSAIRDYIE, &PLLSAIRDYC),
            Self::Lse => (cir::LSERDYF, cir::LSERDYIE, cir::LSERDYC),
            Self::Lsi => (cir::LSIRDYF, cir::LSIRDYIE, cir::LSIRDYC),
        }
    }

    /// How long this source takes to become ready: the typical column of DS9405 rev 13 tables 39 to 42,
    /// and for the PLLs a figure inside table 43's `tLOCK` range, which has no typical. The manual fixes
    /// none of them, so they are the model's own.
    fn startup(self) -> VirtualNanos {
        VirtualNanos(match self {
            Self::Hsi => 2_200,
            Self::Hse => 2_000_000,
            Self::Pll | Self::PllI2s | Self::PllSai => 200_000,
            Self::Lse => 2_000_000_000,
            Self::Lsi => 15_000,
        })
    }
}

/// The reset and clock control of one STM32F42xxx, RM0090 rev 22 chapter 6.
#[derive(Debug)]
pub struct Rcc {
    hse_hz: u64,
    lse_hz: u64,
    pending: [Option<EventId>; Source::ALL.len()],
    shadow: Shadow,
    interrupt: Level,
}

impl Default for Rcc {
    fn default() -> Self {
        Self::new()
    }
}

impl Rcc {
    /// The RCC of a bare part: nothing on either oscillator pin until a board fits a crystal through
    /// [`Rcc::attach_crystals`]. A pin with nothing on it still reaches ready, at no frequency, which
    /// RL-137 asks about.
    #[must_use]
    pub fn new() -> Self {
        Self {
            hse_hz: 0,
            lse_hz: 0,
            pending: [None; Source::ALL.len()],
            shadow: Shadow::default(),
            interrupt: Level::Low,
        }
    }

    /// The crystals a board fits, in hertz; zero for a pin it leaves empty. RM0090 rev 22 section 6.2.1
    /// takes the high-speed one between 4 and 26 MHz and 6.2.4 the low-speed one at 32.768 kHz.
    ///
    /// A crystal is soldered on, so this is for before the first instruction and takes no register file:
    /// what a running oscillator does when its frequency changes underneath it is not a thing boards do and
    /// not a thing this models. Whether an empty pin should reach ready at all is RL-137.
    pub fn attach_crystals(&mut self, hse_hz: u64, lse_hz: u64) {
        self.hse_hz = hse_hz;
        self.lse_hz = lse_hz;
    }
}

impl Rcc {
    fn source_hz(&self, registers: &RegisterFile, source: Source) -> u64 {
        let (register, _, ready) = source.control();
        if !ready.is_set(registers.get(register)) {
            return 0;
        }
        match source {
            Source::Hsi => HSI_HZ,
            Source::Hse => self.hse_hz,
            Source::Lse => self.lse_hz,
            Source::Lsi => LSI_HZ,
            Source::Pll => self.pll_p(registers),
            // The audio PLLs have no single output: the VCO is what their Q and R taps divide, and the
            // model answers it here so that [`Rcc::plli2s_clock`] and [`Rcc::sai_clock`] can.
            Source::PllI2s | Source::PllSai => self.vco(registers, source),
        }
    }

    /// What clocks the three PLLs: `PLLSRC` picks the HSE or the HSI, RM0090 rev 22 section 6.3.2.
    fn pll_input_hz(&self, registers: &RegisterFile) -> u64 {
        if cr::HSERDY.is_set(registers.get(Register::Cr)) && Self::pll_source_is_hse(registers) {
            self.hse_hz
        } else if cr::HSIRDY.is_set(registers.get(Register::Cr))
            && !Self::pll_source_is_hse(registers)
        {
            HSI_HZ
        } else {
            0
        }
    }

    fn pll_source_is_hse(registers: &RegisterFile) -> bool {
        pllcfgr::PLLSRC.is_set(registers.get(Register::Pllcfgr))
    }

    /// `f(VCO) = f(input) x PLLN / PLLM`, RM0090 rev 22 section 6.3.2; the audio PLLs share `PLLM`.
    fn vco(&self, registers: &RegisterFile, which: Source) -> u64 {
        let pllcfgr = registers.get(Register::Pllcfgr);
        let m = PLLM.get(pllcfgr);
        if m == 0 {
            return 0;
        }
        let n = match which {
            Source::Pll => PLLN.get(pllcfgr),
            Source::PllI2s => plli2scfgr::PLLI2SNX.get(registers.get(Register::Plli2scfgr)),
            Source::PllSai => pllsaicfgr::PLLSAIN.get(registers.get(Register::Pllsaicfgr)),
            _ => return 0,
        };
        // The manual writes the formula as f(in) x (N / M), and the division has to come second: an input
        // that PLLM does not divide evenly otherwise loses up to N - 1 hertz of the VCO.
        self.pll_input_hz(registers).saturating_mul(n) / m
    }

    /// `f(PLL) = f(VCO) / PLLP`, with `PLLP` encoding 2, 4, 6, 8; RM0090 rev 22 section 6.3.2.
    fn pll_p(&self, registers: &RegisterFile) -> u64 {
        let p = (PLLP.get(registers.get(Register::Pllcfgr)) + 1) * 2;
        self.vco(registers, Source::Pll) / p
    }

    /// The 48 MHz domain: `f(VCO) / PLLQ`, RM0090 rev 22 section 6.3.2.
    #[must_use]
    pub fn pll48(&self, registers: &RegisterFile) -> u64 {
        let q = PLLQ.get(registers.get(Register::Pllcfgr));
        if q == 0 {
            return 0;
        }
        self.vco(registers, Source::Pll) / q
    }

    /// The system clock, whichever source `SWS` says is driving it; RM0090 rev 22 section 6.3.3.
    #[must_use]
    pub fn sysclk(&self, registers: &RegisterFile) -> u64 {
        match SWS.get(registers.get(Register::Cfgr)) {
            0 => self.source_hz(registers, Source::Hsi),
            1 => self.source_hz(registers, Source::Hse),
            2 => self.source_hz(registers, Source::Pll),
            _ => 0,
        }
    }

    /// The AHB clock, `SYSCLK / HPRE`. The encoding has no divide by 32: RM0090 rev 22 section 6.3.3.
    #[must_use]
    pub fn hclk(&self, registers: &RegisterFile) -> u64 {
        self.sysclk(registers) / ahb_divider(cfgr::HPRE.get(registers.get(Register::Cfgr)))
    }

    /// The APB1 clock, `HCLK / PPRE1`; RM0090 rev 22 section 6.3.3.
    #[must_use]
    pub fn pclk1(&self, registers: &RegisterFile) -> u64 {
        self.hclk(registers) / apb_divider(cfgr::PPRE1.get(registers.get(Register::Cfgr)))
    }

    /// The APB2 clock, `HCLK / PPRE2`; RM0090 rev 22 section 6.3.3.
    #[must_use]
    pub fn pclk2(&self, registers: &RegisterFile) -> u64 {
        self.hclk(registers) / apb_divider(cfgr::PPRE2.get(registers.get(Register::Cfgr)))
    }

    /// What the timers on one APB domain count, RM0090 rev 22 section 6.2. That section names `TIMPRE` in
    /// `RCC_CFGR` in one sentence and in `RCC_DKCFGR` in the next two; it is in `DCKCFGR`, section 6.3.25.
    #[must_use]
    pub fn timer_clock(&self, registers: &RegisterFile, apb: Apb) -> u64 {
        let field = match apb {
            Apb::One => cfgr::PPRE1,
            Apb::Two => cfgr::PPRE2,
        };
        let divider = apb_divider(field.get(registers.get(Register::Cfgr)));
        let pclk = self.hclk(registers) / divider;
        if dckcfgr::TIMPRE.is_set(registers.get(Register::Dckcfgr)) {
            // Section 6.2 words this as 1, 2, or 4 giving HCLK and anything else four times PCLKx. At a
            // prescaler of exactly 4 the two are the same number, so only 1 and 2 tell the arms apart.
            if divider <= 4 {
                self.hclk(registers)
            } else {
                pclk.saturating_mul(4)
            }
        } else if divider == 1 {
            pclk
        } else {
            pclk.saturating_mul(2)
        }
    }

    /// The RTC clock: the LSE, the LSI, or the HSE divided by `RTCPRE`; RM0090 rev 22 sections 6.2.8, 6.3.3,
    /// and 6.3.20. A `RTCPRE` of 0 or 1 is no clock, and so is an unselected source.
    #[must_use]
    pub fn rtc_clock(&self, registers: &RegisterFile) -> u64 {
        let bdcr_value = registers.get(Register::Bdcr);
        // RM0090 rev 22 section 6.3.20 bit 15: RTCEN gates RTCCLK, and figure 16 puts that gate in front
        // of everything downstream of the selection.
        if !bdcr::RTCEN.is_set(bdcr_value) {
            return 0;
        }
        let select = bdcr::RTCSEL0.get(bdcr_value) | (bdcr::RTCSEL1.get(bdcr_value) << 1);
        match select {
            1 => self.source_hz(registers, Source::Lse),
            2 => self.source_hz(registers, Source::Lsi),
            3 => {
                let prescaler = cfgr::RTCPRE.get(registers.get(Register::Cfgr));
                if prescaler < 2 {
                    0
                } else {
                    self.source_hz(registers, Source::Hse) / prescaler
                }
            }
            _ => 0,
        }
    }

    /// The first clock output, its source divided by its prescaler; RM0090 rev 22 section 6.3.3.
    #[must_use]
    pub fn mco1(&self, registers: &RegisterFile) -> u64 {
        let value = registers.get(Register::Cfgr);
        let source = match cfgr::MCO1.get(value) {
            0 => self.source_hz(registers, Source::Hsi),
            1 => self.source_hz(registers, Source::Lse),
            2 => self.source_hz(registers, Source::Hse),
            _ => self.source_hz(registers, Source::Pll),
        };
        source / mco_divider(cfgr::MCO1PRE.get(value))
    }

    /// The PLLI2S clock output, `f(VCO) / PLLI2SR`; RM0090 rev 22 section 6.3.23. This is what the I2S
    /// peripherals and MCO2 see, where the SAI takes the Q tap instead.
    #[must_use]
    pub fn plli2s_clock(&self, registers: &RegisterFile) -> u64 {
        let r = plli2scfgr::PLLI2SRX.get(registers.get(Register::Plli2scfgr));
        if r == 0 {
            return 0;
        }
        self.source_hz(registers, Source::PllI2s) / r
    }

    /// The second clock output; RM0090 rev 22 section 6.3.3.
    #[must_use]
    pub fn mco2(&self, registers: &RegisterFile) -> u64 {
        let value = registers.get(Register::Cfgr);
        let source = match cfgr::MCO2.get(value) {
            0 => self.sysclk(registers),
            1 => self.plli2s_clock(registers),
            2 => self.source_hz(registers, Source::Hse),
            _ => self.source_hz(registers, Source::Pll),
        };
        source / mco_divider(cfgr::MCO2PRE.get(value))
    }

    /// The SAI1-A clock: the PLLSAI's or the PLLI2S's Q output over its divisor, or an outside clock.
    /// RM0090 rev 22 section 6.3.25. A divisor field of `n` divides by `n + 1`.
    #[must_use]
    pub fn sai_clock(&self, registers: &RegisterFile) -> u64 {
        let dckcfgr_value = registers.get(Register::Dckcfgr);
        match dckcfgr::SAI1ASRC.get(dckcfgr_value) {
            0 => {
                let q = pllsaicfgr::PLLSAIQ.get(registers.get(Register::Pllsaicfgr));
                if q == 0 {
                    return 0;
                }
                self.source_hz(registers, Source::PllSai)
                    / q
                    / (dckcfgr::PLLSAIDIVQ.get(dckcfgr_value) + 1)
            }
            1 => {
                let q = PLLI2SQ.get(registers.get(Register::Plli2scfgr));
                if q == 0 {
                    return 0;
                }
                self.source_hz(registers, Source::PllI2s)
                    / q
                    / (dckcfgr::PLLI2SDIVQ.get(dckcfgr_value) + 1)
            }
            // 10 is an outside clock on the alternate function pin, which no wire reaches; 11 is what the
            // manual calls a wrong configuration.
            _ => 0,
        }
    }
}

/// Which APB domain a timer sits on.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Apb {
    /// The low-speed domain, `PCLK1`.
    One,
    /// The high-speed domain, `PCLK2`.
    Two,
}

/// `HPRE`: `0xxx` is no division, then 2, 4, 8, 16, **64**, 128, 256, 512. RM0090 rev 22 section 6.3.3
/// leaves 32 out of the encoding, so `1100` is 64 and not 32.
fn ahb_divider(encoding: u64) -> u64 {
    match encoding {
        0b1000 => 2,
        0b1001 => 4,
        0b1010 => 8,
        0b1011 => 16,
        0b1100 => 64,
        0b1101 => 128,
        0b1110 => 256,
        0b1111 => 512,
        _ => 1,
    }
}

/// `PPRE1` and `PPRE2`: `0xx` is no division, then 2, 4, 8, 16. RM0090 rev 22 section 6.3.3.
fn apb_divider(encoding: u64) -> u64 {
    match encoding {
        0b100 => 2,
        0b101 => 4,
        0b110 => 8,
        0b111 => 16,
        _ => 1,
    }
}

/// `MCO1PRE` and `MCO2PRE`: `0xx` is no division, then 2, 3, 4, 5. RM0090 rev 22 section 6.3.3.
fn mco_divider(encoding: u64) -> u64 {
    match encoding {
        0b100 => 2,
        0b101 => 3,
        0b110 => 4,
        0b111 => 5,
        _ => 1,
    }
}

/// `PLLSAIRDY`, which RM0090 rev 22 section 6.3.1 types `r` and ST's file types read-write. Every other
/// ready flag of `CR`, and the HSI's factory calibration, are read-only in that file, so the register file
/// already refuses a write to them.
const CR_HARDWARE: u64 = cr::PLLSAIRDY.mask();

/// `PLLSAIRDYF`, which RM0090 rev 22 section 6.3.4 gives as bit 6 and ST's file omits, so the register's
/// own read-write access applies to it. The other six flags and `CSSF` are read-only in that file.
const CIR_HARDWARE: u64 = PLLSAIRDYF.mask();

/// The seven reset flags of `CSR` bits 31:25, `r` in RM0090 rev 22 section 6.3.21 and read-write in ST's
/// file. Only `RMVF` clears them, so without this a plain write of `LSION` would erase the record of why
/// the part restarted. `LSIRDY` beside them is read-only in that file and needs nothing.
const CSR_HARDWARE: u64 = csr::BORRSTF.mask()
    | csr::PADRSTF.mask()
    | csr::PORRSTF.mask()
    | csr::SFTRSTF.mask()
    | csr::WDGRSTF.mask()
    | csr::WWDGRSTF.mask()
    | csr::LPWRRSTF.mask();

/// What a write must not change, kept because a model sees the value written and not the one before it.
/// It starts at the manual's reset values, which `the_shadow_starts_at_the_reset_values` holds against the
/// register file; section 6.3.1 leaves `CR`'s `HSICAL` byte undefined and zero is the model's choice.
#[derive(Debug)]
struct Shadow {
    cr: u64,
    cir: u64,
    csr: u64,
    bdcr: u64,
    pllcfgr: u64,
    plli2scfgr: u64,
    pllsaicfgr: u64,
}

impl Default for Shadow {
    fn default() -> Self {
        Self {
            cr: 0x0000_0083,
            cir: 0,
            csr: 0x0E00_0000,
            bdcr: 0,
            pllcfgr: 0x2400_3010,
            plli2scfgr: 0x2400_3000,
            pllsaicfgr: 0x2400_3000,
        }
    }
}

impl Rcc {
    /// Where a source's pending startup sits in the table.
    fn index_of(source: Source) -> usize {
        Source::ALL
            .iter()
            .position(|candidate| *candidate == source)
            .expect("a source of the table")
    }

    fn requested(registers: &RegisterFile) -> Option<Source> {
        match SW.get(registers.get(Register::Cfgr)) {
            0 => Some(Source::Hsi),
            1 => Some(Source::Hse),
            2 => Some(Source::Pll),
            _ => None,
        }
    }

    fn is_ready(registers: &RegisterFile, source: Source) -> bool {
        let (register, _, ready) = source.control();
        ready.is_set(registers.get(register))
    }

    /// The switch of RM0090 rev 22 section 6.2.6: it happens only when the target is ready, and a request
    /// for a source that is not yet ready is honoured the moment it becomes one.
    fn try_switch(registers: &mut RegisterFile) {
        let Some(target) = Self::requested(registers) else {
            return;
        };
        if !Self::is_ready(registers, target) {
            return;
        }
        let want = SW.get(registers.get(Register::Cfgr));
        let cfgr = registers.get(Register::Cfgr);
        registers.set(Register::Cfgr, SWS.set(cfgr, want));
    }

    /// Whether the system runs on this source, directly or through the PLL; RM0090 rev 22 section 6.3.1
    /// forbids clearing the enable of one that does.
    fn clocks_the_system(registers: &RegisterFile, source: Source) -> bool {
        match SWS.get(registers.get(Register::Cfgr)) {
            0 => source == Source::Hsi,
            1 => source == Source::Hse,
            2 => {
                source == Source::Pll
                    || (source == Source::Hse && Self::pll_source_is_hse(registers))
                    || (source == Source::Hsi && !Self::pll_source_is_hse(registers))
            }
            _ => false,
        }
    }

    /// Raises the RCC interrupt when any enabled ready flag stands; RM0090 rev 22 section 6.3.4.
    ///
    /// The model keeps the level it last drove, so a write that changes nothing reports nothing.
    fn refresh_interrupt(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
        let cir_value = registers.get(Register::Cir);
        let wanted = if Source::ALL.iter().any(|source| {
            let (flag, enable, _) = source.interrupt();
            flag.is_set(cir_value) && enable.is_set(cir_value)
        }) {
            Level::High
        } else {
            Level::Low
        };
        if wanted == self.interrupt {
            return;
        }
        self.interrupt = wanted;
        match wanted {
            Level::High => context.raise(GLOBAL),
            Level::Low => context.lower(GLOBAL),
        }
    }

    /// The HSE has stopped and the clock security system notices, RM0090 rev 22 section 6.2.7. Every
    /// clause of that section is conditional on what the oscillator was clocking, and the model's two
    /// tables say which of them it carries.
    pub fn fail_hse(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let cr_before = registers.get(Register::Cr);
        if !cr::CSSON.is_set(cr_before) || !cr::HSERDY.is_set(cr_before) {
            return;
        }
        let feeds_the_system = Self::clocks_the_system(registers, Source::Hse);
        let through_the_pll = feeds_the_system && SWS.get(registers.get(Register::Cfgr)) == 2;
        let mut cr_value = cr::HSEON.set(cr_before, 0);
        cr_value = cr::HSERDY.set(cr_value, 0);
        self.pending[Self::index_of(Source::Hse)] = None;
        if through_the_pll {
            for source in [Source::Pll, Source::PllI2s] {
                self.pending[Self::index_of(source)] = None;
            }
            cr_value = cr::PLLON.set(cr_value, 0);
            cr_value = cr::PLLRDY.set(cr_value, 0);
            cr_value = cr::PLLI2SON.set(cr_value, 0);
            cr_value = cr::PLLI2SRDY.set(cr_value, 0);
        }
        if feeds_the_system {
            // Hardware starts the oscillator it switches to; a firmware is free to have left it off.
            cr_value = cr::HSION.set(cr_value, 1);
            cr_value = cr::HSIRDY.set(cr_value, 1);
        }
        registers.set(Register::Cr, cr_value);
        if feeds_the_system {
            let mut cfgr_value = SW.set(registers.get(Register::Cfgr), 0);
            cfgr_value = SWS.set(cfgr_value, 0);
            registers.set(Register::Cfgr, cfgr_value);
        }
        let cir_value = registers.get(Register::Cir);
        registers.set(Register::Cir, cir::CSSF.set(cir_value, 1));
        self.store_shadow(registers);
        self.refresh_interrupt(registers, context);
    }
}

impl Device for Rcc {
    type Register = Register;

    fn write(
        &mut self,
        register: Register,
        value: u64,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        match register {
            Register::Cr => self.write_cr(registers, context),
            Register::Cfgr => {
                Self::try_switch(registers);
            }
            Register::Cir => self.write_cir(value, registers, context),
            Register::Bdcr => self.write_bdcr(registers, context),
            Register::Csr => self.write_csr(value, registers, context),
            Register::Pllcfgr | Register::Plli2scfgr | Register::Pllsaicfgr => {
                self.guard_pll_configuration(register, registers);
            }
            _ => {}
        }
        self.store_shadow(registers);
    }

    fn on_event(
        &mut self,
        event: EventId,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        let Some(index) = self
            .pending
            .iter()
            .position(|pending| *pending == Some(event))
        else {
            // A model keeps the id it last scheduled and ignores any other; there is no cancel.
            return;
        };
        self.pending[index] = None;
        let source = Source::ALL[index];
        // The lookup above is the only guard needed: an enable cleared before its startup fires takes the
        // pending id with it, so an event that still matches belongs to a source still asking for it.
        let (register, _, ready) = source.control();
        let value = registers.get(register);
        registers.set(register, ready.set(value, 1));
        // RM0090 rev 22 section 6.3.4: each flag "is set by hardware when ... and xRDYIE is set". A firmware
        // that polls a ready flag with its interrupt disabled is polling a bit hardware never raises; the
        // ready flag of section 6.3.1 is the one to poll, and that is the one the line above sets.
        let (flag, enable, _) = source.interrupt();
        let cir_value = registers.get(Register::Cir);
        if enable.is_set(cir_value) {
            registers.set(Register::Cir, flag.set(cir_value, 1));
        }
        self.follow_cr_enables(registers, context);
        Self::try_switch(registers);
        self.store_shadow(registers);
        self.refresh_interrupt(registers, context);
    }
}

impl Rcc {
    fn store_shadow(&mut self, registers: &RegisterFile) {
        self.shadow = Shadow {
            cr: registers.get(Register::Cr),
            cir: registers.get(Register::Cir),
            csr: registers.get(Register::Csr),
            bdcr: registers.get(Register::Bdcr),
            pllcfgr: registers.get(Register::Pllcfgr),
            plli2scfgr: registers.get(Register::Plli2scfgr),
            pllsaicfgr: registers.get(Register::Pllsaicfgr),
        };
    }

    /// `CR`: the five enables and their ready flags, the bypass rule, and the in-use rule.
    fn write_cr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        registers.restore(Register::Cr, CR_HARDWARE, self.shadow.cr);
        // RM0090 rev 22 section 6.3.1: HSEBYP is writable only while the HSE oscillator is disabled.
        if cr::HSEON.is_set(self.shadow.cr) {
            registers.restore(Register::Cr, cr::HSEBYP.mask(), self.shadow.cr);
        }
        self.follow_cr_enables(registers, context);
        self.refresh_interrupt(registers, context);
    }

    /// Arms, holds, or drops the startup of every source whose enable lives in `CR`, RM0090 rev 22
    /// section 6.3.1. A ready event runs it as well as a write, because an oscillator becoming ready is
    /// what lets a PLL already enabled against it start locking.
    fn follow_cr_enables(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        for (index, source) in Source::ALL.iter().enumerate() {
            let (register, enable, ready) = source.control();
            if register != Register::Cr {
                continue;
            }
            let value = registers.get(register);
            if !enable.is_set(value) {
                if Self::clocks_the_system(registers, *source) {
                    registers.set(register, enable.set(value, 1));
                } else {
                    registers.set(register, ready.set(value, 0));
                    self.pending[index] = None;
                }
                continue;
            }
            // Section 6.3.1 forbids stopping an oscillator that feeds a PLL clocking the system, and says
            // nothing about one that stops while the system runs elsewhere. Holding the flag there would
            // let section 6.2.6's switch accept a PLL at no frequency, so the model drops it.
            if source.is_pll() && self.pll_input_hz(registers) == 0 {
                registers.set(register, ready.set(value, 0));
                self.pending[index] = None;
                continue;
            }
            if !ready.is_set(value) && self.pending[index].is_none() {
                self.pending[index] =
                    Some(context.schedule(VirtualNanos(context.now().0 + source.startup().0)));
            }
        }
    }

    /// `CIR`: the clear bits clear their flags and read zero; RM0090 rev 22 section 6.3.4.
    fn write_cir(
        &mut self,
        written: u64,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        registers.restore(Register::Cir, CIR_HARDWARE, self.shadow.cir);
        let mut value = registers.get(Register::Cir);
        for source in Source::ALL {
            let (flag, _, clear) = source.interrupt();
            if clear.is_set(written) {
                value = flag.set(value, 0);
            }
            value = clear.set(value, 0);
        }
        if cir::CSSC.is_set(written) {
            value = cir::CSSF.set(value, 0);
        }
        value = cir::CSSC.set(value, 0);
        registers.set(Register::Cir, value);
        self.refresh_interrupt(registers, context);
    }

    /// `BDCR`: the LSE, the RTC source, and the backup reset; RM0090 rev 22 section 6.3.20.
    fn write_bdcr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        // RM0090 rev 22 section 6.3.20: BDRST is set and cleared by software, so it stands until the
        // firmware clears it and holds the rest of the domain at its reset value while it does.
        if bdcr::BDRST.is_set(registers.get(Register::Bdcr)) {
            registers.set(Register::Bdcr, bdcr::BDRST.mask());
            self.pending[Self::index_of(Source::Lse)] = None;
            self.refresh_interrupt(registers, context);
            return;
        }
        // RM0090 rev 22 section 6.3.20 bit 2: LSEBYP is writable only while the LSE clock is disabled,
        // which is the rule section 6.3.1 gives HSEBYP.
        if bdcr::LSEON.is_set(self.shadow.bdcr) {
            registers.restore(Register::Bdcr, bdcr::LSEBYP.mask(), self.shadow.bdcr);
        }
        // RM0090 rev 22 section 6.3.20 bits 9:8 and section 6.2.8: once the RTC source has been chosen it
        // cannot be changed again unless the backup domain is reset, and BDRST above is what resets it.
        let selection = bdcr::RTCSEL0.mask() | bdcr::RTCSEL1.mask();
        if self.shadow.bdcr & selection != 0 {
            registers.restore(Register::Bdcr, selection, self.shadow.bdcr);
        }
        self.follow_enable(Source::Lse, registers, context);
    }

    /// `CSR`: the LSI, and `RMVF` clearing the reset flags; RM0090 rev 22 section 6.3.21.
    fn write_csr(
        &mut self,
        written: u64,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        registers.restore(Register::Csr, CSR_HARDWARE, self.shadow.csr);
        if csr::RMVF.is_set(written) {
            let mut value = registers.get(Register::Csr);
            for flag in [
                csr::BORRSTF,
                csr::PADRSTF,
                csr::PORRSTF,
                csr::SFTRSTF,
                csr::WDGRSTF,
                csr::WWDGRSTF,
                csr::LPWRRSTF,
            ] {
                value = flag.set(value, 0);
            }
            registers.set(Register::Csr, csr::RMVF.set(value, 0));
        }
        self.follow_enable(Source::Lsi, registers, context);
    }

    /// One oscillator outside `CR`: schedule its ready when enabled, drop it when not.
    fn follow_enable(
        &mut self,
        source: Source,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        let index = Self::index_of(source);
        let (register, enable, ready) = source.control();
        let value = registers.get(register);
        if enable.is_set(value) {
            if !ready.is_set(value) && self.pending[index].is_none() {
                self.pending[index] =
                    Some(context.schedule(VirtualNanos(context.now().0 + source.startup().0)));
            }
        } else {
            registers.set(register, ready.set(value, 0));
            self.pending[index] = None;
        }
        self.refresh_interrupt(registers, context);
    }

    /// RM0090 rev 22 section 6.2.3: a PLL's factors cannot change while it is enabled, so the write is
    /// ignored and the register keeps what it had.
    fn guard_pll_configuration(&mut self, register: Register, registers: &mut RegisterFile) {
        let (locked, previous) = match register {
            Register::Pllcfgr => (cr::PLLON, self.shadow.pllcfgr),
            Register::Plli2scfgr => (cr::PLLI2SON, self.shadow.plli2scfgr),
            Register::Pllsaicfgr => (cr::PLLSAION, self.shadow.pllsaicfgr),
            _ => return,
        };
        let cr_value = registers.get(Register::Cr);
        if locked.is_set(cr_value) {
            registers.set(register, previous);
            return;
        }
        // RM0090 rev 22 section 6.3.2 bits 5:0 and 22: PLLM and PLLSRC feed the PLLI2S too, so each can be
        // written only while both are disabled. The rest of PLLCFGR is the PLL's, and the guard above has it.
        // A firmware that writes the whole word while the PLLI2S runs therefore gets the factors it asked
        // for and the input it had, which is what a per-field interlock does on the part: the bits that
        // may not be written simply do not latch, and the ones beside them do.
        if register == Register::Pllcfgr && cr::PLLI2SON.is_set(cr_value) {
            let value = registers.get(register);
            let kept = PLLM.set(value, PLLM.get(previous));
            registers.set(
                register,
                pllcfgr::PLLSRC.set(kept, pllcfgr::PLLSRC.get(previous)),
            );
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::soc::stm32f427;

    #[test]
    fn the_shadow_starts_at_the_reset_values() {
        let mut soc = crate::mcu::Single::new(stm32f427::builder(), "RCC", Rcc::new()).unwrap();
        let registers = soc.registers();
        let shadow = Shadow::default();
        for (name, seeded, register) in [
            ("CR", shadow.cr, Register::Cr),
            ("CIR", shadow.cir, Register::Cir),
            ("CSR", shadow.csr, Register::Csr),
            ("BDCR", shadow.bdcr, Register::Bdcr),
            ("PLLCFGR", shadow.pllcfgr, Register::Pllcfgr),
            ("PLLI2SCFGR", shadow.plli2scfgr, Register::Plli2scfgr),
            ("PLLSAICFGR", shadow.pllsaicfgr, Register::Pllsaicfgr),
        ] {
            assert_eq!(seeded, registers.get(register), "{name} out of reset");
        }
    }
}