Skip to content

Commit 10461e6

Browse files
committed
bzimage: Rewrite Linux Kernel Loading code
This allows Linux to be booted with any boot protocol. The old code took in the Zeropage passed in via the Linux Kernel Boot Protocol, modified it, and passed it into the Linux Kernel. This is not the correct way to boot Linux per the documentation: https://www.kernel.org/doc/Documentation/x86/boot.txt This code now correctly: - Uses a brand-new Zeropage inside the `Kernel` struct - Adds in the E820 map and RSDP pointer from the boot::Info - Reads the header from the file and copies it into the Zeropage - Loads the kernel and initrd into avalible memory - Properly manages the command-line at a fixed memory location - Jumps to the appropriate starting address Signed-off-by: Joe Richey <joerichey@google.com>
1 parent a18b600 commit 10461e6

File tree

2 files changed

+129
-162
lines changed

2 files changed

+129
-162
lines changed

src/bzimage.rs

Lines changed: 119 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14+
use atomic_refcell::AtomicRefCell;
1415

15-
use crate::fat::{self, Read};
16+
use crate::{
17+
boot::{E820Entry, Header, Info, Params},
18+
fat::{self, Read},
19+
mem::MemoryRegion,
20+
};
1621

1722
#[derive(Debug)]
1823
pub enum Error {
1924
FileError(fat::Error),
20-
KernelOld,
25+
NoInitrdMemory,
2126
MagicMissing,
2227
NotRelocatable,
2328
}
@@ -28,185 +33,145 @@ impl From<fat::Error> for Error {
2833
}
2934
}
3035

31-
// From firecracker
32-
/// Kernel command line start address.
33-
const CMDLINE_START: usize = 0x4b000;
34-
/// Kernel command line start address maximum size.
35-
const CMDLINE_MAX_SIZE: usize = 0x10000;
36-
/// The 'zero page', a.k.a linux kernel bootparams.
37-
pub const ZERO_PAGE_START: usize = 0x7000;
36+
const KERNEL_LOCATION: u64 = 0x20_0000;
3837

39-
const KERNEL_LOCATION: u32 = 0x20_0000;
38+
#[repr(transparent)]
39+
pub struct Kernel(Params);
4040

41-
const E820_RAM: u32 = 1;
42-
43-
#[repr(C, packed)]
44-
struct E820Entry {
45-
addr: u64,
46-
size: u64,
47-
entry_type: u32,
48-
}
49-
50-
pub fn load_initrd(f: &mut dyn Read) -> Result<(), Error> {
51-
let mut zero_page = crate::mem::MemoryRegion::new(ZERO_PAGE_START as u64, 4096);
52-
53-
let mut max_load_address = u64::from(zero_page.read_u32(0x22c));
54-
if max_load_address == 0 {
55-
max_load_address = 0x37ff_ffff;
41+
impl Kernel {
42+
pub fn new(info: &dyn Info) -> Self {
43+
let mut kernel = Self(Params::default());
44+
kernel.0.acpi_rsdp_addr = info.rsdp_addr();
45+
kernel.0.set_entries(info);
46+
kernel
5647
}
5748

58-
let e820_count = zero_page.read_u8(0x1e8);
59-
let e820_table = zero_page.as_mut_slice::<E820Entry>(0x2d0, u64::from(e820_count));
49+
pub fn load_kernel(&mut self, f: &mut dyn Read) -> Result<(), Error> {
50+
self.0.hdr = Header::from_file(f)?;
6051

61-
// Search E820 table for highest usable ram location that is below the limit.
62-
let mut top_of_usable_ram = 0;
63-
for entry in e820_table {
64-
if entry.entry_type == E820_RAM {
65-
let m = entry.addr + entry.size - 1;
66-
if m > top_of_usable_ram && m < max_load_address {
67-
top_of_usable_ram = m;
68-
}
52+
if self.0.hdr.boot_flag != 0xAA55 || self.0.hdr.header != *b"HdrS" {
53+
return Err(Error::MagicMissing);
54+
}
55+
// Check relocatable
56+
if self.0.hdr.version < 0x205 || self.0.hdr.relocatable_kernel == 0 {
57+
return Err(Error::NotRelocatable);
6958
}
70-
}
7159

72-
if top_of_usable_ram > max_load_address {
73-
top_of_usable_ram = max_load_address;
60+
// Skip over the setup sectors
61+
let setup_sects = match self.0.hdr.setup_sects {
62+
0 => 4,
63+
n => n as u32,
64+
};
65+
let setup_bytes = (setup_sects + 1) * 512;
66+
let remaining_bytes = f.get_size() - setup_bytes;
67+
68+
let mut region = MemoryRegion::new(KERNEL_LOCATION, remaining_bytes as u64);
69+
f.seek(setup_bytes)?;
70+
f.load_file(&mut region)?;
71+
72+
// Fill out "write/modify" fields
73+
self.0.hdr.type_of_loader = 0xff; // Unknown Loader
74+
self.0.hdr.code32_start = KERNEL_LOCATION as u32; // Where we load the kernel
75+
self.0.hdr.cmd_line_ptr = CMDLINE_START as u32; // Where we load the cmdline
76+
Ok(())
7477
}
7578

76-
// Align address to 2MiB boundary as we use 2 MiB pages
77-
let initrd_address = (top_of_usable_ram - u64::from(f.get_size())) & !((2 << 20) - 1);
78-
let mut initrd_region = crate::mem::MemoryRegion::new(initrd_address, u64::from(f.get_size()));
79-
80-
let mut offset = 0;
81-
while offset < f.get_size() {
82-
let bytes_remaining = f.get_size() - offset;
83-
84-
// Use intermediata buffer for last, partial sector
85-
if bytes_remaining < 512 {
86-
let mut data: [u8; 512] = [0; 512];
87-
match f.read(&mut data) {
88-
Err(crate::fat::Error::EndOfFile) => break,
89-
Err(e) => return Err(Error::FileError(e)),
90-
Ok(_) => {}
91-
}
92-
let dst = initrd_region.as_mut_slice(u64::from(offset), u64::from(bytes_remaining));
93-
dst.copy_from_slice(&data[0..bytes_remaining as usize]);
94-
break;
95-
}
79+
// Compute the load address for the initial ramdisk
80+
fn initrd_addr(&self, size: u64) -> Option<u64> {
81+
let initrd_addr_max = match self.0.hdr.initrd_addr_max {
82+
0 => 0x37FF_FFFF,
83+
a => a as u64,
84+
};
85+
let max_start = (initrd_addr_max + 1) - size;
9686

97-
let dst = initrd_region.as_mut_slice(u64::from(offset), 512);
98-
match f.read(dst) {
99-
Err(crate::fat::Error::EndOfFile) => break,
100-
Err(e) => return Err(Error::FileError(e)),
101-
Ok(_) => {}
87+
let mut option_addr = None;
88+
for i in 0..self.0.num_entries() {
89+
let entry = self.0.entry(i);
90+
if entry.entry_type != E820Entry::RAM_TYPE {
91+
continue;
92+
}
93+
let addr = entry.addr + entry.size - size;
94+
// Align address to 2MiB boundary as we use 2 MiB pages
95+
let addr = addr & !((2 << 20) - 1);
96+
// The ramdisk must fit in the region completely
97+
if addr > max_start || addr < entry.addr {
98+
continue;
99+
}
100+
// Use the largest address we can find
101+
if let Some(load_addr) = option_addr {
102+
if load_addr >= addr {
103+
continue;
104+
}
105+
}
106+
option_addr = Some(addr)
102107
}
103-
104-
offset += 512;
108+
option_addr
105109
}
106110

107-
// initrd pointer/size
108-
zero_page.write_u32(0x218, initrd_address as u32);
109-
zero_page.write_u32(0x21c, f.get_size());
110-
Ok(())
111-
}
112-
113-
pub fn append_commandline(addition: &str) -> Result<(), Error> {
114-
let mut cmdline_region =
115-
crate::mem::MemoryRegion::new(CMDLINE_START as u64, CMDLINE_MAX_SIZE as u64);
116-
let zero_page = crate::mem::MemoryRegion::new(ZERO_PAGE_START as u64, 4096);
117-
118-
let cmdline = cmdline_region.as_mut_slice::<u8>(0, CMDLINE_MAX_SIZE as u64);
119-
120-
// Use the actual string length but limit to the orgiginal incoming size
121-
let orig_len = zero_page.read_u32(0x238) as usize;
122-
123-
let orig_cmdline = unsafe {
124-
core::str::from_utf8_unchecked(&cmdline[0..orig_len]).trim_matches(char::from(0))
125-
};
126-
let orig_len = orig_cmdline.len();
127-
128-
cmdline[orig_len] = b' ';
129-
cmdline[orig_len + 1..orig_len + 1 + addition.len()].copy_from_slice(addition.as_bytes());
130-
cmdline[orig_len + 1 + addition.len()] = 0;
131-
132-
// Commandline pointer/size
133-
zero_page.write_u32(0x228, CMDLINE_START as u32);
134-
zero_page.write_u32(0x238, (orig_len + addition.len() + 1) as u32);
135-
136-
Ok(())
137-
}
138-
139-
pub fn load_kernel(f: &mut dyn Read) -> Result<u64, Error> {
140-
f.seek(0)?;
141-
142-
let mut buf: [u8; 1024] = [0; 1024];
143-
144-
f.read(&mut buf[0..512])?;
145-
f.read(&mut buf[512..])?;
146-
147-
let setup = crate::mem::MemoryRegion::from_bytes(&mut buf[..]);
111+
pub fn load_initrd(&mut self, f: &mut dyn Read) -> Result<(), Error> {
112+
let size = f.get_size() as u64;
113+
let addr = match self.initrd_addr(size) {
114+
Some(addr) => addr,
115+
None => return Err(Error::NoInitrdMemory),
116+
};
148117

149-
if setup.read_u16(0x1fe) != 0xAA55 {
150-
return Err(Error::MagicMissing);
151-
}
118+
let mut region = MemoryRegion::new(addr, size);
119+
f.seek(0)?;
120+
f.load_file(&mut region)?;
152121

153-
if setup.read_u32(0x202) != 0x5372_6448 {
154-
return Err(Error::MagicMissing);
122+
// initrd pointer/size
123+
self.0.hdr.ramdisk_image = addr as u32;
124+
self.0.hdr.ramdisk_size = size as u32;
125+
Ok(())
155126
}
156127

157-
// Need for relocation
158-
if setup.read_u16(0x206) < 0x205 {
159-
return Err(Error::KernelOld);
128+
pub fn append_cmdline(&mut self, addition: &[u8]) {
129+
if !addition.is_empty() {
130+
CMDLINE.borrow_mut().append(addition);
131+
assert!(CMDLINE.borrow().len() < self.0.hdr.cmdline_size);
132+
}
160133
}
161134

162-
// Check relocatable
163-
if setup.read_u8(0x234) == 0 {
164-
return Err(Error::NotRelocatable);
135+
pub fn boot(&mut self) {
136+
// 0x200 is the startup_64 offset
137+
let jump_address = self.0.hdr.code32_start as u64 + 0x200;
138+
// Rely on x86 C calling convention where second argument is put into %rsi register
139+
let ptr = jump_address as *const ();
140+
let code: extern "C" fn(usize, usize) = unsafe { core::mem::transmute(ptr) };
141+
(code)(0 /* dummy value */, &mut self.0 as *mut _ as usize);
165142
}
143+
}
166144

167-
let header_start = 0x1f1 as usize;
168-
let header_end = 0x202 + buf[0x0201] as usize;
169-
170-
// Reuse the zero page that we were originally given
171-
// TODO: Zero and fill it ourself but will need to save E820 details
172-
let mut zero_page = crate::mem::MemoryRegion::new(ZERO_PAGE_START as u64, 4096);
173-
174-
let dst = zero_page.as_mut_slice(header_start as u64, (header_end - header_start) as u64);
175-
dst.copy_from_slice(&buf[header_start..header_end]);
176-
177-
// Unknown loader
178-
zero_page.write_u8(0x210, 0xff);
145+
// This is the highest region at which we can load the kernel command line.
146+
const CMDLINE_START: u64 = 0x4b000;
147+
const CMDLINE_MAX_LEN: u64 = 0x10000;
179148

180-
// Where we will load the kernel into
181-
zero_page.write_u32(0x214, KERNEL_LOCATION);
149+
static CMDLINE: AtomicRefCell<CmdLine> = AtomicRefCell::new(CmdLine::new());
182150

183-
let mut setup_sects = buf[header_start] as usize;
151+
struct CmdLine {
152+
region: MemoryRegion,
153+
length: usize, // Does not include null pointer
154+
}
184155

185-
if setup_sects == 0 {
186-
setup_sects = 4;
156+
impl CmdLine {
157+
const fn new() -> Self {
158+
Self {
159+
region: MemoryRegion::new(CMDLINE_START, CMDLINE_MAX_LEN),
160+
length: 0,
161+
}
187162
}
188163

189-
setup_sects += 1; // Include the boot sector
190-
191-
let setup_bytes = setup_sects * 512; // Use to start reading the main image
192-
193-
let mut load_offset = u64::from(KERNEL_LOCATION);
194-
195-
f.seek(setup_bytes as u32)?;
196-
197-
loop {
198-
let mut dst = crate::mem::MemoryRegion::new(load_offset, 512);
199-
let dst = dst.as_mut_slice(0, 512);
164+
const fn len(&self) -> u32 {
165+
self.length as u32
166+
}
200167

201-
match f.read(dst) {
202-
Err(crate::fat::Error::EndOfFile) => {
203-
// 0x200 is the startup_64 offset
204-
return Ok(u64::from(KERNEL_LOCATION) + 0x200);
205-
}
206-
Err(e) => return Err(Error::FileError(e)),
207-
Ok(_) => {}
208-
};
168+
fn append(&mut self, args: &[u8]) {
169+
let bytes = self.region.as_bytes();
170+
bytes[self.length] = b' ';
171+
self.length += 1;
209172

210-
load_offset += 512;
173+
bytes[self.length..self.length + args.len()].copy_from_slice(args);
174+
self.length += args.len();
175+
bytes[self.length] = 0;
211176
}
212177
}

src/loader.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
// limitations under the License.
1414

1515
use crate::{
16-
bzimage,
16+
boot,
17+
bzimage::{self, Kernel},
1718
common::ascii_strip,
1819
fat::{self, Read},
1920
};
@@ -121,7 +122,7 @@ fn default_entry_path(fs: &fat::Filesystem) -> Result<[u8; 260], fat::Error> {
121122
Ok(entry_path)
122123
}
123124

124-
pub fn load_default_entry(fs: &fat::Filesystem) -> Result<u64, Error> {
125+
pub fn load_default_entry(fs: &fat::Filesystem, info: &dyn boot::Info) -> Result<Kernel, Error> {
125126
let default_entry_path = default_entry_path(&fs)?;
126127
let default_entry_path = ascii_strip(&default_entry_path);
127128

@@ -132,19 +133,20 @@ pub fn load_default_entry(fs: &fat::Filesystem) -> Result<u64, Error> {
132133
let initrd_path = ascii_strip(&entry.initrd_path);
133134
let cmdline = ascii_strip(&entry.cmdline);
134135

136+
let mut kernel = Kernel::new(info);
137+
135138
let mut bzimage_file = fs.open(bzimage_path)?;
136-
let jump_address = bzimage::load_kernel(&mut bzimage_file)?;
139+
kernel.load_kernel(&mut bzimage_file)?;
137140

138141
if !initrd_path.is_empty() {
139142
let mut initrd_file = fs.open(initrd_path)?;
140-
bzimage::load_initrd(&mut initrd_file)?;
143+
kernel.load_initrd(&mut initrd_file)?;
141144
}
142145

143-
if !cmdline.is_empty() {
144-
bzimage::append_commandline(cmdline)?
145-
}
146+
kernel.append_cmdline(info.cmdline());
147+
kernel.append_cmdline(cmdline.as_bytes());
146148

147-
Ok(jump_address)
149+
Ok(kernel)
148150
}
149151

150152
#[cfg(test)]

0 commit comments

Comments
 (0)