9880e0a5b2
Consolidate the active desktop path around redbear-full while landing the greeter/session stack and the runtime fixes needed to keep Wayland and KWin bring-up moving forward.
860 lines
31 KiB
Diff
860 lines
31 KiB
Diff
diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs
|
|
index 4dc23883..a8e6fe8f 100644
|
|
--- a/src/acpi/madt/arch/x86.rs
|
|
+++ b/src/acpi/madt/arch/x86.rs
|
|
@@ -35,18 +35,19 @@ pub(super) fn init(madt: Madt) {
|
|
return;
|
|
}
|
|
|
|
- // Map trampoline
|
|
+ // Map trampoline writable and executable (trampoline page holds both code
|
|
+ // and AP argument data — AP writes ap_ready on the same page, so W^X is
|
|
+ // not possible without splitting code/data across pages).
|
|
let trampoline_frame = Frame::containing(PhysicalAddress::new(TRAMPOLINE));
|
|
let trampoline_page = Page::containing_address(VirtualAddress::new(TRAMPOLINE));
|
|
let (result, page_table_physaddr) = unsafe {
|
|
- //TODO: do not have writable and executable!
|
|
let mut mapper = KernelMapper::lock_rw();
|
|
|
|
let result = mapper
|
|
.map_phys(
|
|
trampoline_page.start_address(),
|
|
trampoline_frame.base(),
|
|
- PageFlags::new().execute(true).write(true),
|
|
+ PageFlags::new().write(true).execute(true),
|
|
)
|
|
.expect("failed to map trampoline");
|
|
|
|
@@ -147,6 +148,160 @@ pub(super) fn init(madt: Madt) {
|
|
|
|
RmmA::invalidate_all();
|
|
}
|
|
+ } else if let MadtEntry::LocalX2Apic(ap_x2apic) = madt_entry {
|
|
+ if ap_x2apic.x2apic_id == me.get() {
|
|
+ debug!(" This is my local x2APIC");
|
|
+ } else if ap_x2apic.flags & 1 == 1 {
|
|
+ let cpu_id = LogicalCpuId::next();
|
|
+
|
|
+ let stack_start = RmmA::phys_to_virt(
|
|
+ allocate_p2frame(4)
|
|
+ .expect("no more frames in acpi stack_start")
|
|
+ .base(),
|
|
+ )
|
|
+ .data();
|
|
+ let stack_end = stack_start + (PAGE_SIZE << 4);
|
|
+
|
|
+ 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);
|
|
+
|
|
+ // Same ICR delivery-mode bits are used by xAPIC and x2APIC; only the
|
|
+ // destination field encoding changes between the MMIO and MSR forms.
|
|
+ const ICR_INIT_ASSERT: u64 = 0x4500;
|
|
+ const ICR_STARTUP: u64 = 0x4600;
|
|
+
|
|
+ // ICR bits 10:8 = 0b101 (INIT), bit 14 = level assert.
|
|
+ // Send INIT IPI (x2APIC always uses 32-bit APIC ID in bits 32-63)
|
|
+ {
|
|
+ let mut icr = ICR_INIT_ASSERT;
|
|
+ icr |= u64::from(ap_x2apic.x2apic_id) << 32;
|
|
+ local_apic.set_icr(icr);
|
|
+ }
|
|
+
|
|
+ // Wait for INIT delivery (~10 μs de-assert window per Intel SDM)
|
|
+ for _ in 0..100_000 {
|
|
+ hint::spin_loop();
|
|
+ }
|
|
+
|
|
+ // ICR bits 10:8 = 0b110 (STARTUP), bit 14 = level assert.
|
|
+ // Send STARTUP IPI
|
|
+ {
|
|
+ let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
|
+ let mut icr = ICR_STARTUP | ap_segment as u64;
|
|
+ icr |= u64::from(ap_x2apic.x2apic_id) << 32;
|
|
+ local_apic.set_icr(icr);
|
|
+ }
|
|
+
|
|
+ // Wait ~200 μs, then send second STARTUP IPI per the universal
|
|
+ // startup algorithm.
|
|
+ for _ in 0..2_000_000 {
|
|
+ hint::spin_loop();
|
|
+ }
|
|
+ {
|
|
+ let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
|
+ let mut icr = ICR_STARTUP | ap_segment as u64;
|
|
+ icr |= u64::from(ap_x2apic.x2apic_id) << 32;
|
|
+ local_apic.set_icr(icr);
|
|
+ }
|
|
+
|
|
+ // Known limitation: cpu_id and per-CPU bootstrap state are allocated
|
|
+ // before the timeout checks, so a timed-out AP still consumes a
|
|
+ // logical CPU slot until startup rollback/teardown is implemented.
|
|
+ let mut timeout = 100_000_000u32;
|
|
+ while unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } == 0 {
|
|
+ hint::spin_loop();
|
|
+ timeout -= 1;
|
|
+ if timeout == 0 {
|
|
+ let x2apic_id = ap_x2apic.x2apic_id;
|
|
+ debug!("x2APIC AP {} trampoline startup timed out", x2apic_id);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ let mut timeout = 100_000_000u32;
|
|
+ while !AP_READY.load(Ordering::SeqCst) {
|
|
+ hint::spin_loop();
|
|
+ timeout -= 1;
|
|
+ if timeout == 0 {
|
|
+ let x2apic_id = ap_x2apic.x2apic_id;
|
|
+ debug!("x2APIC AP {} kernel startup timed out", x2apic_id);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ RmmA::invalidate_all();
|
|
+ }
|
|
+ } else if let MadtEntry::LocalApicNmi(nmi) = madt_entry {
|
|
+ let target_id = nmi.processor;
|
|
+ let nmi_pin = nmi.nmi_pin;
|
|
+ let nmi_flags = nmi.flags;
|
|
+ if target_id == 0xFF {
|
|
+ debug!(
|
|
+ " NMI: all processors, pin={}, flags={:#x}",
|
|
+ nmi_pin, nmi_flags
|
|
+ );
|
|
+ unsafe {
|
|
+ local_apic.set_lvt_nmi(nmi_pin, nmi_flags);
|
|
+ }
|
|
+ } else {
|
|
+ let my_apic_id = local_apic.id().get() as u8;
|
|
+ if target_id == my_apic_id {
|
|
+ debug!(
|
|
+ " NMI: processor {}, pin={}, flags={:#x}",
|
|
+ target_id, nmi_pin, nmi_flags
|
|
+ );
|
|
+ unsafe {
|
|
+ local_apic.set_lvt_nmi(nmi_pin, nmi_flags);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } else if let MadtEntry::LocalX2ApicNmi(nmi) = madt_entry {
|
|
+ let target_uid = nmi.processor_uid;
|
|
+ let nmi_pin = nmi.nmi_pin;
|
|
+ let nmi_flags = nmi.flags;
|
|
+ if target_uid == 0xFFFFFFFF {
|
|
+ debug!(
|
|
+ " x2APIC NMI: all processors, pin={}, flags={:#x}",
|
|
+ nmi_pin, nmi_flags
|
|
+ );
|
|
+ unsafe {
|
|
+ local_apic.set_lvt_nmi(nmi_pin, nmi_flags);
|
|
+ }
|
|
+ } else {
|
|
+ debug!(
|
|
+ " x2APIC NMI: uid {}, pin={}, flags={:#x}",
|
|
+ target_uid, nmi_pin, nmi_flags
|
|
+ );
|
|
+ unsafe {
|
|
+ local_apic.set_lvt_nmi(nmi_pin, nmi_flags);
|
|
+ }
|
|
+ }
|
|
+ } else if let MadtEntry::LapicAddressOverride(addr) = madt_entry {
|
|
+ let lapic_addr = addr.local_apic_address;
|
|
+ if lapic_addr != 0 {
|
|
+ debug!(" LAPIC address override: {:#x}", lapic_addr);
|
|
+ }
|
|
}
|
|
}
|
|
|
|
diff --git a/src/acpi/madt/mod.rs b/src/acpi/madt/mod.rs
|
|
index 3159b9c4..da6c12af 100644
|
|
--- a/src/acpi/madt/mod.rs
|
|
+++ b/src/acpi/madt/mod.rs
|
|
@@ -146,6 +146,52 @@ pub struct MadtGicd {
|
|
_reserved2: [u8; 3],
|
|
}
|
|
|
|
+/// MADT Local x2APIC (entry type 0x9)
|
|
+/// Used by modern AMD and Intel platforms with APIC IDs >= 255.
|
|
+#[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)
|
|
+/// Configures NMI routing to a processor's LINT0/LINT1 pin.
|
|
+#[derive(Clone, Copy, Debug)]
|
|
+#[repr(C, packed)]
|
|
+pub struct MadtLocalApicNmi {
|
|
+ pub processor: u8, // 0xFF = all processors
|
|
+ pub flags: u16, // bits 0-1: polarity, bits 2-3: trigger mode
|
|
+ pub nmi_pin: u8, // 0 = LINT0, 1 = LINT1
|
|
+}
|
|
+
|
|
+/// MADT Local APIC Address Override (entry type 0x5)
|
|
+/// Provides 64-bit override for the 32-bit local APIC address.
|
|
+#[derive(Clone, Copy, Debug)]
|
|
+#[repr(C, packed)]
|
|
+pub struct MadtLapicAddressOverride {
|
|
+ _reserved: u16,
|
|
+ pub local_apic_address: u64,
|
|
+}
|
|
+
|
|
+/// MADT Local x2APIC NMI (entry type 0xA)
|
|
+/// x2APIC equivalent of type 0x4 for APIC IDs >= 255.
|
|
+#[derive(Clone, Copy, Debug)]
|
|
+#[repr(C, packed)]
|
|
+pub struct MadtLocalX2ApicNmi {
|
|
+ _reserved: u16,
|
|
+ pub processor_uid: u32, // 0xFFFFFFFF = all processors
|
|
+ pub flags: u16,
|
|
+ pub nmi_pin: u8, // 0 = LINT0, 1 = LINT1
|
|
+ _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)]
|
|
@@ -160,6 +206,14 @@ pub enum MadtEntry {
|
|
InvalidGicc(usize),
|
|
Gicd(&'static MadtGicd),
|
|
InvalidGicd(usize),
|
|
+ LocalX2Apic(&'static MadtLocalX2Apic),
|
|
+ InvalidLocalX2Apic(usize),
|
|
+ LocalApicNmi(&'static MadtLocalApicNmi),
|
|
+ InvalidLocalApicNmi(usize),
|
|
+ LapicAddressOverride(&'static MadtLapicAddressOverride),
|
|
+ InvalidLapicAddressOverride(usize),
|
|
+ LocalX2ApicNmi(&'static MadtLocalX2ApicNmi),
|
|
+ InvalidLocalX2ApicNmi(usize),
|
|
Unknown(u8),
|
|
}
|
|
|
|
@@ -176,6 +230,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 => {
|
|
@@ -224,6 +282,15 @@ impl Iterator for MadtIter {
|
|
MadtEntry::InvalidGicd(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)
|
|
+ }
|
|
+ }
|
|
_ => MadtEntry::Unknown(entry_type),
|
|
};
|
|
|
|
diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs
|
|
index 59e35265..80a40a01 100644
|
|
--- a/src/acpi/mod.rs
|
|
+++ b/src/acpi/mod.rs
|
|
@@ -138,6 +138,15 @@ pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) {
|
|
for sdt_address in rxsdt.iter() {
|
|
let sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt);
|
|
|
|
+ if !sdt.validate_checksum() {
|
|
+ let sig = &sdt.signature;
|
|
+ warn!(
|
|
+ "ACPI table {:?} at {:#x} has invalid checksum",
|
|
+ sig,
|
|
+ sdt_address.data()
|
|
+ );
|
|
+ }
|
|
+
|
|
let signature = get_sdt_signature(sdt);
|
|
if let Some(ref mut ptrs) = *(SDT_POINTERS.write()) {
|
|
ptrs.insert(signature, sdt);
|
|
diff --git a/src/acpi/sdt.rs b/src/acpi/sdt.rs
|
|
index 83ff67da..f49b6212 100644
|
|
--- a/src/acpi/sdt.rs
|
|
+++ b/src/acpi/sdt.rs
|
|
@@ -24,4 +24,15 @@ impl Sdt {
|
|
let header_size = size_of::<Sdt>();
|
|
total_size.saturating_sub(header_size)
|
|
}
|
|
+
|
|
+ /// Validate that the sum of all bytes in this table is zero (ACPI spec requirement).
|
|
+ /// Returns false if the length is too small or the checksum doesn't match.
|
|
+ pub fn validate_checksum(&self) -> bool {
|
|
+ let len = self.length as usize;
|
|
+ if len < size_of::<Sdt>() {
|
|
+ return false;
|
|
+ }
|
|
+ let bytes = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, len) };
|
|
+ bytes.iter().fold(0u8, |sum, &b| sum.wrapping_add(b)) == 0
|
|
+ }
|
|
}
|
|
diff --git a/src/arch/x86_shared/cpuid.rs b/src/arch/x86_shared/cpuid.rs
|
|
index b3683125..be7db1be 100644
|
|
--- a/src/arch/x86_shared/cpuid.rs
|
|
+++ b/src/arch/x86_shared/cpuid.rs
|
|
@@ -1,11 +1,8 @@
|
|
use raw_cpuid::{CpuId, CpuIdResult, ExtendedFeatures, FeatureInfo};
|
|
|
|
+#[cfg(target_arch = "x86_64")]
|
|
pub fn cpuid() -> CpuId {
|
|
- // FIXME check for cpuid availability during early boot and error out if it doesn't exist.
|
|
CpuId::with_cpuid_fn(|a, c| {
|
|
- #[cfg(target_arch = "x86")]
|
|
- let result = unsafe { core::arch::x86::__cpuid_count(a, c) };
|
|
- #[cfg(target_arch = "x86_64")]
|
|
let result = unsafe { core::arch::x86_64::__cpuid_count(a, c) };
|
|
CpuIdResult {
|
|
eax: result.eax,
|
|
@@ -16,6 +13,19 @@ pub fn cpuid() -> CpuId {
|
|
})
|
|
}
|
|
|
|
+#[cfg(target_arch = "x86")]
|
|
+pub fn cpuid() -> CpuId {
|
|
+ CpuId::with_cpuid_fn(|a, c| {
|
|
+ let result = unsafe { core::arch::x86::__cpuid_count(a, c) };
|
|
+ CpuIdResult {
|
|
+ eax: result.eax,
|
|
+ ebx: result.ebx,
|
|
+ ecx: result.ecx,
|
|
+ edx: result.edx,
|
|
+ }
|
|
+ })
|
|
+}
|
|
+
|
|
#[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))]
|
|
pub fn feature_info() -> FeatureInfo {
|
|
cpuid()
|
|
diff --git a/src/arch/x86_shared/device/local_apic.rs b/src/arch/x86_shared/device/local_apic.rs
|
|
index b6afe02a..e256d160 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,30 @@ impl LocalApic {
|
|
}
|
|
}
|
|
}
|
|
+ /// Configure LVT NMI entry. `pin` is 0 for LINT0, 1 for LINT1.
|
|
+ /// `flags` encodes polarity and trigger mode per MADT NMI spec.
|
|
+ pub unsafe fn set_lvt_nmi(&mut self, pin: u8, flags: u16) {
|
|
+ let lvt_value = (flags as u32) | 0x400; /* bit 10 = NMI delivery mode, masked off if flags don't set it */
|
|
+ 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/context/memory.rs b/src/context/memory.rs
|
|
index 94519448..0db1de53 100644
|
|
--- a/src/context/memory.rs
|
|
+++ b/src/context/memory.rs
|
|
@@ -927,8 +927,8 @@ impl UserGrants {
|
|
.take_while(move |(base, info)| PageSpan::new(**base, info.page_count).intersects(span))
|
|
.map(|(base, info)| (*base, info))
|
|
}
|
|
- /// Return a free region with the specified size
|
|
- // TODO: Alignment (x86_64: 4 KiB, 2 MiB, or 1 GiB).
|
|
+ /// Return a free region with the specified size, optionally aligned to a power-of-two
|
|
+ /// boundary (x86_64 supports 4 KiB, 2 MiB, or 1 GiB pages).
|
|
// TODO: Support finding grant close to a requested address?
|
|
pub fn find_free_near(
|
|
&self,
|
|
@@ -936,29 +936,42 @@ impl UserGrants {
|
|
page_count: usize,
|
|
_near: Option<Page>,
|
|
) -> Option<PageSpan> {
|
|
- // Get first available hole, but do reserve the page starting from zero as most compiled
|
|
- // languages cannot handle null pointers safely even if they point to valid memory. If an
|
|
- // application absolutely needs to map the 0th page, they will have to do so explicitly via
|
|
- // MAP_FIXED/MAP_FIXED_NOREPLACE.
|
|
- // TODO: Allow explicitly allocating guard pages? Perhaps using mprotect or mmap with
|
|
- // PROT_NONE?
|
|
+ self.find_free_near_aligned(min, page_count, _near, 0)
|
|
+ }
|
|
+ pub fn find_free_near_aligned(
|
|
+ &self,
|
|
+ min: usize,
|
|
+ page_count: usize,
|
|
+ _near: Option<Page>,
|
|
+ page_alignment: usize,
|
|
+ ) -> Option<PageSpan> {
|
|
+ let alignment = if page_alignment == 0 {
|
|
+ PAGE_SIZE
|
|
+ } else {
|
|
+ assert!(page_alignment.is_power_of_two(), "page_alignment must be a power of two");
|
|
+ page_alignment * PAGE_SIZE
|
|
+ };
|
|
|
|
let (hole_start, _hole_size) = self
|
|
.holes
|
|
.iter()
|
|
.skip_while(|(hole_offset, hole_size)| hole_offset.data() + **hole_size <= min)
|
|
.find(|(hole_offset, hole_size)| {
|
|
- let avail_size =
|
|
- if hole_offset.data() <= min && min <= hole_offset.data() + **hole_size {
|
|
- **hole_size - (min - hole_offset.data())
|
|
- } else {
|
|
- **hole_size
|
|
- };
|
|
+ let base = cmp::max(hole_offset.data(), min);
|
|
+ let aligned_base = (base + alignment - 1) & !(alignment - 1);
|
|
+ let avail_size = if aligned_base <= hole_offset.data() + **hole_size {
|
|
+ hole_offset.data() + **hole_size - aligned_base
|
|
+ } else {
|
|
+ 0
|
|
+ };
|
|
page_count * PAGE_SIZE <= avail_size
|
|
})?;
|
|
- // Create new region
|
|
+
|
|
+ let base = cmp::max(hole_start.data(), min);
|
|
+ let aligned_base = (base + alignment - 1) & !(alignment - 1);
|
|
+
|
|
Some(PageSpan::new(
|
|
- Page::containing_address(VirtualAddress::new(cmp::max(hole_start.data(), min))),
|
|
+ Page::containing_address(VirtualAddress::new(aligned_base)),
|
|
page_count,
|
|
))
|
|
}
|
|
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 = {
|