//! x86-64 architecture implementation use core::{mem::size_of, ops::DerefMut, sync::atomic::Ordering}; use abi::error::Error; use acpi_lib::{mcfg::Mcfg, AcpiTables, InterruptModel}; use alloc::boxed::Box; use device_api::{ interrupt::{ExternalInterruptController, MessageInterruptController}, timer::MonotonicTimestampProviderDevice, Device, }; use git_version::git_version; use kernel_fs::devfs; use libk::{ mem::{ address::{FromRaw, IntoRaw, PhysicalAddress}, device::{DeviceMemoryAttributes, RawDeviceMemoryMapping}, table::EntryLevelExt, }, sync::SpinFence, util::OneTimeInit, }; use yboot_proto::{v1::AvailableMemoryRegion, LoadProtocolV1}; use ygg_driver_pci::PciBusManager; mod acpi; mod apic; mod boot; pub mod context; pub mod cpu; mod cpuid; mod exception; mod gdt; mod intrinsics; pub mod mem; mod peripherals; mod registers; mod smp; mod syscall; use crate::{ arch::x86_64::{ intrinsics::{IoPort, IoPortAccess}, mem::{ map_heap_block, table::{PageTable, L2}, HEAP_MAPPING_OFFSET, }, }, debug::{self, LogLevel}, device::{ self, display::{console, fb_console::FramebufferConsole, linear_fb::LinearFramebuffer}, }, fs::{Initrd, INITRD_DATA}, mem::{ heap, phys::{self, reserved::reserve_region, PhysicalMemoryRegion}, table::EntryLevel, }, }; use self::{ acpi::{AcpiAllocator, AcpiHandlerImpl}, apic::{ioapic::IoApic, local::LocalApic}, boot::BootData, cpu::Cpu, cpuid::{ProcessorFeatures, PROCESSOR_FEATURES}, mem::{ init_fixed_tables, table::{PageAttributes, PageEntry, L1, L3}, EarlyMapping, MEMORY_LIMIT, RAM_MAPPING_L1, RAM_MAPPING_OFFSET, }, peripherals::{i8253::I8253, ps2::PS2Controller, serial::ComPort}, smp::CPU_COUNT, }; use super::{Architecture, CpuAccess, CpuMessage}; /// x86-64-specific interrupt number #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum IrqNumber { /// Legacy (ISA) interrupt Isa(u8), /// Global System Interrupt Gsi(u8), } /// x86-64 architecture implementation pub struct X86_64 { boot_data: OneTimeInit, acpi: OneTimeInit>, // Display framebuffer: OneTimeInit, fbconsole: OneTimeInit, ioapic: OneTimeInit, timer: OneTimeInit, } static SHUTDOWN_FENCE: SpinFence = SpinFence::new(); /// Global x86-64 architecture value pub static ARCHITECTURE: X86_64 = X86_64 { boot_data: OneTimeInit::new(), acpi: OneTimeInit::new(), framebuffer: OneTimeInit::new(), fbconsole: OneTimeInit::new(), ioapic: OneTimeInit::new(), timer: OneTimeInit::new(), }; impl Architecture for X86_64 { const KERNEL_VIRT_OFFSET: usize = 0xFFFFFF8000000000; type IrqNumber = IrqNumber; type L3 = mem::table::L3; unsafe fn start_application_processors(&self) { if let Some(acpi) = self.acpi.try_get() { let Some(pinfo) = acpi .platform_info_in(AcpiAllocator) .ok() .and_then(|p| p.processor_info) else { return; }; smp::start_ap_cores(&pinfo); } } fn cpu_count() -> usize { CPU_COUNT.load(Ordering::Acquire) } unsafe fn set_interrupt_mask(mask: bool) { if mask { core::arch::asm!("cli"); } else { core::arch::asm!("sti"); } } fn interrupt_mask() -> bool { let mut flags: u64; unsafe { core::arch::asm!("pushfq; pop {0}", out(reg) flags, options(att_syntax)); } // If IF is zero, interrupts are disabled (masked) flags & (1 << 9) == 0 } fn wait_for_interrupt() { unsafe { core::arch::asm!("hlt"); } } #[inline] unsafe fn map_device_memory( &self, base: PhysicalAddress, size: usize, attrs: DeviceMemoryAttributes, ) -> Result { mem::map_device_memory(base, size, attrs) } #[inline] unsafe fn unmap_device_memory(&self, map: &RawDeviceMemoryMapping) { mem::unmap_device_memory(map) } fn map_physical_memory + Clone>( &self, it: I, _memory_start: PhysicalAddress, memory_end: PhysicalAddress, ) -> Result<(), Error> { let end_l1i = (IntoRaw::::into_raw(memory_end) + (1 << 30) - 1) >> 30; if end_l1i > 512 { todo!( "Cannot handle {}GiB of RAM", end_l1i * L1::SIZE / (1024 * 1024 * 1024) ); } MEMORY_LIMIT.init(memory_end.into_raw()); // Check if 1GiB pages are supported if PROCESSOR_FEATURES .get() .contains(ProcessorFeatures::PDPE1GB) { // Just map gigabytes of RAM for l1i in 0..end_l1i { // TODO NX unsafe { RAM_MAPPING_L1[l1i] = PageEntry::::block( PhysicalAddress::from_raw(l1i << 30), PageAttributes::WRITABLE, ); } } } else { // Allocate the intermediate tables first let l2_tables_start = phys::find_contiguous_region(it, end_l1i) .expect("Could not allocate the memory for RAM mapping L2 tables"); unsafe { reserve_region( "ram-l2-tables", PhysicalMemoryRegion { base: l2_tables_start, size: end_l1i * 0x1000, }, ); } // Fill in the tables for l1i in 0..end_l1i { let l2_phys_addr = l2_tables_start.add(l1i * 0x1000); // TODO (minor) the slice is uninitialized, maybe find some way to deal with that // case nicely // Safety: ok, the mapping is done to the memory obtained from // find_contiguous_region() let mut l2_data = unsafe { EarlyMapping::<[PageEntry; 512]>::map(l2_phys_addr)? }; // Safety: ok, the slice comes from EarlyMapping of a page-aligned region let l2 = unsafe { PageTable::from_raw_slice_mut(l2_data.deref_mut()) }; for l2i in 0..512 { // TODO NX l2[l2i] = PageEntry::::block( PhysicalAddress::from_raw((l1i << 30) | (l2i << 21)), PageAttributes::WRITABLE, ); } // Point the L1 entry to the L2 table unsafe { RAM_MAPPING_L1[l1i] = PageEntry::::table(l2_phys_addr, PageAttributes::WRITABLE) }; intrinsics::flush_cpu_cache(); // The EarlyMapping is then dropped } } Ok(()) } #[inline] fn virtualize(address: u64) -> usize { let address = address as usize; if address < *mem::MEMORY_LIMIT.get() { address + RAM_MAPPING_OFFSET } else { panic!("Invalid physical address: {:#x}", address); } } #[inline] fn physicalize(address: usize) -> u64 { if address < RAM_MAPPING_OFFSET || address - RAM_MAPPING_OFFSET >= *mem::MEMORY_LIMIT.get() { panic!("Not a virtualized physical address: {:#x}", address); } (address - RAM_MAPPING_OFFSET) as _ } fn external_interrupt_controller( &'static self, ) -> &'static dyn ExternalInterruptController { self.ioapic.get() } fn message_interrupt_controller(&'static self) -> &'static dyn MessageInterruptController { Cpu::local().local_apic() } fn monotonic_timer(&'static self) -> &'static dyn MonotonicTimestampProviderDevice { self.timer.get() } } impl X86_64 { unsafe fn handle_ipi(&self, _msg: CpuMessage) { warnln!("Received an IPI"); todo!(); } fn set_boot_data(&self, data: BootData) { match data { BootData::YBoot(data) => { // Reserve the memory map unsafe { reserve_region( "mmap", PhysicalMemoryRegion { base: PhysicalAddress::from_raw(data.memory_map.address), size: data.memory_map.len as usize * size_of::(), }, ); } // Reserve initrd, if not NULL if data.initrd_address != 0 && data.initrd_size != 0 { let aligned_start = data.initrd_address & !0xFFF; let aligned_end = (data.initrd_address + data.initrd_size + 0xFFF) & !0xFFF; unsafe { reserve_region( "initrd", PhysicalMemoryRegion { base: PhysicalAddress::from_raw(aligned_start), size: (aligned_end - aligned_start) as usize, }, ); } } } } self.boot_data.init(data); } unsafe fn init_physical_memory_from_yboot(data: &LoadProtocolV1) -> Result<(), Error> { let mmap = EarlyMapping::::map_slice( PhysicalAddress::from_raw(data.memory_map.address), data.memory_map.len as usize, )?; phys::init_from_iter(mmap.as_ref().iter().map(|reg| PhysicalMemoryRegion { base: PhysicalAddress::from_raw(reg.start_address), size: reg.page_count as usize * 0x1000, })) } unsafe fn init_memory_management(&self) -> Result<(), Error> { const HEAP_PAGES: usize = 16; init_fixed_tables(); // Reserve lower 4MiB just in case reserve_region( "lowmem", PhysicalMemoryRegion { base: PhysicalAddress::ZERO, size: 4 * 1024 * 1024, }, ); match self.boot_data.get() { &BootData::YBoot(data) => Self::init_physical_memory_from_yboot(data)?, } // Setup heap for i in 0..HEAP_PAGES { // Allocate in 2MiB chunks let l2_page = phys::alloc_2m_page()?; map_heap_block(i, l2_page); } heap::init_heap(HEAP_MAPPING_OFFSET, HEAP_PAGES * L2::SIZE); Ok(()) } unsafe fn init_platform(&'static self, cpu_id: usize) -> Result<(), Error> { Cpu::init_local(LocalApic::new(), cpu_id as _); if cpu_id == 0 { // Register the PCI drivers // TODO make this implicit init ygg_driver_pci::register_class_driver( "NVMe Host Controller", 0x01, Some(0x08), Some(0x02), ygg_driver_nvme::probe, ); ygg_driver_pci::register_class_driver( "AHCI SATA Controller", 0x01, Some(0x06), Some(0x01), ygg_driver_ahci::probe, ); ygg_driver_pci::register_vendor_driver( "Virtio PCI Network Device", 0x1AF4, 0x1000, ygg_driver_virtio_net::probe, ); match self.boot_data.get() { &BootData::YBoot(data) => { let start = PhysicalAddress::from_raw(data.initrd_address); Self::init_initrd(start, start.add(data.initrd_size as usize)); } } self.init_acpi_from_boot_data()?; Self::disable_8259(); self.timer.init(I8253::new()); let com1_3 = Box::leak(Box::new(ComPort::new(0x3F8, 0x3E8, IrqNumber::Isa(4)))); debug::add_sink(com1_3.port_a(), LogLevel::Debug); self.init_framebuffer()?; debug::init(); infoln!( "Yggdrasil v{} ({})", env!("CARGO_PKG_VERSION"), git_version!() ); let ps2 = Box::leak(Box::new(PS2Controller::new( IrqNumber::Isa(1), IrqNumber::Isa(12), 0x64, 0x60, ))); ps2.init()?; if let Some(acpi) = self.acpi.try_get() { self.init_platform_from_acpi(acpi)?; } self.timer.get().init_irq()?; // ps2.connect(self.tty.get()); ps2.init_irq()?; device::register_device(self.ioapic.get()); device::register_device(ps2); PciBusManager::setup_bus_devices()?; } Ok(()) } unsafe fn init_acpi_from_boot_data(&self) -> Result<(), Error> { match self.boot_data.get() { &BootData::YBoot(data) => self.init_acpi_from_rsdp(data.rsdp_address as usize), } } unsafe fn init_acpi_from_rsdp(&self, rsdp: usize) -> Result<(), Error> { let acpi_tables = AcpiTables::from_rsdp(AcpiHandlerImpl, rsdp).map_err(|err| { errorln!("Could not initialize ACPI tables: {:?}", err); Error::InvalidArgument })?; self.acpi.init(acpi_tables); Ok(()) } unsafe fn init_platform_from_acpi( &self, acpi: &'static AcpiTables, ) -> Result<(), Error> { let platform_info = acpi.platform_info_in(AcpiAllocator).map_err(|err| { errorln!("Could not get ACPI platform info: {:?}", err); Error::InvalidArgument })?; let InterruptModel::Apic(apic_info) = platform_info.interrupt_model else { panic!("The processor does not support APIC"); }; self.ioapic.init(IoApic::from_acpi(&apic_info)?); // acpi::init_acpi(acpi).unwrap(); if let Ok(mcfg) = acpi.find_table::() { for entry in mcfg.entries() { PciBusManager::add_segment_from_mcfg(entry)?; } } Ok(()) } unsafe fn init_framebuffer(&'static self) -> Result<(), Error> { match self.boot_data.get() { &BootData::YBoot(data) => { let info = &data.opt_framebuffer; self.framebuffer.init(LinearFramebuffer::from_physical_bits( PhysicalAddress::from_raw(info.res_address), info.res_size as usize, info.res_stride as usize, info.res_width, info.res_height, )?); } } self.fbconsole.init(FramebufferConsole::from_framebuffer( self.framebuffer.get(), None, )?); debug::add_sink(self.fbconsole.get(), LogLevel::Info); // self.tty.init(CombinedTerminal::new(self.fbconsole.get())); devfs::add_named_block_device(self.framebuffer.get(), "fb0")?; // devfs::add_char_device(self.tty.get(), CharDeviceType::TtyRegular)?; console::add_console_autoflush(self.fbconsole.get()); Ok(()) } fn init_initrd(initrd_start: PhysicalAddress, initrd_end: PhysicalAddress) { if initrd_start.is_zero() || initrd_end <= initrd_start { infoln!("No initrd loaded"); return; } let start_aligned = initrd_start.page_align_down::(); let end_aligned = initrd_start.page_align_up::(); let data = unsafe { core::slice::from_raw_parts( start_aligned.virtualize_raw() as *const u8, initrd_end - initrd_start, ) }; let initrd = Initrd { phys_page_start: start_aligned, phys_page_len: end_aligned - start_aligned, data, }; INITRD_DATA.init(initrd); } unsafe fn disable_8259() { infoln!("Disabling i8259 PIC"); // TODO should I make a module for 8259 if I don't even use it? let pic_master_cmd = IoPort::::new(0x20); let pic_master_data = IoPort::::new(0x21); let pic_slave_cmd = IoPort::::new(0xA0); let pic_slave_data = IoPort::::new(0xA1); // Remap PIC IRQ vectors to 32.. pic_master_cmd.write(0x11); pic_slave_cmd.write(0x11); pic_master_data.write(32); pic_slave_data.write(32 + 8); pic_slave_data.write(0xFF); pic_master_data.write(0xFF); pic_master_cmd.write(0x20); pic_slave_cmd.write(0x20); } }