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