-
Notifications
You must be signed in to change notification settings - Fork 182
/
main.rs
1433 lines (1211 loc) · 48.9 KB
/
main.rs
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
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Server for managing the Gimlet sequencing process.
#![no_std]
#![no_main]
mod seq_spi;
mod vcore;
use counters::*;
use ringbuf::*;
use userlib::{
hl, set_timer_relative, sys_get_timer, sys_recv_notification,
sys_set_timer, task_slot, units, RecvMessage, TaskId, UnwrapLite,
};
use drv_gimlet_seq_api::{PowerState, SeqError};
use drv_hf_api as hf_api;
use drv_i2c_api as i2c;
use drv_ice40_spi_program as ice40;
use drv_packrat_vpd_loader::{read_vpd_and_load_packrat, Packrat};
use drv_spi_api::{SpiDevice, SpiServer};
use drv_stm32xx_sys_api as sys_api;
use idol_runtime::{NotificationHandler, RequestError};
use seq_spi::{Addr, Reg};
use static_assertions::const_assert;
use task_jefe_api::Jefe;
task_slot!(SYS, sys);
task_slot!(SPI, spi_driver);
task_slot!(I2C, i2c_driver);
task_slot!(HF, hf);
task_slot!(JEFE, jefe);
task_slot!(PACKRAT, packrat);
include!(concat!(env!("OUT_DIR"), "/i2c_config.rs"));
#[cfg_attr(target_board = "gimlet-b", path = "payload_b.rs")]
#[cfg_attr(
any(
target_board = "gimlet-c",
target_board = "gimlet-d",
target_board = "gimlet-e",
target_board = "gimlet-f",
),
path = "payload_cdef.rs"
)]
mod payload;
#[derive(Copy, Clone, PartialEq, Count)]
enum I2cTxn {
SpdLoad(u8, u8),
SpdLoadTop(u8, u8),
VCoreOn,
VCoreOff,
VCoreUndervoltageInitialize,
SocOn,
SocOff,
}
#[derive(Copy, Clone, PartialEq, Count)]
enum Trace {
Ice40Rails(bool, bool),
IdentValid(#[count(children)] bool),
ChecksumValid(#[count(children)] bool),
Reprogram(#[count(children)] bool),
Programmed,
Programming,
Ice40PowerGoodV1P2(#[count(children)] bool),
Ice40PowerGoodV3P3(#[count(children)] bool),
RailsOff,
Ident(u16),
A2Status(u8),
A2,
A0FailureDetails(Addr, u8),
A0Failed(#[count(children)] SeqError),
A1Status(u8),
CPUPresent(#[count(children)] bool),
Coretype {
coretype: bool,
sp3r1: bool,
sp3r2: bool,
},
A0Status(u8),
A0Power(u8),
NICPowerEnableLow(bool),
RailsOn,
UartEnabled,
A0(u16),
SetState(PowerState, PowerState, u64),
UpdateState(#[count(children)] PowerState),
ClockConfigWrite,
ClockConfigSuccess,
Status {
ier: u8,
ifr: u8,
amd_status: u8,
amd_a0: u8,
},
PGStatus {
b_pg: u8,
c_pg: u8,
nic: u8,
},
SMStatus {
a1: u8,
a0: u8,
},
NICStatus {
nic_ctrl: u8,
nic_status: u8,
out_status_nic1: u8,
out_status_nic2: u8,
},
ResetCounts {
rstn: u8,
pwrokn: u8,
},
PowerControl(u8),
InterruptFlags(u8),
V3P3SysA0VOut(units::Volts),
SpdBankAbsent(u8),
SpdAbsent(u8, u8, u8),
SpdDimmsFound(usize),
I2cError {
txn: I2cTxn,
#[count(children)]
code: i2c::ResponseCode,
},
I2cFault(I2cTxn),
I2cRetry {
#[count(children)]
txn: I2cTxn,
retries_remaining: u8,
},
StartFailed(#[count(children)] SeqError),
#[count(skip)]
None,
}
counted_ringbuf!(Trace, 128, Trace::None);
#[export_name = "main"]
fn main() -> ! {
let sys = sys_api::Sys::from(SYS.get_task_id());
let jefe = Jefe::from(JEFE.get_task_id());
let spi = drv_spi_api::Spi::from(SPI.get_task_id());
let hf = hf_api::HostFlash::from(HF.get_task_id());
match ServerImpl::init(&sys, jefe, spi, hf) {
// Set up everything nicely, time to start serving incoming messages.
Ok(mut server) => {
let mut buffer = [0; idl::INCOMING_SIZE];
loop {
idol_runtime::dispatch(&mut buffer, &mut server);
}
}
// Initializing the sequencer failed.
Err(_) => {
// Tell everyone that something's broken, as loudly as possible.
ringbuf_entry!(Trace::StartFailed(SeqError::I2cFault));
// Leave FAULT_PIN_L low (which is done at the start of init)
// All these moments will be lost in time, like tears in rain...
// Time to die.
loop {
// Sleeping with all bits in the notification mask clear means
// we should never be notified --- and if one never wakes up,
// the difference between sleeping and dying seems kind of
// irrelevant. But, `rustc` doesn't realize that this should
// never return, we'll stick it in a `loop` anyway so the main
// function can return `!`
sys_recv_notification(0);
}
}
}
}
struct ServerImpl<S: SpiServer> {
state: PowerState,
sys: sys_api::Sys,
seq: seq_spi::SequencerFpga<S>,
jefe: Jefe,
hf: hf_api::HostFlash,
vcore: vcore::VCore,
deadline: u64,
}
const TIMER_INTERVAL: u32 = 10;
impl<S: SpiServer + Clone> ServerImpl<S> {
fn init(
sys: &sys_api::Sys,
jefe: Jefe,
spi: S,
hf: hf_api::HostFlash,
) -> Result<Self, i2c::ResponseCode> {
// Ensure the SP fault pin is configured as an open-drain output, and pull
// it low to make the sequencer restart externally visible.
sys.gpio_configure_output(
FAULT_PIN_L,
sys_api::OutputType::OpenDrain,
sys_api::Speed::Low,
sys_api::Pull::None,
);
sys.gpio_reset(FAULT_PIN_L);
// Turn off the chassis LED, in case this is a task restart (and not a
// full chip restart, which would leave the GPIO unconfigured).
sys.gpio_configure_output(
CHASSIS_LED,
sys_api::OutputType::PushPull,
sys_api::Speed::Low,
sys_api::Pull::None,
);
sys.gpio_reset(CHASSIS_LED);
// To allow for the possibility that we are restarting, rather than
// starting, we take care during early sequencing to _not turn anything
// off,_ only on. This means if it was _already_ on, the outputs should not
// glitch.
// Unconditionally set our power-good detects as inputs.
//
// This is the expected reset state, but, good to be sure.
sys.gpio_configure_input(PGS_PINS, PGS_PULL);
// Set SP3_TO_SP_NIC_PWREN_L to be an input
sys.gpio_configure_input(NIC_PWREN_L_PINS, NIC_PWREN_L_PULL);
// Set all of the presence-related pins to be inputs
sys.gpio_configure_input(CORETYPE, CORETYPE_PULL);
sys.gpio_configure_input(CPU_PRESENT_L, CPU_PRESENT_L_PULL);
sys.gpio_configure_input(SP3R1, SP3R1_PULL);
sys.gpio_configure_input(SP3R2, SP3R2_PULL);
// Unconditionally set our sequencing-related GPIOs to outputs.
//
// If the processor has reset, these will start out low. Since neither rail
// has external pullups, this puts the regulators into a well-defined "off"
// state instead of leaving them floating, which is the state when A2 power
// starts coming up.
//
// If it's just our driver that has reset, this will have no effect, and
// will continue driving the lines at whatever level we left them in.
sys.gpio_configure_output(
ENABLES,
sys_api::OutputType::PushPull,
sys_api::Speed::High,
sys_api::Pull::None,
);
// To talk to the sequencer we need to configure its pins, obvs. Note that
// the SPI and CS lines are separately managed by the SPI server; the ice40
// crate handles the CRESETB and CDONE signals, and takes care not to
// generate surprise resets.
ice40::configure_pins(sys, &ICE40_CONFIG);
let pg = sys.gpio_read_input(PGS_PORT);
let v1p2 = pg & PG_V1P2_MASK != 0;
let v3p3 = pg & PG_V3P3_MASK != 0;
ringbuf_entry!(Trace::Ice40Rails(v1p2, v3p3));
// Force iCE40 CRESETB low before turning power on. This is nice because it
// prevents the iCE40 from racing us and deciding it should try to load from
// Flash. TODO: this may cause trouble with hot restarts, test.
sys.gpio_reset(ICE40_CONFIG.creset);
// Begin, or resume, the power supply sequencing process for the FPGA. We're
// going to be reading back our enable line states to get the real state
// being seen by the regulators, etc.
// The V1P2 regulator comes up first. It may already be on from a past life
// of ours. Ensuring that it's on by writing the pin is just as cheap as
// sensing its current state, and less code than _conditionally_ writing the
// pin, so:
sys.gpio_set(ENABLE_V1P2);
// We don't actually know how long ago the regulator turned on. Could have
// been _just now_ (above) or may have already been on. We'll use the PG pin
// to detect when it's stable. But -- the PG pin on the LT3072 is initially
// high when you turn the regulator on, and then takes time to drop if
// there's a problem. So, to ensure that there has been at least 1ms since
// regulator-on, we will delay for 2.
hl::sleep_for(2);
// Now, monitor the PG pin.
loop {
// active high
let pg = sys.gpio_read_input(PGS_PORT) & PG_V1P2_MASK != 0;
ringbuf_entry!(Trace::Ice40PowerGoodV1P2(pg));
if pg {
break;
}
// Do _not_ burn CPU constantly polling, it's rude. We could also set up
// pin-change interrupts but we only do this once per power on, so it
// seems like a lot of work.
hl::sleep_for(2);
}
// We believe V1P2 is good. Now, for V3P3! Set it active (high).
sys.gpio_set(ENABLE_V3P3);
// Delay to be sure.
hl::sleep_for(2);
// Now, monitor the PG pin.
loop {
// active high
let pg = sys.gpio_read_input(PGS_PORT) & PG_V3P3_MASK != 0;
ringbuf_entry!(Trace::Ice40PowerGoodV3P3(pg));
if pg {
break;
}
// Do _not_ burn CPU constantly polling, it's rude.
hl::sleep_for(2);
}
// Now, V2P5 is chained off V3P3 and comes up on its own with no
// synchronization. It takes about 500us in practice. We'll delay for 1ms,
// plus give the iCE40 a good 10ms to come out of power-down.
hl::sleep_for(1 + 10);
// Sequencer FPGA power supply sequencing (meta-sequencing?) is complete.
// Now, let's find out if we need to program the sequencer.
if let Some(pin) = GLOBAL_RESET {
// Also configure our design reset net -- the signal that resets the
// logic _inside_ the FPGA instead of the FPGA itself. We're assuming
// push-pull because all our boards with reset nets are lacking pullups
// right now. It's active low, so, set up the pin before exposing the
// output to ensure we don't glitch.
sys.gpio_set(pin);
sys.gpio_configure_output(
pin,
sys_api::OutputType::PushPull,
sys_api::Speed::High,
sys_api::Pull::None,
);
}
// If the sequencer is already loaded and operational, the design loaded
// into it should be willing to talk to us over SPI, and should be able to
// serve up a recognizable ident code.
let seq = seq_spi::SequencerFpga::new(
spi.device(drv_spi_api::devices::SEQUENCER),
);
// If the image announces the correct identifier and has a matching
// bitstream checksum, then we can skip reprogramming;
let ident_valid = seq.valid_ident();
ringbuf_entry!(Trace::IdentValid(ident_valid));
let checksum_valid = seq.valid_checksum();
ringbuf_entry!(Trace::ChecksumValid(checksum_valid));
let reprogram = !ident_valid || !checksum_valid;
ringbuf_entry!(Trace::Reprogram(reprogram));
// We only want to reset and reprogram the FPGA when absolutely required.
if reprogram {
if let Some(pin) = GLOBAL_RESET {
// Assert the design reset signal (not the same as the FPGA
// programming logic reset signal). We do this during reprogramming
// to avoid weird races that make our brains hurt.
sys.gpio_reset(pin);
}
// Reprogramming will continue until morale improves -- to a point.
loop {
let prog = spi.device(drv_spi_api::devices::ICE40);
ringbuf_entry!(Trace::Programming);
match reprogram_fpga(&prog, sys, &ICE40_CONFIG) {
Ok(()) => {
// yay
break;
}
Err(_) => {
// Try and put state back to something reasonable. We
// don't know if we're still locked, so ignore the
// complaint if we're not.
let _ = prog.release();
}
}
}
if let Some(pin) = GLOBAL_RESET {
// Deassert design reset signal. We set the pin, as it's
// active low.
sys.gpio_set(pin);
}
// Store our bitstream checksum in the FPGA's checksum registers
// (which are initialized to zero). This value is read back before
// programming the FPGA image (e.g. if this task restarts or the SP
// itself is reflashed), and used to decide whether FPGA programming
// is required.
seq.write_checksum().unwrap_lite();
}
ringbuf_entry!(Trace::Programmed);
vcore_soc_off()?;
ringbuf_entry!(Trace::RailsOff);
let ident = seq.read_ident().unwrap_lite();
ringbuf_entry!(Trace::Ident(ident));
loop {
let mut status = [0u8];
seq.read_bytes(Addr::PWR_CTRL, &mut status).unwrap_lite();
ringbuf_entry!(Trace::A2Status(status[0]));
if status[0] == 0 {
break;
}
hl::sleep_for(1);
}
//
// If our clock generator is configured to load from external EEPROM,
// we need to wait for up to 150 ms here (!).
//
hl::sleep_for(150);
//
// And now load our clock configuration
//
let clockgen = i2c_config::devices::idt8a34003(I2C.get_task_id())[0];
payload::idt8a3xxxx_payload(|buf| match clockgen.write(buf) {
Err(err) => Err(err),
Ok(_) => {
ringbuf_entry!(Trace::ClockConfigWrite);
Ok(())
}
})?;
// Populate packrat with our mac address and identity.
let packrat = Packrat::from(PACKRAT.get_task_id());
read_vpd_and_load_packrat(&packrat, I2C.get_task_id());
jefe.set_state(PowerState::A2 as u32);
ringbuf_entry!(Trace::ClockConfigSuccess);
ringbuf_entry_v3p3_sys_a0_vout();
ringbuf_entry!(Trace::A2);
// After declaring A2 but before transitioning to A0 (either automatically
// or in response to an IPC), populate packrat with EEPROM contents for use
// by the SPD task.
//
// Per JEDEC 1791.12a, we must wait for tINIT (10ms) between power on and
// sending the first SPD command.
hl::sleep_for(10);
read_spd_data_and_load_packrat(&packrat, I2C.get_task_id())?;
// Turn on the chassis LED once we reach A2
sys.gpio_set(CHASSIS_LED);
let (device, rail) = i2c_config::pmbus::vdd_vcore(I2C.get_task_id());
let mut server = Self {
state: PowerState::A2,
sys: sys.clone(),
seq,
jefe,
hf,
deadline: 0,
vcore: vcore::VCore::new(sys, &device, rail),
};
// Power on, unless suppressed by the `stay-in-a2` feature
if !cfg!(feature = "stay-in-a2") {
_ = server.set_state_internal(PowerState::A0);
}
//
// Configure the NMI pin. Note that this needs to be configured as open
// drain rather than push/pull: SP_TO_SP3_NMI_SYNC_FLOOD_L is pulled up
// to V3P3_SYS_A0 (by R5583) and we do not want to backdrive it when in
// A2, lest we prevent the PCA9535 GPIO expander (U307) from resetting!
//
sys.gpio_set(SP_TO_SP3_NMI_SYNC_FLOOD_L);
sys.gpio_configure_output(
SP_TO_SP3_NMI_SYNC_FLOOD_L,
sys_api::OutputType::OpenDrain,
sys_api::Speed::Low,
sys_api::Pull::None,
);
// Clear the external fault now that we're about to start serving messages
// and fewer things can go wrong.
sys.gpio_set(FAULT_PIN_L);
Ok(server)
}
}
impl<S: SpiServer> NotificationHandler for ServerImpl<S> {
fn current_notification_mask(&self) -> u32 {
notifications::TIMER_MASK | self.vcore.mask()
}
fn handle_notification(&mut self, bits: u32) {
if (bits & self.vcore.mask()) != 0 {
self.vcore.handle_notification();
}
if (bits & notifications::TIMER_MASK) == 0 {
return;
}
let ifr = self.seq.read_byte(Addr::IFR).unwrap_lite();
ringbuf_entry!(Trace::Status {
ier: self.seq.read_byte(Addr::IER).unwrap_lite(),
ifr,
amd_status: self.seq.read_byte(Addr::AMD_STATUS).unwrap_lite(),
amd_a0: self.seq.read_byte(Addr::AMD_A0).unwrap_lite(),
});
if self.state == PowerState::A0 || self.state == PowerState::A0PlusHP {
//
// The first order of business is to check if sequencer saw a
// falling edge on PWROK (denoting a reset) or a THERMTRIP. If it
// did, we will go to A0Reset or A0Thermtrip as appropriate (and
// if both are indicated, we will clear both conditions -- but
// land in A0Thermtrip).
//
self.check_reset(ifr);
self.check_thermtrip(ifr);
//
// Now we need to check NIC_PWREN_L to assure that our power state
// matches it, clearing or setting NIC_CTRL in the sequencer as
// needed.
//
let sys = sys_api::Sys::from(SYS.get_task_id());
let pwren_l = sys.gpio_read(NIC_PWREN_L_PINS) != 0;
let cld_rst = Reg::NIC_CTRL::CLD_RST;
match (self.state, pwren_l) {
(PowerState::A0, false) => {
ringbuf_entry!(Trace::NICPowerEnableLow(pwren_l));
self.seq
.clear_bytes(Addr::NIC_CTRL, &[cld_rst])
.unwrap_lite();
self.update_state_internal(PowerState::A0PlusHP);
}
(PowerState::A0PlusHP, true) => {
ringbuf_entry!(Trace::NICPowerEnableLow(pwren_l));
//
// The NIC was powered on, but is now being powered off.
// Something might be wrong, so record the sequencer's NIC
// registers.
//
ringbuf_entry!(Trace::NICStatus {
nic_ctrl: self
.seq
.read_byte(Addr::NIC_CTRL)
.unwrap_lite(),
nic_status: self
.seq
.read_byte(Addr::NIC_STATUS)
.unwrap_lite(),
out_status_nic1: self
.seq
.read_byte(Addr::OUT_STATUS_NIC1)
.unwrap_lite(),
out_status_nic2: self
.seq
.read_byte(Addr::OUT_STATUS_NIC2)
.unwrap_lite(),
});
self.seq
.set_bytes(Addr::NIC_CTRL, &[cld_rst])
.unwrap_lite();
self.update_state_internal(PowerState::A0);
}
(PowerState::A0, true) | (PowerState::A0PlusHP, false) => {
//
// Our power state matches NIC_PWREN_L -- nothing to do
//
}
(PowerState::A0Reset, _) | (PowerState::A0Thermtrip, _) => {
//
// We must have just sent ourselves here; nothing to do.
//
}
(PowerState::A2, _)
| (PowerState::A2PlusFans, _)
| (PowerState::A1, _) => {
//
// We can only be in this larger block if the state is A0
// or A0PlusHP; we must have matched one of the arms above.
// (We deliberately exhaustively match on power state to
// force any power state addition to consider this case.)
//
unreachable!();
}
}
}
if let Some(interval) = self.poll_interval() {
self.deadline += interval;
sys_set_timer(Some(self.deadline), notifications::TIMER_MASK);
}
}
}
fn retry_i2c_txn<T, E>(
which: I2cTxn,
mut txn: impl FnMut() -> Result<T, E>,
) -> Result<T, i2c::ResponseCode>
where
i2c::ResponseCode: From<E>,
{
// Chosen by fair dice roll, seems reasonable-ish?
let mut retries_remaining = 3;
loop {
match txn() {
Ok(x) => return Ok(x),
Err(e) => {
let code = e.into();
ringbuf_entry!(Trace::I2cError { txn: which, code });
if retries_remaining == 0 {
ringbuf_entry!(Trace::I2cFault(which));
return Err(code);
}
ringbuf_entry!(Trace::I2cRetry {
txn: which,
retries_remaining
});
retries_remaining -= 1;
}
}
}
}
impl<S: SpiServer> ServerImpl<S> {
fn update_state_internal(&mut self, state: PowerState) {
ringbuf_entry!(Trace::UpdateState(state));
self.state = state;
self.jefe.set_state(state as u32);
}
fn set_state_internal(
&mut self,
state: PowerState,
) -> Result<(), SeqError> {
let sys = sys_api::Sys::from(SYS.get_task_id());
let now = sys_get_timer().now;
ringbuf_entry!(Trace::SetState(self.state, state, now));
ringbuf_entry_v3p3_sys_a0_vout();
ringbuf_entry!(Trace::PGStatus {
b_pg: self.seq.read_byte(Addr::GROUPB_PG).unwrap_lite(),
c_pg: self.seq.read_byte(Addr::GROUPC_PG).unwrap_lite(),
nic: self.seq.read_byte(Addr::NIC_STATUS).unwrap_lite(),
});
ringbuf_entry!(Trace::SMStatus {
a1: self.seq.read_byte(Addr::A1SMSTATUS).unwrap_lite(),
a0: self.seq.read_byte(Addr::A0SMSTATUS).unwrap_lite(),
});
ringbuf_entry!(Trace::PowerControl(
self.seq.read_byte(Addr::PWR_CTRL).unwrap_lite(),
));
ringbuf_entry!(Trace::InterruptFlags(
self.seq.read_byte(Addr::IFR).unwrap_lite(),
));
match (self.state, state) {
(PowerState::A2, PowerState::A0) => {
//
// First, set our mux state to be the HostCPU
//
if self.hf.set_mux(hf_api::HfMuxState::HostCPU).is_err() {
return Err(SeqError::MuxToHostCPUFailed);
}
//
// If we fail to initialize our UV warning despite retries, we
// will drive on: the failures will be logged, and this isn't
// strictly required to sequence.
//
_ = retry_i2c_txn(I2cTxn::VCoreUndervoltageInitialize, || {
self.vcore.initialize_uv_warning()
});
let start = sys_get_timer().now;
let deadline = start + A0_TIMEOUT_MILLIS;
//
// We are going to pass through A1 on the way to A0. A1 is
// more or less an implementation detail of our journey to A0,
// but we'll stop there long enough to check our presence and
// CPU type: if we don't have a CPU (or have the wrong type)
// we want to fail cleanly rather than have the appearance of
// failing to sequence.
//
let a1 = Reg::PWR_CTRL::A1PWREN;
self.seq.write_bytes(Addr::PWR_CTRL, &[a1]).unwrap_lite();
loop {
let mut status = [0u8];
self.seq
.read_bytes(Addr::A1SMSTATUS, &mut status)
.unwrap_lite();
ringbuf_entry!(Trace::A1Status(status[0]));
if status[0] == Reg::A1SMSTATUS::A1SmEncoded::Done as u8 {
break;
}
if sys_get_timer().now > deadline {
return Err(self.a0_failure(SeqError::A1Timeout));
}
hl::sleep_for(1);
}
//
// Check for CPU presence first, as this is the more likely
// failure.
//
let present = sys.gpio_read(CPU_PRESENT_L) == 0;
ringbuf_entry!(Trace::CPUPresent(present));
if !present {
return Err(self.a0_failure(SeqError::CPUNotPresent));
}
let coretype = sys.gpio_read(CORETYPE) != 0;
let sp3r1 = sys.gpio_read(SP3R1) != 0;
let sp3r2 = sys.gpio_read(SP3R2) != 0;
ringbuf_entry!(Trace::Coretype {
coretype,
sp3r1,
sp3r2
});
//
// Check that we have the type of CPU we expect: we expect
// CORETYPE to be high (not connected on Family 19h), SP3R1 to
// be high (not connected on Type-0/Type-1/Type-2), and SP3R2
// to be low (VSS on Type-0/Type-1/Type-2).
//
if !coretype || !sp3r1 || sp3r2 {
return Err(self.a0_failure(SeqError::UnrecognizedCPU));
}
//
// Onward to A0!
//
let a0 = Reg::PWR_CTRL::A0A_EN;
self.seq.write_bytes(Addr::PWR_CTRL, &[a0]).unwrap_lite();
loop {
let mut status = [0u8];
self.seq
.read_bytes(Addr::A0SMSTATUS, &mut status)
.unwrap_lite();
ringbuf_entry!(Trace::A0Status(status[0]));
if status[0] == Reg::A0SMSTATUS::A0SmEncoded::GroupcPg as u8
{
break;
}
if sys_get_timer().now > deadline {
return Err(self.a0_failure(SeqError::A0TimeoutGroupC));
}
hl::sleep_for(1);
}
//
// And power up!
//
if vcore_soc_on().is_err() {
// Uh-oh, the I2C write failed a bunch of times. Guess I'll
// die!
return Err(self.a0_failure(SeqError::I2cFault));
}
ringbuf_entry!(Trace::RailsOn);
//
// Now wait for the end of Group C.
//
loop {
let mut status = [0u8];
self.seq
.read_bytes(Addr::A0SMSTATUS, &mut status)
.unwrap_lite();
ringbuf_entry!(Trace::A0Power(status[0]));
if status[0] == Reg::A0SMSTATUS::A0SmEncoded::Done as u8 {
break;
}
if sys_get_timer().now > deadline {
return Err(self.a0_failure(SeqError::A0Timeout));
}
hl::sleep_for(1);
}
//
// And establish our timer to check SP3_TO_SP_NIC_PWREN_L.
//
self.deadline = set_timer_relative(
TIMER_INTERVAL,
notifications::TIMER_MASK,
);
//
// Finally, enable transmission to the SP3's UART
//
uart_sp_to_sp3_enable();
ringbuf_entry!(Trace::UartEnabled);
// Using wrapping_sub here because the timer is monotonic, so
// we, the programmers, know that now > start. rustc, the
// compiler, is not aware of this.
ringbuf_entry!(Trace::A0(
(sys_get_timer().now.wrapping_sub(start)) as u16
));
self.update_state_internal(PowerState::A0);
Ok(())
}
(PowerState::A0, PowerState::A2)
| (PowerState::A0PlusHP, PowerState::A2)
| (PowerState::A0Thermtrip, PowerState::A2)
| (PowerState::A0Reset, PowerState::A2) => {
//
// Flip the UART mux back to disabled
//
uart_sp_to_sp3_disable();
//
// To assure that we always enter A0 the same way, set CLD_RST
// in NIC_CTRL on our way back to A2.
//
let cld_rst = Reg::NIC_CTRL::CLD_RST;
self.seq.set_bytes(Addr::NIC_CTRL, &[cld_rst]).unwrap_lite();
//
// Start FPGA down-sequence. Clearing the enables immediately
// de-asserts PWR_GOOD to the SP3 processor which the EDS
// says is required before taking the rails out.
// We also need to be headed down before the rails get taken out
// so as not to trip a MAPO fault.
//
let a1a0 = Reg::PWR_CTRL::A1PWREN | Reg::PWR_CTRL::A0A_EN;
self.seq.clear_bytes(Addr::PWR_CTRL, &[a1a0]).unwrap_lite();
//
// FPGA de-asserts PWR_GOOD for 2 ms before yanking enables,
// we wait for a tick here to make sure the SPI command to the
// FPGA propagated and the FPGA has had time to act. AMD's EDS
// doesn't give a minimum time so we'll give them 1 ms.
//
hl::sleep_for(1);
if vcore_soc_off().is_err() {
return Err(SeqError::I2cFault);
}
if self.hf.set_mux(hf_api::HfMuxState::SP).is_err() {
return Err(SeqError::MuxToSPFailed);
}
self.update_state_internal(PowerState::A2);
ringbuf_entry_v3p3_sys_a0_vout();
ringbuf_entry!(Trace::A2);
//
// Our rails should be draining. We'll take two additional
// measurements (for a total of three) each 100 ms apart.
//
for _i in 0..2 {
hl::sleep_for(100);
ringbuf_entry_v3p3_sys_a0_vout();
}
Ok(())
}
_ => Err(SeqError::IllegalTransition),
}
}
fn a0_failure(&mut self, err: SeqError) -> SeqError {
let record_reg = |addr| {
ringbuf_entry!(Trace::A0FailureDetails(
addr,
self.seq.read_byte(addr).unwrap_lite(),
));
};
//
// We are not going to space today. Record information in our ring
// buffer to allow this to be debugged.
//
ringbuf_entry!(Trace::A0Failed(err));
record_reg(Addr::IFR);
record_reg(Addr::DBG_MAX_A0SMSTATUS);
record_reg(Addr::MAX_GROUPB_PG);
record_reg(Addr::MAX_GROUPC_PG);
record_reg(Addr::FLT_A0_SMSTATUS);
record_reg(Addr::FLT_GROUPB_PG);
record_reg(Addr::FLT_GROUPC_PG);
//
// Now put ourselves back in A2.
//
let a1a0 = Reg::PWR_CTRL::A1PWREN | Reg::PWR_CTRL::A0A_EN;
self.seq.clear_bytes(Addr::PWR_CTRL, &[a1a0]).unwrap_lite();
hl::sleep_for(1);
// If this I2C write fails, that's bad news, but we're already dying...
let _ = vcore_soc_off();
_ = self.hf.set_mux(hf_api::HfMuxState::SP);
err
}
//
// Check for a THERMTRIP, sending ourselves to A0Thermtrip if we've
// seen it (and knowing that the FPGA has already taken care of the
// time-critical bits to assure that we don't melt!).
//
fn check_thermtrip(&mut self, ifr: u8) {
let thermtrip = Reg::IFR::THERMTRIP;
if ifr & thermtrip != 0 {
self.seq.clear_bytes(Addr::IFR, &[thermtrip]).unwrap_lite();
self.update_state_internal(PowerState::A0Thermtrip);
}
}
//
// Check for a reset by looking for a latched falling edge on PWROK.
// (Host software explicitly configures this by setting rsttocpupwrgden
// in FCH::PM::RESETCONTROL1.) The sequencer also latches the number of
// such edges that it has seen -- along with the number of falling edges
// of RESET_L. If we have seen a host reset, we send ourselves to
// A0Reset.
//
fn check_reset(&mut self, ifr: u8) {
let pwrok_fedge = Reg::IFR::AMD_PWROK_FEDGE;
if ifr & pwrok_fedge != 0 {
let mut cnts = [0u8; 2];
const_assert!(Addr::AMD_RSTN_CNTS.precedes(Addr::AMD_PWROKN_CNTS));
self.seq
.read_bytes(Addr::AMD_RSTN_CNTS, &mut cnts)
.unwrap_lite();
let (rstn, pwrokn) = (cnts[0], cnts[1]);
ringbuf_entry!(Trace::ResetCounts { rstn, pwrokn });
//
// Clear the counts to denote that we wish to re-latch any
// falling PWROK/RESET_L edge.
//
self.seq
.write_bytes(Addr::AMD_RSTN_CNTS, &[0, 0])
.unwrap_lite();