diff --git a/kernel/arch/riscv64/src/sbi.rs b/kernel/arch/riscv64/src/sbi.rs index 0c217e3a..f7b14569 100644 --- a/kernel/arch/riscv64/src/sbi.rs +++ b/kernel/arch/riscv64/src/sbi.rs @@ -4,6 +4,8 @@ const EXT_HSM: u64 = 0x48534D; const EXT_TIME: u64 = 0x54494D45; const EXT_DBCN: u64 = 0x4442434E; const EXT_SPI: u64 = 0x735049; +const EXT_SYSTEM_SHUTDOWN: u64 = 0x53525354; +const EXT_SYSTEM_SHUTDOWN_LEGACY: u64 = 0x08; primitive_enum! { pub enum Status: i64 { @@ -108,3 +110,9 @@ pub fn sbi_debug_console_write_byte(byte: u8) { pub fn sbi_set_timer(next_event: u64) { unsafe { sbi_do_call(EXT_TIME, 0x00, next_event, 0, 0, 0, 0, 0) }.ok(); } + +pub fn sbi_system_shutdown() -> ! { + unsafe { sbi_do_call(EXT_SYSTEM_SHUTDOWN, 0x00, 0, 0, 0, 0, 0, 0) }.ok(); + unsafe { sbi_do_call(EXT_SYSTEM_SHUTDOWN_LEGACY, 0x00, 0, 0, 0, 0, 0, 0) }.ok(); + unreachable!() +} diff --git a/kernel/driver/acpi/src/lib.rs b/kernel/driver/acpi/src/lib.rs index 1ca97bf0..1d55bb15 100644 --- a/kernel/driver/acpi/src/lib.rs +++ b/kernel/driver/acpi/src/lib.rs @@ -1,8 +1,8 @@ -#![feature(allocator_api)] +#![feature(allocator_api, never_type)] #![no_std] use acpi::AcpiTables; -use acpi_system::{AcpiInterruptMethod, AcpiSystem}; +use acpi_system::{AcpiInterruptMethod, AcpiSleepState, AcpiSystem}; use alloc::boxed::Box; use libk::error::Error; use libk_util::{sync::IrqSafeSpinlock, OneTimeInit}; @@ -45,6 +45,17 @@ pub fn get_pci_route( .ok() } +pub fn power_off() -> Result { + let system = ACPI_SYSTEM.get(); + unsafe { + system.lock().enter_sleep_state(AcpiSleepState::S5).ok(); + + loop { + core::arch::asm!("cli; hlt"); + } + } +} + /// Initializes ACPI management pub fn switch_to_acpi(tables: &'static AcpiTables) -> Result<(), Error> { // NOTE mostly broken for real HW @@ -67,12 +78,6 @@ pub fn switch_to_acpi(tables: &'static AcpiTables) -> Result<() // // 6. Do something with the devices // // 7. Actually enter the S5 state - // unsafe { - // PLATFORM - // .send_ipi(IpiDeliveryTarget::OtherCpus, IpiMessage::Shutdown) - // .unwrap(); - // } - // SHUTDOWN_FENCE.signal(); // SHUTDOWN_FENCE.wait_all(CPU_COUNT.load(Ordering::Acquire)); diff --git a/kernel/src/arch/aarch64/gic/mod.rs b/kernel/src/arch/aarch64/gic/mod.rs index f122d178..c181447b 100644 --- a/kernel/src/arch/aarch64/gic/mod.rs +++ b/kernel/src/arch/aarch64/gic/mod.rs @@ -141,8 +141,9 @@ impl ExternalInterruptController for Gic { gicc.clear_irq(irq_number); if irq_number == IPI_VECTOR as usize { - // TODO pop entry - crate::panic::panic_secondary(); + let ipi = Cpu::local().get_ipi(); + AArch64::handle_ipi(ipi); + return; } if irq_number < GIC_PPI_START as usize { diff --git a/kernel/src/arch/aarch64/mod.rs b/kernel/src/arch/aarch64/mod.rs index cc15d760..cdb2120f 100644 --- a/kernel/src/arch/aarch64/mod.rs +++ b/kernel/src/arch/aarch64/mod.rs @@ -5,14 +5,15 @@ use core::sync::atomic::{self, Ordering}; use aarch64_cpu::registers::{CNTP_CTL_EL0, CNTP_TVAL_EL0}; use alloc::sync::Arc; use device_api::{ - interrupt::{Irq, LocalInterruptController}, + interrupt::{IpiDeliveryTarget, IpiMessage, Irq, LocalInterruptController}, ResetDevice, }; use device_tree::{ driver::{unflatten_device_tree, InitSequence}, DeviceTree, DeviceTreeNodeExt, }; -use kernel_arch_aarch64::{mem, ArchitectureImpl, PerCpuData}; +use kernel_arch::Architecture; +use kernel_arch_aarch64::{mem, ArchitectureImpl, PerCpuData, CPU_COUNT}; use libk::{arch::Cpu, config, debug, device::external_interrupt_controller, error::Error}; use libk_mm::{ address::PhysicalAddress, @@ -20,7 +21,7 @@ use libk_mm::{ pointer::PhysicalRef, table::EntryLevelExt, }; -use libk_util::OneTimeInit; +use libk_util::{sync::SpinFence, OneTimeInit}; use tock_registers::interfaces::Writeable; use ygg_driver_pci::PciBusManager; @@ -28,6 +29,7 @@ use crate::{ arch::{aarch64::gic::Gic, Platform}, device::{power::arm_psci::Psci, MACHINE_NAME}, fs::{Initrd, INITRD_DATA}, + panic, util::call_init_array, }; @@ -68,6 +70,8 @@ impl BootStack { } } +static SHUTDOWN_FENCE: SpinFence = SpinFence::new(); + impl Platform for AArch64 { unsafe fn start_application_processors(&self) { if let Some(compatible) = self.machine_compatible.try_get() { @@ -96,6 +100,18 @@ impl Platform for AArch64 { psci.reset() } } + + unsafe fn power_off(&self) -> Result { + Self::halt_aps()?; + + if let Some(psci) = self.psci.try_get() { + log::info!("Powering off"); + psci.power_off() + } else { + log::warn!("No power off method, halting"); + ArchitectureImpl::halt(); + } + } } impl AArch64 { @@ -103,6 +119,41 @@ impl AArch64 { GIC.init(gic); } + fn handle_ipi(ipi: Option) { + let Some(ipi) = ipi else { + log::warn!("Spurious IPI received, assuming panic"); + panic::panic_secondary(); + }; + + match ipi { + IpiMessage::Panic => panic::panic_secondary(), + IpiMessage::Shutdown => Self::secondary_halt_handler(), + } + } + + fn secondary_halt_handler() { + log::info!("CPU halted"); + SHUTDOWN_FENCE.signal(); + ArchitectureImpl::halt(); + } + + unsafe fn halt_aps() -> Result<(), Error> { + let ap_count = CPU_COUNT.load(Ordering::Acquire) - 1; + + if ap_count > 0 { + if let Some(gic) = GIC.try_get() { + gic.send_ipi(IpiDeliveryTarget::OtherCpus, IpiMessage::Shutdown)?; + SHUTDOWN_FENCE.wait_all(ap_count); + Ok(()) + } else { + log::warn!("No way to handle secondary CPUs"); + Err(Error::NotImplemented) + } + } else { + Ok(()) + } + } + #[inline(never)] unsafe fn init_memory_management( &'static self, diff --git a/kernel/src/arch/mod.rs b/kernel/src/arch/mod.rs index dbaa3ad9..d1343df3 100644 --- a/kernel/src/arch/mod.rs +++ b/kernel/src/arch/mod.rs @@ -1,5 +1,6 @@ //! Provides architecture/platform-specific implementation details +use abi::error::Error; use kernel_arch::{Architecture, ArchitectureImpl}; #[cfg(any(target_arch = "aarch64", rust_analyzer))] @@ -51,6 +52,19 @@ pub trait Platform { } } + /// Performs full system powerdown. + /// + /// # Safety + /// + /// The caller must ensure it is actually safe to power down, i.e. no critical processes will be + /// aborted and no data will be lost. + unsafe fn power_off(&self) -> Result { + ArchitectureImpl::set_interrupt_mask(true); + loop { + ArchitectureImpl::wait_for_interrupt(); + } + } + // /// Adds a reset device to the system // fn register_reset_device(&self, reset: Arc) -> Result<(), Error> { // Err(Error::NotImplemented) diff --git a/kernel/src/arch/riscv64/mod.rs b/kernel/src/arch/riscv64/mod.rs index 311413dc..c0816e75 100644 --- a/kernel/src/arch/riscv64/mod.rs +++ b/kernel/src/arch/riscv64/mod.rs @@ -11,7 +11,7 @@ use kernel_arch::{Architecture, ArchitectureImpl}; use kernel_arch_riscv64::{ mem, registers::{SIE, SSTATUS}, - PerCpuData, + sbi, PerCpuData, }; use libk::{arch::Cpu, config}; use libk_mm::{ @@ -51,6 +51,10 @@ impl Platform for Riscv64 { ArchitectureImpl::halt(); } + unsafe fn power_off(&self) -> Result { + sbi::sbi_system_shutdown() + } + unsafe fn start_application_processors(&self) { // TODO asymmetric systems with different hart types are not yet supported. // e.g., in JH7110 there're two different types of cores diff --git a/kernel/src/arch/x86_64/apic/mod.rs b/kernel/src/arch/x86_64/apic/mod.rs index d4baf409..42d05b84 100644 --- a/kernel/src/arch/x86_64/apic/mod.rs +++ b/kernel/src/arch/x86_64/apic/mod.rs @@ -11,6 +11,8 @@ use libk::{ }; use static_assertions::{const_assert, const_assert_eq}; +use crate::arch::x86_64::X86_64; + use super::exception; pub mod ioapic; @@ -115,8 +117,8 @@ unsafe extern "C" fn dummy_irq_handler() { } unsafe extern "C" fn ipi_handler() { - let cpu = Cpu::local(); - todo!("Processor {} received an IPI", cpu.id()); + let ipi = Cpu::local().get_ipi(); + X86_64::handle_ipi(ipi); } global_asm!( diff --git a/kernel/src/arch/x86_64/exception.rs b/kernel/src/arch/x86_64/exception.rs index aa0f89e1..4035c067 100644 --- a/kernel/src/arch/x86_64/exception.rs +++ b/kernel/src/arch/x86_64/exception.rs @@ -4,12 +4,12 @@ use core::{arch::global_asm, mem::size_of}; use abi::{bitflags, primitive_enum, process::Signal}; use kernel_arch_x86::registers::{CR2, CR3}; use kernel_arch_x86_64::context::ExceptionFrame; -use libk::task::thread::Thread; +use libk::{arch::Cpu, task::thread::Thread}; use libk_mm::PageFaultKind; use libk_util::sync::spin_rwlock::IrqSafeRwLock; use tock_registers::interfaces::Readable; -use crate::arch::x86_64::apic; +use crate::arch::x86_64::{apic, X86_64}; primitive_enum! { enum ExceptionKind: u64 { @@ -231,7 +231,13 @@ extern "C" fn __x86_64_exception_handler(frame: *mut ExceptionFrame) { } extern "C" fn __x86_64_nmi_handler() -> ! { - crate::panic::panic_secondary(); + let ipi = Cpu::local().get_ipi(); + if let Some(ipi) = ipi { + X86_64::handle_ipi(Some(ipi)); + unreachable!(); + } else { + panic!("Spurious/unknown NMI received"); + } } /// Initializes the interrupt descriptor table for the given CPU. diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index b8809864..37fa0b75 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -1,16 +1,20 @@ //! x86-64 architecture implementation -use core::ptr::null_mut; +use core::{ptr::null_mut, sync::atomic::Ordering}; use abi::error::Error; use acpi::{mcfg::Mcfg, AcpiTables, HpetInfo, InterruptModel}; use alloc::sync::Arc; -use device_api::device::Device; +use device_api::{ + device::Device, + interrupt::{IpiDeliveryTarget, IpiMessage}, +}; +use kernel_arch::{Architecture, ArchitectureImpl}; use kernel_arch_x86::{ cpuid::{self, CpuFeatures, EcxFeatures, EdxFeatures, ExtEdxFeatures}, gdt, }; -use kernel_arch_x86_64::{mem, LocalApicInterface, PerCpuData}; +use kernel_arch_x86_64::{mem, LocalApicInterface, PerCpuData, CPU_COUNT}; use libk::{ arch::Cpu, config, debug, @@ -27,7 +31,10 @@ use libk_mm::{ pointer::PhysicalRef, table::EntryLevel, }; -use libk_util::OneTimeInit; +use libk_util::{ + sync::{IrqGuard, SpinFence}, + OneTimeInit, +}; use yboot_proto::{ v1::{self, AvailableMemoryRegion}, LoadProtocolV1, @@ -45,6 +52,7 @@ use crate::{ Platform, }, device::display::linear_fb::LinearFramebuffer, + panic, util::call_init_array, }; @@ -72,7 +80,9 @@ pub static PLATFORM: X86_64 = X86_64 { fbconsole: OneTimeInit::new(), }; -// + +static SHUTDOWN_FENCE: SpinFence = SpinFence::new(); + impl Platform for X86_64 { unsafe fn start_application_processors(&self) { if let Some(acpi) = self.acpi.try_get() { @@ -87,9 +97,42 @@ impl Platform for X86_64 { smp::start_ap_cores(&pinfo); } } + + unsafe fn power_off(&self) -> Result { + let _guard = IrqGuard::acquire(); + + let ap_count = CPU_COUNT.load(Ordering::Acquire) - 1; + if ap_count > 0 { + self.send_ipi(IpiDeliveryTarget::OtherCpus, IpiMessage::Shutdown)?; + SHUTDOWN_FENCE.wait_all(ap_count); + } + + ygg_driver_acpi::power_off() + } } impl X86_64 { + fn handle_ipi(ipi: Option) { + let Some(ipi) = ipi else { + log::warn!("Spurious IPI received, assuming panic"); + panic::panic_secondary(); + }; + + match ipi { + IpiMessage::Shutdown => { + log::info!("Core halted"); + SHUTDOWN_FENCE.signal(); + ArchitectureImpl::halt() + } + IpiMessage::Panic => panic::panic_secondary(), + } + } + + fn send_ipi(&self, target: IpiDeliveryTarget, message: IpiMessage) -> Result<(), Error> { + let cpu = Cpu::local(); + cpu.local_apic.send_ipi(target, message) + } + fn set_boot_data(&self, data: BootData) { match data { BootData::YBoot(data) => { @@ -196,6 +239,12 @@ impl X86_64 { call_init_array(); x86::init_platform_devices(early); + + extern "C" { + static __kernel_start: u8; + } + + log::info!("Load base: {:p}", &raw const __kernel_start); } Ok(()) diff --git a/kernel/src/device/power/arm_psci.rs b/kernel/src/device/power/arm_psci.rs index 4fc85022..130dfdbf 100644 --- a/kernel/src/device/power/arm_psci.rs +++ b/kernel/src/device/power/arm_psci.rs @@ -61,6 +61,7 @@ impl ResetDevice for Psci { impl Psci { const SYSTEM_RESET: u32 = 0x84000009; + const SYSTEM_OFF: u32 = 0x84000008; #[inline] unsafe fn call(&self, mut x0: u64, x1: u64, x2: u64, x3: u64) -> u64 { @@ -74,6 +75,12 @@ impl Psci { } x0 } + + /// Shut down the system + pub unsafe fn power_off(&self) -> Result { + self.call(Self::SYSTEM_OFF as _, 0, 0, 0); + unreachable!() + } } device_tree_driver! { diff --git a/kernel/src/main.rs b/kernel/src/main.rs index c4c5e972..6cb33016 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -75,8 +75,10 @@ cfg_if::cfg_if! { if #[cfg(target_arch = "x86_64")] { extern crate ygg_driver_net_igbe; } else if #[cfg(target_arch = "aarch64")] { + extern crate ygg_driver_bsp_arm; extern crate ygg_driver_bsp_bcm283x; } else if #[cfg(target_arch = "riscv64")] { + extern crate ygg_driver_bsp_riscv; extern crate ygg_driver_bsp_jh7110; extern crate ygg_driver_net_stmmac; diff --git a/kernel/src/syscall/imp/mod.rs b/kernel/src/syscall/imp/mod.rs index 1c50431e..c668fb79 100644 --- a/kernel/src/syscall/imp/mod.rs +++ b/kernel/src/syscall/imp/mod.rs @@ -16,7 +16,7 @@ use abi::{ option::OptionValue, path::Path, process::{ExecveOptions, ProcessId}, - system::{self, SystemInfoVariant}, + system::{self, SystemControlVariant, SystemInfoVariant}, time::{ClockType, SystemTime}, }; use libk::{ @@ -26,7 +26,10 @@ use libk::{ }; use libk_mm::phys; -use crate::fs; +use crate::{ + arch::{Platform, PLATFORM}, + fs, +}; use super::run_with_io; @@ -86,6 +89,16 @@ pub(crate) fn set_clock(ty: ClockType, value: &SystemTime) -> Result<(), Error> } } +pub(crate) fn system_control(option: u32, value: &mut [u8], size: usize) -> Result { + let _ = (value, size); + let option = SystemControlVariant::try_from(option)?; + match option { + SystemControlVariant::PowerOff => { + unsafe { PLATFORM.power_off() }?; + } + } +} + pub(crate) fn get_system_info(option: u32, buffer: &mut [u8]) -> Result { let option = SystemInfoVariant::try_from(option)?; match option { diff --git a/lib/abi/def/yggdrasil.abi b/lib/abi/def/yggdrasil.abi index d7c20754..02e51639 100644 --- a/lib/abi/def/yggdrasil.abi +++ b/lib/abi/def/yggdrasil.abi @@ -80,6 +80,7 @@ syscall unmount(opts: &UnmountOptions) -> Result<()>; syscall load_module(path: &str) -> Result<()>; syscall filesystem_control(fd: Option, option: u32, value: &mut [u8], len: usize) -> Result; +syscall system_control(option: u32, value: &mut [u8], len: usize) -> Result; syscall get_system_info(option: u32, value: &mut [u8]) -> Result; diff --git a/lib/abi/src/system.rs b/lib/abi/src/system.rs index f37443d2..d9b52070 100644 --- a/lib/abi/src/system.rs +++ b/lib/abi/src/system.rs @@ -21,6 +21,14 @@ option_group!( } ); +request_group!( + #[doc = "Common system controls"] + pub enum SystemControlVariant<'de> { + #[doc = "Power down the system"] + 0x1000: PowerOff((), ()), + } +); + abi_serde::impl_struct_serde!( SystemMemoryStats: [total_usable_pages, allocated_pages, free_pages, page_size] ); diff --git a/lib/runtime/src/system.rs b/lib/runtime/src/system.rs index 13b6edcd..e7b3aa9c 100644 --- a/lib/runtime/src/system.rs +++ b/lib/runtime/src/system.rs @@ -1,7 +1,10 @@ //! System-related parameters pub use abi::system::*; -use abi::{error::Error, option::OptionValue}; +use abi::{ + error::Error, + option::{OptionValue, RequestValue}, +}; /// Helper macro for [get_system_info] pub macro get_system_info($variant_ty:ty) {{ @@ -14,3 +17,13 @@ pub fn get_system_info<'de, T: OptionValue<'de>>(buffer: &'de mut [u8]) -> Resul let len = unsafe { crate::sys::get_system_info(T::VARIANT.into(), buffer) }?; T::load(&buffer[..len]) } + +/// Performs an operation on the system, like power management, reset etc +pub fn system_control<'de, T: RequestValue<'de>>( + buffer: &'de mut [u8], + request: &T::Request, +) -> Result { + let len = T::store_request(request, buffer)?; + let len = unsafe { crate::sys::system_control(T::VARIANT.into(), buffer, len) }?; + T::load_response(&buffer[..len]) +} diff --git a/userspace/sysutils/Cargo.toml b/userspace/sysutils/Cargo.toml index 4189af43..b8660ebb 100644 --- a/userspace/sysutils/Cargo.toml +++ b/userspace/sysutils/Cargo.toml @@ -33,6 +33,9 @@ yggdrasil-abi.workspace = true yggdrasil-rt.workspace = true runtime.workspace = true +[dev-dependencies] +runtime.workspace = true + [lib] path = "src/lib.rs" @@ -41,6 +44,14 @@ path = "src/lib.rs" name = "mount" path = "src/mount.rs" +[[bin]] +name = "poweroff" +path = "src/poweroff.rs" + +[[bin]] +name = "reboot" +path = "src/reboot.rs" + [[bin]] name = "chroot" path = "src/chroot.rs" diff --git a/userspace/sysutils/src/poweroff.rs b/userspace/sysutils/src/poweroff.rs new file mode 100644 index 00000000..3017ad8b --- /dev/null +++ b/userspace/sysutils/src/poweroff.rs @@ -0,0 +1,15 @@ +#![feature(rustc_private)] + +use std::{io, process::ExitCode}; + +use runtime::{abi::system, rt::system::system_control}; + +fn main() -> ExitCode { + if let Err(error) = system_control::(&mut [], &()) { + let error = io::Error::from(error); + eprintln!("{error}"); + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } +} diff --git a/userspace/sysutils/src/reboot.rs b/userspace/sysutils/src/reboot.rs new file mode 100644 index 00000000..f328e4d9 --- /dev/null +++ b/userspace/sysutils/src/reboot.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/xtask/src/build/userspace.rs b/xtask/src/build/userspace.rs index 943043fd..b2be21a0 100644 --- a/xtask/src/build/userspace.rs +++ b/xtask/src/build/userspace.rs @@ -29,35 +29,37 @@ const PROGRAMS: &[(&str, &str)] = &[ // shell ("shell", "bin/sh"), // sysutils - ("echo", "bin/echo"), - ("mount", "sbin/mount"), - ("chroot", "sbin/chroot"), - ("login", "sbin/login"), - ("strace", "bin/strace"), - ("ls", "bin/ls"), - ("mv", "bin/mv"), - ("ln", "bin/ln"), - ("mkdir", "bin/mkdir"), - ("touch", "bin/touch"), - ("env", "bin/env"), - ("rm", "bin/rm"), ("cat", "bin/cat"), - ("hexd", "bin/hexd"), - ("dd", "bin/dd"), - ("random", "bin/random"), - ("view", "bin/view"), - ("grep", "bin/grep"), ("chmod", "bin/chmod"), - ("sha256sum", "bin/sha256sum"), - ("sysmon", "bin/sysmon"), + ("chroot", "sbin/chroot"), ("date", "bin/date"), - ("sync", "bin/sync"), - ("sleep", "bin/sleep"), + ("dd", "bin/dd"), + ("echo", "bin/echo"), + ("env", "bin/env"), + ("grep", "bin/grep"), + ("hexd", "bin/hexd"), + ("ln", "bin/ln"), + ("login", "sbin/login"), + ("ls", "bin/ls"), ("lspci", "bin/lspci"), - ("ps", "bin/ps"), - ("top", "bin/top"), - ("tst", "bin/tst"), ("md2txt", "bin/md2txt"), + ("mkdir", "bin/mkdir"), + ("mount", "sbin/mount"), + ("mv", "bin/mv"), + ("poweroff", "sbin/poweroff"), + ("ps", "bin/ps"), + ("random", "bin/random"), + ("reboot", "sbin/reboot"), + ("rm", "bin/rm"), + ("sha256sum", "bin/sha256sum"), + ("sleep", "bin/sleep"), + ("strace", "bin/strace"), + ("sync", "bin/sync"), + ("sysmon", "bin/sysmon"), + ("top", "bin/top"), + ("touch", "bin/touch"), + ("tst", "bin/tst"), + ("view", "bin/view"), // netutils ("netconf", "sbin/netconf"), ("dhcp-client", "sbin/dhcp-client"),