blob: 4389c1462ffe6f1e616c9d506aa63eee97d7db75 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001/*
2 * Copyright The Monogon Project Authors.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
Lorenz Brun54a5a052023-10-02 16:40:11 +02006#![no_main]
7#![no_std]
8
9extern crate alloc;
10
11use alloc::vec::Vec;
Lorenz Brun54a5a052023-10-02 16:40:11 +020012use core::fmt;
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +020013use core::result::Result;
Lorenz Brun54a5a052023-10-02 16:40:11 +020014use prost::Message;
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +020015use uefi::boot::{self, ScopedProtocol};
Lorenz Brun54a5a052023-10-02 16:40:11 +020016use uefi::fs::FileSystem;
17use uefi::proto::device_path::build::media::FilePath;
18use uefi::proto::device_path::build::DevicePathBuilder;
19use uefi::proto::device_path::{DeviceSubType, DeviceType, LoadedImageDevicePath};
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +020020use uefi::proto::media::fs::SimpleFileSystem;
Lorenz Brun54a5a052023-10-02 16:40:11 +020021use uefi::{prelude::*, CStr16};
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +020022use uefi::println;
Lorenz Brun54a5a052023-10-02 16:40:11 +020023
Jan Schär69b76872025-05-14 16:39:47 +000024use abloader_proto::metropolis::node::abloader::spec::*;
Lorenz Brun54a5a052023-10-02 16:40:11 +020025
26const A_LOADER_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\boot-a.efi");
27const B_LOADER_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\boot-b.efi");
28
29const LOADER_STATE_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\loader_state.pb");
30
31enum ValidSlot {
32 A,
33 B,
34}
35
36impl 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
53enum ReadLoaderStateError {
54 FSReadError(uefi::fs::Error),
55 DecodeError(prost::DecodeError),
56}
57
58impl fmt::Display for ReadLoaderStateError {
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 match self {
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +020061 ReadLoaderStateError::FSReadError(e) => write!(f, "while reading state file: {}", e),
62 ReadLoaderStateError::DecodeError(e) => write!(f, "while decoding state file contents: {}", e),
Lorenz Brun54a5a052023-10-02 16:40:11 +020063 }
64 }
65}
66
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +010067fn read_loader_state(fs: &mut FileSystem) -> Result<AbLoaderData, ReadLoaderStateError> {
Lorenz Brun54a5a052023-10-02 16:40:11 +020068 let state_raw = fs.read(&LOADER_STATE_PATH).map_err(|e| ReadLoaderStateError::FSReadError(e))?;
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +010069 AbLoaderData::decode(state_raw.as_slice()).map_err(|e| ReadLoaderStateError::DecodeError(e))
Lorenz Brun54a5a052023-10-02 16:40:11 +020070}
71
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +020072fn load_slot_image(slot: &ValidSlot) -> uefi::Result<Handle> {
Lorenz Brun54a5a052023-10-02 16:40:11 +020073 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 Windelschmidt10d47cc2025-07-01 00:34:51 +020079 let loaded_image_device_path =
80 boot::open_protocol_exclusive::<LoadedImageDevicePath>(boot::image_handle())?;
Lorenz Brun54a5a052023-10-02 16:40:11 +020081
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 Windelschmidt10d47cc2025-07-01 00:34:51 +0200101 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 Brun54a5a052023-10-02 16:40:11 +0200108}
109
110#[entry]
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +0200111fn main() -> Status {
Lorenz Brun54a5a052023-10-02 16:40:11 +0200112 let boot_slot_raw = {
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +0200113 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 Brun54a5a052023-10-02 16:40:11 +0200117
118 let mut loader_data = match read_loader_state(&mut esp_fs) {
Jan Schär69b76872025-05-14 16:39:47 +0000119 Ok(d) => d,
Lorenz Brun54a5a052023-10-02 16:40:11 +0200120 Err(e) => {
121 println!("Unable to load A/B loader state, using default slot A: {}", e);
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +0100122 AbLoaderData {
123 active_slot: Slot::A.into(),
124 next_slot: Slot::None.into(),
Lorenz Brun54a5a052023-10-02 16:40:11 +0200125 }
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är69b76872025-05-14 16:39:47 +0000131 // be permanently activated, it is the OS's job to put it into
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +0100132 if loader_data.next_slot != Slot::None.into() {
Lorenz Brun54a5a052023-10-02 16:40:11 +0200133 let next_slot = loader_data.next_slot;
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +0100134 loader_data.next_slot = Slot::None.into();
Lorenz Brun54a5a052023-10-02 16:40:11 +0200135 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 Windelschmidt2f9f6242025-01-11 08:25:54 +0100145 let boot_slot = match Slot::try_from(boot_slot_raw) {
146 Ok(Slot::A) => ValidSlot::A,
147 Ok(Slot::B) => ValidSlot::B,
Lorenz Brun54a5a052023-10-02 16:40:11 +0200148 _ => {
149 println!("Invalid slot ({}) active, falling back to A", boot_slot_raw);
150 ValidSlot::A
151 }
152 };
153
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +0200154 let payload_image = match load_slot_image(&boot_slot) {
Lorenz Brun54a5a052023-10-02 16:40:11 +0200155 Ok(img) => img,
156 Err(e) => {
157 println!("Error loading intended slot, falling back to other slot: {}", e);
Tim Windelschmidt10d47cc2025-07-01 00:34:51 +0200158 match load_slot_image(&boot_slot.other()) {
Lorenz Brun54a5a052023-10-02 16:40:11 +0200159 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 Windelschmidt10d47cc2025-07-01 00:34:51 +0200168 boot::start_image(payload_image).expect("failed to start payload");
Lorenz Brun54a5a052023-10-02 16:40:11 +0200169 Status::SUCCESS
170}