Implement linux x86 32bit legacy boot protocol

This commit is contained in:
Zhang Junyang 2023-10-23 17:11:22 +08:00 committed by Tate, Hongliang Tian
parent 9d0e0bbc70
commit a532340c65
19 changed files with 261 additions and 159 deletions

View File

@ -17,12 +17,16 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Syscall Test (Multiboot)
id: syscall_test_mb
run: RUSTFLAGS="-C opt-level=1" make run AUTO_SYSCALL_TEST=1 ENABLE_KVM=0 SKIP_GRUB_MENU=1 BOOT_METHOD=qemu-grub BOOT_PROTOCOL=multiboot
- name: Syscall Test (Multiboot2) - name: Syscall Test (Multiboot2)
id: syscall_test_mb2 id: syscall_test_mb2
run: RUSTFLAGS="-C opt-level=1" make run AUTO_SYSCALL_TEST=1 ENABLE_KVM=0 SKIP_GRUB_MENU=1 BOOT_METHOD=qemu-grub BOOT_PROTOCOL=multiboot2 run: RUSTFLAGS="-C opt-level=1" make run AUTO_SYSCALL_TEST=1 ENABLE_KVM=0 SKIP_GRUB_MENU=1 BOOT_METHOD=qemu-grub BOOT_PROTOCOL=multiboot2
# TODO: include the integration tests for Multiboot/MicroVM/Linux boot methods, which are not ready yet. - name: Syscall Test (Linux Boot Protocol)
id: syscall_test_lbp
run: RUSTFLAGS="-C opt-level=1" make run AUTO_SYSCALL_TEST=1 ENABLE_KVM=0 SKIP_GRUB_MENU=1 BOOT_METHOD=qemu-grub BOOT_PROTOCOL=linux
# - name: Syscall Test (Linux Boot Protocol) # TODO: include the integration tests for MicroVM, which is not ready yet.
# id: syscall_test_lbp
# run: RUSTFLAGS="-C opt-level=1" make run AUTO_SYSCALL_TEST=1 ENABLE_KVM=0 SKIP_GRUB_MENU=1 BOOT_METHOD=qemu-grub BOOT_PROTOCOL=linux

3
Cargo.lock generated
View File

@ -647,7 +647,6 @@ dependencies = [
"volatile", "volatile",
"x86", "x86",
"x86_64", "x86_64",
"xmas-elf",
] ]
[[package]] [[package]]
@ -655,6 +654,7 @@ name = "jinux-frame-x86-boot-setup"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"uart_16550", "uart_16550",
"xmas-elf",
] ]
[[package]] [[package]]
@ -729,6 +729,7 @@ dependencies = [
"clap", "clap",
"glob", "glob",
"rand", "rand",
"xmas-elf",
] ]
[[package]] [[package]]

View File

@ -30,8 +30,5 @@ aml = "0.16.3"
multiboot2 = "0.16.0" multiboot2 = "0.16.0"
rsdp = "2.0.0" rsdp = "2.0.0"
[build-dependencies]
xmas-elf = "0.8.0"
[features] [features]
intel_tdx = ["dep:tdx-guest"] intel_tdx = ["dep:tdx-guest"]

View File

@ -1,22 +1,13 @@
use std::{ use std::{
error::Error, error::Error,
io::{Seek, Write}, io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use xmas_elf::program::{ProgramHeader, SegmentData};
// We chose the legacy setup sections to be 7 so that the setup header
// is page-aligned and the legacy setup section size would be 0x1000.
const LEGACY_SETUP_SECS: usize = 7;
const LEGACY_SETUP_SEC_SIZE: usize = 0x200 * (LEGACY_SETUP_SECS + 1);
const SETUP32_LMA: usize = 0x100000;
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
let source_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); let source_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
build_linux_setup_header(&source_dir, &out_dir)?; build_linux_setup_header(&source_dir, &out_dir)?;
copy_to_raw_binary(&out_dir)?;
Ok(()) Ok(())
} }
@ -68,44 +59,3 @@ fn build_linux_setup_header(
Ok(()) Ok(())
} }
/// We need a binary which satisfies `LMA == File_Offset`, and objcopy
/// does not satisfy us well, so we should parse the ELF and do our own
/// objcopy job.
///
/// Interstingly, the resulting binary should be the same as the memory
/// dump of the kernel setup header when it's loaded by the bootloader.
fn copy_to_raw_binary(out_dir: &Path) -> Result<(), Box<dyn Error + Send + Sync>> {
// Strip the elf header to get the raw header.
let elf_path = out_dir.join("bin").join("jinux-frame-x86-boot-setup");
let bin_path = out_dir.join("bin").join("jinux-frame-x86-boot-setup.bin");
let elf_file = std::fs::read(elf_path)?;
let elf = xmas_elf::ElfFile::new(&elf_file)?;
let bin_file = std::fs::File::create(bin_path)?;
let mut bin_writer = std::io::BufWriter::new(bin_file);
for ph in elf.program_iter() {
let ProgramHeader::Ph32(program) = ph else {
return Err("Unexpected program header type".into());
};
if program.get_type().unwrap() == xmas_elf::program::Type::Load {
let dest_file_offset = program.virtual_addr as usize + LEGACY_SETUP_SEC_SIZE - SETUP32_LMA;
bin_writer.seek(std::io::SeekFrom::End(0))?;
let cur_file_offset = bin_writer.stream_position().unwrap() as usize;
if cur_file_offset < dest_file_offset {
let padding = vec![0; dest_file_offset - cur_file_offset];
bin_writer.write_all(&padding)?;
} else {
bin_writer.seek(std::io::SeekFrom::Start(dest_file_offset as u64))?;
}
let SegmentData::Undefined(header_data) = program.get_data(&elf).unwrap() else {
return Err("Unexpected segment data type".into());
};
bin_writer.write_all(header_data)?;
}
}
Ok(())
}

View File

@ -15,7 +15,7 @@ MULTIBOOT_ENTRY_MAGIC = 0x2BADB002
MULTIBOOT2_ENTRY_MAGIC = 0x36D76289 MULTIBOOT2_ENTRY_MAGIC = 0x36D76289
// The Linux 32-bit Boot Protocol entry point. // The Linux 32-bit Boot Protocol entry point.
// Must be located at 0x100000, ABI immutable! // Must be located at 0x8001000, ABI immutable!
.code32 .code32
.org 0x000 .org 0x000
.global __linux32_boot .global __linux32_boot
@ -34,7 +34,7 @@ __linux32_boot:
jmp initial_boot_setup jmp initial_boot_setup
// The Linux 64-bit Boot Protocol entry point. // The Linux 64-bit Boot Protocol entry point.
// Must be located at 0x100200, ABI immutable! // Must be located at 0x8001200, ABI immutable!
.code64 .code64
.org 0x200 .org 0x200
.global __linux64_boot_tag .global __linux64_boot_tag

View File

@ -2,7 +2,8 @@ ENTRY(__multiboot_boot)
OUTPUT_ARCH(i386:x86-64) OUTPUT_ARCH(i386:x86-64)
OUTPUT_FORMAT(elf64-x86-64) OUTPUT_FORMAT(elf64-x86-64)
KERNEL_LMA = 0x100000; KERNEL_LMA = 0x8000000;
LINUX_32_ENTRY = 0x8001000;
KERNEL_VMA = 0xffffffff80000000; KERNEL_VMA = 0xffffffff80000000;
SECTIONS SECTIONS
@ -13,6 +14,9 @@ SECTIONS
.multiboot_header : { KEEP(*(.multiboot_header)) } .multiboot_header : { KEEP(*(.multiboot_header)) }
.multiboot2_header : { KEEP(*(.multiboot2_header)) } .multiboot2_header : { KEEP(*(.multiboot2_header)) }
. = LINUX_32_ENTRY;
.boot : { KEEP(*(.boot)) } .boot : { KEEP(*(.boot)) }
. += KERNEL_VMA; . += KERNEL_VMA;

View File

@ -6,6 +6,7 @@
//! currently not needed by Jinux. //! currently not needed by Jinux.
//! //!
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct ScreenInfo { pub(super) struct ScreenInfo {
pub(super) orig_x: u8, /* 0x00 */ pub(super) orig_x: u8, /* 0x00 */
@ -48,6 +49,7 @@ pub(super) struct ScreenInfo {
pub(super) _reserved: [u8; 2], /* 0x3e */ pub(super) _reserved: [u8; 2], /* 0x3e */
} }
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct ApmBiosInfo { pub(super) struct ApmBiosInfo {
pub(super) version: u16, pub(super) version: u16,
@ -61,6 +63,7 @@ pub(super) struct ApmBiosInfo {
pub(super) dseg_len: u16, pub(super) dseg_len: u16,
} }
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct IstInfo { pub(super) struct IstInfo {
pub(super) signature: u32, pub(super) signature: u32,
@ -69,12 +72,14 @@ pub(super) struct IstInfo {
pub(super) perf_level: u32, pub(super) perf_level: u32,
} }
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct SysDescTable { pub(super) struct SysDescTable {
pub(super) length: u16, pub(super) length: u16,
pub(super) table: [u8; 14], pub(super) table: [u8; 14],
} }
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct OlpcOfwHeader { pub(super) struct OlpcOfwHeader {
pub(super) ofw_magic: u32, /* OFW signature */ pub(super) ofw_magic: u32, /* OFW signature */
@ -83,11 +88,13 @@ pub(super) struct OlpcOfwHeader {
pub(super) irq_desc_table: u32, pub(super) irq_desc_table: u32,
} }
#[derive(Copy, Clone, Debug)]
#[repr(C)] #[repr(C)]
pub(super) struct EdidInfo { pub(super) struct EdidInfo {
pub(super) dummy: [u8; 128], pub(super) dummy: [u8; 128],
} }
#[derive(Copy, Clone, Debug)]
#[repr(C)] #[repr(C)]
pub(super) struct EfiInfo { pub(super) struct EfiInfo {
pub(super) efi_loader_signature: u32, pub(super) efi_loader_signature: u32,
@ -100,8 +107,6 @@ pub(super) struct EfiInfo {
pub(super) efi_memmap_hi: u32, pub(super) efi_memmap_hi: u32,
} }
/// Magic stored in SetupHeader.boot_flag.
pub(super) const LINUX_BOOT_FLAG_MAGIC: u16 = 0xAA55;
/// Magic stored in SetupHeader.header. /// Magic stored in SetupHeader.header.
pub(super) const LINUX_BOOT_HEADER_MAGIC: u32 = 0x53726448; pub(super) const LINUX_BOOT_HEADER_MAGIC: u32 = 0x53726448;
@ -109,6 +114,7 @@ pub(super) const LINUX_BOOT_HEADER_MAGIC: u32 = 0x53726448;
/// ///
/// Originally defined in the linux source tree: /// Originally defined in the linux source tree:
/// `linux/arch/x86/include/uapi/asm/bootparam.h` /// `linux/arch/x86/include/uapi/asm/bootparam.h`
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct SetupHeader { pub(super) struct SetupHeader {
pub(super) setup_sects: u8, pub(super) setup_sects: u8,
@ -156,7 +162,7 @@ pub(super) struct SetupHeader {
/// ///
/// Originally defined in the linux source tree: /// Originally defined in the linux source tree:
/// `linux/arch/x86/include/asm/e820/types.h` /// `linux/arch/x86/include/asm/e820/types.h`
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug)]
#[repr(u32)] #[repr(u32)]
pub(super) enum E820Type { pub(super) enum E820Type {
Ram = 1, Ram = 1,
@ -194,6 +200,7 @@ pub(super) enum E820Type {
ReservedKern = 128, ReservedKern = 128,
} }
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct BootE820Entry { pub(super) struct BootE820Entry {
pub(super) addr: u64, pub(super) addr: u64,
@ -203,6 +210,7 @@ pub(super) struct BootE820Entry {
const E820_MAX_ENTRIES_ZEROPAGE: usize = 128; const E820_MAX_ENTRIES_ZEROPAGE: usize = 128;
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct EddDeviceParams { pub(super) struct EddDeviceParams {
// TODO: We currently have no plans to support the edd device, // TODO: We currently have no plans to support the edd device,
@ -212,6 +220,7 @@ pub(super) struct EddDeviceParams {
pub(super) _dummy: [u8; (0xeec - 0xd00) / 6 - 8], pub(super) _dummy: [u8; (0xeec - 0xd00) / 6 - 8],
} }
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct EddInfo { pub(super) struct EddInfo {
pub(super) device: u8, pub(super) device: u8,
@ -230,6 +239,7 @@ const EDDMAXNR: usize = 6;
/// ///
/// Originally defined in the linux source tree: /// Originally defined in the linux source tree:
/// `linux/arch/x86/include/uapi/asm/bootparam.h` /// `linux/arch/x86/include/uapi/asm/bootparam.h`
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)] #[repr(C, packed)]
pub(super) struct BootParams { pub(super) struct BootParams {
pub(super) screen_info: ScreenInfo, /* 0x000 */ pub(super) screen_info: ScreenInfo, /* 0x000 */

View File

@ -78,16 +78,14 @@ fn init_initramfs(initramfs: &'static Once<&'static [u8]>) {
} }
fn init_acpi_arg(acpi: &'static Once<BootloaderAcpiArg>) { fn init_acpi_arg(acpi: &'static Once<BootloaderAcpiArg>) {
acpi.call_once(|| { let rsdp = BOOT_PARAMS.get().unwrap().acpi_rsdp_addr;
BootloaderAcpiArg::Rsdp( if rsdp == 0 {
BOOT_PARAMS acpi.call_once(|| BootloaderAcpiArg::NotProvided);
.get() } else {
.unwrap() acpi.call_once(|| {
.acpi_rsdp_addr BootloaderAcpiArg::Rsdp(rsdp.try_into().expect("RSDP address overflowed!"))
.try_into() });
.unwrap(), }
)
});
} }
fn init_framebuffer_info(framebuffer_arg: &'static Once<BootloaderFramebufferArg>) { fn init_framebuffer_info(framebuffer_arg: &'static Once<BootloaderFramebufferArg>) {
@ -133,10 +131,9 @@ extern "Rust" {
} }
/// The entry point of Rust code called by the Linux 64-bit boot compatible bootloader. /// The entry point of Rust code called by the Linux 64-bit boot compatible bootloader.
/// It is the ELF entrypoint.
#[no_mangle] #[no_mangle]
unsafe extern "sysv64" fn __linux64_boot(params: boot_params::BootParams) -> ! { unsafe extern "sysv64" fn __linux64_boot(params_ptr: *const boot_params::BootParams) -> ! {
assert_eq!({ params.hdr.boot_flag }, boot_params::LINUX_BOOT_FLAG_MAGIC); let params = *params_ptr;
assert_eq!({ params.hdr.header }, boot_params::LINUX_BOOT_HEADER_MAGIC); assert_eq!({ params.hdr.header }, boot_params::LINUX_BOOT_HEADER_MAGIC);
BOOT_PARAMS.call_once(|| params); BOOT_PARAMS.call_once(|| params);
crate::boot::register_boot_init_callbacks( crate::boot::register_boot_init_callbacks(

View File

@ -7,3 +7,4 @@ edition = "2021"
[dependencies] [dependencies]
uart_16550 = "0.3.0" uart_16550 = "0.3.0"
xmas-elf = "0.8.0"

View File

@ -0,0 +1,12 @@
const FIELD_PAYLOAD_OFFSET: u32 = 0x248;
const FIELD_PAYLOAD_LENGTH: u32 = 0x24c;
/// Safty: user must ensure that the boot_params_ptr is valid
pub unsafe fn get_payload_offset(boot_params_ptr: u32) -> u32 {
*((boot_params_ptr + FIELD_PAYLOAD_OFFSET) as *const u32)
}
/// Safty: user must ensure that the boot_params_ptr is valid
pub unsafe fn get_payload_length(boot_params_ptr: u32) -> u32 {
*((boot_params_ptr + FIELD_PAYLOAD_LENGTH) as *const u32)
}

View File

@ -6,11 +6,13 @@ struct Stdout {
serial_port: SerialPort, serial_port: SerialPort,
} }
static mut STDOUT: Stdout = Stdout { serial_port: unsafe { SerialPort::new(0x0) } }; static mut STDOUT: Stdout = Stdout {
serial_port: unsafe { SerialPort::new(0x0) },
};
/// safety: this function must only be called once /// safety: this function must only be called once
pub unsafe fn init() { pub unsafe fn init() {
STDOUT = Stdout::init(); STDOUT = Stdout::init();
} }
impl Stdout { impl Stdout {

View File

@ -0,0 +1,29 @@
use xmas_elf::program::{ProgramHeader, SegmentData};
pub fn load_elf(file: &[u8]) -> u32 {
let elf = xmas_elf::ElfFile::new(file).unwrap();
for ph in elf.program_iter() {
let ProgramHeader::Ph64(program) = ph else {
panic!("[setup] Unexpected program header type!");
};
if program.get_type().unwrap() == xmas_elf::program::Type::Load {
let SegmentData::Undefined(header_data) = program.get_data(&elf).unwrap() else {
panic!("[setup] Unexpected segment data type!");
};
// Safety: the physical address from the ELF file is valid
let dst_slice = unsafe {
core::slice::from_raw_parts_mut(
program.physical_addr as *mut u8,
program.mem_size as usize,
)
};
dst_slice[..program.file_size as usize].copy_from_slice(header_data);
let zero_slice = &mut dst_slice[program.file_size as usize..];
zero_slice.fill(0);
}
}
// Return the Linux 32-bit Boot Protocol entry point defined by Jinux.
0x8001000
}

View File

@ -1,22 +1,44 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
mod boot_params;
mod console; mod console;
mod loader;
use core::arch::global_asm; use core::arch::{asm, global_asm};
global_asm!(include_str!("header.S")); global_asm!(include_str!("header.S"));
unsafe fn call_jinux_entrypoint(entrypoint: u32, boot_params_ptr: u32) -> ! {
asm!("mov esi, {}", in(reg) boot_params_ptr);
asm!("mov eax, {}", in(reg) entrypoint);
asm!("jmp eax");
unreachable!();
}
#[no_mangle] #[no_mangle]
pub extern "cdecl" fn _rust_setup_entry(boot_params_ptr: u32) -> ! { pub extern "cdecl" fn _rust_setup_entry(boot_params_ptr: u32) -> ! {
// safety: this init function is only called once // Safety: this init function is only called once.
unsafe { console::init() }; unsafe { console::init() };
println!("[setup] boot_params_ptr: {:#x}", boot_params_ptr); println!("[setup] boot_params_ptr: {:#x}", boot_params_ptr);
#[allow(clippy::empty_loop)]
loop {} let payload_offset = unsafe { boot_params::get_payload_offset(boot_params_ptr) };
let payload_length = unsafe { boot_params::get_payload_length(boot_params_ptr) };
let payload = unsafe {
core::slice::from_raw_parts_mut(payload_offset as *mut u8, payload_length as usize)
};
println!("[setup] loading ELF payload...");
let entrypoint = loader::load_elf(payload);
println!("[setup] entrypoint: {:#x}", entrypoint);
// Safety: the entrypoint and the ptr is valid.
unsafe { call_jinux_entrypoint(entrypoint, boot_params_ptr) };
} }
#[panic_handler] #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { fn panic(info: &core::panic::PanicInfo) -> ! {
println!("panic: {:?}", info);
loop {} loop {}
} }

View File

@ -3,7 +3,6 @@ use multiboot2::MemoryAreaType;
use spin::Once; use spin::Once;
use crate::{ use crate::{
arch::x86::kernel::acpi::AcpiMemoryHandler,
boot::{ boot::{
kcmdline::KCmdlineArg, kcmdline::KCmdlineArg,
memory_region::{non_overlapping_regions_from, MemoryRegion, MemoryRegionType}, memory_region::{non_overlapping_regions_from, MemoryRegion, MemoryRegionType},
@ -88,20 +87,7 @@ fn init_initramfs(initramfs: &'static Once<&'static [u8]>) {
fn init_acpi_arg(acpi: &'static Once<BootloaderAcpiArg>) { fn init_acpi_arg(acpi: &'static Once<BootloaderAcpiArg>) {
// The multiboot protocol does not contain RSDP address. // The multiboot protocol does not contain RSDP address.
// TODO: What about UEFI? // TODO: What about UEFI?
let rsdp = unsafe { rsdp::Rsdp::search_for_on_bios(AcpiMemoryHandler {}) }; acpi.call_once(|| BootloaderAcpiArg::NotProvided);
match rsdp {
Ok(map) => match map.validate() {
Ok(_) => acpi.call_once(|| {
if map.revision() > 0 {
BootloaderAcpiArg::Xsdt(map.xsdt_address() as usize)
} else {
BootloaderAcpiArg::Rsdt(map.rsdt_address() as usize)
}
}),
Err(_) => acpi.call_once(|| BootloaderAcpiArg::NotExists),
},
Err(_) => acpi.call_once(|| BootloaderAcpiArg::NotExists),
};
} }
fn init_framebuffer_info(framebuffer_arg: &'static Once<BootloaderFramebufferArg>) { fn init_framebuffer_info(framebuffer_arg: &'static Once<BootloaderFramebufferArg>) {

View File

@ -104,9 +104,42 @@ pub fn init() {
BootloaderAcpiArg::Xsdt(addr) => unsafe { BootloaderAcpiArg::Xsdt(addr) => unsafe {
AcpiTables::from_rsdt(AcpiMemoryHandler {}, 1, addr).unwrap() AcpiTables::from_rsdt(AcpiMemoryHandler {}, 1, addr).unwrap()
}, },
BootloaderAcpiArg::NotExists => { BootloaderAcpiArg::NotProvided => {
warn!("Not found ACPI table"); // We search by ourselves if the bootloader decides not to provide a rsdp location.
return; let rsdp = unsafe { rsdp::Rsdp::search_for_on_bios(AcpiMemoryHandler {}) };
match rsdp {
Ok(map) => match map.validate() {
Ok(_) => {
if map.revision() > 0 {
unsafe {
AcpiTables::from_rsdt(
AcpiMemoryHandler {},
1,
map.xsdt_address() as usize,
)
.unwrap()
}
} else {
unsafe {
AcpiTables::from_rsdt(
AcpiMemoryHandler {},
0,
map.rsdt_address() as usize,
)
.unwrap()
}
}
}
Err(_) => {
warn!("ACPI info not found!");
return;
}
},
Err(_) => {
warn!("ACPI info not found!");
return;
}
}
} }
}; };

View File

@ -18,7 +18,8 @@ use spin::Once;
/// This is because bootloaders differ in such behaviors. /// This is because bootloaders differ in such behaviors.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum BootloaderAcpiArg { pub enum BootloaderAcpiArg {
NotExists, /// The bootloader does not provide one, a manual search is needed.
NotProvided,
/// Physical address of the RSDP. /// Physical address of the RSDP.
Rsdp(usize), Rsdp(usize),
/// Address of RSDT provided in RSDP v1. /// Address of RSDT provided in RSDP v1.

View File

@ -9,3 +9,4 @@ anyhow = "1.0.32"
clap = { version = "4.3.19", features = ["derive"] } clap = { version = "4.3.19", features = ["derive"] }
glob = "0.3.1" glob = "0.3.1"
rand = "0.8.5" rand = "0.8.5"
xmas-elf = "0.8.0"

View File

@ -0,0 +1,98 @@
use std::{
fs::File,
io::{Read, Write},
path::Path,
};
use xmas_elf::program::{ProgramHeader, SegmentData};
// We chose the legacy setup sections to be 7 so that the setup header
// is page-aligned and the legacy setup section size would be 0x1000.
const LEGACY_SETUP_SECS: usize = 7;
const LEGACY_SETUP_SEC_SIZE: usize = 0x200 * (LEGACY_SETUP_SECS + 1);
const SETUP32_LMA: usize = 0x100000;
/// We need a binary which satisfies `LMA == File_Offset`, and objcopy
/// does not satisfy us well, so we should parse the ELF and do our own
/// objcopy job.
///
/// Interstingly, the resulting binary should be the same as the memory
/// dump of the kernel setup header when it's loaded by the bootloader.
fn header_to_raw_binary(elf_file: &[u8]) -> Vec<u8> {
let elf = xmas_elf::ElfFile::new(&elf_file).unwrap();
let mut bin = Vec::<u8>::new();
for ph in elf.program_iter() {
let ProgramHeader::Ph32(program) = ph else {
panic!("Unexpected program header type");
};
if program.get_type().unwrap() == xmas_elf::program::Type::Load {
let SegmentData::Undefined(header_data) = program.get_data(&elf).unwrap() else {
panic!("Unexpected segment data type");
};
let dst_file_offset =
program.virtual_addr as usize + LEGACY_SETUP_SEC_SIZE - SETUP32_LMA;
let dst_file_length = program.file_size as usize;
if bin.len() < dst_file_offset + dst_file_length {
bin.resize(dst_file_offset + dst_file_length, 0);
}
let dest_slice = bin[dst_file_offset..dst_file_offset + dst_file_length].as_mut();
dest_slice.copy_from_slice(header_data);
}
}
bin
}
/// This function sould be used when generating the Linux x86 Boot setup header.
/// Some fields in the Linux x86 Boot setup header should be filled after assembled.
/// And the filled fields must have the bytes with values of 0xAB. See
/// `framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/header.S` for more
/// info on this mechanism.
fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) {
let size = value.len();
assert_eq!(
&header[offset..offset + size],
vec![0xABu8; size].as_slice()
);
header[offset..offset + size].copy_from_slice(value);
}
pub fn make_bzimage(path: &Path, kernel_path: &Path, header_path: &Path) -> std::io::Result<()> {
let mut header = Vec::new();
File::open(header_path)?.read_to_end(&mut header)?;
let mut header = header_to_raw_binary(&header);
// Pad the header to let the payload starts with 8-byte alignment.
header.resize((header.len() + 7) & !7, 0x00);
let mut kernel = Vec::new();
File::open(kernel_path)?.read_to_end(&mut kernel)?;
let header_len = header.len();
let kernel_len = kernel.len();
let payload_offset = header_len - LEGACY_SETUP_SEC_SIZE + SETUP32_LMA;
fill_header_field(
&mut header,
0x248, /* payload_offset */
&(payload_offset as u32).to_le_bytes(),
);
fill_header_field(
&mut header,
0x24C, /* payload_length */
&(kernel_len as u32).to_le_bytes(),
);
fill_header_field(
&mut header,
0x260, /* init_size */
&((header_len + kernel_len) as u32).to_le_bytes(),
);
let mut kernel_image = File::create(path)?;
kernel_image.write_all(&header)?;
kernel_image.write_all(&kernel)?;
Ok(())
}

View File

@ -1,6 +1,8 @@
mod linux_boot;
use std::{ use std::{
fs::{self, File}, fs,
io::{Read, Write}, io::Read,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -85,13 +87,14 @@ pub fn create_bootdev_image(
BootProtocol::Linux => { BootProtocol::Linux => {
// Find the setup header in the build script output directory. // Find the setup header in the build script output directory.
let bs_out_dir = glob("target/x86_64-custom/debug/build/jinux-frame-*").unwrap(); let bs_out_dir = glob("target/x86_64-custom/debug/build/jinux-frame-*").unwrap();
let header_bin = Path::new(bs_out_dir.into_iter().next().unwrap().unwrap().as_path()) let header_path = Path::new(bs_out_dir.into_iter().next().unwrap().unwrap().as_path())
.join("out") .join("out")
.join("bin") .join("bin")
.join("jinux-frame-x86-boot-setup.bin"); .join("jinux-frame-x86-boot-setup");
// Make the `zimage`-compatible kernel image and place it in the boot directory. // Make the `bzImage`-compatible kernel image and place it in the boot directory.
let target_path = iso_root.join("boot").join("jinuz"); let target_path = iso_root.join("boot").join("jinuz");
make_zimage(&target_path, &jinux_path.as_path(), &header_bin.as_path()).unwrap(); linux_boot::make_bzimage(&target_path, &jinux_path.as_path(), &header_path.as_path())
.unwrap();
target_path target_path
} }
BootProtocol::Multiboot | BootProtocol::Multiboot2 => { BootProtocol::Multiboot | BootProtocol::Multiboot2 => {
@ -162,52 +165,3 @@ pub fn generate_grub_cfg(
buffer buffer
} }
/// This function sould be used when generating the Linux x86 Boot setup header.
/// Some fields in the Linux x86 Boot setup header should be filled after assembled.
/// And the filled fields must have the bytes with values of 0xAB. See
/// `framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/header.S` for more
/// info on this mechanism.
fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) {
let size = value.len();
assert_eq!(
&header[offset..offset + size],
vec![0xABu8; size].as_slice()
);
header[offset..offset + size].copy_from_slice(value);
}
fn make_zimage(path: &Path, kernel_path: &Path, header_path: &Path) -> std::io::Result<()> {
let mut header = Vec::new();
File::open(header_path)?.read_to_end(&mut header)?;
// Pad the header to let the payload starts with 8-byte alignment.
header.resize((header.len() + 7) & !7, 0x00);
let mut kernel = Vec::new();
File::open(kernel_path)?.read_to_end(&mut kernel)?;
let header_len = header.len();
let kernel_len = kernel.len();
fill_header_field(
&mut header,
0x248, /* payload_offset */
&(header_len as u32).to_le_bytes(),
);
fill_header_field(
&mut header,
0x24C, /* payload_length */
&(kernel_len as u32).to_le_bytes(),
);
fill_header_field(
&mut header,
0x260, /* init_size */
&((kernel_len + header_len) as u32).to_le_bytes(),
);
let mut kernel_image = File::create(path)?;
kernel_image.write_all(&header)?;
kernel_image.write_all(&kernel)?;
Ok(())
}