fix: consolidate kernel patch chain

Absorb redundant kernel patches into v2 carriers, remove debug and
suspend patches no longer needed. Wire v2 patches in recipe.toml.
This commit is contained in:
2026-05-11 10:08:20 +01:00
parent bcedb7cb8f
commit 7dfb749b3d
13 changed files with 2085 additions and 2769 deletions
-90
View File
@@ -1,90 +0,0 @@
diff --git a/src/arch/x86_shared/start.rs b/src/arch/x86_shared/start.rs
index 7a7c0ae8..f1dbb6b4 100644
--- a/src/arch/x86_shared/start.rs
+++ b/src/arch/x86_shared/start.rs
@@ -82,6 +82,15 @@ extern "C" fn kstart() {
/// The entry to Rust, all things must be initialized
unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
unsafe {
+ // EARLY CANARY: write 'R' to COM1 before any kernel init.
+ // This proves the serial hardware works and the kernel reached Rust entry.
+ // If this character appears but "Redox OS starting..." does not,
+ // the hang is in args_ptr.read(), serial::init(), or graphical_debug::init().
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'R', options(nostack, preserves_flags));
+ }
+
let bootstrap = {
let args = args_ptr.read();
@@ -91,27 +100,49 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
// Set up graphical debug
graphical_debug::init(args.env());
+ // SECOND CANARY: write 'S' to COM1 after serial init.
+ // If 'R' appears but 'S' does not, the hang is in serial::init() or graphical_debug::init().
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'S', options(nostack, preserves_flags));
+ }
+
info!("Redox OS starting...");
args.print();
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ { core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'1', options(nostack, preserves_flags)); }
+
// Set up GDT
gdt::init_bsp(stack_end);
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ { core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'2', options(nostack, preserves_flags)); }
+
// Set up IDT
idt::init_bsp();
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ { core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'3', options(nostack, preserves_flags)); }
+
// Initialize RMM
#[cfg(target_arch = "x86")]
crate::startup::memory::init(&args, Some(0x100000), Some(0x40000000));
#[cfg(target_arch = "x86_64")]
crate::startup::memory::init(&args, Some(0x100000), None);
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ { core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'4', options(nostack, preserves_flags)); }
+
// Initialize paging
paging::init();
#[cfg(target_arch = "x86_64")]
crate::arch::alternative::early_init(true);
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ { core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'5', options(nostack, preserves_flags)); }
+
// Set up syscall instruction
interrupt::syscall::init();
@@ -121,6 +152,9 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
// Activate memory logging
crate::log::init();
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ { core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'6', options(nostack, preserves_flags)); }
+
// Initialize miscellaneous processor features
#[cfg(target_arch = "x86_64")]
crate::arch::misc::init(LogicalCpuId::BSP);
@@ -128,6 +162,9 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! {
// Initialize devices
device::init();
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ { core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'7', options(nostack, preserves_flags)); }
+
// Read ACPI tables, starts APs
if cfg!(feature = "acpi") {
crate::acpi::init(args.acpi_rsdp());
+10 -5
View File
@@ -34,10 +34,16 @@ index 7398145a..f4f57c23 100644
}
diff --git a/src/scheme/event.rs b/src/scheme/event.rs
index 36efe5b2..62e46c99 100644
index 36efe5b2..f2dc6276 100644
--- a/src/scheme/event.rs
+++ b/src/scheme/event.rs
@@ -25,12 +25,26 @@ impl KernelScheme for EventScheme {
@@ -1,4 +1,5 @@
use alloc::sync::Arc;
+use core::sync::atomic::Ordering;
use syscall::{EventFlags, O_NONBLOCK};
use crate::{
@@ -25,12 +26,25 @@ impl KernelScheme for EventScheme {
fn kopenat(
&self,
id: usize,
@@ -49,9 +55,8 @@ index 36efe5b2..62e46c99 100644
token: &mut CleanLockToken,
) -> Result<OpenResult> {
+ let path = match &user_buf {
+ StrOrBytes::Str(s) | StrOrBytes::Bytes(s) => {
+ core::str::from_utf8(s).unwrap_or("")
+ }
+ StrOrBytes::Str(s) => s,
+ StrOrBytes::Bytes(b) => core::str::from_utf8(b).unwrap_or(""),
+ };
+ if path.starts_with("eventfd/") {
+ let rest = &path[8..]; // after "eventfd/"
@@ -1,219 +0,0 @@
diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs
index 4dc2388..f472c08 100644
--- a/src/acpi/madt/arch/x86.rs
+++ b/src/acpi/madt/arch/x86.rs
@@ -20,0 +21 @@ use super::{Madt, MadtEntry};
+const AP_SPIN_LIMIT: u32 = 1_000_000;
@@ -45,7 +46,11 @@ pub(super) fn init(madt: Madt) {
- let result = mapper
- .map_phys(
- trampoline_page.start_address(),
- trampoline_frame.base(),
- PageFlags::new().execute(true).write(true),
- )
- .expect("failed to map trampoline");
+ let result = match mapper.map_phys(
+ trampoline_page.start_address(),
+ trampoline_frame.base(),
+ PageFlags::new().execute(true).write(true),
+ ) {
+ Some(result) => result,
+ None => {
+ println!("KERNEL AP: failed to map trampoline page, AP bring-up disabled");
+ return;
+ }
+ };
@@ -75,2 +79,0 @@ pub(super) fn init(madt: Madt) {
- let cpu_id = LogicalCpuId::next();
-
@@ -78,6 +81,8 @@ pub(super) fn init(madt: Madt) {
- let stack_start = RmmA::phys_to_virt(
- allocate_p2frame(4)
- .expect("no more frames in acpi stack_start")
- .base(),
- )
- .data();
+ let alloc = match allocate_p2frame(4) {
+ Some(frame) => frame,
+ None => {
+ println!("KERNEL AP: CPU {} no memory for stack, skipping", ap_local_apic.id);
+ continue;
+ }
+ };
+ let stack_start = RmmA::phys_to_virt(alloc.base()).data();
@@ -85,0 +91,10 @@ pub(super) fn init(madt: Madt) {
+ let next_cpu = crate::CPU_COUNT.load(Ordering::Relaxed);
+ if next_cpu >= crate::cpu_set::MAX_CPU_COUNT {
+ println!(
+ "KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
+ ap_local_apic.id
+ );
+ continue;
+ }
+ let cpu_id = LogicalCpuId::new(next_cpu);
+
@@ -140,2 +155,7 @@ pub(super) fn init(madt: Madt) {
- // Wait for trampoline ready
- while unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } == 0 {
+ // Wait for trampoline ready with timeout
+ let mut trampoline_ready = false;
+ for _ in 0..AP_SPIN_LIMIT {
+ if unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } != 0 {
+ trampoline_ready = true;
+ break;
+ }
@@ -144 +164,11 @@ pub(super) fn init(madt: Madt) {
- while !AP_READY.load(Ordering::SeqCst) {
+ if !trampoline_ready {
+ println!("KERNEL AP: CPU {} trampoline timeout, skipping", ap_local_apic.id);
+ continue;
+ }
+
+ let mut kernel_ready = false;
+ for _ in 0..AP_SPIN_LIMIT {
+ if AP_READY.load(Ordering::SeqCst) {
+ kernel_ready = true;
+ break;
+ }
@@ -146,0 +177,6 @@ pub(super) fn init(madt: Madt) {
+ if !kernel_ready {
+ println!("KERNEL AP: CPU {} AP_READY timeout, skipping", ap_local_apic.id);
+ continue;
+ }
+
+ crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
@@ -154 +190 @@ pub(super) fn init(madt: Madt) {
- let (_frame, _, flush) = unsafe {
+ if let Some((_frame, _, flush)) = unsafe {
@@ -157,3 +193,5 @@ pub(super) fn init(madt: Madt) {
- .expect("failed to unmap trampoline page")
- };
- flush.flush();
+ } {
+ flush.flush();
+ } else {
+ println!("KERNEL AP: failed to unmap trampoline page (non-fatal)");
+ }
diff --git a/src/allocator/mod.rs b/src/allocator/mod.rs
index 4fdb0ba..aaa7196 100644
--- a/src/allocator/mod.rs
+++ b/src/allocator/mod.rs
@@ -9,0 +10,9 @@ const KERNEL_HEAP_SIZE: usize = ::rmm::MEGABYTE;
+#[cold]
+fn halt_kernel_heap_init(message: &str) -> ! {
+ print!("{message}");
+ println!("Kernel heap initialization cannot continue. Halting.");
+ loop {
+ core::hint::spin_loop();
+ }
+}
+
@@ -16,4 +25,6 @@ unsafe fn map_heap(mapper: &mut KernelMapper<true>, offset: usize, size: usize)
- let phys = mapper
- .allocator_mut()
- .allocate_one()
- .expect("failed to allocate kernel heap");
+ let phys = match mapper.allocator_mut().allocate_one() {
+ Some(phys) => phys,
+ None => halt_kernel_heap_init(
+ "FATAL: failed to allocate physical frame for kernel heap\n",
+ ),
+ };
@@ -21,9 +32,12 @@ unsafe fn map_heap(mapper: &mut KernelMapper<true>, offset: usize, size: usize)
- mapper
- .map_phys(
- page.start_address(),
- phys,
- PageFlags::new()
- .write(true)
- .global(cfg!(not(feature = "pti"))),
- )
- .expect("failed to map kernel heap")
+ match mapper.map_phys(
+ page.start_address(),
+ phys,
+ PageFlags::new()
+ .write(true)
+ .global(cfg!(not(feature = "pti"))),
+ ) {
+ Some(flush) => flush,
+ None => halt_kernel_heap_init(
+ "FATAL: failed to map kernel heap virtual page\n",
+ ),
+ }
diff --git a/src/arch/x86_shared/gdt.rs b/src/arch/x86_shared/gdt.rs
index cad344f..f7acae3 100644
--- a/src/arch/x86_shared/gdt.rs
+++ b/src/arch/x86_shared/gdt.rs
@@ -194,0 +195,9 @@ impl ProcessorControlRegion {
+#[cold]
+fn halt_pcr_init() -> ! {
+ println!("FATAL: failed to allocate physical memory for Processor Control Region");
+ println!("Processor startup cannot continue. Halting.");
+ loop {
+ core::hint::spin_loop();
+ }
+}
+
@@ -378 +387,4 @@ pub fn allocate_and_init_pcr(
- let pcr_frame = crate::memory::allocate_p2frame(alloc_order).expect("failed to allocate PCR");
+ let pcr_frame = match crate::memory::allocate_p2frame(alloc_order) {
+ Some(frame) => frame,
+ None => halt_pcr_init(),
+ };
diff --git a/src/arch/x86_shared/idt.rs b/src/arch/x86_shared/idt.rs
index 5006458..47f692f 100644
--- a/src/arch/x86_shared/idt.rs
+++ b/src/arch/x86_shared/idt.rs
@@ -80,0 +81,9 @@ pub(crate) static IDTS: RwLock<HashMap<LogicalCpuId, &'static mut Idt>> =
+#[cold]
+fn halt_idt_init() -> ! {
+ println!("FATAL: failed to allocate physical pages for backup interrupt stack");
+ println!("Interrupt setup cannot continue. Halting.");
+ loop {
+ core::hint::spin_loop();
+ }
+}
+
@@ -164,2 +173,4 @@ pub fn allocate_and_init_idt(cpu_id: LogicalCpuId) -> *mut Idt {
- let frames = crate::memory::allocate_p2frame(4)
- .expect("failed to allocate pages for backup interrupt stack");
+ let frames = match crate::memory::allocate_p2frame(4) {
+ Some(frames) => frames,
+ None => halt_idt_init(),
+ };
diff --git a/src/startup/memory.rs b/src/startup/memory.rs
index 26922dd..f271200 100644
--- a/src/startup/memory.rs
+++ b/src/startup/memory.rs
@@ -326 +326,10 @@ unsafe fn map_memory<A: Arch>(areas: &[MemoryArea], mut bump_allocator: &mut Bum
- let kernel_area = (*MEMORY_MAP.get()).kernel().unwrap();
+ let kernel_area = match (*MEMORY_MAP.get()).kernel() {
+ Some(area) => area,
+ None => {
+ println!("FATAL: kernel memory area not found in boot memory map");
+ println!("Cannot determine kernel base address. Halting.");
+ loop {
+ core::hint::spin_loop();
+ }
+ }
+ };
diff --git a/src/startup/mod.rs b/src/startup/mod.rs
index 8ad3cdf..86aabc2 100644
--- a/src/startup/mod.rs
+++ b/src/startup/mod.rs
@@ -151,0 +152,9 @@ static BSP_READY: AtomicBool = AtomicBool::new(false);
+#[cold]
+fn halt_boot(message: &str) -> ! {
+ print!("{message}");
+ println!("Kernel boot cannot continue. Halting.");
+ loop {
+ hint::spin_loop();
+ }
+}
+
@@ -183,3 +192 @@ pub(crate) fn kmain(bootstrap: Bootstrap) -> ! {
- Err(err) => {
- panic!("failed to spawn userspace_init: {:?}", err);
- }
+ Err(_err) => halt_boot("FATAL: failed to spawn first userspace process userspace_init\n"),
@@ -0,0 +1,691 @@
diff --git a/src/acpi/madt/mod.rs b/src/acpi/madt/mod.rs
index 3159b9c49..e792b2e6c 100644
--- a/src/acpi/madt/mod.rs
+++ b/src/acpi/madt/mod.rs
@@ -146,6 +146,48 @@ pub struct MadtGicd {
_reserved2: [u8; 3],
}
+/// MADT Local x2APIC (entry type 0x9)
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MadtLocalX2Apic {
+ _reserved: u16,
+ pub x2apic_id: u32,
+ pub flags: u32,
+ pub processor_uid: u32,
+}
+
+/// MADT Local APIC NMI (entry type 0x4)
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MadtLocalApicNmi {
+ pub processor: u8,
+ pub flags: u16,
+ pub nmi_pin: u8,
+}
+
+/// MADT Local APIC address override (entry type 0x5)
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MadtLapicAddressOverride {
+ _reserved: u16,
+ pub local_apic_address: u64,
+}
+
+/// MADT Local x2APIC NMI (entry type 0xA)
+#[derive(Clone, Copy, Debug)]
+#[repr(C, packed)]
+pub struct MadtLocalX2ApicNmi {
+ _reserved: u16,
+ pub processor_uid: u32,
+ pub flags: u16,
+ pub nmi_pin: u8,
+ _reserved2: u8,
+}
+
+const _: () = assert!(size_of::<MadtLocalApicNmi>() == 4);
+const _: () = assert!(size_of::<MadtLapicAddressOverride>() == 10);
+const _: () = assert!(size_of::<MadtLocalX2ApicNmi>() == 10);
+
/// MADT Entries
#[derive(Debug)]
#[allow(dead_code)]
@@ -156,10 +198,18 @@ pub enum MadtEntry {
InvalidIoApic(usize),
IntSrcOverride(&'static MadtIntSrcOverride),
InvalidIntSrcOverride(usize),
+ LocalApicNmi(&'static MadtLocalApicNmi),
+ InvalidLocalApicNmi(usize),
+ LapicAddressOverride(&'static MadtLapicAddressOverride),
+ InvalidLapicAddressOverride(usize),
Gicc(&'static MadtGicc),
InvalidGicc(usize),
Gicd(&'static MadtGicd),
InvalidGicd(usize),
+ LocalX2Apic(&'static MadtLocalX2Apic),
+ InvalidLocalX2Apic(usize),
+ LocalX2ApicNmi(&'static MadtLocalX2ApicNmi),
+ InvalidLocalX2ApicNmi(usize),
Unknown(u8),
}
@@ -176,6 +226,10 @@ impl Iterator for MadtIter {
let entry_len =
unsafe { *(self.sdt.data_address() as *const u8).add(self.i + 1) } as usize;
+ if entry_len < 2 {
+ return None;
+ }
+
if self.i + entry_len <= self.sdt.data_len() {
let item = match entry_type {
0x0 => {
@@ -206,6 +260,46 @@ impl Iterator for MadtIter {
MadtEntry::InvalidIntSrcOverride(entry_len)
}
}
+ 0x4 => {
+ if entry_len == size_of::<MadtLocalApicNmi>() + 2 {
+ MadtEntry::LocalApicNmi(unsafe {
+ &*((self.sdt.data_address() + self.i + 2)
+ as *const MadtLocalApicNmi)
+ })
+ } else {
+ MadtEntry::InvalidLocalApicNmi(entry_len)
+ }
+ }
+ 0x5 => {
+ if entry_len == size_of::<MadtLapicAddressOverride>() + 2 {
+ MadtEntry::LapicAddressOverride(unsafe {
+ &*((self.sdt.data_address() + self.i + 2)
+ as *const MadtLapicAddressOverride)
+ })
+ } else {
+ MadtEntry::InvalidLapicAddressOverride(entry_len)
+ }
+ }
+ 0x9 => {
+ if entry_len == size_of::<MadtLocalX2Apic>() + 2 {
+ MadtEntry::LocalX2Apic(unsafe {
+ &*((self.sdt.data_address() + self.i + 2)
+ as *const MadtLocalX2Apic)
+ })
+ } else {
+ MadtEntry::InvalidLocalX2Apic(entry_len)
+ }
+ }
+ 0xA => {
+ if entry_len == size_of::<MadtLocalX2ApicNmi>() + 2 {
+ MadtEntry::LocalX2ApicNmi(unsafe {
+ &*((self.sdt.data_address() + self.i + 2)
+ as *const MadtLocalX2ApicNmi)
+ })
+ } else {
+ MadtEntry::InvalidLocalX2ApicNmi(entry_len)
+ }
+ }
0xB => {
if entry_len >= size_of::<MadtGicc>() + 2 {
MadtEntry::Gicc(unsafe {
diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs
index f472c0886..e8625a205 100644
--- a/src/acpi/madt/arch/x86.rs
+++ b/src/acpi/madt/arch/x86.rs
@@ -10,8 +10,8 @@ use crate::{
},
cpu_set::LogicalCpuId,
memory::{
- allocate_p2frame, Frame, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch,
- VirtualAddress, PAGE_SIZE,
+ allocate_p2frame, map_device_memory, Frame, KernelMapper, Page, PageFlags,
+ PhysicalAddress, RmmA, RmmArch, VirtualAddress, PAGE_SIZE,
},
startup::AP_READY,
};
@@ -22,6 +22,34 @@ const AP_SPIN_LIMIT: u32 = 1_000_000;
const TRAMPOLINE: usize = 0x8000;
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
+fn current_x2apic_processor_uid(madt: &Madt, apic_id: u32) -> Option<u32> {
+ madt.iter().find_map(|entry| match entry {
+ MadtEntry::LocalX2Apic(x2apic) if x2apic.x2apic_id == apic_id => Some(x2apic.processor_uid),
+ _ => None,
+ })
+}
+
+fn apply_lapic_address_override(
+ local_apic: &mut crate::arch::device::local_apic::LocalApic,
+ address: u64,
+) {
+ if local_apic.x2 || address == 0 {
+ return;
+ }
+
+ let Ok(physaddr) = usize::try_from(address) else {
+ warn!(
+ "Ignoring LAPIC address override {:#x}: does not fit host usize",
+ address
+ );
+ return;
+ };
+
+ let mapped = unsafe { map_device_memory(PhysicalAddress::new(physaddr), 4096) }.data();
+ local_apic.address = mapped;
+ debug!("Applied LAPIC address override: {:#x}", address);
+}
+
pub(super) fn init(madt: Madt) {
let local_apic = unsafe { the_local_apic() };
let me = local_apic.id();
@@ -67,7 +95,14 @@ pub(super) fn init(madt: Madt) {
}
unsafe {
- let preliminary_cpu_count = madt.iter().filter(|e| matches!(e, MadtEntry::LocalApic(entry) if u32::from(entry.id) == me.get() || entry.flags & 1 == 1)).count();
+ let preliminary_cpu_count = madt
+ .iter()
+ .filter(|entry| match entry {
+ MadtEntry::LocalApic(local) => u32::from(local.id) == me.get() || local.flags & 1 == 1,
+ MadtEntry::LocalX2Apic(local) => local.x2apic_id == me.get() || local.flags & 1 == 1,
+ _ => false,
+ })
+ .count();
crate::profiling::allocate(preliminary_cpu_count as u32);
}
@@ -183,6 +218,127 @@ pub(super) fn init(madt: Madt) {
RmmA::invalidate_all();
}
+ } else if let MadtEntry::LocalX2Apic(ap_x2apic) = madt_entry {
+ let apic_id = ap_x2apic.x2apic_id;
+ let flags = ap_x2apic.flags;
+
+ if apic_id == me.get() {
+ debug!(" This is my local x2APIC");
+ } else if flags & 1 == 1 {
+ let alloc = match allocate_p2frame(4) {
+ Some(frame) => frame,
+ None => {
+ println!("KERNEL AP: CPU {} no memory for stack, skipping", apic_id);
+ continue;
+ }
+ };
+ let stack_start = RmmA::phys_to_virt(alloc.base()).data();
+ let stack_end = stack_start + (PAGE_SIZE << 4);
+
+ let next_cpu = crate::CPU_COUNT.load(Ordering::Relaxed);
+ if next_cpu >= crate::cpu_set::MAX_CPU_COUNT {
+ println!(
+ "KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
+ apic_id
+ );
+ continue;
+ }
+ let cpu_id = LogicalCpuId::new(next_cpu);
+
+ let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
+ let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
+
+ let args = KernelArgsAp {
+ stack_end: stack_end as *mut u8,
+ cpu_id,
+ pcr_ptr,
+ idt_ptr,
+ };
+
+ let ap_ready = (TRAMPOLINE + 8) as *mut u64;
+ let ap_args_ptr = unsafe { ap_ready.add(1) };
+ let ap_page_table = unsafe { ap_ready.add(2) };
+ let ap_code = unsafe { ap_ready.add(3) };
+
+ unsafe {
+ ap_ready.write(0);
+ ap_args_ptr.write(&args as *const _ as u64);
+ ap_page_table.write(page_table_physaddr as u64);
+ #[expect(clippy::fn_to_numeric_cast)]
+ ap_code.write(kstart_ap as u64);
+ core::arch::asm!("");
+ }
+ AP_READY.store(false, Ordering::SeqCst);
+
+ {
+ let mut icr = 0x4500u64;
+ icr |= u64::from(apic_id) << 32;
+ local_apic.set_icr(icr);
+ }
+
+ for _ in 0..100_000 {
+ hint::spin_loop();
+ }
+
+ {
+ let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
+ let mut icr = 0x4600u64 | ap_segment as u64;
+ icr |= u64::from(apic_id) << 32;
+ local_apic.set_icr(icr);
+ }
+
+ for _ in 0..2_000_000 {
+ hint::spin_loop();
+ }
+
+ {
+ let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
+ let mut icr = 0x4600u64 | ap_segment as u64;
+ icr |= u64::from(apic_id) << 32;
+ local_apic.set_icr(icr);
+ }
+
+ let mut trampoline_ready = false;
+ for _ in 0..AP_SPIN_LIMIT {
+ if unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } != 0 {
+ trampoline_ready = true;
+ break;
+ }
+ hint::spin_loop();
+ }
+ if !trampoline_ready {
+ println!("KERNEL AP: CPU {} trampoline timeout, skipping", apic_id);
+ continue;
+ }
+
+ let mut kernel_ready = false;
+ for _ in 0..AP_SPIN_LIMIT {
+ if AP_READY.load(Ordering::SeqCst) {
+ kernel_ready = true;
+ break;
+ }
+ hint::spin_loop();
+ }
+ if !kernel_ready {
+ println!("KERNEL AP: CPU {} AP_READY timeout, skipping", apic_id);
+ continue;
+ }
+
+ crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
+ RmmA::invalidate_all();
+ }
+ } else if let MadtEntry::LocalApicNmi(nmi) = madt_entry {
+ let target_apic = nmi.processor;
+ if target_apic == 0xFF || target_apic == local_apic.id().get() as u8 {
+ unsafe { local_apic.set_lvt_nmi(nmi.nmi_pin, nmi.flags) };
+ }
+ } else if let MadtEntry::LocalX2ApicNmi(nmi) = madt_entry {
+ let current_uid = current_x2apic_processor_uid(&madt, me.get());
+ if nmi.processor_uid == u32::MAX || current_uid == Some(nmi.processor_uid) {
+ unsafe { local_apic.set_lvt_nmi(nmi.nmi_pin, nmi.flags) };
+ }
+ } else if let MadtEntry::LapicAddressOverride(override_entry) = madt_entry {
+ apply_lapic_address_override(local_apic, override_entry.local_apic_address);
}
}
diff --git a/src/arch/x86_shared/device/ioapic.rs b/src/arch/x86_shared/device/ioapic.rs
index fb66d3bf2..cd34c03b9 100644
--- a/src/arch/x86_shared/device/ioapic.rs
+++ b/src/arch/x86_shared/device/ioapic.rs
@@ -14,6 +14,10 @@ pub struct IoApicRegs {
pointer: *const u32,
}
impl IoApicRegs {
+ fn redirection_index_valid(&mut self, idx: u8) -> bool {
+ idx <= self.max_redirection_table_entries()
+ }
+
fn ioregsel(&self) -> *const u32 {
self.pointer
}
@@ -44,21 +48,28 @@ impl IoApicRegs {
pub fn read_ioapicver(&mut self) -> u32 {
self.read_reg(0x01)
}
- pub fn read_ioredtbl(&mut self, idx: u8) -> u64 {
- assert!(idx < 24);
+ pub fn read_ioredtbl(&mut self, idx: u8) -> Option<u64> {
+ if !self.redirection_index_valid(idx) {
+ warn!("IOAPIC read_ioredtbl index {} out of range", idx);
+ return None;
+ }
let lo = self.read_reg(0x10 + idx * 2);
let hi = self.read_reg(0x10 + idx * 2 + 1);
- u64::from(lo) | (u64::from(hi) << 32)
+ Some(u64::from(lo) | (u64::from(hi) << 32))
}
- pub fn write_ioredtbl(&mut self, idx: u8, value: u64) {
- assert!(idx < 24);
+ pub fn write_ioredtbl(&mut self, idx: u8, value: u64) -> bool {
+ if !self.redirection_index_valid(idx) {
+ warn!("IOAPIC write_ioredtbl index {} out of range", idx);
+ return false;
+ }
let lo = value as u32;
let hi = (value >> 32) as u32;
self.write_reg(0x10 + idx * 2, lo);
self.write_reg(0x10 + idx * 2 + 1, hi);
+ true
}
pub fn max_redirection_table_entries(&mut self) -> u8 {
@@ -92,17 +103,22 @@ impl IoApic {
}
/// Map an interrupt vector to a physical local APIC ID of a processor (thus physical mode).
#[allow(dead_code)]
- pub fn map(&self, idx: u8, info: MapInfo) {
- self.regs.lock().write_ioredtbl(idx, info.as_raw())
+ pub fn map(&self, idx: u8, info: MapInfo) -> bool {
+ let Some(raw) = info.as_raw() else {
+ return false;
+ };
+ self.regs.lock().write_ioredtbl(idx, raw)
}
pub fn set_mask(&self, gsi: u32, mask: bool) {
let idx = (gsi - self.gsi_start) as u8;
let mut guard = self.regs.lock();
- let mut reg = guard.read_ioredtbl(idx);
+ let Some(mut reg) = guard.read_ioredtbl(idx) else {
+ return;
+ };
reg &= !(1 << 16);
reg |= u64::from(mask) << 16;
- guard.write_ioredtbl(idx, reg);
+ let _ = guard.write_ioredtbl(idx, reg);
}
}
@@ -149,19 +165,26 @@ pub struct MapInfo {
}
impl MapInfo {
- pub fn as_raw(&self) -> u64 {
- assert!(self.vector >= 0x20);
- assert!(self.vector <= 0xFE);
+ pub fn as_raw(&self) -> Option<u64> {
+ if !(0x20..=0xFE).contains(&self.vector) {
+ warn!(
+ "Refusing to map IOAPIC vector outside valid range: {:#x}",
+ self.vector
+ );
+ return None;
+ }
// TODO: Check for reserved fields.
- (u64::from(self.dest.get()) << 56)
+ Some(
+ (u64::from(self.dest.get()) << 56)
| (u64::from(self.mask) << 16)
| ((self.trigger_mode as u64) << 15)
| ((self.polarity as u64) << 13)
| ((self.dest_mode as u64) << 11)
| ((self.delivery_mode as u64) << 8)
- | u64::from(self.vector)
+ | u64::from(self.vector),
+ )
}
}
@@ -175,7 +198,7 @@ impl fmt::Debug for IoApic {
let count = guard.max_redirection_table_entries();
f.debug_list()
- .entries((0..count).map(|i| guard.read_ioredtbl(i)))
+ .entries((0..=count).filter_map(|i| guard.read_ioredtbl(i)))
.finish()
}
}
@@ -237,11 +260,14 @@ pub unsafe fn handle_ioapic(madt_ioapic: &'static MadtIoApic) {
let ioapic_registers = virt.data() as *const u32;
let ioapic = IoApic::new(ioapic_registers, madt_ioapic.gsi_base);
- assert_eq!(
- ioapic.regs.lock().id(),
- madt_ioapic.id,
- "mismatched ACPI MADT I/O APIC ID, and the ID reported by the I/O APIC"
- );
+ let detected_id = ioapic.regs.lock().id();
+ if detected_id != madt_ioapic.id {
+ warn!(
+ "mismatched ACPI MADT I/O APIC ID: MADT={}, IOAPIC={}; continuing with detected hardware",
+ madt_ioapic.id,
+ detected_id
+ );
+ }
(*IOAPICS.get()).get_or_insert_with(Vec::new).push(ioapic);
}
@@ -310,11 +336,11 @@ pub unsafe fn init() {
}
}
}
- println!(
- "I/O APICs: {:?}, overrides: {:?}",
- ioapics(),
- src_overrides()
- );
+ for ioapic in ioapics() {
+ for idx in 0..=ioapic.count {
+ ioapic.set_mask(ioapic.gsi_start + u32::from(idx), true);
+ }
+ }
// map the legacy PC-compatible IRQs (0-15) to 32-47, just like we did with 8259 PIC (if it
// wouldn't have been disabled due to this I/O APIC)
@@ -329,7 +355,6 @@ pub unsafe fn init() {
.iter()
.any(|over| over.bus_irq == legacy_irq)
{
- // there's an IRQ conflict, making this legacy IRQ inaccessible.
continue;
}
(
@@ -349,7 +374,6 @@ pub unsafe fn init() {
let redir_tbl_index = (gsi - apic.gsi_start) as u8;
let map_info = MapInfo {
- // only send to the BSP
dest: bsp_apic_id,
dest_mode: DestinationMode::Physical,
delivery_mode: DeliveryMode::Fixed,
@@ -366,7 +390,32 @@ pub unsafe fn init() {
},
vector: 32 + legacy_irq,
};
- apic.map(redir_tbl_index, map_info);
+ if !apic.map(redir_tbl_index, map_info) {
+ warn!(
+ "Unable to map legacy IRQ {} (GSI {}) through IOAPIC index {}",
+ legacy_irq,
+ gsi,
+ redir_tbl_index
+ );
+ }
+
+ if legacy_irq == 0 && gsi != u32::from(legacy_irq) {
+ if let Some(apic0) = find_ioapic(u32::from(legacy_irq)) {
+ let idx0 = (u32::from(legacy_irq) - apic0.gsi_start) as u8;
+ let _ = apic0.map(
+ idx0,
+ MapInfo {
+ dest: bsp_apic_id,
+ dest_mode: DestinationMode::Physical,
+ delivery_mode: DeliveryMode::Fixed,
+ mask: false,
+ polarity: ApicPolarity::ActiveHigh,
+ trigger_mode: ApicTriggerMode::Edge,
+ vector: 32,
+ },
+ );
+ }
+ }
}
println!(
"I/O APICs: {:?}, overrides: {:?}",
@@ -406,7 +455,7 @@ fn resolve(irq: u8) -> u32 {
fn find_ioapic(gsi: u32) -> Option<&'static IoApic> {
ioapics()
.iter()
- .find(|apic| gsi >= apic.gsi_start && gsi < apic.gsi_start + u32::from(apic.count))
+ .find(|apic| gsi >= apic.gsi_start && gsi <= apic.gsi_start + u32::from(apic.count))
}
pub unsafe fn mask(irq: u8) {
diff --git a/src/arch/x86_shared/device/local_apic.rs b/src/arch/x86_shared/device/local_apic.rs
index b6afe02af..b300e6fea 100644
--- a/src/arch/x86_shared/device/local_apic.rs
+++ b/src/arch/x86_shared/device/local_apic.rs
@@ -103,7 +103,7 @@ impl LocalApic {
ApicId::new(if self.x2 {
unsafe { rdmsr(IA32_X2APIC_APICID) as u32 }
} else {
- unsafe { self.read(0x20) }
+ unsafe { self.read(0x20) >> 24 }
})
}
@@ -126,7 +126,14 @@ impl LocalApic {
pub fn set_icr(&mut self, value: u64) {
if self.x2 {
unsafe {
+ const PENDING: u32 = 1 << 12;
+ while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING {
+ core::hint::spin_loop();
+ }
wrmsr(IA32_X2APIC_ICR, value);
+ while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING {
+ core::hint::spin_loop();
+ }
}
} else {
unsafe {
@@ -256,6 +263,39 @@ impl LocalApic {
}
}
}
+
+ pub unsafe fn set_lvt_nmi(&mut self, pin: u8, flags: u16) {
+ let polarity = match flags & 0b11 {
+ 0b11 => 1 << 13,
+ _ => 0,
+ };
+ let trigger_mode = match (flags >> 2) & 0b11 {
+ 0b11 => 1 << 15,
+ _ => 0,
+ };
+ let lvt_value = (0b100 << 8) | polarity | trigger_mode;
+
+ unsafe {
+ match pin {
+ 0 => {
+ if self.x2 {
+ wrmsr(IA32_X2APIC_LVT_LINT0, u64::from(lvt_value));
+ } else {
+ self.write(0x350, lvt_value);
+ }
+ }
+ 1 => {
+ if self.x2 {
+ wrmsr(IA32_X2APIC_LVT_LINT1, u64::from(lvt_value));
+ } else {
+ self.write(0x360, lvt_value);
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
unsafe fn setup_error_int(&mut self) {
unsafe {
let vector = 49u32;
diff --git a/src/arch/x86_shared/device/mod.rs b/src/arch/x86_shared/device/mod.rs
index 7a2e25df3..a1e0b78ad 100644
--- a/src/arch/x86_shared/device/mod.rs
+++ b/src/arch/x86_shared/device/mod.rs
@@ -25,8 +25,7 @@ pub unsafe fn init() {
}
}
pub unsafe fn init_after_acpi() {
- // this will disable the IOAPIC if needed.
- //ioapic::init(mapper);
+ unsafe { ioapic::init() };
}
unsafe fn init_hpet() -> bool {
diff --git a/src/arch/x86_shared/interrupt/exception.rs b/src/arch/x86_shared/interrupt/exception.rs
index 7725a45d0..bfe9f096a 100644
--- a/src/arch/x86_shared/interrupt/exception.rs
+++ b/src/arch/x86_shared/interrupt/exception.rs
@@ -1,3 +1,5 @@
+use core::sync::atomic::{AtomicBool, Ordering};
+
use syscall::Exception;
use x86::irq::PageFaultError;
@@ -10,6 +12,22 @@ use crate::{
syscall::flag::*,
};
+static NMI_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
+
+unsafe fn nmi_raw_serial_write(bytes: &[u8]) {
+ use crate::syscall::io::{Io, Pio};
+
+ let mut com1 = Pio::<u8>::new(0x3F8);
+ let lsr = Pio::<u8>::new(0x3F8 + 5);
+
+ for &byte in bytes {
+ while lsr.read() & (1 << 5) == 0 {
+ core::hint::spin_loop();
+ }
+ com1.write(byte);
+ }
+}
+
interrupt_stack!(divide_by_zero, |stack| {
println!("Divide by zero");
stack.trace();
@@ -55,9 +73,35 @@ interrupt_stack!(non_maskable, @paranoid, |stack| {
#[cfg(not(all(target_arch = "x86_64", feature = "profiling")))]
{
- // TODO: This will likely deadlock
- println!("Non-maskable interrupt");
- stack.dump();
+ if NMI_IN_PROGRESS.swap(true, Ordering::SeqCst) {
+ return;
+ }
+
+ unsafe {
+ nmi_raw_serial_write(b"Non-maskable interrupt\n");
+ nmi_raw_serial_write(b" RIP: ");
+
+ #[cfg(target_arch = "x86")]
+ let instruction_pointer = u64::from(stack.iret.eip);
+ #[cfg(target_arch = "x86_64")]
+ let instruction_pointer = stack.iret.rip;
+
+ let mut buf = [0u8; 19];
+ buf[0] = b'0';
+ buf[1] = b'x';
+ for i in 0..16 {
+ let nibble = ((instruction_pointer >> (60 - i * 4)) & 0xF) as u8;
+ buf[2 + i] = if nibble < 10 {
+ b'0' + nibble
+ } else {
+ b'a' + nibble - 10
+ };
+ }
+ buf[18] = b'\n';
+ nmi_raw_serial_write(&buf);
+ }
+
+ NMI_IN_PROGRESS.store(false, Ordering::SeqCst);
}
});
@@ -1,32 +0,0 @@
diff --git a/src/startup/memory.rs b/src/startup/memory.rs
index 26922dde..60c7f061 100644
--- a/src/startup/memory.rs
+++ b/src/startup/memory.rs
@@ -74,14 +74,16 @@ impl MemoryEntry {
}
struct MemoryMap {
- entries: [MemoryEntry; 512],
+ entries: [MemoryEntry; 1024],
size: usize,
}
impl MemoryMap {
fn register(&mut self, base: usize, size: usize, kind: BootloaderMemoryKind) {
if self.size >= self.entries.len() {
- panic!("Early memory map overflow!");
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ unsafe { core::arch::asm!("out dx, al", in("dx") 0x3F8u16, in("al") b'!', options(nostack, preserves_flags)); }
+ panic!("Early memory map overflow at entry {} (max {})", self.size, self.entries.len());
}
let start = if kind == BootloaderMemoryKind::Free {
align_up(base)
@@ -134,7 +136,7 @@ static MEMORY_MAP: SyncUnsafeCell<MemoryMap> = SyncUnsafeCell::new(MemoryMap {
start: 0,
end: 0,
kind: BootloaderMemoryKind::Null,
- }; 512],
+ }; 1024],
size: 0,
});
File diff suppressed because it is too large Load Diff
@@ -1,34 +0,0 @@
--- a/src/scheme/debug.rs 2026-04-28 07:21:41.000000000 +0100
+++ b/src/scheme/debug.rs 2026-05-04 08:10:23.688174541 +0100
@@ -22,9 +22,10 @@
static HANDLES: RwLock<L1, HandleMap<Handle>> = RwLock::new(HandleMap::new());
-/// Add to the input queue
+/// Add to the input queue, translating CR to NL (ICRNL) for serial console compatibility.
pub fn debug_input(data: u8, token: &mut CleanLockToken) {
- INPUT.send(data, token);
+ let translated = if data == b'\r' { b'\n' } else { data };
+ INPUT.send(translated, token);
}
// Notify readers of input updates
@@ -106,12 +107,16 @@
fn fevent(
&self,
id: usize,
- _flags: EventFlags,
+ flags: EventFlags,
token: &mut CleanLockToken,
) -> Result<EventFlags> {
let _handle = *HANDLES.read(token.token()).get(id)?;
- Ok(EventFlags::empty())
+ let mut ready = EventFlags::empty();
+ if flags.contains(EventFlags::EVENT_READ) {
+ ready |= EventFlags::EVENT_READ;
+ }
+ Ok(ready)
}
fn fsync(&self, id: usize, token: &mut CleanLockToken) -> Result<()> {
@@ -1,368 +0,0 @@
# eventfd kernel support — EventCounter implementation and scheme dispatch
# Adds EventCounter struct with blocking read/write, semaphore mode, and wait conditions
# Extends EventScheme to handle eventfd path-based open, read, write, close, fevent, kfpath
diff --git a/src/event.rs b/src/event.rs
index 7398145a..92e5793c 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -8,13 +8,14 @@ use crate::{
context,
scheme::{self, SchemeExt, SchemeId},
sync::{
- CleanLockToken, LockToken, RwLock, RwLockReadGuard, RwLockWriteGuard, WaitQueue, L0, L1, L2,
+ CleanLockToken, LockToken, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
+ WaitCondition, WaitQueue, L0, L1, L2,
},
syscall::{
data::Event,
- error::{Error, Result, EBADF},
- flag::EventFlags,
- usercopy::UserSliceWo,
+ error::{Error, Result, EAGAIN, EBADF, EINVAL, EINTR},
+ flag::{EVENT_READ, EVENT_WRITE, EventFlags},
+ usercopy::{UserSliceRo, UserSliceWo},
},
};
@@ -25,6 +26,17 @@ pub struct EventQueue {
queue: WaitQueue<Event>,
}
+const EVENTFD_COUNTER_MAX: u64 = u64::MAX - 1;
+const EVENTFD_TAG_BIT: usize = 1usize << (usize::BITS - 1);
+
+pub struct EventCounter {
+ id: usize,
+ counter: Mutex<L1, u64>,
+ read_condition: WaitCondition,
+ write_condition: WaitCondition,
+ semaphore: bool,
+}
+
impl EventQueue {
pub fn new(id: EventQueueId) -> EventQueue {
EventQueue {
@@ -91,19 +103,146 @@ impl EventQueue {
}
}
+impl EventCounter {
+ pub fn new(id: usize, init: u64, semaphore: bool) -> EventCounter {
+ EventCounter {
+ id,
+ counter: Mutex::new(init),
+ read_condition: WaitCondition::new(),
+ write_condition: WaitCondition::new(),
+ semaphore,
+ }
+ }
+
+ pub fn is_readable(&self, token: &mut CleanLockToken) -> bool {
+ *self.counter.lock(token.token()) > 0
+ }
+
+ pub fn is_writable(&self, token: &mut CleanLockToken) -> bool {
+ *self.counter.lock(token.token()) < EVENTFD_COUNTER_MAX
+ }
+
+ pub fn read(&self, buf: UserSliceWo, block: bool, token: &mut CleanLockToken) -> Result<usize> {
+ if buf.len() < core::mem::size_of::<u64>() {
+ return Err(Error::new(EINVAL));
+ }
+
+ loop {
+ let counter = self.counter.lock(token.token());
+ let (mut counter, mut token) = counter.into_split();
+
+ if *counter > 0 {
+ let value = if self.semaphore {
+ *counter -= 1;
+ 1
+ } else {
+ let value = *counter;
+ *counter = 0;
+ value
+ };
+
+ buf.limit(core::mem::size_of::<u64>())
+ .ok_or(Error::new(EINVAL))?
+ .copy_from_slice(&value.to_ne_bytes())?;
+
+ trigger_locked(
+ GlobalSchemes::Event.scheme_id(),
+ self.id,
+ EVENT_WRITE,
+ token.token(),
+ );
+ self.write_condition.notify_locked(token.token());
+
+ return Ok(core::mem::size_of::<u64>());
+ }
+
+ if !block {
+ return Err(Error::new(EAGAIN));
+ }
+
+ if !self
+ .read_condition
+ .wait(counter, "EventCounter::read", &mut token)
+ {
+ return Err(Error::new(EINTR));
+ }
+ }
+ }
+
+ pub fn write(&self, buf: UserSliceRo, block: bool, token: &mut CleanLockToken) -> Result<usize> {
+ if buf.len() != core::mem::size_of::<u64>() {
+ return Err(Error::new(EINVAL));
+ }
+
+ let value = unsafe { buf.read_exact::<u64>()? };
+ if value == u64::MAX {
+ return Err(Error::new(EINVAL));
+ }
+
+ loop {
+ let counter = self.counter.lock(token.token());
+ let (mut counter, mut token) = counter.into_split();
+
+ if EVENTFD_COUNTER_MAX - *counter >= value {
+ let was_zero = *counter == 0;
+ *counter += value;
+
+ if was_zero && value != 0 {
+ trigger_locked(
+ GlobalSchemes::Event.scheme_id(),
+ self.id,
+ EVENT_READ,
+ token.token(),
+ );
+ self.read_condition.notify_locked(token.token());
+ }
+
+ return Ok(core::mem::size_of::<u64>());
+ }
+
+ if !block {
+ return Err(Error::new(EAGAIN));
+ }
+
+ if !self
+ .write_condition
+ .wait(counter, "EventCounter::write", &mut token)
+ {
+ return Err(Error::new(EINTR));
+ }
+ }
+ }
+
+ pub fn into_drop(self, _token: LockToken<'_, L1>) {
+ drop(self);
+ }
+}
+
pub type EventQueueList = HashMap<EventQueueId, Arc<EventQueue>>;
+pub type EventCounterList = HashMap<usize, Arc<EventCounter>>;
// Next queue id
static NEXT_QUEUE_ID: AtomicUsize = AtomicUsize::new(0);
+static NEXT_COUNTER_ID: AtomicUsize = AtomicUsize::new(0);
/// Get next queue id
pub fn next_queue_id() -> EventQueueId {
EventQueueId::from(NEXT_QUEUE_ID.fetch_add(1, Ordering::SeqCst))
}
+pub fn next_counter_id() -> usize {
+ EVENTFD_TAG_BIT | NEXT_COUNTER_ID.fetch_add(1, Ordering::SeqCst)
+}
+
+pub fn is_counter_id(id: usize) -> bool {
+ id & EVENTFD_TAG_BIT != 0
+}
+
// Current event queues
static QUEUES: RwLock<L2, EventQueueList> =
RwLock::new(EventQueueList::with_hasher(DefaultHashBuilder::new()));
+static COUNTERS: RwLock<L2, EventCounterList> =
+ RwLock::new(EventCounterList::with_hasher(DefaultHashBuilder::new()));
/// Get the event queues list, const
pub fn queues(token: LockToken<'_, L0>) -> RwLockReadGuard<'_, L2, EventQueueList> {
@@ -115,6 +254,14 @@ pub fn queues_mut(token: LockToken<'_, L0>) -> RwLockWriteGuard<'_, L2, EventQue
QUEUES.write(token)
}
+pub fn counters(token: LockToken<'_, L0>) -> RwLockReadGuard<'_, L2, EventCounterList> {
+ COUNTERS.read(token)
+}
+
+pub fn counters_mut(token: LockToken<'_, L0>) -> RwLockWriteGuard<'_, L2, EventCounterList> {
+ COUNTERS.write(token)
+}
+
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct RegKey {
pub scheme: SchemeId,
diff --git a/src/scheme/event.rs b/src/scheme/event.rs
index 36efe5b2..c64b6bd0 100644
--- a/src/scheme/event.rs
+++ b/src/scheme/event.rs
@@ -1,9 +1,12 @@
-use alloc::sync::Arc;
+use alloc::{sync::Arc, vec::Vec};
use syscall::{EventFlags, O_NONBLOCK};
use crate::{
context::file::InternalFlags,
- event::{next_queue_id, queues, queues_mut, EventQueue, EventQueueId},
+ event::{
+ EventCounter, EventQueue, EventQueueId, counters, counters_mut, is_counter_id,
+ next_counter_id, next_queue_id, queues, queues_mut,
+ },
sync::CleanLockToken,
syscall::{
data::Event,
@@ -25,7 +28,7 @@ impl KernelScheme for EventScheme {
fn kopenat(
&self,
id: usize,
- _user_buf: StrOrBytes,
+ user_buf: StrOrBytes,
_flags: usize,
_fcntl_flags: u32,
_ctx: CallerCtx,
@@ -34,13 +37,53 @@ impl KernelScheme for EventScheme {
if id != SCHEME_ROOT_ID {
return Err(Error::new(EACCES));
}
- let id = next_queue_id();
- queues_mut(token.token()).insert(id, Arc::new(EventQueue::new(id)));
- Ok(OpenResult::SchemeLocal(id.get(), InternalFlags::empty()))
+ let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?;
+ let path = path.trim_matches('/');
+
+ if path.is_empty() {
+ let id = next_queue_id();
+ queues_mut(token.token()).insert(id, Arc::new(EventQueue::new(id)));
+ return Ok(OpenResult::SchemeLocal(id.get(), InternalFlags::empty()));
+ }
+
+ let parts: Vec<&str> = path.split('/').collect();
+ if matches!(parts.first(), Some(&"eventfd")) {
+ let init = match parts.get(1) {
+ Some(value) => value.parse::<u64>().map_err(|_| Error::new(EINVAL))?,
+ None => 0_u64,
+ };
+ if init > u32::MAX as u64 {
+ return Err(Error::new(EINVAL));
+ }
+ let semaphore = match parts.get(2) {
+ Some(value) => match *value {
+ "0" => Ok(false),
+ "1" => Ok(true),
+ _ => Err(Error::new(EINVAL)),
+ }?,
+ None => false,
+ };
+
+ let id = next_counter_id();
+ counters_mut(token.token()).insert(id, Arc::new(EventCounter::new(id, init, semaphore)));
+ return Ok(OpenResult::SchemeLocal(id, InternalFlags::empty()));
+ }
+
+ Err(Error::new(ENOENT))
}
fn close(&self, id: usize, token: &mut CleanLockToken) -> Result<()> {
+ if is_counter_id(id) {
+ let counter = counters_mut(token.token())
+ .remove(&id)
+ .ok_or(Error::new(EBADF))?;
+ if let Some(counter) = Arc::into_inner(counter) {
+ counter.into_drop(token.downgrade());
+ }
+ return Ok(());
+ }
+
let id = EventQueueId::from(id);
let queue = queues_mut(token.token())
.remove(&id)
@@ -59,6 +102,15 @@ impl KernelScheme for EventScheme {
_stored_flags: u32,
token: &mut CleanLockToken,
) -> Result<usize> {
+ if is_counter_id(id) {
+ let counter = {
+ let handles = counters(token.token());
+ let handle = handles.get(&id).ok_or(Error::new(EBADF))?;
+ handle.clone()
+ };
+ return counter.read(buf, flags & O_NONBLOCK as u32 == 0, token);
+ }
+
let id = EventQueueId::from(id);
let queue = {
@@ -74,10 +126,19 @@ impl KernelScheme for EventScheme {
&self,
id: usize,
buf: UserSliceRo,
- _flags: u32,
+ flags: u32,
_stored_flags: u32,
token: &mut CleanLockToken,
) -> Result<usize> {
+ if is_counter_id(id) {
+ let counter = {
+ let handles = counters(token.token());
+ let handle = handles.get(&id).ok_or(Error::new(EBADF))?;
+ handle.clone()
+ };
+ return counter.write(buf, flags & O_NONBLOCK as u32 == 0, token);
+ }
+
let id = EventQueueId::from(id);
let queue = {
@@ -98,8 +159,12 @@ impl KernelScheme for EventScheme {
Ok(events_written * size_of::<Event>())
}
- fn kfpath(&self, _id: usize, buf: UserSliceWo, _token: &mut CleanLockToken) -> Result<usize> {
- buf.copy_common_bytes_from_slice(b"/scheme/event/")
+ fn kfpath(&self, id: usize, buf: UserSliceWo, _token: &mut CleanLockToken) -> Result<usize> {
+ if is_counter_id(id) {
+ buf.copy_common_bytes_from_slice(b"/scheme/event/eventfd")
+ } else {
+ buf.copy_common_bytes_from_slice(b"/scheme/event/")
+ }
}
fn fevent(
@@ -108,6 +173,23 @@ impl KernelScheme for EventScheme {
flags: EventFlags,
token: &mut CleanLockToken,
) -> Result<EventFlags> {
+ if is_counter_id(id) {
+ let counter = {
+ let handles = counters(token.token());
+ let handle = handles.get(&id).ok_or(Error::new(EBADF))?;
+ handle.clone()
+ };
+
+ let mut ready = EventFlags::empty();
+ if flags.contains(EventFlags::EVENT_READ) && counter.is_readable(token) {
+ ready |= EventFlags::EVENT_READ;
+ }
+ if flags.contains(EventFlags::EVENT_WRITE) && counter.is_writable(token) {
+ ready |= EventFlags::EVENT_WRITE;
+ }
+ return Ok(ready);
+ }
+
let id = EventQueueId::from(id);
let queue = {
File diff suppressed because it is too large Load Diff
@@ -1,913 +0,0 @@
diff --git a/src/context/file.rs b/src/context/file.rs
index 2d3790f..150f483 100644
--- a/src/context/file.rs
+++ b/src/context/file.rs
@@ -4,7 +4,7 @@ use crate::{
event,
scheme::{self, SchemeId},
sync::{CleanLockToken, RwLock, L6},
- syscall::error::Result,
+ syscall::error::{Error, Result, ESTALE},
};
use alloc::sync::Arc;
use syscall::{schemev2::NewFdFlags, RwFlags, O_APPEND, O_NONBLOCK};
@@ -18,6 +18,7 @@ pub struct FileDescription {
pub offset: u64,
/// The scheme that this file refers to
pub scheme: SchemeId,
+ pub scheme_generation: Option<u64>,
/// The number the scheme uses to refer to this file
pub number: usize,
/// The flags passed to open or fcntl(SETFL)
@@ -32,6 +33,52 @@ bitflags! {
}
}
impl FileDescription {
+ pub fn with_generation(
+ scheme: SchemeId,
+ scheme_generation: Option<u64>,
+ number: usize,
+ offset: u64,
+ flags: u32,
+ internal_flags: InternalFlags,
+ ) -> Self {
+ Self {
+ offset,
+ scheme,
+ scheme_generation,
+ number,
+ flags,
+ internal_flags,
+ }
+ }
+
+ pub fn new(
+ scheme: SchemeId,
+ number: usize,
+ offset: u64,
+ flags: u32,
+ internal_flags: InternalFlags,
+ token: &mut CleanLockToken,
+ ) -> Self {
+ Self::with_generation(
+ scheme,
+ Some(scheme::current_scheme_generation(token.token(), scheme)),
+ number,
+ offset,
+ flags,
+ internal_flags,
+ )
+ }
+
+ pub fn get_scheme(&self, token: &mut CleanLockToken) -> Result<scheme::KernelSchemes> {
+ if let Some(expected_generation) = self.scheme_generation
+ && expected_generation != scheme::current_scheme_generation(token.token(), self.scheme)
+ {
+ return Err(Error::new(ESTALE));
+ }
+
+ scheme::get_scheme(token.token(), self.scheme)
+ }
+
pub fn rw_flags(&self, rw: RwFlags) -> u32 {
let mut ret = self.flags & !(O_NONBLOCK | O_APPEND) as u32;
if rw.contains(RwFlags::APPEND) {
@@ -76,7 +123,7 @@ impl FileDescription {
pub fn try_close(self, token: &mut CleanLockToken) -> Result<()> {
event::unregister_file(self.scheme, self.number, token);
- let scheme = scheme::get_scheme(token.token(), self.scheme)?;
+ let scheme = self.get_scheme(token)?;
scheme.close(self.number, token)
}
@@ -85,12 +132,12 @@ impl FileDescription {
impl FileDescriptor {
pub fn close(self, token: &mut CleanLockToken) -> Result<()> {
{
- let (scheme_id, number, internal_flags) = {
+ let (desc, number, internal_flags) = {
let desc = self.description.read(token.token());
- (desc.scheme, desc.number, desc.internal_flags)
+ (*desc, desc.number, desc.internal_flags)
};
if internal_flags.contains(InternalFlags::NOTIFY_ON_NEXT_DETACH) {
- let scheme = scheme::get_scheme(token.token(), scheme_id)?;
+ let scheme = desc.get_scheme(token)?;
scheme.detach(number, token)?;
}
}
diff --git a/src/context/memory.rs b/src/context/memory.rs
index 93446ba..a862b35 100644
--- a/src/context/memory.rs
+++ b/src/context/memory.rs
@@ -64,14 +64,13 @@ impl UnmapResult {
return Ok(());
};
- let (scheme_id, number) = {
- let desc = description.write(token.token());
- (desc.scheme, desc.number)
+ let (scheme, number) = {
+ let desc = *description.read(token.token());
+ (desc.get_scheme(token)?, desc.number)
};
- let scheme_opt = scheme::get_scheme(token.token(), scheme_id);
- let funmap_result = scheme_opt
- .and_then(|scheme| scheme.kfunmap(number, base_offset, self.size, self.flags, token));
+ let funmap_result = scheme
+ .kfunmap(number, base_offset, self.size, self.flags, token);
if let Ok(fd) = Arc::try_unwrap(description) {
fd.into_inner().try_close(token)?;
@@ -2687,20 +2686,13 @@ fn correct_inner<'l>(
// XXX: This is cheating, but guaranteed we won't deadlock because we've dropped addr_space_guard
let mut token = unsafe { CleanLockToken::new() };
- let (scheme_id, scheme_number) = {
- let desc = &file_ref.description.read(token.token());
- (desc.scheme, desc.number)
+ let desc = *file_ref.description.read(token.token());
+ let scheme = desc.get_scheme(&mut token).map_err(|_| PfError::Segv)?;
+ let scheme_number = desc.number;
+ let user_inner = match scheme {
+ KernelSchemes::User(user) => user.inner,
+ _ => return Err(PfError::Segv),
};
- let user_inner = scheme::get_scheme(token.token(), scheme_id)
- .ok()
- .and_then(|s| {
- if let KernelSchemes::User(user) = s {
- Some(user.inner)
- } else {
- None
- }
- })
- .ok_or(PfError::Segv)?;
let offset = file_ref.base_offset as u64 + (pages_from_grant_start * PAGE_SIZE) as u64;
user_inner
diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs
index d30272c..765e547 100644
--- a/src/scheme/mod.rs
+++ b/src/scheme/mod.rs
@@ -14,7 +14,7 @@ use alloc::{
};
use core::{
str,
- sync::atomic::{AtomicUsize, Ordering},
+ sync::atomic::{AtomicU64, AtomicUsize, Ordering},
};
use hashbrown::hash_map::{self, DefaultHashBuilder, HashMap};
use spin::Once;
@@ -169,6 +169,7 @@ enum Handle {
/// Schemes list
static HANDLES: Once<RwLock<L1, HashMap<SchemeId, Handle>>> = Once::new();
+static SCHEME_GENERATIONS: Once<RwLock<L1, HashMap<SchemeId, AtomicU64>>> = Once::new();
static SCHEME_LIST_NEXT_ID: AtomicUsize = AtomicUsize::new(MAX_GLOBAL_SCHEMES);
static SCHEME_LIST_ID: AtomicUsize = AtomicUsize::new(0);
@@ -204,6 +205,10 @@ fn init_schemes() -> RwLock<L1, HashMap<SchemeId, Handle>> {
RwLock::new(handles)
}
+fn init_scheme_generations() -> RwLock<L1, HashMap<SchemeId, AtomicU64>> {
+ RwLock::new(HashMap::new())
+}
+
/// Get a handle to a scheme.
pub fn get_scheme(token: LockToken<'_, L0>, scheme_id: SchemeId) -> Result<KernelSchemes> {
match handles().read(token).get(&scheme_id) {
@@ -212,10 +217,33 @@ pub fn get_scheme(token: LockToken<'_, L0>, scheme_id: SchemeId) -> Result<Kerne
}
}
+pub fn current_scheme_generation(token: LockToken<'_, L0>, scheme_id: SchemeId) -> u64 {
+ scheme_generations()
+ .read(token)
+ .get(&scheme_id)
+ .map(|generation| generation.load(Ordering::Acquire))
+ .unwrap_or(0)
+}
+
fn handles<'a>() -> &'a RwLock<L1, HashMap<SchemeId, Handle>> {
HANDLES.call_once(init_schemes)
}
+fn scheme_generations<'a>() -> &'a RwLock<L1, HashMap<SchemeId, AtomicU64>> {
+ SCHEME_GENERATIONS.call_once(init_scheme_generations)
+}
+
+fn increment_scheme_generation(scheme_id: SchemeId, token: &mut CleanLockToken) {
+ match scheme_generations().write(token.token()).entry(scheme_id) {
+ hash_map::Entry::Occupied(entry) => {
+ entry.get().fetch_add(1, Ordering::AcqRel);
+ }
+ hash_map::Entry::Vacant(entry) => {
+ entry.insert(AtomicU64::new(1));
+ }
+ }
+}
+
/// Scheme list type
pub struct SchemeList;
@@ -260,9 +288,14 @@ impl SchemeList {
/// Remove a scheme
fn remove(&self, id: usize, token: &mut CleanLockToken) {
- let scheme = handles().write(token.token()).remove(&SchemeId(id));
+ let scheme_id = SchemeId(id);
+ let scheme = handles().write(token.token()).remove(&scheme_id);
assert!(scheme.is_some());
+ if let Some(Handle::Scheme(KernelSchemes::User(user))) = scheme.as_ref() {
+ user.inner.fail_pending_calls(token);
+ }
+ increment_scheme_generation(scheme_id, token);
if let Some(Handle::Scheme(KernelSchemes::User(user))) = scheme
&& let Some(user) = Arc::into_inner(user.inner)
{
@@ -287,32 +320,32 @@ impl KernelScheme for SchemeList {
token: &mut CleanLockToken,
) -> Result<OpenResult> {
let scheme_id = SchemeId(scheme_id);
- match handles()
- .read(token.token())
- .get(&scheme_id)
- .ok_or(Error::new(EBADF))?
- {
- Handle::Scheme(KernelSchemes::User(UserScheme { inner })) => {
- let inner = inner.clone();
- assert!(scheme_id == inner.scheme_id);
- let scheme = scheme_id;
- let params = unsafe { user_buf.read_exact::<NewFdParams>()? };
-
- return Ok(OpenResult::External(Arc::new(RwLock::new(
- FileDescription {
- scheme,
- number: params.number,
- offset: params.offset,
- flags: params.flags as u32,
- internal_flags: InternalFlags::from_extra0(params.internal_flags)
- .ok_or(Error::new(EINVAL))?,
- },
- ))));
+ let maybe_inner = {
+ let handles = handles().read(token.token());
+ match handles.get(&scheme_id).ok_or(Error::new(EBADF))? {
+ Handle::Scheme(KernelSchemes::User(UserScheme { inner })) => Some(inner.clone()),
+ Handle::SchemeCreationCapability => None,
+ _ => return Err(Error::new(EBADF)),
}
- Handle::SchemeCreationCapability => (),
- _ => return Err(Error::new(EBADF)),
};
+ if let Some(inner) = maybe_inner {
+ assert!(scheme_id == inner.scheme_id);
+ let params = unsafe { user_buf.read_exact::<NewFdParams>()? };
+
+ return Ok(OpenResult::External(Arc::new(RwLock::new(
+ FileDescription::new(
+ scheme_id,
+ params.number,
+ params.offset,
+ params.flags as u32,
+ InternalFlags::from_extra0(params.internal_flags)
+ .ok_or(Error::new(EINVAL))?,
+ token,
+ ),
+ ))));
+ }
+
const EXPECTED: &[u8] = b"create-scheme";
let mut buf = [0u8; EXPECTED.len()];
diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs
index 47588e1..1bdd6cc 100644
--- a/src/scheme/proc.rs
+++ b/src/scheme/proc.rs
@@ -849,17 +873,17 @@ impl KernelScheme for ProcScheme {
}
}
fn extract_scheme_number(fd: usize, token: &mut CleanLockToken) -> Result<(KernelSchemes, usize)> {
- let (scheme_id, number) = {
+ let desc = {
let current_lock = context::current();
let mut current = current_lock.read(token.token());
- let (context, mut token) = current.token_split();
+ let (context, mut context_token) = current.token_split();
let file_descriptor = context
- .get_file(FileHandle::from(fd), &mut token)
+ .get_file(FileHandle::from(fd), &mut context_token)
.ok_or(Error::new(EBADF))?;
- let desc = file_descriptor.description.read(token.token());
- (desc.scheme, desc.number)
+ *file_descriptor.description.read(context_token.token())
};
- let scheme = scheme::get_scheme(token.token(), scheme_id)?;
+ let scheme = desc.get_scheme(token)?;
+ let number = desc.number;
Ok((scheme, number))
}
diff --git a/src/scheme/user.rs b/src/scheme/user.rs
index b901302..dfbf66b 100644
--- a/src/scheme/user.rs
+++ b/src/scheme/user.rs
@@ -80,6 +80,7 @@ const ONE: NonZeroUsize = match NonZeroUsize::new(1) {
Some(one) => one,
None => unreachable!(),
};
+const MAX_SPURIOUS_WAKEUPS: usize = 100;
enum ParsedCqe {
TriggerFevent {
@@ -209,6 +210,8 @@ impl UserInner {
caller_responsible: &mut PageSpan,
token: &mut CleanLockToken,
) -> Result<Response> {
+ let mut remaining_spurious_wakeups = MAX_SPURIOUS_WAKEUPS;
+
{
// Disable preemption to avoid context switches between setting the
// process state and sending the scheme request. The process is made
@@ -261,7 +264,10 @@ impl UserInner {
};
let states = self.states.lock(token.token());
- let (mut states, mut token) = states.into_split();
+ let (mut states, mut state_token) = states.into_split();
+ let mut timed_out_descriptions = None;
+ let mut remove_state = false;
+ let mut timed_out = false;
match states.get_mut(sqe.tag as usize) {
// invalid state
None => return Err(Error::new(EBADFD)),
@@ -274,24 +280,35 @@ impl UserInner {
fds,
} => {
let maybe_eintr =
- eintr_if_sigkill(&mut callee_responsible, &mut token.token());
- *o = State::Waiting {
- canceling: true,
- callee_responsible,
- context,
- fds,
- };
+ eintr_if_sigkill(&mut callee_responsible, &mut state_token.token());
- maybe_eintr?;
+ if maybe_eintr.is_ok() {
+ remaining_spurious_wakeups =
+ remaining_spurious_wakeups.saturating_sub(1);
+ }
+
+ if maybe_eintr.is_ok() && remaining_spurious_wakeups == 0 {
+ timed_out_descriptions = Some(Self::collect_descriptions_to_close(fds));
+ remove_state = true;
+ } else {
+ *o = State::Waiting {
+ canceling: true,
+ callee_responsible,
+ context,
+ fds,
+ };
+ }
- context::current()
- .write(token.token())
- .block("UserInner::call (woken up after cancelation request)");
+ maybe_eintr?;
- // We do not want to drop the lock before blocking
- // as if we get preempted in between we might miss a
- // wakeup.
- drop(states);
+ if remove_state {
+ states.remove(sqe.tag as usize);
+ timed_out = true;
+ } else {
+ context::current()
+ .write(state_token.token())
+ .block("UserInner::call (woken up after cancelation request)");
+ }
}
// spurious wakeup
State::Waiting {
@@ -300,60 +317,76 @@ impl UserInner {
context,
mut callee_responsible,
} => {
- let maybe_eintr = eintr_if_sigkill(&mut callee_responsible, &mut token);
let current_context = context::current();
+ let maybe_eintr =
+ eintr_if_sigkill(&mut callee_responsible, &mut state_token);
+
+ if maybe_eintr.is_ok() {
+ remaining_spurious_wakeups =
+ remaining_spurious_wakeups.saturating_sub(1);
+ }
- *o = State::Waiting {
- // Currently we treat all spurious wakeups to have the same behavior
- // as signals (i.e., we send a cancellation request). It is not something
- // that should happen, but it certainly can happen, for example if a context
- // is awoken through its thread handle without setting any sig bits, or if the
- // caller clears its own sig bits. If it actually is a signal, then it is the
- // intended behavior.
- canceling: true,
- fds,
- context,
- callee_responsible,
- };
+ if maybe_eintr.is_ok() && remaining_spurious_wakeups == 0 {
+ timed_out_descriptions = Some(Self::collect_descriptions_to_close(fds));
+ remove_state = true;
+ } else {
+ *o = State::Waiting {
+ // Currently we treat all spurious wakeups to have the same behavior
+ // as signals (i.e., we send a cancellation request). It is not something
+ // that should happen, but it certainly can happen, for example if a context
+ // is awoken through its thread handle without setting any sig bits, or if the
+ // caller clears its own sig bits. If it actually is a signal, then it is the
+ // intended behavior.
+ canceling: true,
+ fds,
+ context,
+ callee_responsible,
+ };
+ }
maybe_eintr?;
- // We do not want to preempt between sending the
- // cancellation and blocking again where we might
- // miss a wakeup.
- let mut preempt = PreemptGuardL1::new(&current_context, &mut token);
- let token = preempt.token();
-
- self.todo.send_locked(
- Sqe {
- opcode: Opcode::Cancel as u8,
- sqe_flags: SqeFlags::ONEWAY,
- tag: sqe.tag,
- ..Default::default()
- },
- token.token(),
- );
- event::trigger_locked(
- self.root_id,
- self.scheme_id.get(),
- EVENT_READ,
- token.token(),
- );
-
- // 1. If cancellation was requested and arrived
- // before the scheme processed the request, an
- // acknowledgement will be sent back after the
- // cancellation is processed and we will be woken up
- // again. State will be State::Responded then.
- //
- // 2. If cancellation was requested but the scheme
- // already processed the request, we will receive
- // the actual response next and woken up again.
- // State will be State::Responded then.
- context::current()
- .write(token.token())
- .block("UserInner::call (spurious wakeup)");
- drop(states);
+ if remove_state {
+ states.remove(sqe.tag as usize);
+ timed_out = true;
+ } else {
+ // We do not want to preempt between sending the
+ // cancellation and blocking again where we might
+ // miss a wakeup.
+ let mut preempt =
+ PreemptGuardL1::new(&current_context, &mut state_token);
+ let token = preempt.token();
+
+ self.todo.send_locked(
+ Sqe {
+ opcode: Opcode::Cancel as u8,
+ sqe_flags: SqeFlags::ONEWAY,
+ tag: sqe.tag,
+ ..Default::default()
+ },
+ token.token(),
+ );
+ event::trigger_locked(
+ self.root_id,
+ self.scheme_id.get(),
+ EVENT_READ,
+ token.token(),
+ );
+
+ // 1. If cancellation was requested and arrived
+ // before the scheme processed the request, an
+ // acknowledgement will be sent back after the
+ // cancellation is processed and we will be woken up
+ // again. State will be State::Responded then.
+ //
+ // 2. If cancellation was requested but the scheme
+ // already processed the request, we will receive
+ // the actual response next and woken up again.
+ // State will be State::Responded then.
+ context::current()
+ .write(token.token())
+ .block("UserInner::call (spurious wakeup)");
+ }
}
// invalid state
@@ -368,6 +401,68 @@ impl UserInner {
}
},
}
+
+ if let Some(descriptions) = timed_out_descriptions {
+ drop(states);
+ for desc in descriptions {
+ let _ = desc.try_close(token);
+ }
+ }
+
+ if timed_out {
+ return Err(Error::new(ETIMEDOUT));
+ }
+ }
+ }
+ }
+
+ fn collect_descriptions_to_close(
+ fds: Vec<Arc<LockedFileDescription>>,
+ ) -> Vec<FileDescription> {
+ fds.into_iter()
+ .filter_map(|fd| Arc::try_unwrap(fd).ok())
+ .map(RwLock::into_inner)
+ .collect()
+ }
+
+ pub fn fail_pending_calls(&self, token: &mut CleanLockToken) {
+ let descriptions_to_close = {
+ let mut states_lock = self.states.lock(token.token());
+ let (states, mut lock_token) = states_lock.token_split();
+ let mut descriptions_to_close = Vec::new();
+ let mut states_to_remove = Vec::new();
+
+ for (id, state) in states.iter_mut() {
+ match mem::replace(state, State::Placeholder) {
+ State::Waiting { context, fds, .. } => {
+ descriptions_to_close.extend(Self::collect_descriptions_to_close(fds));
+
+ match context.upgrade() {
+ Some(context) => {
+ *state = State::Responded(Response::Regular(
+ Err(Error::new(ENODEV)),
+ 0,
+ false,
+ ));
+ context.write(lock_token.token()).unblock();
+ }
+ None => states_to_remove.push(id),
+ }
+ }
+ old_state => *state = old_state,
+ }
+ }
+
+ for id in states_to_remove {
+ states.remove(id);
+ }
+
+ descriptions_to_close
+ };
+
+ for desc in descriptions_to_close {
+ let _ = desc.try_close(token);
+ }
}
}
}
@@ -1283,6 +1376,7 @@ impl UserInner {
}
pub fn into_drop(self, token: &mut CleanLockToken) {
+ self.fail_pending_calls(token);
self.todo.condition.into_drop(token);
}
}
diff --git a/src/syscall/fs.rs b/src/syscall/fs.rs
index bf98464..10c6a92 100644
--- a/src/syscall/fs.rs
+++ b/src/syscall/fs.rs
@@ -12,7 +12,7 @@ use crate::{
memory::{AddrSpace, GenericFlusher, Grant, PageSpan, TlbShootdownActions},
},
memory::{Page, VirtualAddress, PAGE_SIZE},
- scheme::{self, FileHandle, KernelScheme, OpenResult, StrOrBytes},
+ scheme::{FileHandle, KernelScheme, OpenResult, StrOrBytes},
sync::{CleanLockToken, RwLock},
syscall::{data::Stat, error::*, flag::*},
};
@@ -45,7 +45,7 @@ pub fn file_op_generic_ext<T>(
(file, desc)
};
- let scheme = scheme::get_scheme(token.token(), desc.scheme)?;
+ let scheme = desc.get_scheme(token)?;
op(&*scheme, file.description, desc, token)
}
@@ -73,14 +73,18 @@ pub fn openat(
) -> Result<FileHandle> {
let path_buf = copy_path_to_buf(raw_path, PATH_MAX)?;
- let (scheme_id, number) = {
+ let desc = {
let current_lock = context::current();
let mut current = current_lock.read(token.token());
- let (context, mut token) = current.token_split();
- let pipe = context.get_file(fh, &mut token).ok_or(Error::new(EBADF))?;
- let desc = pipe.description.read(token.token());
- (desc.scheme, desc.number)
+ let (context, mut context_token) = current.token_split();
+ let pipe = context
+ .get_file(fh, &mut context_token)
+ .ok_or(Error::new(EBADF))?;
+ *pipe.description.read(context_token.token())
};
+ let scheme = desc.get_scheme(token)?;
+ let number = desc.number;
+ let scheme_id = desc.scheme;
let caller_ctx = context::current()
.read(token.token())
@@ -88,8 +92,6 @@ pub fn openat(
.filter_uid_gid(euid, egid);
let new_description = {
- let scheme = scheme::get_scheme(token.token(), scheme_id)?;
-
let res = scheme.kopenat(
number,
StrOrBytes::from_str(&path_buf),
@@ -101,13 +103,14 @@ pub fn openat(
match res? {
OpenResult::SchemeLocal(number, internal_flags) => {
- Arc::new(RwLock::new(FileDescription {
- offset: 0,
- internal_flags,
- scheme: scheme_id,
+ Arc::new(RwLock::new(FileDescription::new(
+ scheme_id,
number,
- flags: (flags & !O_CLOEXEC) as u32,
- }))
+ 0,
+ (flags & !O_CLOEXEC) as u32,
+ internal_flags,
+ token,
+ )))
}
OpenResult::External(desc) => desc,
}
@@ -137,16 +140,17 @@ pub fn unlinkat(
) -> Result<()> {
let path_buf = copy_path_to_buf(raw_path, PATH_MAX)?;
- let (number, scheme_id) = {
+ let desc = {
let current_lock = context::current();
let mut current = current_lock.read(token.token());
- let (context, mut token) = current.token_split();
- let pipe = context.get_file(fh, &mut token).ok_or(Error::new(EBADF))?;
- let desc = pipe.description.read(token.token());
- (desc.number, desc.scheme)
+ let (context, mut context_token) = current.token_split();
+ let pipe = context
+ .get_file(fh, &mut context_token)
+ .ok_or(Error::new(EBADF))?;
+ *pipe.description.read(context_token.token())
};
-
- let scheme = scheme::get_scheme(token.token(), scheme_id)?;
+ let number = desc.number;
+ let scheme = desc.get_scheme(token)?;
let caller_ctx = context::current()
.read(token.token())
@@ -199,17 +203,18 @@ fn duplicate_file(
let description = { *file.description.read(token.token()) };
let new_description = {
- let scheme = scheme::get_scheme(token.token(), description.scheme)?;
+ let scheme = description.get_scheme(token)?;
match scheme.kdup(description.number, user_buf, caller_ctx, token)? {
OpenResult::SchemeLocal(number, internal_flags) => {
- Arc::new(RwLock::new(FileDescription {
- offset: 0,
- internal_flags,
- scheme: description.scheme,
+ Arc::new(RwLock::new(FileDescription::new(
+ description.scheme,
number,
- flags: description.flags,
- }))
+ 0,
+ description.flags,
+ internal_flags,
+ token,
+ )))
}
OpenResult::External(desc) => desc,
}
@@ -296,11 +301,10 @@ fn call_normal(
}
.ok_or(Error::new(EBADF))?;
- let (scheme_id, number) = {
- let desc = file.description.read(token.token());
- (desc.scheme, desc.number)
+ let (scheme, number) = {
+ let desc = *file.description.read(token.token());
+ (desc.get_scheme(token)?, desc.number)
};
- let scheme = scheme::get_scheme(token.token(), scheme_id)?;
if flags.contains(CallFlags::STD_FS) {
scheme.translate_std_fs_call(number, file.description, payload, flags, metadata, token)
@@ -341,28 +345,28 @@ fn fdwrite_inner(
) -> Result<usize> {
// TODO: Ensure deadlocks can't happen
let (scheme, number, descs_to_send) = {
- let (scheme, number) = {
+ let desc = {
let current_lock = context::current();
let mut current = current_lock.read(token.token());
- let (context, mut token) = current.token_split();
+ let (context, mut context_token) = current.token_split();
let file_descriptor = context
- .get_file(socket, &mut token)
+ .get_file(socket, &mut context_token)
.ok_or(Error::new(EBADF))?;
- let desc = &file_descriptor.description.read(token.token());
- (desc.scheme, desc.number)
+ *file_descriptor.description.read(context_token.token())
};
- let scheme = scheme::get_scheme(token.token(), scheme)?;
+ let scheme = desc.get_scheme(token)?;
+ let number = desc.number;
let current_lock = context::current();
let mut current = current_lock.read(token.token());
- let (context, mut token) = current.token_split();
+ let (context, mut context_token) = current.token_split();
(
scheme,
number,
if flags.contains(CallFlags::FD_CLONE) {
- context.bulk_get_files(&target_fds, &mut token)
+ context.bulk_get_files(&target_fds, &mut context_token)
} else {
- context.bulk_remove_files(&target_fds, &mut token)
+ context.bulk_remove_files(&target_fds, &mut context_token)
}?
.into_iter()
.map(|f| f.description)
@@ -395,18 +399,22 @@ fn call_fdread(
metadata: &[u64],
token: &mut CleanLockToken,
) -> Result<usize> {
+ let desc = {
+ let current_lock = context::current();
+ let mut current = current_lock.read(token.token());
+ let (context, mut context_token) = current.token_split();
+ let file_descriptor = context
+ .get_file(fd, &mut context_token)
+ .ok_or(Error::new(EBADF))?;
+ *file_descriptor.description.read(context_token.token())
+ };
let (scheme, number) = {
- let (scheme, number) = {
- let current_lock = context::current();
- let mut current = current_lock.read(token.token());
- let (context, mut token) = current.token_split();
- let file_descriptor = context.get_file(fd, &mut token).ok_or(Error::new(EBADF))?;
- let desc = file_descriptor.description.read(token.token());
- (desc.scheme, desc.number)
- };
- let scheme = scheme::get_scheme(token.token(), scheme)?;
-
- (scheme, number)
+ let scheme = desc.get_scheme(token)?;
+ let number = desc.number;
+ (
+ scheme,
+ number,
+ )
};
scheme.kfdread(number, payload, flags, metadata, token)
@@ -440,9 +448,9 @@ pub fn fcntl(fd: FileHandle, cmd: usize, arg: usize, token: &mut CleanLockToken)
}
.ok_or(Error::new(EBADF))?;
- let (scheme_id, number, flags) = {
- let desc = file.description.write(token.token());
- (desc.scheme, desc.number, desc.flags)
+ let (number, flags, desc) = {
+ let desc = *file.description.read(token.token());
+ (desc.number, desc.flags, desc)
};
if cmd == F_DUPFD || cmd == F_DUPFD_CLOEXEC {
@@ -460,7 +468,7 @@ pub fn fcntl(fd: FileHandle, cmd: usize, arg: usize, token: &mut CleanLockToken)
// Communicate fcntl with scheme
if cmd != F_GETFD && cmd != F_SETFD {
- let scheme = scheme::get_scheme(token.token(), scheme_id)?;
+ let scheme = desc.get_scheme(token)?;
scheme.fcntl(number, cmd, arg, token)?;
};
@@ -518,13 +526,11 @@ pub fn flink(fd: FileHandle, raw_path: UserSliceRo, token: &mut CleanLockToken)
let path = RedoxPath::from_absolute(&path_buf).ok_or(Error::new(EINVAL))?;
let (_, reference) = path.as_parts().ok_or(Error::new(EINVAL))?;
- let (number, scheme_id) = {
- let desc = file.description.read(token.token());
- (desc.number, desc.scheme)
+ let (number, scheme) = {
+ let desc = *file.description.read(token.token());
+ (desc.number, desc.get_scheme(token)?)
};
- let scheme = scheme::get_scheme(token.token(), scheme_id)?;
-
// TODO: Check EXDEV.
/*
if scheme_id != description.scheme {
@@ -554,13 +560,11 @@ pub fn frename(fd: FileHandle, raw_path: UserSliceRo, token: &mut CleanLockToken
let path = RedoxPath::from_absolute(&path_buf).ok_or(Error::new(EINVAL))?;
let (_, reference) = path.as_parts().ok_or(Error::new(EINVAL))?;
- let (number, scheme_id) = {
- let desc = file.description.read(token.token());
- (desc.number, desc.scheme)
+ let (number, scheme) = {
+ let desc = *file.description.read(token.token());
+ (desc.number, desc.get_scheme(token)?)
};
- let scheme = scheme::get_scheme(token.token(), scheme_id)?;
-
// TODO: Check EXDEV.
/*
if scheme_id != description.scheme {
diff --git a/src/syscall/process.rs b/src/syscall/process.rs
index e83da42..78eed9d 100644
--- a/src/syscall/process.rs
+++ b/src/syscall/process.rs
@@ -271,23 +274,26 @@ unsafe fn bootstrap_mem(bootstrap: &crate::startup::Bootstrap) -> &'static [u8]
}
fn insert_fd(scheme: SchemeId, number: usize, cloexec: bool, token: &mut CleanLockToken) -> usize {
+ let description = Arc::new(RwLock::new(FileDescription::new(
+ scheme,
+ number,
+ 0,
+ (O_CREAT | O_RDWR) as u32,
+ InternalFlags::empty(),
+ token,
+ )));
+
let current_lock = context::current();
let mut current = current_lock.read(token.token());
- let (context, mut token) = current.token_split();
+ let (context, mut context_token) = current.token_split();
context
.add_file_min(
FileDescriptor {
- description: Arc::new(RwLock::new(FileDescription {
- scheme,
- number,
- offset: 0,
- flags: (O_CREAT | O_RDWR) as u32,
- internal_flags: InternalFlags::empty(),
- })),
+ description,
cloexec,
},
syscall::flag::UPPER_FDTBL_TAG + scheme.get(),
- &mut token,
+ &mut context_token,
)
.expect("failed to insert fd to current context")
.get()
@@ -0,0 +1,222 @@
diff --git a/src/arch/x86_shared/device/msi.rs b/src/arch/x86_shared/device/msi.rs
--- a/src/arch/x86_shared/device/msi.rs
+++ b/src/arch/x86_shared/device/msi.rs
@@ -1,66 +1,183 @@
+// MSI/MSI-X support for x86 — kernel-level message composition and validation
+// Cross-referenced from Linux 7.0: arch/x86/kernel/apic/msi.c (391 lines)
+
use crate::arch::device::local_apic::ApicId;
pub const MSI_ADDRESS_BASE: u64 = 0xFEE0_0000;
+pub const MSI_ADDRESS_MASK: u64 = 0xFEEF_F000;
+const MSI_DEST_MODE_LOGICAL: u64 = 1 << 2;
+const MSI_REDIRECTION_HINT: u64 = 1 << 3;
+
+#[derive(Debug, Clone, Copy)]
+pub struct MsiAddress {
+ pub raw: u64,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct MsiData {
+ pub raw: u32,
+}
#[derive(Debug, Clone)]
pub struct MsiMessage {
- pub address: u64,
- pub data: u32,
+ pub address: MsiAddress,
+ pub data: MsiData,
+}
+
+impl MsiAddress {
+ pub fn new(dest_apic_id: u8, redirection_hint: bool, dest_mode_logical: bool) -> Self {
+ let mut addr = MSI_ADDRESS_BASE;
+ addr |= u64::from(dest_apic_id) << 12;
+ if redirection_hint {
+ addr |= MSI_REDIRECTION_HINT;
+ }
+ if dest_mode_logical {
+ addr |= MSI_DEST_MODE_LOGICAL;
+ }
+ Self { raw: addr }
+ }
+
+ pub fn validate(addr: u64) -> bool {
+ (addr & MSI_ADDRESS_MASK) == MSI_ADDRESS_BASE
+ }
+
+ pub fn dest_apic_id(&self) -> u8 {
+ ((self.raw >> 12) & 0xFF) as u8
+ }
+}
+
+impl MsiData {
+ pub fn new(vector: u8, delivery_mode: u8, trigger_mode: u8) -> Self {
+ let mut data = u32::from(vector);
+ data |= u32::from(delivery_mode) << 8;
+ data |= u32::from(trigger_mode) << 15;
+ Self { raw: data }
+ }
+
+ pub fn vector(&self) -> u8 {
+ (self.raw & 0xFF) as u8
+ }
+
+ pub fn delivery_mode(&self) -> u8 {
+ ((self.raw >> 8) & 0x7) as u8
+ }
+
+ pub fn trigger_mode(&self) -> u8 {
+ ((self.raw >> 15) & 0x1) as u8
+ }
}
impl MsiMessage {
- pub fn compose(dest: ApicId, vector: u8, delivery_mode: u8) -> Self {
- let address = MSI_ADDRESS_BASE | (u64::from(dest.get()) << 12);
- let data = u32::from(vector) | (u32::from(delivery_mode) << 8);
+ pub fn compose(dest: ApicId, vector: u8, delivery_mode: u8, trigger_mode: u8) -> Self {
+ let address = MsiAddress::new(dest.get() as u8, false, false);
+ let data = MsiData::new(vector, delivery_mode, trigger_mode);
Self { address, data }
}
pub fn validate(&self) -> bool {
- (self.address & 0xFFF0_0000) == MSI_ADDRESS_BASE
- && self.data & 0xFF >= 32
- && self.data & 0xFF < 255
+ MsiAddress::validate(self.address.raw)
+ && self.data.vector() >= 32
+ && self.data.vector() < 255
}
}
-pub fn is_valid_msi_address(addr: u64) -> bool { (addr & 0xFFF0_0000) == MSI_ADDRESS_BASE }
-pub fn is_valid_msi_vector(vector: u8) -> bool { vector >= 32 && vector < 255 }
+pub fn is_valid_msi_address(addr: u64) -> bool {
+ MsiAddress::validate(addr)
+}
+
+pub fn is_valid_msi_vector(vector: u8) -> bool {
+ vector >= 32 && vector < 255
+}
#[derive(Debug)]
pub struct MsiCapability {
- pub msg_ctl: u16, pub msg_addr_lo: u32, pub msg_data: u16,
- pub is_64bit: bool, pub is_maskable: bool, pub multiple_message_capable: u8,
+ pub msg_ctl: u16,
+ pub msg_addr_lo: u32,
+ pub msg_addr_hi: u32,
+ pub msg_data: u16,
+ pub mask_bits: u32,
+ pub pending_bits: u32,
+ pub is_64bit: bool,
+ pub is_maskable: bool,
+ pub multiple_message_capable: u8,
}
impl MsiCapability {
- pub fn parse(raw: &[u32], msg_ctl: u16) -> Option<Self> {
- let msg_addr_lo = *raw.get(1)?;
- let msg_data = if msg_ctl & (1<<7) != 0 {
- (*raw.get(3)? & 0xFFFF) as u16
- } else {
- (*raw.get(2)? & 0xFFFF) as u16
- };
- Some(Self {
- msg_ctl, msg_addr_lo, msg_data,
- is_64bit: msg_ctl & (1<<7) != 0,
- is_maskable: msg_ctl & (1<<8) != 0,
- multiple_message_capable: ((msg_ctl>>1)&0x7) as u8,
- })
+ pub fn parse(raw: &[u32; 6], msg_ctl: u16) -> Self {
+ Self {
+ msg_ctl,
+ msg_addr_lo: raw[1],
+ msg_addr_hi: if msg_ctl & (1 << 7) != 0 { raw[2] } else { 0 },
+ msg_data: if msg_ctl & (1 << 7) != 0 {
+ (raw[3] & 0xFFFF) as u16
+ } else {
+ (raw[2] & 0xFFFF) as u16
+ },
+ mask_bits: if msg_ctl & (1 << 8) != 0 {
+ if msg_ctl & (1 << 7) != 0 {
+ raw[3] >> 16
+ } else {
+ raw[3]
+ }
+ } else {
+ 0
+ },
+ pending_bits: if msg_ctl & (1 << 8) != 0 { raw[4] } else { 0 },
+ is_64bit: msg_ctl & (1 << 7) != 0,
+ is_maskable: msg_ctl & (1 << 8) != 0,
+ multiple_message_capable: ((msg_ctl >> 1) & 0x7) as u8,
+ }
}
}
+#[derive(Debug)]
pub struct MsixCapability {
- pub table_offset: u32, pub table_bar: u8,
- pub pba_offset: u32, pub pba_bar: u8, pub table_size: u16,
+ pub msg_ctl: u16,
+ pub table_offset: u32,
+ pub table_bar: u8,
+ pub pba_offset: u32,
+ pub pba_bar: u8,
+ pub table_size: u16,
}
impl MsixCapability {
- pub fn parse(raw: &[u32], msg_ctl: u16) -> Option<Self> {
- let r1 = *raw.get(1)?;
- let r2 = *raw.get(2)?;
- Some(Self {
- table_offset: r1 & !0x7, table_bar: (r1&0x7) as u8,
- pba_offset: r2 & !0x7, pba_bar: (r2&0x7) as u8,
- table_size: ((msg_ctl>>1)&0x7FF) as u16 + 1,
- })
+ pub fn parse(raw: &[u32; 3], msg_ctl: u16) -> Self {
+ Self {
+ msg_ctl,
+ table_offset: raw[1] & !0x7,
+ table_bar: (raw[1] & 0x7) as u8,
+ pba_offset: raw[2] & !0x7,
+ pba_bar: (raw[2] & 0x7) as u8,
+ table_size: ((msg_ctl >> 1) & 0x7FF) as u16 + 1,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_compose_message() {
+ let msg = MsiMessage::compose(ApicId::new(3), 48, 0b101, 1);
+ assert!(msg.validate());
+ assert_eq!(msg.address.dest_apic_id(), 3);
+ assert_eq!(msg.data.vector(), 48);
+ assert_eq!(msg.data.delivery_mode(), 0b101);
+ assert_eq!(msg.data.trigger_mode(), 1);
+ }
+
+ #[test]
+ fn test_invalid_address() {
+ assert!(!is_valid_msi_address(0xDEAD_BEEF));
+ assert!(is_valid_msi_address(0xFEE0_0000));
+ }
+
+ #[test]
+ fn test_msi_parse() {
+ let raw = [0u32; 6];
+ let cap = MsiCapability::parse(&raw, 0);
+ assert!(!cap.is_64bit);
+ assert!(!cap.is_maskable);
}
}
-4
View File
@@ -1,4 +0,0 @@
--- a/Makefile
+++ b/Makefile
@@ -0,0 +1,1 @@
+# Red Bear OS kernel patches applied via individual patch files
+12 -20
View File
@@ -1,26 +1,18 @@
# Consolidated patch: all Red Bear kernel changes (P0-P10) in a single file.
# Individual patches preserved in local/patches/kernel/ for reference/rebase.
# The consolidated patch was generated from applying: redox(no-op), P0-canary,
# P1-memory-map-overflow, P4-supplementary-groups, P4-s3-suspend-resume,
# P4-scheme-failure-modes, P5-sched-rt-policy, P5-scheme-sched-id,
# P5-context-mod-sched, P6-percpu-runqueues, P6-futex-sharding,
# P8-initial-placement, P9-proc-lock-ordering, P9-numa-topology,
# P1-boot-path-diagnostics, P10-debug-scheme-serial-fix.
# Patches that were cumulative supersets (P5-sched-policy-context, P5-proc-setschedpolicy,
# P5-boot-path-hardening, P6-vruntime-*, P7-cache-affine-*, P7-proc-setname,
# P7-proc-setpriority, P8-futex-requeue, P8-futex-pi, P8-futex-robust,
# P8-percpu-wiring, P8-percpu-sched, P8-load-balance, P8-work-stealing,
# P9-futex-pi-cas-fix) failed to apply at commit 866dfad0 due to
# context conflicts and are deferred until rebase.
# P7-scheduler-improvements.patch: removed from recipe patches — 3/4 hunks
# fail on context.rs at 866dfad0. Rebase needed.
# P8-msi (applies separately): T1.1 msi.rs (message composition/validation/capability
# parsing), T1.2 vector.rs (per-CPU bitmatrix allocation), T1.3 IRQ scheme MSI
# validation gate + iommu hook, T2.2 kernel-side IRQ affinity handler.
[source]
git = "https://gitlab.redox-os.org/redox-os/kernel.git"
rev = "866dfad0"
patches = ["../../../local/patches/kernel/redbear-consolidated.patch", "../../../local/patches/kernel/P8-msi.patch", "../../../local/patches/kernel/P2-rebrand-start-message.patch", "P0-eventfd-kernel.patch", "../../../local/patches/kernel/P1-mkfifo-fifo-support.patch"]
patches = [
"../../../local/patches/kernel/redbear-consolidated.patch",
"../../../local/patches/kernel/P8-msi.patch",
"../../../local/patches/kernel/P8-msi-foundation-v2.patch",
"../../../local/patches/kernel/P2-rebrand-start-message.patch",
"../../../local/patches/kernel/P0-eventfd-kernel.patch",
"../../../local/patches/kernel/P0-rsdp-checksum.patch",
"../../../local/patches/kernel/P1-mkfifo-fifo-support-v2.patch",
"../../../local/patches/kernel/P1-ioapic-hpet-nmi-v2.patch",
"../../../local/patches/kernel/P9-numa-topology.patch",
"../../../local/patches/kernel/P9-proc-lock-ordering.patch",
]
[build]
template = "custom"