1 // Copyright 2023, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Boot protocol implementation for x86 platforms.
16 //!
17 //! For linux, the library currently only supports bzimage and protocol version 2.06+.
18 //! Specifically, modern memory layout is used, protected kernel is loaded to high address at
19 //! 0x100000 and command line size can be greater than 255 characters.
20 //!
21 //! ~ ~
22 //! | Protected-mode kernel |
23 //! 100000 +------------------------+
24 //! | I/O memory hole |
25 //! 0A0000 +------------------------+
26 //! | Reserved for BIOS | Leave as much as possible unused
27 //! ~ ~
28 //! | Command line | (Can also be below the X+10000 mark)
29 //! +------------------------+
30 //! | Stack/heap | For use by the kernel real-mode code.
31 //! low_mem_addr+08000 +------------------------+
32 //! | Kernel setup | The kernel real-mode code.
33 //! | Kernel boot sector | The kernel legacy boot sector.
34 //! low_mem_addr +------------------------+
35 //! | Boot loader | <- Boot sector entry point 0000:7C00
36 //! 001000 +------------------------+
37 //! | Reserved for MBR/BIOS |
38 //! 000800 +------------------------+
39 //! | Typically used by MBR |
40 //! 000600 +------------------------+
41 //! | BIOS use only |
42 //! 000000 +------------------------+
43 //!
44 //! See https://www.kernel.org/doc/html/v5.11/x86/boot.html#the-linux-x86-boot-protocol for more
45 //! detail.
46
47 use core::arch::asm;
48 use core::mem::size_of;
49 use core::slice::from_raw_parts_mut;
50 use liberror::{Error, Result};
51 use zbi::ZbiContainer;
52
53 pub use x86_bootparam_defs::{boot_params, e820entry, setup_header};
54 use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};
55
56 // Sector size is fixed to 512
57 const SECTOR_SIZE: usize = 512;
58 /// Boot sector and setup code section is 32K at most.
59 const BOOT_SETUP_LOAD_SIZE: usize = 0x8000;
60 /// Address for loading the protected mode kernel
61 const LOAD_ADDR_HIGH: usize = 0x10_0000;
62 // Flag value to use high address for protected mode kernel.
63 const LOAD_FLAG_LOADED_HIGH: u8 = 0x1;
64
65 /// E820 RAM address range type.
66 pub const E820_ADDRESS_TYPE_RAM: u32 = 1;
67 /// E820 reserved address range type.
68 pub const E820_ADDRESS_TYPE_RESERVED: u32 = 2;
69 /// E820 ACPI address range type.
70 pub const E820_ADDRESS_TYPE_ACPI: u32 = 3;
71 /// E820 NVS address range type.
72 pub const E820_ADDRESS_TYPE_NVS: u32 = 4;
73 /// E820 unusable address range type.
74 pub const E820_ADDRESS_TYPE_UNUSABLE: u32 = 5;
75 /// E820 PMEM address range type.
76 pub const E820_ADDRESS_TYPE_PMEM: u32 = 7;
77
78 /// Wrapper for `struct boot_params {}` C structure
79 #[repr(transparent)]
80 #[derive(Copy, Clone, AsBytes, FromBytes, FromZeroes)]
81 pub struct BootParams(boot_params);
82
83 impl BootParams {
84 /// Cast a bytes into a reference of BootParams header
from_bytes_ref(buffer: &[u8]) -> Result<&BootParams>85 pub fn from_bytes_ref(buffer: &[u8]) -> Result<&BootParams> {
86 Ok(Ref::<_, BootParams>::new_from_prefix(buffer)
87 .ok_or(Error::BufferTooSmall(Some(size_of::<BootParams>())))?
88 .0
89 .into_ref())
90 }
91
92 /// Cast a bytes into a mutable reference of BootParams header.
from_bytes_mut(buffer: &mut [u8]) -> Result<&mut BootParams>93 pub fn from_bytes_mut(buffer: &mut [u8]) -> Result<&mut BootParams> {
94 Ok(Ref::<_, BootParams>::new_from_prefix(buffer)
95 .ok_or(Error::BufferTooSmall(Some(size_of::<BootParams>())))?
96 .0
97 .into_mut())
98 }
99
100 /// Return a mutable reference of the `setup_header` struct field in `boot_params`
setup_header_mut(&mut self) -> &mut setup_header101 pub fn setup_header_mut(&mut self) -> &mut setup_header {
102 &mut self.0.hdr
103 }
104
105 /// Return a const reference of the `setup_header` struct field in `boot_params`
setup_header_ref(&self) -> &setup_header106 pub fn setup_header_ref(&self) -> &setup_header {
107 &self.0.hdr
108 }
109
110 /// Checks whether image is valid and version is supported.
check(&self) -> Result<()>111 pub fn check(&self) -> Result<()> {
112 // Check magic.
113 if !(self.setup_header_ref().boot_flag == 0xAA55
114 && self.setup_header_ref().header.to_le_bytes() == *b"HdrS")
115 {
116 return Err(Error::BadMagic);
117 }
118
119 // Check if it is bzimage and version is supported.
120 if !(self.0.hdr.version >= 0x0206
121 && ((self.setup_header_ref().loadflags & LOAD_FLAG_LOADED_HIGH) != 0))
122 {
123 return Err(Error::UnsupportedVersion);
124 }
125
126 Ok(())
127 }
128
129 /// Gets the number of sectors in the setup code section.
setup_sects(&self) -> usize130 pub fn setup_sects(&self) -> usize {
131 match self.setup_header_ref().setup_sects {
132 0 => 4,
133 v => v as usize,
134 }
135 }
136
137 /// Gets the offset to the protected mode kernel in the image.
138 ///
139 /// The value is also the same as the sum of legacy boot sector plus setup code size.
kernel_off(&self) -> usize140 pub fn kernel_off(&self) -> usize {
141 // one boot sector + setup sectors
142 (1 + self.setup_sects()) * SECTOR_SIZE
143 }
144
145 /// Gets e820 map entries.
e820_map(&mut self) -> &mut [e820entry]146 pub fn e820_map(&mut self) -> &mut [e820entry] {
147 &mut self.0.e820_map[..]
148 }
149 }
150
151 /// Boots a Linux bzimage.
152 ///
153 /// # Args
154 ///
155 /// * `kernel`: Buffer holding the loaded bzimage.
156 ///
157 /// * `ramdisk`: Buffer holding the loaded ramdisk.
158 ///
159 /// * `cmdline`: Command line argument blob.
160 ///
161 /// * `mmap_cb`: A caller provided callback for setting the e820 memory map. The callback takes in
162 /// a mutable reference of e820 map entries (&mut [e820entry]). On success, it should return
163 /// the number of used entries. On error, it can return a
164 /// `Error::MemoryMapCallbackError(<code>)` to propagate a custom error code.
165 ///
166 /// * `low_mem_addr`: The lowest memory touched by the bootloader section. This is where boot param
167 /// starts.
168 ///
169 /// * The API is not expected to return on success.
170 ///
171 /// # Safety
172 ///
173 /// * Caller must ensure that `kernel` contains a valid Linux kernel and `low_mem_addr` is valid
174 ///
175 /// * Caller must ensure that there is enough memory at address 0x10_0000 for relocating `kernel`.
boot_linux_bzimage<F>( kernel: &[u8], ramdisk: &[u8], cmdline: &[u8], mmap_cb: F, low_mem_addr: usize, ) -> Result<()> where F: FnOnce(&mut [e820entry]) -> Result<u8>,176 pub unsafe fn boot_linux_bzimage<F>(
177 kernel: &[u8],
178 ramdisk: &[u8],
179 cmdline: &[u8],
180 mmap_cb: F,
181 low_mem_addr: usize,
182 ) -> Result<()>
183 where
184 F: FnOnce(&mut [e820entry]) -> Result<u8>,
185 {
186 let bootparam = BootParams::from_bytes_ref(&kernel[..])?;
187 bootparam.check()?;
188
189 // low memory address greater than 0x9_0000 is bogus.
190 assert!(low_mem_addr <= 0x9_0000);
191 // SAFETY: By safety requirement of this function, `low_mem_addr` points to sufficiently large
192 // memory.
193 let boot_param_buffer =
194 unsafe { from_raw_parts_mut(low_mem_addr as *mut _, BOOT_SETUP_LOAD_SIZE) };
195 // Note: We currently boot directly from protected mode kernel and bypass real-mode kernel.
196 // Thus we omit the heap section. Revisit this if we encounter platforms that have to boot from
197 // real-mode kernel.
198 let cmdline_start = low_mem_addr + BOOT_SETUP_LOAD_SIZE;
199 // Should not reach into I/O memory hole section.
200 assert!(cmdline_start + cmdline.len() <= 0x0A0000);
201 // SAFETY: By safety requirement of this function, `low_mem_addr` points to sufficiently large
202 // memory.
203 let cmdline_buffer = unsafe { from_raw_parts_mut(cmdline_start as *mut _, cmdline.len()) };
204
205 let boot_sector_size = bootparam.kernel_off();
206 // Copy protected mode kernel to load address
207 // SAFETY: By safety requirement of this function, `LOAD_ADDR_HIGH` points to sufficiently
208 // large memory.
209 unsafe {
210 from_raw_parts_mut(LOAD_ADDR_HIGH as *mut u8, kernel[boot_sector_size..].len())
211 .clone_from_slice(&kernel[boot_sector_size..]);
212 }
213
214 // Zeroizes the entire boot sector.
215 boot_param_buffer.fill(0);
216 let bootparam_fixup = BootParams::from_bytes_mut(boot_param_buffer)?;
217 // Only copies over the header. Leaves the rest zeroes.
218 *bootparam_fixup.setup_header_mut() =
219 *BootParams::from_bytes_ref(&kernel[..])?.setup_header_ref();
220
221 let bootparam_fixup = BootParams::from_bytes_mut(boot_param_buffer)?;
222
223 // Sets commandline.
224 cmdline_buffer.clone_from_slice(cmdline);
225 bootparam_fixup.setup_header_mut().cmd_line_ptr = cmdline_start.try_into().unwrap();
226 bootparam_fixup.setup_header_mut().cmdline_size = cmdline.len().try_into().unwrap();
227
228 // Sets ramdisk address.
229 bootparam_fixup.setup_header_mut().ramdisk_image = (ramdisk.as_ptr() as usize).try_into()?;
230 bootparam_fixup.setup_header_mut().ramdisk_size = ramdisk.len().try_into()?;
231
232 // Sets to loader type "special loader". (Anything other than 0, otherwise linux kernel ignores
233 // ramdisk.)
234 bootparam_fixup.setup_header_mut().type_of_loader = 0xff;
235
236 // Fix up e820 memory map.
237 let num_entries = mmap_cb(bootparam_fixup.e820_map())?;
238 bootparam_fixup.0.e820_entries = num_entries;
239
240 // Clears stack pointers, interrupt and jumps to protected mode kernel.
241 // SAFETY: By safety requirement of this function, input contains a valid linux kernel.
242 #[cfg(target_arch = "x86_64")]
243 unsafe {
244 asm!(
245 "xor ebp, ebp",
246 "xor esp, esp",
247 "cld",
248 "cli",
249 "jmp {ep}",
250 ep = in(reg) LOAD_ADDR_HIGH,
251 in("rsi") low_mem_addr,
252 );
253 }
254 // SAFETY: By safety requirement of this function, input contains a valid linux kernel.
255 #[cfg(target_arch = "x86")]
256 unsafe {
257 asm!(
258 "xor ebp, ebp",
259 "xor esp, esp",
260 "mov esi, eax",
261 "cld",
262 "cli",
263 "jmp {ep}",
264 ep = in(reg) LOAD_ADDR_HIGH,
265 in("eax") low_mem_addr,
266 );
267 }
268
269 Ok(())
270 }
271
272 /// Jump to prepared ZBI Fuchsia entry
273 ///
274 /// SAFETY:
275 /// Caller must ensure `entry` is valid entry point for kernel.
276 /// Caller must ensure `data` is valid ZBI data and is 4K aligned.
zbi_boot_raw(entry: usize, data: &[u8]) -> !277 pub unsafe fn zbi_boot_raw(entry: usize, data: &[u8]) -> ! {
278 // Clears stack pointers, interrupt and jumps to protected mode kernel.
279 // SAFETY: By safety requirement of this function, input contains a valid ZBI kernel.
280 #[cfg(target_arch = "x86_64")]
281 unsafe {
282 asm!(
283 "xor ebp, ebp",
284 "xor esp, esp",
285 "cld",
286 "cli",
287 "jmp {ep}",
288 ep = in(reg) entry,
289 in("rsi") data.as_ptr(),
290 );
291 }
292 // SAFETY: By safety requirement of this function, input contains a valid ZBI kernel.
293 #[cfg(target_arch = "x86")]
294 unsafe {
295 asm!(
296 "xor ebp, ebp",
297 "xor esp, esp",
298 "mov esi, eax",
299 "cld",
300 "cli",
301 "jmp {ep}",
302 ep = in(reg) entry,
303 in("eax") data.as_ptr(),
304 );
305 }
306
307 unreachable!();
308 }
309
310 /// Boot ZBI kernel from provided ZBI containers
311 ///
312 /// SAFETY:
313 /// Caller must ensure `kernel` is valid ZBI kernel and is 4K aligned.
314 /// Caller must ensure `data` is valid ZBI data and is 4K aligned.
zbi_boot(kernel: &[u8], data: &[u8]) -> !315 pub unsafe fn zbi_boot(kernel: &[u8], data: &[u8]) -> ! {
316 let (entry, _) =
317 ZbiContainer::parse(kernel).unwrap().get_kernel_entry_and_reserved_memory_size().unwrap();
318 let addr = (kernel.as_ptr() as usize).checked_add(usize::try_from(entry).unwrap()).unwrap();
319 // SAFETY:
320 // `addr` is calculated from kernel ZBI, which is valid according to safety requirements for
321 // `zbi_boot()` function.
322 // `data` is required to be valid ZBI data as per safety requirements for `zbi_boot()`
323 unsafe { zbi_boot_raw(addr, data) };
324 }
325