• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::{FromBytes, Immutable, IntoBytes, KnownLayout, 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 /// In 64-bit boot protocol, the kernel is started by jumping to the
66 /// 64-bit kernel entry point, which is the start address of loaded
67 /// 64-bit kernel plus 0x200.
68 #[cfg(target_arch = "x86_64")]
69 const ENTRY_POINT_OFFSET: usize = 0x200;
70 
71 /// E820 RAM address range type.
72 pub const E820_ADDRESS_TYPE_RAM: u32 = 1;
73 /// E820 reserved address range type.
74 pub const E820_ADDRESS_TYPE_RESERVED: u32 = 2;
75 /// E820 ACPI address range type.
76 pub const E820_ADDRESS_TYPE_ACPI: u32 = 3;
77 /// E820 NVS address range type.
78 pub const E820_ADDRESS_TYPE_NVS: u32 = 4;
79 /// E820 unusable address range type.
80 pub const E820_ADDRESS_TYPE_UNUSABLE: u32 = 5;
81 /// E820 PMEM address range type.
82 pub const E820_ADDRESS_TYPE_PMEM: u32 = 7;
83 
84 /// Wrapper for `struct boot_params {}` C structure
85 #[repr(transparent)]
86 #[derive(Copy, Clone, Immutable, IntoBytes, FromBytes, KnownLayout)]
87 pub struct BootParams(boot_params);
88 
89 impl BootParams {
90     /// Cast a bytes into a reference of BootParams header
from_bytes_ref(buffer: &[u8]) -> Result<&BootParams>91     pub fn from_bytes_ref(buffer: &[u8]) -> Result<&BootParams> {
92         Ok(Ref::into_ref(
93             Ref::<_, BootParams>::new_from_prefix(buffer)
94                 .ok_or(Error::BufferTooSmall(Some(size_of::<BootParams>())))?
95                 .0,
96         ))
97     }
98 
99     /// Cast a bytes into a mutable reference of BootParams header.
from_bytes_mut(buffer: &mut [u8]) -> Result<&mut BootParams>100     pub fn from_bytes_mut(buffer: &mut [u8]) -> Result<&mut BootParams> {
101         Ok(Ref::into_mut(
102             Ref::<_, BootParams>::new_from_prefix(buffer)
103                 .ok_or(Error::BufferTooSmall(Some(size_of::<BootParams>())))?
104                 .0,
105         ))
106     }
107 
108     /// Return a mutable reference of the `setup_header` struct field in `boot_params`
setup_header_mut(&mut self) -> &mut setup_header109     pub fn setup_header_mut(&mut self) -> &mut setup_header {
110         &mut self.0.hdr
111     }
112 
113     /// Return a const reference of the `setup_header` struct field in `boot_params`
setup_header_ref(&self) -> &setup_header114     pub fn setup_header_ref(&self) -> &setup_header {
115         &self.0.hdr
116     }
117 
118     /// Checks whether image is valid and version is supported.
check(&self) -> Result<()>119     pub fn check(&self) -> Result<()> {
120         // Check magic.
121         if !(self.setup_header_ref().boot_flag == 0xAA55
122             && self.setup_header_ref().header.to_le_bytes() == *b"HdrS")
123         {
124             return Err(Error::BadMagic);
125         }
126 
127         // Check if it is bzimage and version is supported.
128         if !(self.0.hdr.version >= 0x0206
129             && ((self.setup_header_ref().loadflags & LOAD_FLAG_LOADED_HIGH) != 0))
130         {
131             return Err(Error::UnsupportedVersion);
132         }
133 
134         Ok(())
135     }
136 
137     /// Gets the number of sectors in the setup code section.
setup_sects(&self) -> usize138     pub fn setup_sects(&self) -> usize {
139         match self.setup_header_ref().setup_sects {
140             0 => 4,
141             v => v as usize,
142         }
143     }
144 
145     /// Gets the offset to the protected mode kernel in the image.
146     ///
147     /// The value is also the same as the sum of legacy boot sector plus setup code size.
kernel_off(&self) -> usize148     pub fn kernel_off(&self) -> usize {
149         // one boot sector + setup sectors
150         (1 + self.setup_sects()) * SECTOR_SIZE
151     }
152 
153     /// Gets e820 map entries.
e820_map(&mut self) -> &mut [e820entry]154     pub fn e820_map(&mut self) -> &mut [e820entry] {
155         &mut self.0.e820_map[..]
156     }
157 }
158 
159 /// Boots a Linux bzimage.
160 ///
161 /// # Args
162 ///
163 /// * `kernel`: Buffer holding the loaded bzimage.
164 ///
165 /// * `ramdisk`: Buffer holding the loaded ramdisk.
166 ///
167 /// * `cmdline`: Command line argument blob.
168 ///
169 /// * `mmap_cb`: A caller provided callback for setting the e820 memory map. The callback takes in
170 ///     a mutable reference of e820 map entries (&mut [e820entry]). On success, it should return
171 ///     the number of used entries. On error, it can return a
172 ///     `Error::MemoryMapCallbackError(<code>)` to propagate a custom error code.
173 ///
174 /// * `low_mem_addr`: The lowest memory touched by the bootloader section. This is where boot param
175 ///      starts.
176 ///
177 /// * The API is not expected to return on success.
178 ///
179 /// # Safety
180 ///
181 /// * Caller must ensure that `kernel` contains a valid Linux kernel and `low_mem_addr` is valid
182 ///
183 /// * 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>,184 pub unsafe fn boot_linux_bzimage<F>(
185     kernel: &[u8],
186     ramdisk: &[u8],
187     cmdline: &[u8],
188     mmap_cb: F,
189     low_mem_addr: usize,
190 ) -> Result<()>
191 where
192     F: FnOnce(&mut [e820entry]) -> Result<u8>,
193 {
194     let bootparam = BootParams::from_bytes_ref(&kernel[..])?;
195     bootparam.check()?;
196 
197     // low memory address greater than 0x9_0000 is bogus.
198     assert!(low_mem_addr <= 0x9_0000);
199     // SAFETY: By safety requirement of this function, `low_mem_addr` points to sufficiently large
200     // memory.
201     let boot_param_buffer =
202         unsafe { from_raw_parts_mut(low_mem_addr as *mut _, BOOT_SETUP_LOAD_SIZE) };
203     // Note: We currently boot directly from protected mode kernel and bypass real-mode kernel.
204     // Thus we omit the heap section. Revisit this if we encounter platforms that have to boot from
205     // real-mode kernel.
206     let cmdline_start = low_mem_addr + BOOT_SETUP_LOAD_SIZE;
207     // Should not reach into I/O memory hole section.
208     assert!(cmdline_start + cmdline.len() <= 0x0A0000);
209     // SAFETY: By safety requirement of this function, `low_mem_addr` points to sufficiently large
210     // memory.
211     let cmdline_buffer = unsafe { from_raw_parts_mut(cmdline_start as *mut _, cmdline.len()) };
212 
213     let boot_sector_size = bootparam.kernel_off();
214     // Copy protected mode kernel to load address
215     // SAFETY: By safety requirement of this function, `LOAD_ADDR_HIGH` points to sufficiently
216     // large memory.
217     unsafe {
218         from_raw_parts_mut(LOAD_ADDR_HIGH as *mut u8, kernel[boot_sector_size..].len())
219             .clone_from_slice(&kernel[boot_sector_size..]);
220     }
221 
222     // Zeroizes the entire boot sector.
223     boot_param_buffer.fill(0);
224     let bootparam_fixup = BootParams::from_bytes_mut(boot_param_buffer)?;
225     // Only copies over the header. Leaves the rest zeroes.
226     *bootparam_fixup.setup_header_mut() =
227         *BootParams::from_bytes_ref(&kernel[..])?.setup_header_ref();
228 
229     let bootparam_fixup = BootParams::from_bytes_mut(boot_param_buffer)?;
230 
231     // Sets commandline.
232     cmdline_buffer.clone_from_slice(cmdline);
233     bootparam_fixup.setup_header_mut().cmd_line_ptr = cmdline_start.try_into().unwrap();
234     bootparam_fixup.setup_header_mut().cmdline_size = cmdline.len().try_into().unwrap();
235 
236     // Sets ramdisk address.
237     bootparam_fixup.setup_header_mut().ramdisk_image = (ramdisk.as_ptr() as usize).try_into()?;
238     bootparam_fixup.setup_header_mut().ramdisk_size = ramdisk.len().try_into()?;
239 
240     // Sets to loader type "special loader". (Anything other than 0, otherwise linux kernel ignores
241     // ramdisk.)
242     bootparam_fixup.setup_header_mut().type_of_loader = 0xff;
243 
244     // Fix up e820 memory map.
245     let num_entries = mmap_cb(bootparam_fixup.e820_map())?;
246     bootparam_fixup.0.e820_entries = num_entries;
247 
248     // Clears stack pointers, interrupt and jumps to protected mode kernel.
249     // SAFETY: By safety requirement of this function, input contains a valid linux kernel.
250     #[cfg(target_arch = "x86_64")]
251     unsafe {
252         asm!(
253             "xor ebp, ebp",
254             "xor esp, esp",
255             "cld",
256             "cli",
257             "jmp {ep}",
258             ep = in(reg) LOAD_ADDR_HIGH + ENTRY_POINT_OFFSET,
259             in("rsi") low_mem_addr,
260         );
261     }
262     // SAFETY: By safety requirement of this function, input contains a valid linux kernel.
263     #[cfg(target_arch = "x86")]
264     unsafe {
265         asm!(
266             "xor ebp, ebp",
267             "xor esp, esp",
268             "mov esi, eax",
269             "cld",
270             "cli",
271             "jmp {ep}",
272             ep = in(reg) LOAD_ADDR_HIGH,
273             in("eax") low_mem_addr,
274         );
275     }
276 
277     Ok(())
278 }
279 
280 /// Jump to prepared ZBI Fuchsia entry
281 ///
282 /// SAFETY:
283 /// Caller must ensure `entry` is valid entry point for kernel.
284 /// Caller must ensure `data` is valid ZBI data and is 4K aligned.
zbi_boot_raw(entry: usize, data: &[u8]) -> !285 pub unsafe fn zbi_boot_raw(entry: usize, data: &[u8]) -> ! {
286     // Clears stack pointers, interrupt and jumps to protected mode kernel.
287     // SAFETY: By safety requirement of this function, input contains a valid ZBI kernel.
288     #[cfg(target_arch = "x86_64")]
289     unsafe {
290         asm!(
291             "xor ebp, ebp",
292             "xor esp, esp",
293             "cld",
294             "cli",
295             "jmp {ep}",
296             ep = in(reg) entry,
297             in("rsi") data.as_ptr(),
298         );
299     }
300     // SAFETY: By safety requirement of this function, input contains a valid ZBI kernel.
301     #[cfg(target_arch = "x86")]
302     unsafe {
303         asm!(
304             "xor ebp, ebp",
305             "xor esp, esp",
306             "mov esi, eax",
307             "cld",
308             "cli",
309             "jmp {ep}",
310             ep = in(reg) entry,
311             in("eax") data.as_ptr(),
312         );
313     }
314 
315     unreachable!();
316 }
317 
318 /// Boot ZBI kernel from provided ZBI containers
319 ///
320 /// SAFETY:
321 /// Caller must ensure `kernel` is valid ZBI kernel and is 4K aligned.
322 /// Caller must ensure `data` is valid ZBI data and is 4K aligned.
zbi_boot(kernel: &[u8], data: &[u8]) -> !323 pub unsafe fn zbi_boot(kernel: &[u8], data: &[u8]) -> ! {
324     let (entry, _) =
325         ZbiContainer::parse(kernel).unwrap().get_kernel_entry_and_reserved_memory_size().unwrap();
326     let addr = (kernel.as_ptr() as usize).checked_add(usize::try_from(entry).unwrap()).unwrap();
327     // SAFETY:
328     // `addr` is calculated from kernel ZBI, which is valid according to safety requirements for
329     // `zbi_boot()` function.
330     // `data` is required to be valid ZBI data as per safety requirements for `zbi_boot()`
331     unsafe { zbi_boot_raw(addr, data) };
332 }
333