Skip to main content

hermit/arch/x86_64/kernel/
mod.rs

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