Skip to main content

hermit/arch/x86_64/kernel/
mod.rs

1#[cfg(feature = "common-os")]
2use core::arch::asm;
3use core::ptr;
4use core::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
5#[cfg(feature = "common-os")]
6use core::{mem, slice};
7
8use hermit_entry::boot_info::{PlatformInfo, RawBootInfo};
9use memory_addresses::PhysAddr;
10use x86_64::registers::control::{Cr0, Cr4};
11
12use crate::arch::x86_64::kernel::core_local::*;
13use crate::env::{self, is_uhyve};
14
15#[cfg(feature = "acpi")]
16pub mod acpi;
17pub mod apic;
18pub mod core_local;
19pub mod gdt;
20pub mod interrupts;
21#[cfg(feature = "kernel-stack")]
22pub mod kernel_stack;
23#[cfg(all(not(feature = "pci"), feature = "virtio"))]
24pub mod mmio;
25#[cfg(feature = "pci")]
26pub mod pci;
27pub mod pic;
28pub mod pit;
29pub mod processor;
30pub mod scheduler;
31pub mod serial;
32#[cfg(target_os = "none")]
33mod start;
34pub mod switch;
35#[cfg(feature = "common-os")]
36mod syscall;
37pub(crate) mod systemtime;
38#[cfg(feature = "vga")]
39pub mod vga;
40
41pub fn get_ram_address() -> PhysAddr {
42	PhysAddr::new(env::boot_info().hardware_info.phys_addr_range.start)
43}
44
45#[cfg(feature = "smp")]
46pub fn get_possible_cpus() -> u32 {
47	use core::cmp;
48
49	match env::boot_info().platform_info {
50		// FIXME: Remove get_processor_count after a transition period for uhyve 0.1.3 adoption
51		PlatformInfo::Uhyve { num_cpus, .. } => cmp::max(
52			u32::try_from(num_cpus.get()).unwrap(),
53			get_processor_count(),
54		),
55		_ => apic::local_apic_id_count(),
56	}
57}
58
59#[cfg(feature = "smp")]
60pub fn get_processor_count() -> u32 {
61	CPU_ONLINE.load(Ordering::Acquire)
62}
63
64#[cfg(not(feature = "smp"))]
65pub fn get_processor_count() -> u32 {
66	1
67}
68
69pub fn is_uhyve_with_pci() -> bool {
70	matches!(
71		env::boot_info().platform_info,
72		PlatformInfo::Uhyve { has_pci: true, .. }
73	)
74}
75
76pub fn args() -> Option<&'static str> {
77	match env::boot_info().platform_info {
78		PlatformInfo::Multiboot { command_line, .. }
79		| PlatformInfo::LinuxBootParams { command_line, .. } => command_line,
80		_ => None,
81	}
82}
83
84/// Real Boot Processor initialization as soon as we have put the first Welcome message on the screen.
85#[cfg(target_os = "none")]
86pub fn boot_processor_init() {
87	processor::detect_features();
88	processor::configure();
89
90	if cfg!(feature = "vga") && !env::is_uhyve() {
91		#[cfg(feature = "vga")]
92		vga::init();
93	}
94
95	crate::mm::init();
96	crate::mm::print_information();
97	CoreLocal::get().add_irq_counter();
98	env::init();
99	gdt::add_current_core();
100	interrupts::load_idt();
101	pic::init();
102
103	processor::detect_frequency();
104	crate::logging::KERNEL_LOGGER.set_time(true);
105	processor::print_information();
106	debug!("Cr0 = {:?}", Cr0::read());
107	debug!("Cr4 = {:?}", Cr4::read());
108	interrupts::install();
109	systemtime::init();
110
111	if !env::is_uhyve() {
112		#[cfg(feature = "acpi")]
113		acpi::init();
114	}
115	if is_uhyve_with_pci() || !is_uhyve() {
116		#[cfg(feature = "pci")]
117		pci::init();
118	}
119
120	apic::init();
121	scheduler::install_timer_handler();
122	finish_processor_init();
123}
124
125/// Application Processor initialization
126#[cfg(all(target_os = "none", feature = "smp"))]
127pub fn application_processor_init() {
128	CoreLocal::install();
129	processor::configure();
130	gdt::add_current_core();
131	interrupts::load_idt();
132	if processor::supports_x2apic() {
133		apic::init_x2apic();
134	}
135	apic::init_local_apic();
136	debug!("Cr0 = {:?}", Cr0::read());
137	debug!("Cr4 = {:?}", Cr4::read());
138	finish_processor_init();
139}
140
141fn finish_processor_init() {
142	if env::is_uhyve() {
143		// uhyve does not use apic::detect_from_acpi and therefore does not know the number of processors and
144		// their APIC IDs in advance.
145		// Therefore, we have to add each booted processor into the CPU_LOCAL_APIC_IDS vector ourselves.
146		// Fortunately, the Local APIC IDs of uhyve are sequential and therefore match the Core IDs.
147		apic::add_local_apic_id(core_id() as u8);
148
149		// uhyve also boots each processor into _start itself and does not use apic::boot_application_processors.
150		// Therefore, the current processor already needs to prepare the processor variables for a possible next processor.
151		apic::init_next_processor_variables();
152	}
153}
154
155pub fn boot_next_processor() {
156	// This triggers apic::boot_application_processors (bare-metal/QEMU) or uhyve
157	// to initialize the next processor.
158	let cpu_online = CPU_ONLINE.fetch_add(1, Ordering::Release);
159
160	if !env::is_uhyve() {
161		if cpu_online == 0 {
162			#[cfg(all(target_os = "none", feature = "smp"))]
163			apic::boot_application_processors();
164		}
165
166		if !cfg!(feature = "smp") {
167			apic::print_information();
168		}
169	}
170}
171
172pub fn print_statistics() {
173	interrupts::print_statistics();
174}
175
176/// `CPU_ONLINE` is the count of CPUs that finished initialization.
177///
178/// It also synchronizes initialization of CPU cores.
179pub static CPU_ONLINE: AtomicU32 = AtomicU32::new(0);
180
181pub static CURRENT_STACK_ADDRESS: AtomicPtr<u8> = AtomicPtr::new(ptr::null_mut());
182
183#[cfg(target_os = "none")]
184#[inline(never)]
185#[unsafe(no_mangle)]
186unsafe extern "C" fn pre_init(boot_info: Option<&'static RawBootInfo>, cpu_id: u32) -> ! {
187	use x86_64::registers::control::Cr0Flags;
188
189	// Enable caching
190	unsafe {
191		Cr0::update(|flags| flags.remove(Cr0Flags::CACHE_DISABLE | Cr0Flags::NOT_WRITE_THROUGH));
192	}
193
194	if cpu_id == 0 {
195		env::set_boot_info(*boot_info.unwrap());
196
197		crate::boot_processor_main()
198	} else {
199		#[cfg(not(feature = "smp"))]
200		{
201			let style = anstyle::Style::new().fg_color(Some(anstyle::AnsiColor::Red.into()));
202			let preamble = format_args!("[            ][{cpu_id}][{style}ERROR{style:#}]");
203			println!(
204				"{preamble} Secondary core booted, but Hermit was not built with SMP support!"
205			);
206			loop {
207				processor::halt();
208			}
209		}
210		#[cfg(feature = "smp")]
211		crate::application_processor_main();
212	}
213}
214
215#[cfg(feature = "common-os")]
216const LOADER_START: usize = 0x0100_0000_0000;
217#[cfg(feature = "common-os")]
218const LOADER_STACK_SIZE: usize = 0x8000;
219
220#[cfg(feature = "common-os")]
221pub fn load_application<F, T>(code_size: u64, tls_size: u64, func: F) -> T
222where
223	F: FnOnce(&'static mut [u8], Option<&'static mut [u8]>) -> T,
224{
225	use core::slice;
226
227	use align_address::Align;
228	use free_list::PageLayout;
229	use memory_addresses::VirtAddr;
230	use x86_64::structures::paging::{PageSize, Size4KiB as BasePageSize};
231
232	use crate::arch::x86_64::mm::paging::{self, PageTableEntryFlags, PageTableEntryFlagsExt};
233	use crate::mm::{FrameAlloc, PageRangeAllocator};
234
235	let code_size = (code_size as usize + LOADER_STACK_SIZE).align_up(BasePageSize::SIZE as usize);
236	let layout = PageLayout::from_size_align(code_size, BasePageSize::SIZE as usize).unwrap();
237	let frame_range = FrameAlloc::allocate(layout).unwrap();
238	let physaddr = PhysAddr::from(frame_range.start());
239
240	let mut flags = PageTableEntryFlags::empty();
241	flags.normal().writable().user().execute_enable();
242	paging::map::<BasePageSize>(
243		VirtAddr::from(LOADER_START),
244		physaddr,
245		code_size / BasePageSize::SIZE as usize,
246		flags,
247	);
248
249	let loader_start_ptr = ptr::with_exposed_provenance_mut(LOADER_START);
250	let code_slice = unsafe { slice::from_raw_parts_mut(loader_start_ptr, code_size) };
251
252	if tls_size > 0 {
253		// To access TLS blocks on x86-64, TLS offsets are *subtracted* from the thread register value.
254		// So the thread pointer needs to be `block_ptr + tls_offset`.
255		// GNU style TLS requires `fs:0` to represent the same address as the thread pointer.
256		// Since the thread pointer points to the end of the TLS blocks, we need to store it there.
257		let tcb_size = mem::size_of::<*mut ()>();
258		let tls_offset = tls_size as usize;
259
260		let tls_memsz = (tls_offset + tcb_size).align_up(BasePageSize::SIZE as usize);
261		let layout = PageLayout::from_size(tls_memsz).unwrap();
262		let frame_range = FrameAlloc::allocate(layout).unwrap();
263		let physaddr = PhysAddr::from(frame_range.start());
264
265		let mut flags = PageTableEntryFlags::empty();
266		flags.normal().writable().user().execute_disable();
267		let tls_virt = VirtAddr::from(LOADER_START + code_size + BasePageSize::SIZE as usize);
268		paging::map::<BasePageSize>(
269			tls_virt,
270			physaddr,
271			tls_memsz / BasePageSize::SIZE as usize,
272			flags,
273		);
274		let block =
275			unsafe { slice::from_raw_parts_mut(tls_virt.as_mut_ptr(), tls_offset + tcb_size) };
276		for elem in block.iter_mut() {
277			*elem = 0;
278		}
279
280		// thread_ptr = block_ptr + tls_offset
281		let thread_ptr = block[tls_offset..].as_mut_ptr().cast::<()>();
282		unsafe {
283			thread_ptr.cast::<*mut ()>().write(thread_ptr);
284		}
285		crate::arch::x86_64::kernel::processor::writefs(thread_ptr.expose_provenance());
286
287		func(code_slice, Some(block))
288	} else {
289		func(code_slice, None)
290	}
291}
292
293#[cfg(feature = "common-os")]
294pub unsafe fn jump_to_user_land(entry_point: usize, code_size: usize, arg: &[&str]) -> ! {
295	use alloc::ffi::CString;
296
297	use align_address::Align;
298	use x86_64::structures::paging::{PageSize, Size4KiB as BasePageSize};
299
300	use crate::arch::x86_64::kernel::scheduler::TaskStacks;
301
302	info!("Create new file descriptor table");
303	core_scheduler().recreate_objmap().unwrap();
304
305	let entry_point: usize = LOADER_START | entry_point;
306	let stack_pointer: usize = LOADER_START
307		+ (code_size + LOADER_STACK_SIZE).align_up(BasePageSize::SIZE.try_into().unwrap())
308		- 8;
309
310	let stack_pointer = stack_pointer - 128 /* red zone */ - arg.len() * mem::size_of::<*mut u8>();
311	let stack_ptr = ptr::with_exposed_provenance_mut::<*mut u8>(stack_pointer);
312	let argv = unsafe { slice::from_raw_parts_mut(stack_ptr, arg.len()) };
313	let len = arg.iter().fold(0, |acc, x| acc + x.len() + 1);
314	// align stack pointer to fulfill the requirements of the x86_64 ABI
315	let stack_pointer = (stack_pointer - len).align_down(16) - mem::size_of::<usize>();
316
317	let mut pos: usize = 0;
318	for (i, s) in arg.iter().enumerate() {
319		let s = CString::new(*s).unwrap();
320		let bytes = s.as_bytes_with_nul();
321		argv[i] = ptr::with_exposed_provenance_mut::<u8>(stack_pointer + pos);
322		pos += bytes.len();
323
324		unsafe {
325			argv[i].copy_from_nonoverlapping(bytes.as_ptr(), bytes.len());
326		}
327	}
328
329	debug!("Jump to user space at 0x{entry_point:x}, stack pointer 0x{stack_pointer:x}");
330
331	unsafe {
332		asm!(
333			"and rsp, {0}",
334			"swapgs",
335			"push {1}",
336			"push {2}",
337			"push {3}",
338			"push {4}",
339			"push {5}",
340			"mov rdi, {6}",
341			"mov rsi, {7}",
342			"iretq",
343			const u64::MAX - (TaskStacks::MARKER_SIZE as u64 - 1),
344			const 0x23usize,
345			in(reg) stack_pointer,
346			const 0x1202u64,
347			const 0x2busize,
348			in(reg) entry_point,
349			in(reg) argv.len(),
350			in(reg) argv.as_ptr(),
351			options(nostack, noreturn)
352		);
353	}
354}