generation_id/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implementation of Generation ID services (shared across both PCAT and UEFI)
5
6#![forbid(unsafe_code)]
7
8use guestmem::GuestMemory;
9use inspect::InspectMut;
10use mesh::RecvError;
11use std::task::Context;
12use std::task::Poll;
13use vmcore::line_interrupt::LineInterrupt;
14
15/// Various runtime objects used by the GenerationId device.
16#[expect(missing_docs)] // self-explanatory fields
17pub struct GenerationIdRuntimeDeps {
18    pub gm: GuestMemory,
19    pub generation_id_recv: mesh::Receiver<[u8; 16]>,
20    pub notify_interrupt: LineInterrupt,
21}
22
23/// Device providing initial and dynamic Generation ID update capabilities.
24#[derive(InspectMut)]
25#[inspect(extra = "GenerationId::inspect_extra")]
26pub struct GenerationId {
27    // Runtime glue
28    #[inspect(skip)]
29    rt: GenerationIdRuntimeDeps,
30
31    // Volatile state
32    id: [u8; 16],
33    #[inspect(with = "ptr_to_opt_u64")]
34    ptr: [Option<u32>; 2],
35}
36
37impl GenerationId {
38    fn inspect_extra(&mut self, resp: &mut inspect::Response<'_>) {
39        resp.field_mut_with("force_update", |update| {
40            let v = if let Some(update) = update {
41                let update = update.parse()?;
42                if update {
43                    let mut id = [0; 16];
44                    getrandom::fill(&mut id).unwrap();
45                    self.set_and_update_generation_id(id);
46                    tracing::info!("Force updated genid...");
47                }
48                update
49            } else {
50                false
51            };
52            Result::<_, std::str::ParseBoolError>::Ok(v.to_string())
53        });
54    }
55}
56
57impl GenerationId {
58    /// Construct a new GenerationId device.
59    pub fn new(initial_generation_id: [u8; 16], platform: GenerationIdRuntimeDeps) -> Self {
60        Self {
61            id: initial_generation_id,
62            ptr: [None, None],
63            rt: platform,
64        }
65    }
66
67    /// Reset the GenerationId state back to what it was when first constructed.
68    pub fn reset(&mut self) {
69        // Just reset the pointer, not the ID. Since this is not a "time travel"
70        // event, there is no need to change the ID.
71        self.ptr = [None; 2];
72    }
73
74    /// Update the low bits of the generation id pointer.
75    pub fn write_generation_id_low(&mut self, data: u32) {
76        self.set_ptr(0, data)
77    }
78
79    /// Update the high bits of the generation id pointer.
80    pub fn write_generation_id_high(&mut self, data: u32) {
81        self.set_ptr(1, data)
82    }
83
84    /// Polls for new generation IDs.
85    pub fn poll(&mut self, cx: &mut Context<'_>) {
86        while let Poll::Ready(val) = self.rt.generation_id_recv.poll_recv(cx) {
87            match val {
88                Ok(val) => self.set_and_update_generation_id(val),
89                Err(RecvError::Closed) => break,
90                Err(err) => {
91                    tracing::error!(
92                        error = &err as &dyn std::error::Error,
93                        "Error receiving generation ID"
94                    );
95                    break;
96                }
97            }
98        }
99    }
100
101    /// Attempts to write the current generation ID into guest memory at
102    /// the specified location by the BIOS (PCAT/UEFI).
103    fn update_generation_id(&self) {
104        if let Some(ptr) = ptr_to_opt_u64(&self.ptr) {
105            if let Err(e) = self.rt.gm.write_at(ptr, &self.id) {
106                tracelimit::error_ratelimited!(
107                    error = &e as &dyn std::error::Error,
108                    "failed to write generation ID"
109                )
110            }
111        }
112    }
113
114    /// Sets and updates generation id
115    fn set_and_update_generation_id(&mut self, val: [u8; 16]) {
116        self.id = val;
117        self.update_generation_id();
118
119        // Pulse the interrupt line.
120        //
121        // Ideally there would be some notification from the firmware to clear
122        // the interrupt line, but the current firmware DSDT implementations do
123        // not do this.
124        self.rt.notify_interrupt.set_level(true);
125        self.rt.notify_interrupt.set_level(false);
126    }
127
128    fn set_ptr(&mut self, index: usize, data: u32) {
129        self.ptr[index] = Some(data);
130        self.update_generation_id();
131    }
132}
133
134/// Convert the lo/hi pair of genid prt registers to a single u64, returning
135/// `None` if either ho/lo register has yet to be set.
136fn ptr_to_opt_u64(ptr: &[Option<u32>; 2]) -> Option<u64> {
137    Some(((ptr[1]? as u64) << 32) | ptr[0]? as u64)
138}
139
140mod save_restore {
141    use super::*;
142    use vmcore::save_restore::RestoreError;
143    use vmcore::save_restore::SaveError;
144    use vmcore::save_restore::SaveRestore;
145
146    mod state {
147        use mesh::payload::Protobuf;
148
149        #[derive(Protobuf)]
150        #[mesh(package = "firmware.generation_id")]
151        pub struct SavedState {
152            #[mesh(1)]
153            pub id: [u8; 16],
154            #[mesh(2)]
155            pub ptr: [Option<u32>; 2],
156        }
157    }
158
159    impl SaveRestore for GenerationId {
160        type SavedState = state::SavedState;
161
162        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
163            let Self { rt: _, id, ptr } = *self;
164
165            let saved_state = state::SavedState { id, ptr };
166            Ok(saved_state)
167        }
168
169        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
170            let state::SavedState { id, ptr } = state;
171
172            self.id = id;
173            self.ptr = ptr;
174            Ok(())
175        }
176    }
177}