blob: 4489b7338ab6828331c0de805c60cb648c7dcfdd [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;
12use core::result::Result;
13use core::fmt;
14use prost::Message;
15use uefi::fs::FileSystem;
16use uefi::proto::device_path::build::media::FilePath;
17use uefi::proto::device_path::build::DevicePathBuilder;
18use uefi::proto::device_path::{DeviceSubType, DeviceType, LoadedImageDevicePath};
19use uefi::table::boot;
20use uefi::{prelude::*, CStr16};
21use uefi_services::println;
22
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +010023use abloader_proto::metropolis::node::core::abloader::spec::*;
Lorenz Brun54a5a052023-10-02 16:40:11 +020024
25const A_LOADER_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\boot-a.efi");
26const B_LOADER_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\boot-b.efi");
27
28const LOADER_STATE_PATH: &CStr16 = cstr16!("\\EFI\\metropolis\\loader_state.pb");
29
30enum ValidSlot {
31 A,
32 B,
33}
34
35impl ValidSlot {
36 // other returns B if the value is A and A if the value is B.
37 fn other(&self) -> Self {
38 match self {
39 ValidSlot::A => ValidSlot::B,
40 ValidSlot::B => ValidSlot::A,
41 }
42 }
43 // path returns the path to the slot's EFI payload.
44 fn path(&self) -> &'static CStr16 {
45 match self {
46 ValidSlot::A => A_LOADER_PATH,
47 ValidSlot::B => B_LOADER_PATH,
48 }
49 }
50}
51
52enum ReadLoaderStateError {
53 FSReadError(uefi::fs::Error),
54 DecodeError(prost::DecodeError),
55}
56
57impl fmt::Display for ReadLoaderStateError {
58 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59 match self {
60 ReadLoaderStateError::FSReadError(e) => write!(f, "while reading state file: {}", e),
61 ReadLoaderStateError::DecodeError(e) => write!(f, "while decoding state file contents: {}", e),
62 }
63 }
64}
65
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +010066fn read_loader_state(fs: &mut FileSystem) -> Result<AbLoaderData, ReadLoaderStateError> {
Lorenz Brun54a5a052023-10-02 16:40:11 +020067 let state_raw = fs.read(&LOADER_STATE_PATH).map_err(|e| ReadLoaderStateError::FSReadError(e))?;
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +010068 AbLoaderData::decode(state_raw.as_slice()).map_err(|e| ReadLoaderStateError::DecodeError(e))
Lorenz Brun54a5a052023-10-02 16:40:11 +020069}
70
71fn load_slot_image(slot: &ValidSlot, boot_services: &BootServices) -> uefi::Result<Handle> {
72 let mut storage = Vec::new();
73
74 // Build the path to the slot payload. This takes the path to the loader
75 // itself, strips off the file path and following element(s) and appends
76 // the path to the correct slot payload.
77 let new_image_path = {
78 let loaded_image_device_path = boot_services
79 .open_protocol_exclusive::<LoadedImageDevicePath>(boot_services.image_handle())?;
80
81 let mut builder = DevicePathBuilder::with_vec(&mut storage);
82
83 for node in loaded_image_device_path.node_iter() {
84 if node.full_type() == (DeviceType::MEDIA, DeviceSubType::MEDIA_FILE_PATH) {
85 break;
86 }
87
88 builder = builder.push(&node).unwrap();
89 }
90
91 builder = builder
92 .push(&FilePath {
93 path_name: slot.path(),
94 })
95 .unwrap();
96
97 builder.finalize().unwrap()
98 };
99
100 boot_services
101 .load_image(
102 boot_services.image_handle(),
103 boot::LoadImageSource::FromDevicePath {
104 device_path: new_image_path,
105 from_boot_manager: false,
106 },
107 )
108}
109
110#[entry]
111fn main(_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
112 uefi_services::init(&mut system_table).unwrap();
113
114 let boot_services = system_table.boot_services();
115
116 let boot_slot_raw = {
117 let mut esp_fs = boot_services
118 .get_image_file_system(boot_services.image_handle())
119 .expect("image filesystem not available");
120
121 let mut loader_data = match read_loader_state(&mut esp_fs) {
122 Ok(d) => d,
123 Err(e) => {
124 println!("Unable to load A/B loader state, using default slot A: {}", e);
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +0100125 AbLoaderData {
126 active_slot: Slot::A.into(),
127 next_slot: Slot::None.into(),
Lorenz Brun54a5a052023-10-02 16:40:11 +0200128 }
129 }
130 };
131
132 // If next_slot is set, use it as slot to boot but clear it in the
133 // state file as the next boot should not use it again. If it should
134 // be permanently activated, it is the OS's job to put it into
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +0100135 if loader_data.next_slot != Slot::None.into() {
Lorenz Brun54a5a052023-10-02 16:40:11 +0200136 let next_slot = loader_data.next_slot;
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +0100137 loader_data.next_slot = Slot::None.into();
Lorenz Brun54a5a052023-10-02 16:40:11 +0200138 let new_loader_data = loader_data.encode_to_vec();
139 esp_fs
140 .write(&LOADER_STATE_PATH, new_loader_data)
141 .expect("failed to write back abdata");
142 next_slot
143 } else {
144 loader_data.active_slot
145 }
146 };
147
Tim Windelschmidt2f9f6242025-01-11 08:25:54 +0100148 let boot_slot = match Slot::try_from(boot_slot_raw) {
149 Ok(Slot::A) => ValidSlot::A,
150 Ok(Slot::B) => ValidSlot::B,
Lorenz Brun54a5a052023-10-02 16:40:11 +0200151 _ => {
152 println!("Invalid slot ({}) active, falling back to A", boot_slot_raw);
153 ValidSlot::A
154 }
155 };
156
157 let payload_image = match load_slot_image(&boot_slot, boot_services) {
158 Ok(img) => img,
159 Err(e) => {
160 println!("Error loading intended slot, falling back to other slot: {}", e);
161 match load_slot_image(&boot_slot.other(), boot_services) {
162 Ok(img) => img,
163 Err(e) => {
164 panic!("Loading from both slots failed, second slot error: {}", e);
165 },
166 }
167 }
168 };
169
170 // Boot the payload.
171 boot_services
172 .start_image(payload_image)
173 .expect("failed to start payload");
174 Status::SUCCESS
175}