Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions kernel/src/arch_impl/aarch64/boot.S
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ drop_to_el1:
mov x0, #0
orr x0, x0, #(1 << 29) // LSMAOE
orr x0, x0, #(1 << 28) // nTLSMD
orr x0, x0, #(1 << 23) // SPAN (don't auto-set PAN on exception entry)
orr x0, x0, #(1 << 11) // EOS (exception return on stack)
msr sctlr_el1, x0

Expand All @@ -124,6 +125,12 @@ el1_init:
msr cpacr_el1, x0
isb

// Clear PAN — architecturally UNKNOWN if we entered EL1 directly
// (the EL2 path sets PAN=0 via SPSR_EL2, but be safe)
// Encoding: MSR PAN, #0 = 0xD500409F
.inst 0xd500409f
isb

// Set up boot stack pointer (low)
ldr x0, =__boot_stack_top
mov sp, x0
Expand Down Expand Up @@ -768,10 +775,20 @@ secondary_cpu_entry:
mov x19, x0 // PSCI path: cpu_id from x0
1:

// Compute relocation delta: on VMware, the kernel is loaded at a different
// physical address than the linker expects (RAM at 0x80000000 vs link at
// 0x40080000). All `ldr Xn, =symbol` loads get the linker address from the
// literal pool; we must add the delta to dereference them correctly.
// x21 = (actual PC of this label) - (linker address of this label)
adr x21, secondary_cpu_entry
ldr x22, =secondary_cpu_entry
sub x21, x21, x22 // x21 = relocation delta (0 on QEMU, 0x40000000 on VMware)

// Debug breadcrumb: write cpu_id digit + '@' to UART (physical, pre-MMU)
// Load UART physical address from SMP_UART_PHYS (set by CPU 0 Rust code)
// Guard: skip if address is 0 (BSS default before CPU 0 writes it)
ldr x2, =SMP_UART_PHYS
add x2, x2, x21 // relocate: linker addr -> physical addr
ldr x2, [x2] // x2 = UART phys addr
cbz x2, 2f // skip breadcrumbs if 0 (not yet initialized)
add x3, x19, #'0' // '1' for CPU 1, '2' for CPU 2, etc.
Expand Down Expand Up @@ -799,6 +816,7 @@ secondary_el1_init:
// Load UART phys addr into callee-saved x20 for breadcrumbs.
// x20=0 means UART not set; all breadcrumbs guard against this.
ldr x20, =SMP_UART_PHYS
add x20, x20, x21 // relocate
ldr x20, [x20] // x20 = UART phys addr (preserved across init)

// Breadcrumb 'A' = entered EL1 init
Expand All @@ -812,6 +830,24 @@ secondary_el1_init:
msr cpacr_el1, x0
isb

// Initialize SCTLR_EL1 for PSCI direct-EL1 path.
// Secondary CPUs inherit whatever the hypervisor set, which may differ
// from primary. Must match primary CPU's configuration.
mov x0, #0
orr x0, x0, #(1 << 29) // LSMAOE
orr x0, x0, #(1 << 28) // nTLSMD
orr x0, x0, #(1 << 23) // SPAN (don't auto-set PAN on exception entry)
orr x0, x0, #(1 << 11) // EOS
msr sctlr_el1, x0
isb

// Clear PAN (Privileged Access Never).
// PSTATE.PAN is architecturally UNKNOWN on reset — VMware sets it to 1,
// which causes permission faults when kernel code accesses user-mapped pages.
// Encoding: MSR PAN, #0 = 0xD500409F (op1=000, CRm=0000, op2=100)
.inst 0xd500409f
isb

// Set up per-CPU boot stack (physical addresses, before MMU)
// Stack top = SMP_STACK_BASE_PHYS + (cpu_id + 1) * 0x20_0000
// SMP_STACK_BASE_PHYS is set by CPU 0 Rust code to (ram_base + 0x01000000).
Expand All @@ -823,6 +859,7 @@ secondary_el1_init:
add x0, x0, #1 // cpu_id + 1
lsl x0, x0, #21 // * 0x20_0000 (2MB)
ldr x1, =SMP_STACK_BASE_PHYS
add x1, x1, x21 // relocate
ldr x1, [x1] // x1 = actual stack base (set by CPU 0)
add x0, x0, x1
mov sp, x0
Expand All @@ -838,6 +875,7 @@ secondary_el1_init:

// Set VBAR_EL1 to boot exception vectors (low) for now
ldr x0, =exception_vectors_boot
add x0, x0, x21 // relocate
msr vbar_el1, x0
isb

Expand All @@ -852,19 +890,23 @@ secondary_el1_init:
// populates from its actual register values. This handles both
// QEMU (boot.S page tables/config) and Parallels (loader page tables/config).
ldr x0, =SMP_MAIR_PHYS
add x0, x0, x21 // relocate
ldr x0, [x0] // x0 = CPU 0's MAIR value
msr mair_el1, x0
ldr x0, =SMP_TCR_PHYS
add x0, x0, x21 // relocate
ldr x0, [x0] // x0 = CPU 0's TCR value
msr tcr_el1, x0
isb

// Load TTBR0 from SMP_TTBR0_PHYS (set by CPU 0 Rust code)
ldr x0, =SMP_TTBR0_PHYS
add x0, x0, x21 // relocate
ldr x0, [x0] // x0 = actual TTBR0 physical address
msr ttbr0_el1, x0
// Load TTBR1 from SMP_TTBR1_PHYS
ldr x0, =SMP_TTBR1_PHYS
add x0, x0, x21 // relocate
ldr x0, [x0] // x0 = actual TTBR1 physical address
msr ttbr1_el1, x0
dsb ishst
Expand Down Expand Up @@ -941,6 +983,7 @@ secondary_drop_to_el1:
mov x0, #0
orr x0, x0, #(1 << 29) // LSMAOE
orr x0, x0, #(1 << 28) // nTLSMD
orr x0, x0, #(1 << 23) // SPAN (don't auto-set PAN on exception entry)
orr x0, x0, #(1 << 11) // EOS
msr sctlr_el1, x0

Expand Down
8 changes: 6 additions & 2 deletions kernel/src/arch_impl/aarch64/smp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,12 @@ pub fn release_cpu(cpu_id: usize) -> i64 {
return -2; // INVALID_PARAMS
}

// Get the physical address of the secondary entry point in boot.S
let entry_phys = unsafe { core::ptr::read_volatile(&SECONDARY_CPU_ENTRY_PHYS) };
// Get the physical address of the secondary entry point in boot.S.
// SECONDARY_CPU_ENTRY_PHYS holds the linker address (base 0x40080000).
// On VMware, RAM starts at 0x80000000, so the actual physical address
// is offset by ram_base_offset (0x40000000).
let entry_phys = unsafe { core::ptr::read_volatile(&SECONDARY_CPU_ENTRY_PHYS) }
+ crate::platform_config::ram_base_offset();

// MPIDR: Aff0 = cpu_id, all other affinity fields = 0
// This is the standard layout for ARM virt machines (QEMU, Parallels, VMware)
Expand Down
4 changes: 2 additions & 2 deletions kernel/src/arch_impl/aarch64/timer_interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,10 @@ pub extern "C" fn timer_interrupt_handler() {
crate::drivers::usb::xhci::poll_hid_events();
// Poll network RX for incoming packets (PCI INTx routing not wired up)
// Covers both VirtIO net PCI (Parallels) and e1000 (VMware)
// Throttle to every 50th tick (~20Hz at 1000Hz timer) to avoid overhead
// Poll every 10th tick (~100Hz at 1000Hz timer) for responsive networking
if (crate::drivers::virtio::net_pci::is_initialized()
|| crate::drivers::e1000::is_initialized())
&& _count % 50 == 0
&& _count % 10 == 0
{
crate::task::softirqd::raise_softirq(crate::task::softirqd::SoftirqType::NetRx);
}
Expand Down
2 changes: 2 additions & 0 deletions kernel/src/fs/procfs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ fn generate_stat() -> String {
interrupts {}\n\
context_switches {}\n\
timer_ticks {}\n\
global_ticks {}\n\
forks {}\n\
execs {}\n\
cow_faults {}\n\
Expand All @@ -765,6 +766,7 @@ fn generate_stat() -> String {
IRQ_TOTAL.aggregate(),
CTX_SWITCH_TOTAL.aggregate(),
TIMER_TICK_TOTAL.aggregate(),
crate::time::get_ticks(),
FORK_TOTAL.aggregate(),
EXEC_TOTAL.aggregate(),
COW_FAULT_TOTAL.aggregate(),
Expand Down
25 changes: 5 additions & 20 deletions kernel/src/syscall/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3026,17 +3026,17 @@ pub fn sys_fcntl(fd: u64, cmd: u64, arg: u64) -> SyscallResult {
}
};

let manager_guard = match crate::process::try_manager() {
let mut manager_guard = match crate::process::try_manager() {
Some(guard) => guard,
None => {
log::error!("sys_fcntl: Failed to get process manager");
return SyscallResult::Err(9); // EBADF
return SyscallResult::Err(11); // EAGAIN
}
};

let _process = match manager_guard
.as_ref()
.and_then(|m| m.find_process_by_thread(thread_id))
let process = match manager_guard
.as_mut()
.and_then(|m| m.find_process_by_thread_mut(thread_id))
.map(|(_, p)| p)
{
Some(p) => p,
Expand All @@ -3046,21 +3046,6 @@ pub fn sys_fcntl(fd: u64, cmd: u64, arg: u64) -> SyscallResult {
}
};

// Need to reborrow mutably for fd_table operations
drop(manager_guard);
let mut manager_guard = match crate::process::try_manager() {
Some(guard) => guard,
None => return SyscallResult::Err(9),
};
let process = match manager_guard
.as_mut()
.and_then(|m| m.find_process_by_thread_mut(thread_id))
.map(|(_, p)| p)
{
Some(p) => p,
None => return SyscallResult::Err(9),
};

match cmd {
F_DUPFD => {
match process.fd_table.dup_at_least(fd, arg, false) {
Expand Down
72 changes: 58 additions & 14 deletions libs/libbreenix/src/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,17 +539,17 @@ pub fn resolve(hostname: &str, dns_server: [u8; 4]) -> Result<DnsResult, DnsErro
return Err(DnsError::SendError);
}

// Receive response with 5-second timeout
// Receive response with 500ms timeout
let mut resp_buf = [0u8; DNS_BUF_SIZE];
let mut received = false;
let mut resp_len = 0;

// Network packets arrive via interrupt -> softirq -> process_rx().
// We poll recvfrom() with yield_now() between attempts.
// DNS resolution via QEMU SLIRP forwards to host DNS, which can take time.
const TIMEOUT_SECS: u64 = 5;
// Hypervisor DNS resolvers respond in <50ms; public DNS in <200ms.
const TIMEOUT_MS: u64 = 500;
let start = now_monotonic().unwrap_or(Timespec { tv_sec: 0, tv_nsec: 0 });
let deadline_secs = start.tv_sec as u64 + TIMEOUT_SECS;
let start_ms = start.tv_sec as u64 * 1000 + start.tv_nsec as u64 / 1_000_000;

loop {
match recvfrom(fd, &mut resp_buf, None) {
Expand All @@ -559,9 +559,10 @@ pub fn resolve(hostname: &str, dns_server: [u8; 4]) -> Result<DnsResult, DnsErro
break;
}
_ => {
// Check timeout
// Check timeout using millisecond precision
let now = now_monotonic().unwrap_or(Timespec { tv_sec: 0, tv_nsec: 0 });
if now.tv_sec as u64 >= deadline_secs {
let now_ms = now.tv_sec as u64 * 1000 + now.tv_nsec as u64 / 1_000_000;
if now_ms >= start_ms + TIMEOUT_MS {
break; // Timeout
}
// Yield to scheduler - allows timer interrupt to fire and process softirqs
Expand Down Expand Up @@ -605,18 +606,61 @@ pub fn resolve(hostname: &str, dns_server: [u8; 4]) -> Result<DnsResult, DnsErro

/// Resolve a hostname by trying multiple DNS servers automatically.
///
/// Tries Parallels (10.211.55.1), SLIRP (10.0.2.3), and Google (8.8.8.8)
/// in order, returning the first successful result. This makes DNS resolution
/// work across all supported platforms without caller configuration.
/// Tries Google (8.8.8.8) first since it's reachable from all platforms
/// (QEMU SLIRP, Parallels, VMware all NAT/bridge to host networking).
/// Falls back to hypervisor-specific DNS servers if Google fails.
pub fn resolve_auto(hostname: &str) -> Result<DnsResult, DnsError> {
let servers = [PARALLELS_DNS, VMWARE_DNS, SLIRP_DNS, GOOGLE_DNS];
let servers: [([u8; 4], &str); 4] = [
(GOOGLE_DNS, "8.8.8.8"),
(PARALLELS_DNS, "10.211.55.1"),
(VMWARE_DNS, "172.16.45.2"),
(SLIRP_DNS, "10.0.2.3"),
];
#[cfg(feature = "std")]
let total_start = now_monotonic().unwrap_or(Timespec { tv_sec: 0, tv_nsec: 0 });

let mut last_err = DnsError::Timeout;
for server in &servers {
for (server, _name) in &servers {
#[cfg(feature = "std")]
let attempt_start = now_monotonic().unwrap_or(Timespec { tv_sec: 0, tv_nsec: 0 });

match resolve(hostname, *server) {
Ok(r) if r.addr[0] != 0 && r.addr[0] != 127 => return Ok(r),
Ok(_) => continue,
Err(e) => { last_err = e; continue; }
Ok(r) if r.addr[0] != 0 && r.addr[0] != 127 => {
#[cfg(feature = "std")]
{
let elapsed = elapsed_ms(&attempt_start);
let total = elapsed_ms(&total_start);
eprintln!("[dns] resolved '{}' via {} -> {}.{}.{}.{} ({}ms, total {}ms)",
hostname, _name, r.addr[0], r.addr[1], r.addr[2], r.addr[3],
elapsed, total);
}
return Ok(r);
}
Ok(_) => {
#[cfg(feature = "std")]
eprintln!("[dns] '{}' via {}: unusable address ({}ms)",
hostname, _name, elapsed_ms(&attempt_start));
continue;
}
Err(e) => {
#[cfg(feature = "std")]
eprintln!("[dns] '{}' via {}: {:?} ({}ms)",
hostname, _name, e, elapsed_ms(&attempt_start));
last_err = e;
continue;
}
}
}
#[cfg(feature = "std")]
eprintln!("[dns] '{}' FAILED all servers (total {}ms)", hostname, elapsed_ms(&total_start));
Err(last_err)
}

/// Compute elapsed milliseconds since a start time.
#[cfg(feature = "std")]
fn elapsed_ms(start: &Timespec) -> u64 {
let now = now_monotonic().unwrap_or(Timespec { tv_sec: 0, tv_nsec: 0 });
let start_ms = start.tv_sec as u64 * 1000 + start.tv_nsec as u64 / 1_000_000;
let now_ms = now.tv_sec as u64 * 1000 + now.tv_nsec as u64 / 1_000_000;
now_ms.saturating_sub(start_ms)
}
Loading
Loading