| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright The Monogon Project Authors. |
| 3 | * SPDX-License-Identifier: Apache-2.0 |
| 4 | */ |
| 5 | |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 6 | #![no_main] |
| 7 | #![no_std] |
| 8 | |
| 9 | extern crate alloc; |
| 10 | |
| 11 | use alloc::vec::Vec; |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 12 | use core::fmt; |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 13 | use core::result::Result; |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 14 | use prost::Message; |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 15 | use uefi::boot::{self, ScopedProtocol}; |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 16 | use uefi::fs::FileSystem; |
| 17 | use uefi::proto::device_path::build::media::FilePath; |
| 18 | use uefi::proto::device_path::build::DevicePathBuilder; |
| 19 | use uefi::proto::device_path::{DeviceSubType, DeviceType, LoadedImageDevicePath}; |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 20 | use uefi::proto::media::fs::SimpleFileSystem; |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 21 | use uefi::{prelude::*, CStr16}; |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 22 | use uefi::println; |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 23 | |
| Jan Schär | 69b7687 | 2025-05-14 16:39:47 +0000 | [diff] [blame] | 24 | use abloader_proto::metropolis::node::abloader::spec::*; |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 25 | |
| 26 | const A_LOADER_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\boot-a.efi"); |
| 27 | const B_LOADER_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\boot-b.efi"); |
| 28 | |
| 29 | const LOADER_STATE_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\loader_state.pb"); |
| 30 | |
| 31 | enum ValidSlot { |
| 32 | A, |
| 33 | B, |
| 34 | } |
| 35 | |
| 36 | impl ValidSlot { |
| 37 | // other returns B if the value is A and A if the value is B. |
| 38 | fn other(&self) -> Self { |
| 39 | match self { |
| 40 | ValidSlot::A => ValidSlot::B, |
| 41 | ValidSlot::B => ValidSlot::A, |
| 42 | } |
| 43 | } |
| 44 | // path returns the path to the slot's EFI payload. |
| 45 | fn path(&self) -> &'static CStr16 { |
| 46 | match self { |
| 47 | ValidSlot::A => A_LOADER_PATH, |
| 48 | ValidSlot::B => B_LOADER_PATH, |
| 49 | } |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | enum ReadLoaderStateError { |
| 54 | FSReadError(uefi::fs::Error), |
| 55 | DecodeError(prost::DecodeError), |
| 56 | } |
| 57 | |
| 58 | impl fmt::Display for ReadLoaderStateError { |
| 59 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 60 | match self { |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 61 | ReadLoaderStateError::FSReadError(e) => write!(f, "while reading state file: {}", e), |
| 62 | ReadLoaderStateError::DecodeError(e) => write!(f, "while decoding state file contents: {}", e), |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 63 | } |
| 64 | } |
| 65 | } |
| 66 | |
| Tim Windelschmidt | 2f9f624 | 2025-01-11 08:25:54 +0100 | [diff] [blame] | 67 | fn read_loader_state(fs: &mut FileSystem) -> Result<AbLoaderData, ReadLoaderStateError> { |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 68 | let state_raw = fs.read(&LOADER_STATE_PATH).map_err(|e| ReadLoaderStateError::FSReadError(e))?; |
| Tim Windelschmidt | 2f9f624 | 2025-01-11 08:25:54 +0100 | [diff] [blame] | 69 | AbLoaderData::decode(state_raw.as_slice()).map_err(|e| ReadLoaderStateError::DecodeError(e)) |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 70 | } |
| 71 | |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 72 | fn load_slot_image(slot: &ValidSlot) -> uefi::Result<Handle> { |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 73 | let mut storage = Vec::new(); |
| 74 | |
| 75 | // Build the path to the slot payload. This takes the path to the loader |
| 76 | // itself, strips off the file path and following element(s) and appends |
| 77 | // the path to the correct slot payload. |
| 78 | let new_image_path = { |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 79 | let loaded_image_device_path = |
| 80 | boot::open_protocol_exclusive::<LoadedImageDevicePath>(boot::image_handle())?; |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 81 | |
| 82 | let mut builder = DevicePathBuilder::with_vec(&mut storage); |
| 83 | |
| 84 | for node in loaded_image_device_path.node_iter() { |
| 85 | if node.full_type() == (DeviceType::MEDIA, DeviceSubType::MEDIA_FILE_PATH) { |
| 86 | break; |
| 87 | } |
| 88 | |
| 89 | builder = builder.push(&node).unwrap(); |
| 90 | } |
| 91 | |
| 92 | builder = builder |
| 93 | .push(&FilePath { |
| 94 | path_name: slot.path(), |
| 95 | }) |
| 96 | .unwrap(); |
| 97 | |
| 98 | builder.finalize().unwrap() |
| 99 | }; |
| 100 | |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 101 | boot::load_image( |
| 102 | boot::image_handle(), |
| 103 | boot::LoadImageSource::FromDevicePath { |
| 104 | device_path: new_image_path, |
| 105 | boot_policy: uefi::proto::BootPolicy::ExactMatch, |
| 106 | }, |
| 107 | ) |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | #[entry] |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 111 | fn main() -> Status { |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 112 | let boot_slot_raw = { |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 113 | let esp_fs: ScopedProtocol<SimpleFileSystem> = |
| 114 | boot::get_image_file_system(boot::image_handle()) |
| 115 | .expect("image filesystem not available"); |
| 116 | let mut esp_fs = FileSystem::new(esp_fs); |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 117 | |
| 118 | let mut loader_data = match read_loader_state(&mut esp_fs) { |
| Jan Schär | 69b7687 | 2025-05-14 16:39:47 +0000 | [diff] [blame] | 119 | Ok(d) => d, |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 120 | Err(e) => { |
| 121 | println!("Unable to load A/B loader state, using default slot A: {}", e); |
| Tim Windelschmidt | 2f9f624 | 2025-01-11 08:25:54 +0100 | [diff] [blame] | 122 | AbLoaderData { |
| 123 | active_slot: Slot::A.into(), |
| 124 | next_slot: Slot::None.into(), |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 125 | } |
| 126 | } |
| 127 | }; |
| 128 | |
| 129 | // If next_slot is set, use it as slot to boot but clear it in the |
| 130 | // state file as the next boot should not use it again. If it should |
| Jan Schär | 69b7687 | 2025-05-14 16:39:47 +0000 | [diff] [blame] | 131 | // be permanently activated, it is the OS's job to put it into |
| Tim Windelschmidt | 2f9f624 | 2025-01-11 08:25:54 +0100 | [diff] [blame] | 132 | if loader_data.next_slot != Slot::None.into() { |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 133 | let next_slot = loader_data.next_slot; |
| Tim Windelschmidt | 2f9f624 | 2025-01-11 08:25:54 +0100 | [diff] [blame] | 134 | loader_data.next_slot = Slot::None.into(); |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 135 | let new_loader_data = loader_data.encode_to_vec(); |
| 136 | esp_fs |
| 137 | .write(&LOADER_STATE_PATH, new_loader_data) |
| 138 | .expect("failed to write back abdata"); |
| 139 | next_slot |
| 140 | } else { |
| 141 | loader_data.active_slot |
| 142 | } |
| 143 | }; |
| 144 | |
| Tim Windelschmidt | 2f9f624 | 2025-01-11 08:25:54 +0100 | [diff] [blame] | 145 | let boot_slot = match Slot::try_from(boot_slot_raw) { |
| 146 | Ok(Slot::A) => ValidSlot::A, |
| 147 | Ok(Slot::B) => ValidSlot::B, |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 148 | _ => { |
| 149 | println!("Invalid slot ({}) active, falling back to A", boot_slot_raw); |
| 150 | ValidSlot::A |
| 151 | } |
| 152 | }; |
| 153 | |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 154 | let payload_image = match load_slot_image(&boot_slot) { |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 155 | Ok(img) => img, |
| 156 | Err(e) => { |
| 157 | println!("Error loading intended slot, falling back to other slot: {}", e); |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 158 | match load_slot_image(&boot_slot.other()) { |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 159 | Ok(img) => img, |
| 160 | Err(e) => { |
| 161 | panic!("Loading from both slots failed, second slot error: {}", e); |
| 162 | }, |
| 163 | } |
| 164 | } |
| 165 | }; |
| 166 | |
| 167 | // Boot the payload. |
| Tim Windelschmidt | 10d47cc | 2025-07-01 00:34:51 +0200 | [diff] [blame^] | 168 | boot::start_image(payload_image).expect("failed to start payload"); |
| Lorenz Brun | 54a5a05 | 2023-10-02 16:40:11 +0200 | [diff] [blame] | 169 | Status::SUCCESS |
| 170 | } |