-
Notifications
You must be signed in to change notification settings - Fork 235
Expand file tree
/
Copy pathmain.rs
More file actions
2136 lines (2001 loc) · 86.3 KB
/
Copy pathmain.rs
File metadata and controls
2136 lines (2001 loc) · 86.3 KB
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/.
#![no_std]
#![no_main]
#[cfg(any(feature = "stm32h743", feature = "stm32h753"))]
use drv_stm32h7_usart as drv_usart;
use attest_data::messages::{
HostToRotCommand, MAX_DATA_LEN, RecvSprotError as AttestDataSprotError,
RotToHost,
};
use drv_cpu_seq_api::{
PowerState, SeqError, Sequencer, StateChangeReason, Transition,
};
use drv_hf_api::{HfDevSelect, HfMuxState, HostFlash};
use drv_sprot_api::SpRot;
use drv_stm32xx_sys_api as sys_api;
use drv_usart::Usart;
use enum_map::Enum;
use heapless::Vec;
use host_sp_messages::{
Bsu, DecodeFailureReason, Header, HostToSp, Key, KeyLookupResult,
KeySetResult, MAX_MESSAGE_SIZE, MIN_SP_TO_HOST_FILL_DATA_LEN, SpToHost,
Status,
};
use hubpack::SerializedSize;
use idol_runtime::{NotificationHandler, RequestError};
use microcbor::Encode;
use multitimer::{Multitimer, Repeat};
use ringbuf::{counted_ringbuf, ringbuf_entry};
use static_assertions::const_assert;
use static_cell::ClaimOnceCell;
use task_control_plane_agent_api::{
ControlPlaneAgent, MAX_INSTALLINATOR_IMAGE_ID_LEN,
};
use task_host_sp_comms_api::HostSpCommsError;
use task_net_api::Net;
use task_packrat_api::Packrat;
use userlib::{
FromPrimitive, UnwrapLite, sys_get_timer, sys_irq_control, task_slot,
};
mod inventory;
use inventory::INVENTORY_API_VERSION;
#[cfg_attr(
any(
target_board = "gimlet-b",
target_board = "gimlet-c",
target_board = "gimlet-d",
target_board = "gimlet-e",
target_board = "gimlet-f",
),
path = "bsp/gimlet_bcde.rs"
)]
#[cfg_attr(target_board = "gimletlet-2", path = "bsp/gimletlet.rs")]
#[cfg_attr(
any(target_board = "grapefruit-a", target_board = "grapefruit-b",),
path = "bsp/grapefruit.rs"
)]
#[cfg_attr(
any(target_board = "cosmo-a", target_board = "cosmo-b",),
path = "bsp/cosmo_ab.rs"
)]
mod bsp;
use bsp::SP_TO_HOST_CPU_INT_L;
mod tx_buf;
use tx_buf::TxBuf;
task_slot!(CONTROL_PLANE_AGENT, control_plane_agent);
task_slot!(CPU_SEQ, cpu_seq);
task_slot!(PACKRAT, packrat);
task_slot!(NET, net);
task_slot!(SYS, sys);
task_slot!(SPROT, sprot);
task_slot!(pub HOST_FLASH, hf);
// TODO: When rebooting the host, we need to wait for the relevant power rails
// to decay. We ought to do this properly by monitoring the rails, but for now,
// we'll simply wait a fixed period of time. This time is a WAG - we should
// fix this!
const A2_REBOOT_DELAY: u64 = 5_000;
// How frequently should we try to send 0x00 bytes to the host? This only
// applies if our current tx_buf/rx_buf are empty (i.e., we don't have a real
// response to send, and we haven't yet started to receive a request).
const UART_ZERO_DELAY: u64 = 200;
// How long of a host panic / boot fail message are we willing to keep?
const MAX_HOST_FAIL_MESSAGE_LEN: usize = 4096;
// How many MAC addresses should we report to the host? Per RFD 320, a gimlet
// currently needs 5 total:
//
// * 2 for the T6
// * 2 for the management network (already claimed by `net`)
// * 1 for the bootstrap network prefix
//
// Subtracting out the 2 already claimed by `net`, we will only give the host 3
// MAC addresses, even if `net` tells us more are available. In the future, if
// we need to increase the number given to the host, that's easy to do here; if
// we need to increase the number used by the SP, ideally `net` will take care
// of that for us.
const NUM_HOST_MAC_ADDRESSES: u16 = 3;
// The same IO path can be used for both IPCC, and a lower-level debugging
// interface, for testing the IO path itself. We differentiate between the two
// by detecting specific header types, and parsing the corresponding messages
// into a typed enum: debug message handling is then short-circuited.
enum DebugCmd<'a> {
Discard,
Echo(&'a [u8]),
CharGen(u16),
}
#[derive(Debug, Clone, Copy, PartialEq, counters::Count)]
enum Trace {
#[count(skip)]
None,
UartRxOverrun,
ParseError(#[count(children)] DecodeFailureReason),
DebugDiscard,
DebugEcho(u64),
DebugCharGen(u16),
SetState {
now: u64,
#[count(children)]
state: PowerState,
why: StateChangeReason,
},
AlreadyInState {
now: u64,
#[count(children)]
state: PowerState,
why: StateChangeReason,
},
HfMux {
now: u64,
state: Option<HfMuxState>,
},
JefeNotification {
now: u64,
#[count(children)]
state: PowerState,
},
OutOfSyncRequest,
OutOfSyncRxNoise,
Request {
now: u64,
sequence: u64,
#[count(children)]
message: HostToSp,
},
ResponseBufferReset {
now: u64,
},
Response {
now: u64,
sequence: u64,
#[count(children)]
message: SpToHost,
},
ApobWriteError {
offset: u32,
#[count(children)]
err: drv_hf_api::ApobWriteError,
},
ApobReadError {
offset: u32,
#[count(children)]
err: drv_hf_api::ApobReadError,
},
}
counted_ringbuf!(Trace, 20, Trace::None);
#[derive(Debug, Clone, Copy, PartialEq)]
enum TimerDisposition {
LeaveRunning,
Cancel,
}
/// We set the high bit of the sequence number before replying to host requests.
const SEQ_REPLY: u64 = 0x8000_0000_0000_0000;
/// We wrap host/sp messages in corncobs; derive our max packet length from the
/// max unwrapped message length.
const MAX_PACKET_SIZE: usize = corncobs::max_encoded_len(MAX_MESSAGE_SIZE);
#[derive(Copy, Clone, Enum)]
enum Timers {
/// Timer set when we're waiting in A2 before moving back to A0 for a
/// reboot.
WaitingInA2ToReboot,
/// Timer set when we want to send periodic 0x00 bytes on the uart.
TxPeriodicZeroByte,
}
#[unsafe(export_name = "main")]
fn main() -> ! {
let mut server = ServerImpl::claim_static_resources();
// Set our restarted status, which interrupts the host to let them know.
server.set_status_impl(Status::SP_TASK_RESTARTED);
sys_irq_control(notifications::USART_IRQ_MASK, true);
let mut buffer = [0; idl::INCOMING_SIZE];
loop {
idol_runtime::dispatch(&mut buffer, &mut server);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RebootState {
// We've instructed the sequencer to transition to A2; we're waiting to see
// the notification from jefe that that transition has occurred.
WaitingForA2,
// We're in our reboot delay (see `A2_REBOOT_DELAY`). When we transition to
// this state we start our `WaitingInA2ToReboot` timer; when it fires we'll
// transition to A0.
WaitingInA2RebootDelay,
}
const MAX_ETC_SYSTEM_LEN: usize = 256;
const MAX_DTRACE_CONF_LEN: usize = 4096;
// Storage we set aside for any messages where the host wants us to remember
// data for later read back (either by the host itself or by the control plane
// via MGS).
struct HostKeyValueStorage {
last_boot_fail_reason: u8,
last_boot_fail: &'static mut [u8; MAX_HOST_FAIL_MESSAGE_LEN],
last_panic: &'static mut [u8; MAX_HOST_FAIL_MESSAGE_LEN],
etc_system: &'static mut [u8; MAX_ETC_SYSTEM_LEN],
etc_system_len: usize,
dtrace_conf: &'static mut [u8; MAX_DTRACE_CONF_LEN],
dtrace_conf_len: usize,
}
impl HostKeyValueStorage {
fn key_set(&mut self, key: u8, data: &[u8]) -> KeySetResult {
let Some(key) = Key::from_u8(key) else {
return KeySetResult::InvalidKey;
};
let (buf, buf_len) = match key {
// Some keys should not be set by the host:
//
// * `Ping` always returns PONG
// * InstallinatorImageId is set via MGS
// * InventorySize always returns our static inventory size
Key::Ping | Key::InstallinatorImageId | Key::InventorySize => {
return KeySetResult::ReadOnlyKey;
}
Key::EtcSystem => {
(self.etc_system.as_mut_slice(), &mut self.etc_system_len)
}
Key::DtraceConf => {
(self.dtrace_conf.as_mut_slice(), &mut self.dtrace_conf_len)
}
};
if data.len() > buf.len() {
KeySetResult::DataTooLong
} else {
buf[..data.len()].copy_from_slice(data);
*buf_len = data.len();
KeySetResult::Ok
}
}
}
/// Metadata about panics observed from the host
struct HostPanicMetadata {
/// Length in bytes of the currently stored panic message
/// (not currently used, will be used in https://github.com/oxidecomputer/hubris/issues/2504)
_total_length: usize,
/// (hopefully not) Rolling counter of panic messages observed this power cycle
total_count: u32,
}
struct ServerImpl {
uart: Usart,
sys: sys_api::Sys,
timers: Multitimer<Timers>,
tx_buf: TxBuf,
rx_buf: &'static mut Vec<u8, MAX_PACKET_SIZE>,
status: Status,
sequencer: Sequencer,
hf: HostFlash,
net: Net,
cp_agent: ControlPlaneAgent,
packrat: Packrat,
sprot: SpRot,
reboot_state: Option<RebootState>,
host_kv_storage: HostKeyValueStorage,
hf_mux_state: Option<HfMuxState>,
ereporter: Ereporter,
host_panic_state: Option<HostPanicMetadata>,
host_boot_fail_state: Option<HostPanicMetadata>,
/// Temporary space for inventory data, which is a large `enum`
scratch: &'static mut host_sp_messages::InventoryData,
/// Scratch buffer for reading barcodes out of EEPROMs.
///
/// MPN1 barcodes can be up to 128 bytes long, so this is better kept off
/// the stack.
// This is not used on dev board targets.
#[cfg(not(any(
target_board = "grapefruit-a",
target_board = "grapefruit-b",
target_board = "gimletlet-2"
)))]
barcode_buf: &'static mut [u8; oxide_barcode::VpdIdentity::MAX_LEN],
/// Set when the host OS fails to boot or panics, and unset when the system
/// reboots.
///
/// This is used to determine whether a host-triggered power-off is due to a
/// kernel panic, boot failure, or was a normal power-off.
last_power_off: Option<StateChangeReason>,
}
impl ServerImpl {
fn claim_static_resources() -> Self {
let sys = sys_api::Sys::from(SYS.get_task_id());
let uart = configure_uart_device(&sys);
sp_to_sp3_interrupt_enable(&sys);
let mut timers = Multitimer::new(notifications::MULTITIMER_BIT);
timers.set_timer(
Timers::TxPeriodicZeroByte,
sys_get_timer().now,
Some(Repeat::AfterWake(UART_ZERO_DELAY)),
);
struct Bufs {
tx_buf: tx_buf::StaticBufs,
rx_buf: Vec<u8, MAX_PACKET_SIZE>,
last_boot_fail: [u8; MAX_HOST_FAIL_MESSAGE_LEN],
last_panic: [u8; MAX_HOST_FAIL_MESSAGE_LEN],
etc_system: [u8; MAX_ETC_SYSTEM_LEN],
dtrace_conf: [u8; MAX_DTRACE_CONF_LEN],
scratch: host_sp_messages::InventoryData,
#[cfg(not(any(
target_board = "grapefruit-a",
target_board = "grapefruit-b",
target_board = "gimletlet-2"
)))]
barcode_buf: [u8; oxide_barcode::VpdIdentity::MAX_LEN],
}
let &mut Bufs {
ref mut tx_buf,
ref mut rx_buf,
ref mut last_boot_fail,
ref mut last_panic,
ref mut etc_system,
ref mut dtrace_conf,
ref mut scratch,
#[cfg(not(any(
target_board = "grapefruit-a",
target_board = "grapefruit-b",
target_board = "gimletlet-2"
)))]
ref mut barcode_buf,
} = {
static BUFS: ClaimOnceCell<Bufs> = ClaimOnceCell::new(Bufs {
tx_buf: tx_buf::StaticBufs::new(),
rx_buf: Vec::new(),
last_boot_fail: [0; MAX_HOST_FAIL_MESSAGE_LEN],
last_panic: [0; MAX_HOST_FAIL_MESSAGE_LEN],
etc_system: [0; MAX_ETC_SYSTEM_LEN],
dtrace_conf: [0; MAX_DTRACE_CONF_LEN],
#[cfg(not(any(
target_board = "grapefruit-a",
target_board = "grapefruit-b",
target_board = "gimletlet-2"
)))]
barcode_buf: [0; oxide_barcode::VpdIdentity::MAX_LEN],
// Default value for InventoryData
scratch: host_sp_messages::InventoryData::DimmSpd {
id: [0u8; 512],
temp_sensor: 0u32,
},
});
BUFS.claim()
};
let packrat = Packrat::from(PACKRAT.get_task_id());
Self {
uart,
sys,
timers,
tx_buf: tx_buf::TxBuf::new(tx_buf),
rx_buf,
#[cfg(not(any(
target_board = "grapefruit-a",
target_board = "grapefruit-b",
target_board = "gimletlet-2"
)))]
barcode_buf,
status: Status::empty(),
sequencer: Sequencer::from(CPU_SEQ.get_task_id()),
hf: HostFlash::from(HOST_FLASH.get_task_id()),
net: Net::from(NET.get_task_id()),
cp_agent: ControlPlaneAgent::from(
CONTROL_PLANE_AGENT.get_task_id(),
),
packrat: packrat.clone(),
sprot: SpRot::from(SPROT.get_task_id()),
reboot_state: None,
host_kv_storage: HostKeyValueStorage {
last_boot_fail_reason: 0,
last_boot_fail,
last_panic,
etc_system,
etc_system_len: 0,
dtrace_conf,
dtrace_conf_len: 0,
},
hf_mux_state: None,
last_power_off: None,
scratch,
host_panic_state: None,
host_boot_fail_state: None,
ereporter: Ereporter::claim_static_resources(packrat),
}
}
fn set_status_impl(&mut self, status: Status) {
if status != self.status {
self.status = status;
// SP_TO_HOST_CPU_INT_L: `INT_L` is "interrupt low", so we assert the pin
// when we do not have status and deassert it when we do.
if self.status.is_empty() {
self.sys.gpio_set(SP_TO_HOST_CPU_INT_L);
} else {
self.sys.gpio_reset(SP_TO_HOST_CPU_INT_L);
}
}
}
fn update_hf_mux_state(&mut self) {
self.hf_mux_state = self.hf.get_mux().ok();
ringbuf_entry!(Trace::HfMux {
now: sys_get_timer().now,
state: self.hf_mux_state,
});
}
fn set_hf_mux_to_sp(&mut self) {
if self.hf_mux_state != Some(HfMuxState::SP) {
// This can only fail if the `hf` task panics, in which case the
// MUX will be set to the SP when it restarts.
let _ = self.hf.set_mux(HfMuxState::SP);
self.update_hf_mux_state();
}
}
/// Power off the host (i.e., transition to A2).
///
/// If `reboot` is true and we successfully instruct the sequencer to
/// transition to A2, we set `self.reboot_state` to
/// `RebootState::WaitingForA2`. Once we receive the notification from Jefe
/// that the transition is complete, we'll update that state to
/// `RebootState::WaitingInA2RebootDelay` and start our timer.
///
/// If we're not able to instruct the sequencer to transition to A2, we ask
/// the sequencer what the current state is and handle them
/// hopefully-appropriately:
///
/// 1. The sequencer reports the current state is A0 - our request to
/// transition to A2 should have succeeded, so presumably we hit a race
/// window. We retry.
/// 2. We're already in A2 - the host is already powered off. If `reboot` is
/// true, we set `self.reboot_state` to
/// `RebootState::WaitingInA2RebootDelay` and will attempt to move back
/// to A0 once we pass that deadline.
/// 3. We're in A1 - this state should be transitory, so we sleep and retry.
// TODO is error handling in this method correct? I think we should
// basically only ever succeed in our initial set_state() request, so I
// don't know how we'd test it
fn power_off_host(&mut self, reboot: bool) {
let why = match self.last_power_off {
None if reboot => StateChangeReason::HostReboot,
None => StateChangeReason::HostPowerOff,
Some(reason) => reason,
};
loop {
// Attempt to move to A2; given we only call this function in
// response to a host request, we expect we're currently in A0 and
// this should work.
match self.sequencer.set_state_with_reason(PowerState::A2, why) {
Ok(Transition::Changed) => {
ringbuf_entry!(Trace::SetState {
now: sys_get_timer().now,
why,
state: PowerState::A2,
});
if reboot {
self.reboot_state = Some(RebootState::WaitingForA2);
}
return;
}
Ok(Transition::Unchanged) => {
// We're already in A2.
ringbuf_entry!(Trace::AlreadyInState {
now: sys_get_timer().now,
why,
state: PowerState::A2,
});
// If we're not trying to reboot, we're done.
//
// TODO(eliza): perhaps we ought to have a way to indicate
// this to up-stack software...
if !reboot {
return;
}
}
Err(err) => {
// The only error we should see from `set_state()` is an illegal
// transition, if we're not currently in A0.
assert!(matches!(err, SeqError::IllegalTransition));
}
};
// If we can't go to A2, what state are we in, keeping in mind that
// we have a bit of TOCTOU here in that the state might've changed
// since we tried to `set_state()` above?
match self.sequencer.get_state() {
// If we're in A0, we should've been able to transition to A2;
// just repeat our loop and try again.
PowerState::A0
| PowerState::A0PlusHP
| PowerState::A0Thermtrip
| PowerState::A0Reset => continue,
// If we're already in A2 somehow, we're done.
PowerState::A2 | PowerState::A2PlusFans => {
if reboot {
// Somehow we're already in A2 when the host wanted to
// reboot; set our reboot timer.
//
// Using saturating add here because it's cheaper than
// potentially panicking, and timestamps won't saturate
// for 584 million years.
self.timers.set_timer(
Timers::WaitingInA2ToReboot,
sys_get_timer().now.saturating_add(A2_REBOOT_DELAY),
None,
);
self.reboot_state =
Some(RebootState::WaitingInA2RebootDelay);
}
return;
}
}
}
}
fn handle_jefe_notification(&mut self, state: PowerState) {
let now = sys_get_timer().now;
ringbuf_entry!(Trace::JefeNotification { now, state });
self.update_hf_mux_state();
// If we're rebooting and jefe has notified us that we're now in A2,
// move to A0. Otherwise, ignore this notification.
match state {
PowerState::A2 | PowerState::A2PlusFans => {
// Were we waiting for a transition to A2? If so, start our
// timer for going back to A0.
if self.reboot_state == Some(RebootState::WaitingForA2) {
self.timers.set_timer(
Timers::WaitingInA2ToReboot,
now + A2_REBOOT_DELAY,
None,
);
self.reboot_state =
Some(RebootState::WaitingInA2RebootDelay);
}
}
PowerState::A0Reset => {
// We have spontaneously reset. We are in A0 (and indeed,
// by time we get this, the ABL is presumably running), but
// we cannot let the SoC simply reset because the true state
// of hidden cores is unknown: explicitly bounce to A2
// as if the host had requested it.
self.last_power_off = Some(StateChangeReason::CpuReset);
self.power_off_host(true);
}
PowerState::A0 | PowerState::A0PlusHP | PowerState::A0Thermtrip => {
// Clear the last power-off, as we have now reached A0;
// subsequent power-offs will set a new reason.
self.last_power_off = None;
// TODO should we clear self.reboot_state here? What if we
// transitioned from one A0 state to another? For now, leave it
// set, and we'll move back to A0 whenever we transition to
// A2...
}
}
}
// State diagram for our uart handler:
//
// Start (main)
// │
//==========│========================================================
// ┌──────▼──────────────────────────────────────┐
// ┌─► Enable repeating Timers::TxPeriodicZeroByte ◄──┐
// │ └─────────────────────────────────────────────┘ │
//=│==================================================│==============
// │ ┌────────────────────┐ │success
// │ │ Are we waiting │ ┌──────────────┐ │
// │ │to build a response?│ ┌─►try to tx 0x00├─────┘
// │ └┬────────┬──────────┘ │ └─┬────────────┘
// │ │no │yes │ │
// │ │ ┌───▼────────┐ │ │TX FIFO full
// │ │ │Cancel timer│ │ ┌─▼─────────────┐
// │ │ └────────────┘ │ │Enable TX FIFO ◄────────────────┐
// │ │ │ │empty interrupt│ │
// │ ┌─▼────────────────────┐no│ └─┬─────────────┘ │
// │ │Do we have packet data├──┘ │ │
// │ │to tx, or have we rx'd│yes┌──▼────────────────────┐ │
// │ │ a partial packet? ├───►Wait for Uart interrupt◄───── ─┐ │
// │ └──────────────────────┘ └─┬─────────────────────┘ │ │
// │ │ │ │
// │ │interrupt received │ │
// │Timer Interrupt Handler │ │ │
//=│==============================▼=============================│=│==
// │Uart Interrupt Handler │ │
// │ ┌─────────────────────────────┐ │ │
// │ │Do we have packet data to tx?├───┐ │ │
// │ └──────────────┬───────▲──────┘ │yes │ │
// │ │no │ │ │ │
// │ ┌──────────▼────┐ │ ┌───────▼───────────┐ │ │
// │ │Disable TX FIFO│ │ │try to tx data byte│ │ │
// │ │empty interrupt│ │ └─┬──────────▲──┬───┘ │ │
// │ └──────────┬────┘ │ │success │ │tx fifo full │ │
// │ │ └────┘ │ │ │ │
// │ │ │ │ │ │
// │ fail ┌───▼──────────────┐◄─────┐ │ ┌▼────────────┐ │ │
// │ ┌────┤Try to rx one byte│ │ │ │Do we have an│ │ │no
// │ │ └───┬──────────────┘◄──┐ │ │ │out-of-order ├─┼─┘
// │ │ │success │ │ │ │request from │ │
// │ │ ┌───▼──────────────┐no │ │ │ │the host? │ │
// │ │ │ Is this a packet ├───┘ │ │ └─┬───────────┘ │
// │ │ │terminator (0x00)?│ │ │ │ │
// │ │ └───┬──────────────┘ │ │ ┌─▼─────────┐ │
// │ │ │yes │ │ │Discard any│ │
// │ │ ┌─▼────────────┐ yes │ │ │remaining │ │
// │ │ │Is this packet├────────┘ │ │tx data │ │
// │ │ │ empty? │ │ └──┬────────┘ │
// │ │ └─┬────────────┘ │ │ │
// │ │ │no │ │ │
// │ │ ┌─▼─────────────┐ │ │ │
// │ │ │Process Message◄─────────┼────┘ │
// │ │ └─┬─────────────┘ │ │
// │ │ │ │ │
// │ │ ┌─▼─────────────┐ yes │ │
// │ │ │ Do we have a ├─────────┘ │
// │ │ │response ready?│ │
// │ │ └─────┬─────────┘ ┌──────────────────────┐ │
// │ │ │ no │Wait to build │ │
// │ │ └────────────►response (notification│ │
// │ │ │from another task) │ │
// │ │ └──────────────────────┘ │
// │ ┌▼────────────────┐ │
// └────────┤ Have we rx'd ├─────────────────────────────────┘
// no │a partial packet?│ yes
// └─────────────────┘
fn handle_usart_notification(&mut self) {
'tx: loop {
// Clear any RX overrun errors. If we hit this, we will likely fail
// to decode the next message from the host, which will cause us to
// send a `DecodeFailure` response.
if self.uart.check_and_clear_rx_overrun() {
ringbuf_entry!(Trace::UartRxOverrun);
}
let mut processed_out_of_sync_message = false;
// Do we have data to transmit? If so, write as much as we can until
// either the fifo fills (in which case we return before trying to
// receive more) or we finish flushing.
while let Some(b) = self.tx_buf.next_byte_to_send() {
if self.uart.try_tx_push(b) {
self.tx_buf.advance_one_byte();
} else if self.uart_rx_until_maybe_packet() {
// We still have data to send, but the host has sent us a
// packet! First, we'll try to decode it: if that succeeds,
// something has gone wrong (from our point of view the host
// has broken protocol). We'll deal with this by:
//
// 1. Discarding any remaining data we have from the old
// response.
// 2. Sending a 0x00 terminator so the host can detect the
// end of that old (partial) packet.
// 3. Handling the new request.
//
// 1 and 2 are covered by calling `tx_buf.reset()`, which
// `process_message` does at our request only if the
// packet decodes successfully. If the packet does not
// decode successfully, we discard it and assume it was line
// noise.
match self.process_message(true) {
Ok(()) => {
processed_out_of_sync_message = true;
ringbuf_entry!(Trace::OutOfSyncRequest);
}
Err(_) => {
ringbuf_entry!(Trace::OutOfSyncRxNoise);
}
}
} else {
// We have more data to send but the TX FIFO is full; enable
// the TX FIFO empty interrupt and wait for it.
self.timers.clear_timer(Timers::TxPeriodicZeroByte);
self.uart.enable_tx_fifo_empty_interrupt();
return;
}
}
// We're done flushing data; disable the tx fifo interrupt.
self.uart.disable_tx_fifo_empty_interrupt();
// It's possible (but unlikely) we've already received a message in
// this loop iteration. If we have, skip trying to read a request
// here and move on to either looping back to start sending the
// response or setting up timers for future interrupts.
if !processed_out_of_sync_message
&& self.uart_rx_until_maybe_packet()
{
// We received a packet; handle it.
if let Err(reason) = self.process_message(false) {
self.tx_buf.encode_decode_failure_reason(reason);
}
}
// If we have data to send now, immediately loop back to the
// top and start trying to send it.
if self.tx_buf.next_byte_to_send().is_some() {
continue 'tx;
}
// We received everything we could out of the rx fifo and we have
// nothing to send; we're done.
//
// If we haven't receiving anything, set our timer to send out
// periodic zero bytes. If we have received something, leave the
// timer clear - we're waiting on more data from the host.
if self.rx_buf.is_empty() {
self.timers.set_timer(
Timers::TxPeriodicZeroByte,
sys_get_timer().now,
Some(Repeat::AfterWake(UART_ZERO_DELAY)),
);
} else {
self.timers.clear_timer(Timers::TxPeriodicZeroByte);
}
return;
}
}
fn uart_rx_until_maybe_packet(&mut self) -> bool {
while let Some(byte) = self.uart.try_rx_pop() {
if byte == 0x00 {
// COBS terminator; did we get any data?
if self.rx_buf.is_empty() {
continue;
} else {
return true;
}
}
// Not a COBS terminator; buffer it.
if self.rx_buf.push(byte).is_err() {
// Message overflow - nothing we can do here except
// discard data. We'll drop this byte and wait til we
// see a 0 to respond, at which point our
// deserialization will presumably fail and we'll send
// back an error. Should we record that we overflowed
// here?
}
}
false
}
fn handle_control_plane_agent_notification(&mut self) {
// If control-plane-agent notified us, presumably it's telling us that
// the data we asked it to fetch is ready.
if let Some(phase2) = self.tx_buf.get_waiting_for_phase2_data() {
// Borrow `cp_agent` to avoid borrowing `self` in the closure below.
let cp_agent = &self.cp_agent;
self.tx_buf.encode_response(
phase2.sequence,
&SpToHost::Phase2Data,
|dst| {
// Fetch the phase two data directly into `dst` (the buffer
// where we're serializing our response), which is maximally
// sized for what we can send the host in one packet. It is
// almost certainly larger than what control-plane-agent can
// fetch in a single UDP packet.
//
// If we can't get data, all we can do is send the host a
// response with no data; it can decide to retry later.
cp_agent
.get_host_phase2_data(phase2.hash, phase2.offset, dst)
.unwrap_or(0)
},
);
// Call our usart handler, because we now have data to send.
self.handle_usart_notification();
}
}
// Process a request message from the host.
fn process_message(
&mut self,
reset_tx_buf: bool,
) -> Result<(), DecodeFailureReason> {
// Debug messages have a distinct header that separates them from normal
// IPCC messages.
if is_debug_message(self.rx_buf) {
self.process_debug_message(reset_tx_buf)
} else {
self.process_ipcc_message(reset_tx_buf)
}
}
// Process a framed debug packet
fn process_debug_message(
&mut self,
reset_tx_buf: bool,
) -> Result<(), DecodeFailureReason> {
match parse_debug_message(self.rx_buf) {
Ok(cmd) => {
if reset_tx_buf {
self.tx_buf.reset();
}
match cmd {
DebugCmd::Discard => ringbuf_entry!(Trace::DebugDiscard),
DebugCmd::Echo(data) => {
ringbuf_entry!(Trace::DebugEcho(data.len() as u64));
let _ = self.tx_buf.try_copy_raw_data(data);
}
DebugCmd::CharGen(count) => {
ringbuf_entry!(Trace::DebugCharGen(count));
let mut it = (b'!'..=b'~').cycle().take(count.into());
let _ = self.tx_buf.try_fill(&mut it);
}
}
self.rx_buf.clear();
Ok(())
}
Err(err) => {
ringbuf_entry!(Trace::ParseError(err));
self.rx_buf.clear();
Err(err)
}
}
}
// Process the framed packet sitting in `self.rx_buf`. If it warrants a
// response, we configure `self.tx_buf` appropriate: either populating it
// with a response if we can come up with that response immediately, or
// instructing it that we'll fill it in with our response later.
//
// If `reset_tx_buf` is true AND we successfully decode a packet, we will
// call `self.tx_buf.reset()` prior to populating it with a response. This
// should only be set to true if we're being called in an "out of sync"
// path; see the comments in `handle_usart_notification()` where we check
// for an incoming request while we're still trying to send a previous
// response.
//
// This method always (i.e., on success or failure) clears `rx_buf` before
// returning to prepare for the next packet.
fn process_ipcc_message(
&mut self,
reset_tx_buf: bool,
) -> Result<(), DecodeFailureReason> {
let (header, request, data) = match parse_received_message(self.rx_buf)
{
Ok((header, request, data)) => (header, request, data),
Err(err) => {
ringbuf_entry!(Trace::ParseError(err));
self.rx_buf.clear();
return Err(err);
}
};
ringbuf_entry!(Trace::Request {
now: sys_get_timer().now,
sequence: header.sequence,
message: request,
});
// Reset tx_buf if our caller wanted us to in response to a valid
// packet.
if reset_tx_buf {
self.tx_buf.reset();
}
// If we receive an out-of-sequence message, then lock the APOB state
// machine. This makes it harder for malicious hosts to exfiltrate
// data via the host flash APOB slots.
match request {
HostToSp::KeyLookup { .. }
| HostToSp::GetBootStorageUnit
| HostToSp::GetIdentity
| HostToSp::GetStatus
| HostToSp::AckSpStart
| HostToSp::ApobBegin { .. }
| HostToSp::ApobData { .. }
| HostToSp::ApobRead { .. }
| HostToSp::ApobCommit
| HostToSp::BootStage { .. } => {
// These are explicitly allowed
}
_ => {
// Anything not allowed is prohibited!
self.hf.apob_lock();
}
}
// We defer any actions until after we've serialized our response to
// avoid borrow checker issues with calling methods on `self`.
let mut action = None;
let response = match request {
HostToSp::_Unused => {
Some(SpToHost::DecodeFailure(DecodeFailureReason::Deserialize))
}
HostToSp::RequestReboot => {
action = Some(Action::RebootHost);
None
}
HostToSp::RequestPowerOff => {
action = Some(Action::PowerOffHost);
None
}
HostToSp::GetBootStorageUnit => {
// Per RFD 241, the phase 1 device (which we can read via
// `hf`) is tightly bound to the BSU, so we can map flash0 to
// BSU A and flash1 to BSU B.
//
// What should we do if we fail to get the device from the host
// flash task? That should only happen if `hf` is unable to
// respond to us at all, which makes it seem unlikely that the
// host could even be up. We'll default to returning Bsu::A.
let bsu = match self.hf.get_dev() {
Ok(HfDevSelect::Flash0) | Err(_) => Bsu::A,
Ok(HfDevSelect::Flash1) => Bsu::B,
};
Some(SpToHost::BootStorageUnit(bsu))
}
HostToSp::GetIdentity => {
// gimlet-seq populates packrat with our identity from VPD prior
// to transitioning to A2; if the host has requested that
// identity, we're already in A0 and therefore don't have to
// wait for packrat. If `get_identity()` fails, it means the
// sequencer failed to read our VPD; all we can really do is
// send the host a null (default) identity.
let identity = self.packrat.get_identity().unwrap_or_default();
Some(SpToHost::Identity(identity.into()))
}
HostToSp::GetMacAddresses => {
let block = self.net.get_spare_mac_addresses();
let response = if block.count.get() > 0 {
let count =
u16::min(block.count.get(), NUM_HOST_MAC_ADDRESSES);
SpToHost::MacAddresses {
base: block.base_mac,
count,
stride: block.stride,
}
} else {
SpToHost::MacAddresses {
base: [0; 6],
count: 0,
stride: 0,
}
};
Some(response)
}
HostToSp::HostBootFailure { reason } => {
// Indicate that the host boot failed, so that we can then tell
// sequencer why we are asking it to power off the system.
self.last_power_off = Some(StateChangeReason::HostBootFailure);