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 crate::*;
48
49 use core::arch::asm;
50 use core::slice::from_raw_parts_mut;
51
52 pub use x86_bootparam_defs::{boot_params, e820entry, setup_header};
53 use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};
54
55 // Sector size is fixed to 512
56 const SECTOR_SIZE: usize = 512;
57 /// Boot sector and setup code section is 32K at most.
58 const BOOT_SETUP_LOAD_SIZE: usize = 0x8000;
59 /// Address for loading the protected mode kernel
60 const LOAD_ADDR_HIGH: usize = 0x10_0000;
61 // Flag value to use high address for protected mode kernel.
62 const LOAD_FLAG_LOADED_HIGH: u8 = 0x1;
63
64 /// Constant for E820 address range type.
65 pub const E820_ADDRESS_TYPE_RAM: u32 = 1;
66 pub const E820_ADDRESS_TYPE_RESERVED: u32 = 2;
67 pub const E820_ADDRESS_TYPE_ACPI: u32 = 3;
68 pub const E820_ADDRESS_TYPE_NVS: u32 = 4;
69 pub const E820_ADDRESS_TYPE_UNUSABLE: u32 = 5;
70 pub const E820_ADDRESS_TYPE_PMEM: u32 = 7;
71
72 /// Wrapper for `struct boot_params {}` C structure
73 #[repr(transparent)]
74 #[derive(Copy, Clone, AsBytes, FromBytes, FromZeroes)]
75 pub struct BootParams(boot_params);
76
77 impl BootParams {
78 /// Cast a bytes into a reference of BootParams header
from_bytes_ref(buffer: &[u8]) -> Result<&BootParams>79 pub fn from_bytes_ref(buffer: &[u8]) -> Result<&BootParams> {
80 Ok(Ref::<_, BootParams>::new_from_prefix(buffer)
81 .ok_or_else(|| BootError::InvalidInput)?
82 .0
83 .into_ref())
84 }
85
86 /// Cast a bytes into a mutable reference of BootParams header.
from_bytes_mut(buffer: &mut [u8]) -> Result<&mut BootParams>87 pub fn from_bytes_mut(buffer: &mut [u8]) -> Result<&mut BootParams> {
88 Ok(Ref::<_, BootParams>::new_from_prefix(buffer)
89 .ok_or_else(|| BootError::InvalidInput)?
90 .0
91 .into_mut())
92 }
93
94 /// Return a mutable reference of the `setup_header` struct field in `boot_params`
setup_header_mut(&mut self) -> &mut setup_header95 pub fn setup_header_mut(&mut self) -> &mut setup_header {
96 &mut self.0.hdr
97 }
98
99 /// Return a const reference of the `setup_header` struct field in `boot_params`
setup_header_ref(&self) -> &setup_header100 pub fn setup_header_ref(&self) -> &setup_header {
101 &self.0.hdr
102 }
103
104 /// Checks whether image is valid and version is supported.
check(&self) -> Result<()>105 pub fn check(&self) -> Result<()> {
106 // Check magic.
107 if !(self.setup_header_ref().boot_flag == 0xAA55
108 && self.setup_header_ref().header.to_le_bytes() == *b"HdrS")
109 {
110 return Err(BootError::InvalidZImage);
111 }
112
113 // Check if it is bzimage and version is supported.
114 if !(self.0.hdr.version >= 0x0206
115 && ((self.setup_header_ref().loadflags & LOAD_FLAG_LOADED_HIGH) != 0))
116 {
117 return Err(BootError::UnsupportedZImage);
118 }
119
120 Ok(())
121 }
122
123 /// Gets the number of sectors in the setup code section.
setup_sects(&self) -> usize124 pub fn setup_sects(&self) -> usize {
125 match self.setup_header_ref().setup_sects {
126 0 => 4,
127 v => v as usize,
128 }
129 }
130
131 /// Gets the offset to the protected mode kernel in the image.
132 ///
133 /// The value is also the same as the sum of legacy boot sector plus setup code size.
kernel_off(&self) -> usize134 pub fn kernel_off(&self) -> usize {
135 // one boot sector + setup sectors
136 (1 + self.setup_sects()) * SECTOR_SIZE
137 }
138
139 /// Gets e820 map entries.
e820_map(&mut self) -> &mut [e820entry]140 pub fn e820_map(&mut self) -> &mut [e820entry] {
141 &mut self.0.e820_map[..]
142 }
143 }
144
145 /// Boots a Linux bzimage.
146 ///
147 /// # Args
148 ///
149 /// * `kernel`: Buffer holding the loaded bzimage.
150 ///
151 /// * `ramdisk`: Buffer holding the loaded ramdisk.
152 ///
153 /// * `cmdline`: Command line argument blob.
154 ///
155 /// * `mmap_cb`: A caller provided callback for setting the e820 memory map. The callback takes in
156 /// a mutable reference of e820 map entries (&mut [e820entry]). On success, it should return
157 /// the number of used entries. On error, it can return a
158 /// `BootError::E820MemoryMapCallbackError(<code>)` to propagate a custom error code.
159 ///
160 /// * `low_mem_addr`: The lowest memory touched by the bootloader section. This is where boot param
161 /// starts.
162 ///
163 /// * The API is not expected to return on success.
164 ///
165 /// # Safety
166 ///
167 /// * Caller must ensure that `kernel` contains a valid Linux kernel and `low_mem_addr` is valid
168 ///
169 /// * 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>,170 pub unsafe fn boot_linux_bzimage<F>(
171 kernel: &[u8],
172 ramdisk: &[u8],
173 cmdline: &[u8],
174 mmap_cb: F,
175 low_mem_addr: usize,
176 ) -> Result<()>
177 where
178 F: FnOnce(&mut [e820entry]) -> Result<u8>,
179 {
180 let bootparam = BootParams::from_bytes_ref(&kernel[..])?;
181 bootparam.check()?;
182
183 // low memory address greater than 0x9_0000 is bogus.
184 assert!(low_mem_addr <= 0x9_0000);
185 // SAFETY: By safety requirement of this function, `low_mem_addr` points to sufficiently large
186 // memory.
187 let boot_param_buffer =
188 unsafe { from_raw_parts_mut(low_mem_addr as *mut _, BOOT_SETUP_LOAD_SIZE) };
189 // Note: We currently boot directly from protected mode kernel and bypass real-mode kernel.
190 // Thus we omit the heap section. Revisit this if we encounter platforms that have to boot from
191 // real-mode kernel.
192 let cmdline_start = low_mem_addr + BOOT_SETUP_LOAD_SIZE;
193 // Should not reach into I/O memory hole section.
194 assert!(cmdline_start + cmdline.len() <= 0x0A0000);
195 // SAFETY: By safety requirement of this function, `low_mem_addr` points to sufficiently large
196 // memory.
197 let cmdline_buffer = unsafe { from_raw_parts_mut(cmdline_start as *mut _, cmdline.len()) };
198
199 let boot_sector_size = bootparam.kernel_off();
200 // Copy protected mode kernel to load address
201 // SAFETY: By safety requirement of this function, `LOAD_ADDR_HIGH` points to sufficiently
202 // large memory.
203 unsafe {
204 from_raw_parts_mut(LOAD_ADDR_HIGH as *mut u8, kernel[boot_sector_size..].len())
205 .clone_from_slice(&kernel[boot_sector_size..]);
206 }
207
208 // Copy over boot params to boot sector to prepare for fix-up.
209 boot_param_buffer.fill(0);
210 boot_param_buffer[..boot_sector_size].clone_from_slice(&kernel[..boot_sector_size]);
211
212 let bootparam_fixup = BootParams::from_bytes_mut(boot_param_buffer)?;
213
214 // Sets commandline.
215 cmdline_buffer.clone_from_slice(cmdline);
216 bootparam_fixup.setup_header_mut().cmd_line_ptr = cmdline_start.try_into().unwrap();
217 bootparam_fixup.setup_header_mut().cmdline_size = cmdline.len().try_into().unwrap();
218
219 // Sets ramdisk address.
220 bootparam_fixup.setup_header_mut().ramdisk_image =
221 (ramdisk.as_ptr() as usize).try_into().map_err(|_| BootError::IntegerOverflow)?;
222 bootparam_fixup.setup_header_mut().ramdisk_size =
223 ramdisk.len().try_into().map_err(|_| BootError::IntegerOverflow)?;
224
225 // Sets to loader type "special loader". (Anything other than 0, otherwise linux kernel ignores
226 // ramdisk.)
227 bootparam_fixup.setup_header_mut().type_of_loader = 0xff;
228
229 // Fix up e820 memory map.
230 let num_entries = mmap_cb(bootparam_fixup.e820_map())?;
231 bootparam_fixup.0.e820_entries = num_entries;
232
233 // Clears stack pointers, interrupt and jumps to protected mode kernel.
234 // SAFETY: By safety requirement of this function, input contains a valid linux kernel.
235 #[cfg(target_arch = "x86_64")]
236 unsafe {
237 asm!(
238 "xor ebp, ebp",
239 "xor esp, esp",
240 "cld",
241 "cli",
242 "jmp {ep}",
243 ep = in(reg) LOAD_ADDR_HIGH,
244 in("rsi") low_mem_addr,
245 );
246 }
247 // SAFETY: By safety requirement of this function, input contains a valid linux kernel.
248 #[cfg(target_arch = "x86")]
249 unsafe {
250 asm!(
251 "xor ebp, ebp",
252 "xor esp, esp",
253 "mov esi, eax",
254 "cld",
255 "cli",
256 "jmp {ep}",
257 ep = in(reg) LOAD_ADDR_HIGH,
258 in("eax") low_mem_addr,
259 );
260 }
261
262 Ok(())
263 }
264