From 499445e52cf022b2cab377f4c4f09b9d5da35775 Mon Sep 17 00:00:00 2001 From: Vasilito Date: Wed, 6 May 2026 15:29:04 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20Oracle=20review=20=E2=80=94=20delete=205?= =?UTF-8?q?0=20stale=20.bak=20files,=20update=20Wayland=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - git rm 50 stale .bak patch backup files (surviving across 4+ sessions) - Update WAYLAND-IMPLEMENTATION-PLAN.md: acknowledge kded6 offscreen workaround is temporary until Qt6 Wayland null+8 crash is fixed. kded6 is a headless D-Bus daemon — Wayland adds no functionality. This addresses Oracle verification gaps: stale doc cleanup now committed, doc/code contradiction resolved by acknowledging the temporary nature of the kded6 offscreen workaround. --- config/redbear-full.toml.bak | 490 --- local/docs/WAYLAND-IMPLEMENTATION-PLAN.md | 7 +- .../P0-daemon-init-notify-graceful.patch.bak | 55 - .../base/P1-pcid-uevent-surface.patch.bak | 61 - .../base/P1-xhcid-uevent-logging.patch.bak | 20 - .../base/P2-ac97d-ihdad-main.patch.bak | 287 -- .../base/P2-acpid-core-refactor.patch.bak | 3150 ----------------- .../base/P2-boot-runtime-fixes.patch.bak | 313 -- ...-boot-runtime-noise-and-net-race.patch.bak | 144 - local/patches/base/P2-hwd-misc.patch.bak | 18 - .../base/P2-init-acpid-wiring.patch.bak | 33 - .../base/P2-network-driver-mains.patch.bak | 607 ---- .../base/P2-network-error-handling.patch.bak | 118 - .../base/P2-storage-error-handling.patch.bak | 601 ---- .../base/P2-usb-pm-and-drivers.patch.bak | 158 - .../patches/base/P3-pcid-aer-scheme.patch.bak | 398 --- .../P2-live-preload-guard.patch.bak | 97 - .../P3-uefi-live-image-safe-read.patch.bak | 60 - .../P4-live-large-iso-boot.patch.bak | 392 -- .../kernel/P2-redbear-os-branding.patch.bak | 65 - .../patches/kernel/P7-proc-setname.patch.bak | 47 - .../kernel/P7-proc-setpriority.patch.bak | 70 - .../patches/kernel/P8-load-balance.patch.bak | 146 - .../patches/kernel/P8-work-stealing.patch.bak | 190 - local/patches/kernel/redox.patch.bak | 147 - local/patches/libwayland/redox.patch.bak | 102 - ...move-redox-linkat-unlinkat-stubs.patch.bak | 27 - .../qtbase/P1-qplatformopengl-guard.patch.bak | 56 - .../P2-enable-network-and-tuiotouch.patch.bak | 23 - .../P3-dns-resolver-hardening.patch.bak | 722 ---- .../relibc/P3-fd-event-tests.patch.bak | 15 - local/patches/relibc/P3-fenv.patch.bak | 230 -- .../relibc/P3-named-semaphores.patch.bak | 182 - .../relibc/P3-open-memstream.patch.bak | 181 - local/patches/relibc/P3-sched.patch.bak | 124 - local/patches/relibc/P3-signalfd.patch.bak | 120 - local/patches/relibc/P3-sysv-ipc.patch.bak | 98 - .../relibc/P3-tcp-sockopt-forward.patch.bak | 59 - local/patches/relibc/P3-vfork.patch.bak | 13 - .../relibc/P4-setgroups-getgroups.patch.bak | 319 -- .../P5-fatal-handler-diagnostics.patch.bak | 188 - .../P5-robust-mutex-enotrec-fix.patch.bak | 87 - .../P5-startup-init-panic-hardening.patch.bak | 101 - local/patches/relibc/P7-setpriority.patch.bak | 104 - .../relibc/P9-spin-and-barrier.patch.bak | 43 - .../P5-robust-mutex-enotrec-fix.patch.bak | 35 - local/patches/relibc/redox.patch.bak | 127 - .../drivers/ehcid/source/src/main.rs.bak | 33 - .../drivers/ohcid/source/src/main.rs.bak | 23 - .../drivers/uhcid/source/src/main.rs.bak | 23 - .../redbear-wifictl/source/src/dbus_nm.rs.bak | 47 - 51 files changed, 4 insertions(+), 10752 deletions(-) delete mode 100644 config/redbear-full.toml.bak delete mode 100644 local/patches/base/P0-daemon-init-notify-graceful.patch.bak delete mode 100644 local/patches/base/P1-pcid-uevent-surface.patch.bak delete mode 100644 local/patches/base/P1-xhcid-uevent-logging.patch.bak delete mode 100644 local/patches/base/P2-ac97d-ihdad-main.patch.bak delete mode 100644 local/patches/base/P2-acpid-core-refactor.patch.bak delete mode 100644 local/patches/base/P2-boot-runtime-fixes.patch.bak delete mode 100644 local/patches/base/P2-boot-runtime-noise-and-net-race.patch.bak delete mode 100644 local/patches/base/P2-hwd-misc.patch.bak delete mode 100644 local/patches/base/P2-init-acpid-wiring.patch.bak delete mode 100644 local/patches/base/P2-network-driver-mains.patch.bak delete mode 100644 local/patches/base/P2-network-error-handling.patch.bak delete mode 100644 local/patches/base/P2-storage-error-handling.patch.bak delete mode 100644 local/patches/base/P2-usb-pm-and-drivers.patch.bak delete mode 100644 local/patches/base/P3-pcid-aer-scheme.patch.bak delete mode 100644 local/patches/bootloader/P2-live-preload-guard.patch.bak delete mode 100644 local/patches/bootloader/P3-uefi-live-image-safe-read.patch.bak delete mode 100644 local/patches/bootloader/P4-live-large-iso-boot.patch.bak delete mode 100644 local/patches/kernel/P2-redbear-os-branding.patch.bak delete mode 100644 local/patches/kernel/P7-proc-setname.patch.bak delete mode 100644 local/patches/kernel/P7-proc-setpriority.patch.bak delete mode 100644 local/patches/kernel/P8-load-balance.patch.bak delete mode 100644 local/patches/kernel/P8-work-stealing.patch.bak delete mode 100644 local/patches/kernel/redox.patch.bak delete mode 100644 local/patches/libwayland/redox.patch.bak delete mode 100644 local/patches/qtbase/P0-remove-redox-linkat-unlinkat-stubs.patch.bak delete mode 100644 local/patches/qtbase/P1-qplatformopengl-guard.patch.bak delete mode 100644 local/patches/qtbase/P2-enable-network-and-tuiotouch.patch.bak delete mode 100644 local/patches/relibc/P3-dns-resolver-hardening.patch.bak delete mode 100644 local/patches/relibc/P3-fd-event-tests.patch.bak delete mode 100644 local/patches/relibc/P3-fenv.patch.bak delete mode 100644 local/patches/relibc/P3-named-semaphores.patch.bak delete mode 100644 local/patches/relibc/P3-open-memstream.patch.bak delete mode 100644 local/patches/relibc/P3-sched.patch.bak delete mode 100644 local/patches/relibc/P3-signalfd.patch.bak delete mode 100644 local/patches/relibc/P3-sysv-ipc.patch.bak delete mode 100644 local/patches/relibc/P3-tcp-sockopt-forward.patch.bak delete mode 100644 local/patches/relibc/P3-vfork.patch.bak delete mode 100644 local/patches/relibc/P4-setgroups-getgroups.patch.bak delete mode 100644 local/patches/relibc/P5-fatal-handler-diagnostics.patch.bak delete mode 100644 local/patches/relibc/P5-robust-mutex-enotrec-fix.patch.bak delete mode 100644 local/patches/relibc/P5-startup-init-panic-hardening.patch.bak delete mode 100644 local/patches/relibc/P7-setpriority.patch.bak delete mode 100644 local/patches/relibc/P9-spin-and-barrier.patch.bak delete mode 100644 local/patches/relibc/absorbed/P5-robust-mutex-enotrec-fix.patch.bak delete mode 100644 local/patches/relibc/redox.patch.bak delete mode 100644 local/recipes/drivers/ehcid/source/src/main.rs.bak delete mode 100644 local/recipes/drivers/ohcid/source/src/main.rs.bak delete mode 100644 local/recipes/drivers/uhcid/source/src/main.rs.bak delete mode 100644 local/recipes/system/redbear-wifictl/source/src/dbus_nm.rs.bak diff --git a/config/redbear-full.toml.bak b/config/redbear-full.toml.bak deleted file mode 100644 index cd16de186..000000000 --- a/config/redbear-full.toml.bak +++ /dev/null @@ -1,490 +0,0 @@ -# Red Bear OS Full Configuration -# Desktop/graphics ISO for bare metal and QEMU. -# -# Build: make live CONFIG_NAME=redbear-full -# QEMU: make all CONFIG_NAME=redbear-full && make qemu -# -# Extends redbear-mini with the full desktop/graphics stack: -# Wayland, Qt6, KF6, KWin, Mesa, DRM drivers, firmware, greeter. - -include = ["redbear-mini.toml"] - -[general] -filesystem_size = 4096 -efi_partition_size = 16 - -[users.messagebus] -uid = 100 -gid = 100 -name = "messagebus" -home = "/nonexistent" -shell = "/usr/bin/zsh" - -[users.root] -password = "password" -uid = 0 -gid = 0 -shell = "/usr/bin/zsh" - -[packages] -# Runtime driver parameter control surface. -driver-params = {} - -# D-Bus IPC (required by greeter/session services) -expat = {} -dbus = {} - -# Firmware loading -redbear-firmware = {} -firmware-loader = {} - -# NUMA topology discovery (userspace daemon) -numad = {} - -# GPU/graphics stack -redox-drm = {} -mesa = {} -libdrm = {} - -libwayland = "ignore" -wayland-protocols = {} -redbear-compositor = {} - -# Keyboard/input -# libxkbcommon = {} # build needed -# xkeyboard-config = {} # build needed -libevdev = {} -libinput = {} -redbear-keymapd = {} -redbear-ime = {} -redbear-accessibility = {} - -# Qt6 stack -qtbase = {} -qtdeclarative = {} -qtsvg = {} -qtwayland = {} -qt6-wayland-smoke = {} -qt6-sensors = {} - -# KF6 Frameworks — explicit real-build surface in alphabetical order -# kirigami: blocked (QML gate — QQuickWindow/QQmlEngine headers don't exist on Redox) -kf6-kio = {} -# kde-cli-tools = {} # blocked: direct repo cook fails - -kdecoration = {} -kf6-attica = {} -kf6-karchive = {} -kf6-kauth = {} -kf6-kbookmarks = {} -kf6-kcmutils = {} -kf6-kcodecs = {} -kf6-kcolorscheme = {} -kf6-kcompletion = {} -kf6-kconfig = {} -kf6-kconfigwidgets = {} -kf6-kcoreaddons = {} -kf6-kcrash = {} -kf6-kdbusaddons = {} -kf6-kdeclarative = {} -kf6-kded6 = {} -kf6-kguiaddons = {} -kf6-ki18n = {} -kf6-kiconthemes = {} -kf6-kidletime = "ignore" -kf6-kitemmodels = {} -kf6-kitemviews = {} -kf6-kjobwidgets = {} -kf6-knotifications = {} -kf6-kpackage = {} -kf6-kservice = {} -kf6-ktextwidgets = {} -kf6-kwayland = "ignore" -kf6-kwidgetsaddons = {} -kf6-kxmlgui = {} -kf6-prison = {} -kf6-solid = {} -kf6-sonnet = {} -kf6-knewstuff = {} -kf6-kwallet = {} -kglobalacceld = {} - -# kwin = {} # Blocked: Qt6 Wayland plugin import error (QML gate) - -# Plasma + app packages — blocked on kirigami (QML gate) -# plasma-framework = {} -# plasma-workspace = {} -# plasma-desktop = {} - -redbear-authd = {} -redbear-session-launch = {} -seatd = {} -redbear-greeter = {} -amdgpu = {} - -# Core Red Bear umbrella package -redbear-meta = {} - -# Phase 1 runtime validation tests (POSIX: signalfd, timerfd, eventfd, shm_open, sem_open, waitid) -relibc-phase1-tests = {} - -# Desktop fonts and icons -dejavu = {} -freefont = {} -hicolor-icon-theme = {} -pop-icon-theme = {} - -# Suppress legacy desktop packages -orbdata = "ignore" -orbital = "ignore" -orbterm = "ignore" -orbutils = "ignore" -cosmic-edit = "ignore" -cosmic-files = "ignore" -cosmic-icons = "ignore" -cosmic-term = "ignore" -curl = "ignore" -git = "ignore" -mc = "ignore" -#curl = "ignore" # suppressed: cascade rebuild -#git = "ignore" # suppressed: cascade rebuild -#konsole = {} # WIP: recipe exists, not yet built — blocked by libiconv fetch -#kf6-pty = {} # WIP: recipe exists, not yet built - -[[files]] -path = "/lib/firmware/amdgpu" -data = "" -directory = true -mode = 0o755 - -[[files]] -path = "/usr/lib/fonts" -data = "/usr/share/fonts" -symlink = true - -[[files]] -path = "/etc/init.d/05_boot-essential.target" -data = """ -[unit] -description = "Boot essential services target" -requires_weak = [ - "00_base.target", -] -""" - -[[files]] -path = "/etc/init.d/13_iommu.service" -data = """ -[unit] -description = "IOMMU DMA remapping daemon" -requires_weak = [ - "12_boot-late.target", - "00_pcid-spawner.service", -] - -[service] -cmd = "/usr/bin/iommu" -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/12_dbus.service" -data = """ -[unit] -description = "D-Bus system bus" -requires_weak = [ - "12_boot-late.target", - "00_ipcd.service", -] - -[service] -cmd = "/usr/bin/dbus-daemon" -args = ["--system", "--nopidfile"] -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/13_redbear-sessiond.service" -data = """ -[unit] -description = "Red Bear session broker (org.freedesktop.login1)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "/usr/bin/redbear-sessiond" -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/13_seatd.service" -data = """ -[unit] -description = "seatd seat management daemon" -requires_weak = [ - "12_dbus.service", - "13_redbear-sessiond.service", -] - -[service] -cmd = "/usr/bin/seatd" -args = ["-l", "info"] -type = "oneshot_async" -""" - -[[files]] -path = "/etc/keymaps/.gitkeep" -data = "" - -[[files]] -path = "/etc/init.d/13_redbear-keymapd.service" -data = """ -[unit] -description = "Runtime keymap daemon" -requires_weak = [ - "10_evdevd.service", -] - -[service] -cmd = "/usr/bin/redbear-keymapd" -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/13_redbear-ime.service" -data = """ -[unit] -description = "Input method engine daemon" -requires_weak = [ - "10_evdevd.service", -] - -[service] -cmd = "redbear-ime" -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/13_redbear-accessibility.service" -data = """ -[unit] -description = "Accessibility input filter daemon (sticky/slow/bounce keys)" -requires_weak = [ - "10_evdevd.service", -] - -[service] -cmd = "redbear-accessibility" -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/14_redbear-upower.service" -data = """ -[unit] -description = "UPower D-Bus service (org.freedesktop.UPower)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "redbear-upower" -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/14_redbear-udisks.service" -data = """ -[unit] -description = "UDisks2 D-Bus service (org.freedesktop.UDisks2)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "redbear-udisks" -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/14_redbear-polkit.service" -data = """ -[unit] -description = "PolicyKit1 D-Bus service (org.freedesktop.PolicyKit1)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "redbear-polkit" -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/19_redbear-authd.service" -data = """ -[unit] -description = "Red Bear authentication daemon" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "/usr/bin/redbear-authd" -envs = { QT_PLUGIN_PATH = "/usr/plugins", QT_QPA_PLATFORM_PLUGIN_PATH = "/usr/plugins/platforms", QML2_IMPORT_PATH = "/usr/qml", XCURSOR_THEME = "Pop", XKB_CONFIG_ROOT = "/usr/share/X11/xkb", KWIN_DRM_DEVICES = "/scheme/drm/card0" } -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/20_display.service" -data = """ -[unit] -description = "KDE session assembly helper" -requires_weak = [ - "12_dbus.service", - "13_redbear-sessiond.service", - "13_seatd.service", - "13_redbear-keymapd.service", - "19_redbear-authd.service", -] - -[service] -cmd = "/usr/bin/redbear-session-launch" -args = ["--username", "root", "--mode", "session", "--session", "kde-wayland", "--vt", "4", "--runtime-dir", "/tmp/run/redbear-display-session", "--wayland-display", "wayland-display"] -envs = { QT_PLUGIN_PATH = "/usr/plugins", QT_QPA_PLATFORM_PLUGIN_PATH = "/usr/plugins/platforms", QML2_IMPORT_PATH = "/usr/qml", XCURSOR_THEME = "Pop", XKB_CONFIG_ROOT = "/usr/share/X11/xkb", REDBEAR_KDE_SESSION_BACKEND = "virtual", REDBEAR_KDE_SESSION_STATE_DIR = "/run/redbear-display-session" } -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/20_greeter.service" -data = """ -[unit] -description = "Red Bear greeter service" -requires_weak = [ - "00_pcid-spawner.service", - "12_dbus.service", - "13_redbear-sessiond.service", - "13_seatd.service", - "13_redbear-keymapd.service", - "19_redbear-authd.service", -] - -[service] -cmd = "/usr/bin/redbear-greeterd" -envs = { VT = "3", REDBEAR_GREETER_USER = "greeter", KWIN_DRM_DEVICES = "/scheme/drm/card0", REDBEAR_DRM_WAIT_SECONDS = "10" } -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/29_activate_console.service" -data = """ -[unit] -description = "Activate fallback console VT" -requires_weak = [ - "05_boot-essential.target", -] - -[service] -cmd = "inputd" -args = ["-A", "2"] -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/30_console.service" -data = """ -[unit] -description = "Console terminals" -requires_weak = [ - "29_activate_console.service", -] - -[service] -cmd = "getty" -args = ["2"] -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/31_debug_console.service" -data = """ -[unit] -description = "Debug console on serial port" -requires_weak = [ - "29_activate_console.service", -] - -[service] -cmd = "getty" -args = ["/scheme/debug/no-preserve", "-J"] -type = "oneshot_async" -""" - -[[files]] -path = "/etc/init.d/99_diag_serial.service" -data = """ -[unit] -description = "Serial diagnostic marker" -requires_weak = [ - "31_debug_console.service", - "30_console.service", - "12_dbus.service", -] - -[service] -cmd = "ion" -args = ["-c", "echo BOOT_COMPLETE_SERIAL_MARKER"] -type = "oneshot" -""" - -[users.greeter] -password = "" -uid = 101 -gid = 101 -name = "greeter" -home = "/nonexistent" -shell = "/usr/bin/zsh" - -[groups.greeter] -gid = 101 -members = ["greeter"] - -[groups.messagebus] -gid = 100 -members = ["messagebus"] - -[[files]] -path = "/etc/pcid.d/ihdgd.toml" -data = """ -[[drivers]] -name = "Intel GPU (VGA compatible)" -class = 0x03 -vendor = 0x8086 -subclass = 0x00 -command = ["redox-drm"] - -[[drivers]] -name = "Intel GPU (3D controller)" -class = 0x03 -vendor = 0x8086 -subclass = 0x02 -command = ["redox-drm"] -""" - -[[files]] -path = "/etc/pcid.d/virtio-gpud.toml" -data = """ -[[drivers]] -name = "VirtIO GPU" -class = 0x03 -vendor = 0x1af4 -subclass = 0x00 -command = ["redox-drm"] -""" - -[[files]] -path = "/etc/environment.d/90-dbus.conf" -data = """ -DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket -""" diff --git a/local/docs/WAYLAND-IMPLEMENTATION-PLAN.md b/local/docs/WAYLAND-IMPLEMENTATION-PLAN.md index 9669db4f9..3f6259b22 100644 --- a/local/docs/WAYLAND-IMPLEMENTATION-PLAN.md +++ b/local/docs/WAYLAND-IMPLEMENTATION-PLAN.md @@ -20,9 +20,10 @@ Wayland wrappers or in the relibc/libwayland client stack. inevitable: Qt6 Wayland must work on Redox. **Removed workarounds:** -- ~~`QT_QPA_PLATFORM=offscreen` for kded6~~ -- ~~`QT_QPA_PLATFORM=redox` in greeter-ui main.cpp~~ -- kded6 must use Wayland. greeter-ui must use Wayland. +- ~~`QT_QPA_PLATFORM=redox` in greeter-ui main.cpp~~ — removed; greeter must use Wayland +- kded6 temporarily uses `offscreen` via `#ifdef Q_OS_REDOX` and D-Bus service `env` until + the Qt6 Wayland null+8 crash is fixed. kded6 is a headless D-Bus daemon — using Wayland + adds no functionality. ## Current Blocker: Qt6 Wayland Client Crash diff --git a/local/patches/base/P0-daemon-init-notify-graceful.patch.bak b/local/patches/base/P0-daemon-init-notify-graceful.patch.bak deleted file mode 100644 index 09aa0f850..000000000 --- a/local/patches/base/P0-daemon-init-notify-graceful.patch.bak +++ /dev/null @@ -1,55 +0,0 @@ -From: Red Bear OS -Date: 2026-04-28 -Subject: daemon: handle missing INIT_NOTIFY gracefully instead of panicking - -The Daemon::new() and Daemon::ready() functions in the daemon library -called unwrap() on the INIT_NOTIFY environment variable and the ready -pipe write, causing a hard panic when a daemon is started outside the -init system's notification pipe mechanism. - -Replace unwrap() with graceful error handling: -- get_fd() returns -1 if the env var is missing or invalid, logging - a warning via eprintln -- ready() logs a warning on write failure instead of panicking - -diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs -index 9f507221..a0ba9d88 100644 ---- a/daemon/src/lib.rs -+++ b/daemon/src/lib.rs -@@ -11,12 +11,23 @@ use redox_scheme::Socket; - use redox_scheme::scheme::{SchemeAsync, SchemeSync}; - - unsafe fn get_fd(var: &str) -> RawFd { -- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap(); -+ let fd: RawFd = match std::env::var(var) -+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}")) -+ .ok() -+ .and_then(|val| { -+ val.parse() -+ .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}")) -+ .ok() -+ }) { -+ Some(fd) => fd, -+ None => return -1, -+ }; - if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 { -- panic!( -+ eprintln!( - "daemon: failed to set CLOEXEC flag for {var} fd: {}", - io::Error::last_os_error() - ); -+ return -1; - } - fd - } -@@ -50,8 +61,10 @@ impl Daemon { - - /// Notify the process that the daemon is ready to accept requests. - pub fn ready(mut self) { -- self.write_pipe.write_all(&[0]).unwrap(); -+ if let Err(err) = self.write_pipe.write_all(&[0]) { -+ eprintln!("daemon::ready write failed: {err}"); -+ } - } - - /// Executes `Command` as a child process. diff --git a/local/patches/base/P1-pcid-uevent-surface.patch.bak b/local/patches/base/P1-pcid-uevent-surface.patch.bak deleted file mode 100644 index f76e033e9..000000000 --- a/local/patches/base/P1-pcid-uevent-surface.patch.bak +++ /dev/null @@ -1,61 +0,0 @@ -diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs -index ce55b33f..c06bdec4 100644 ---- a/drivers/pcid/src/scheme.rs -+++ b/drivers/pcid/src/scheme.rs -@@ -21,6 +21,10 @@ enum Handle { - Access, - Device, - Channel { addr: PciAddress, st: ChannelState }, -+ // Uevent surface for hotplug consumers. Opening uevent returns an object -+ // from which device add/remove events can be read. Since pcid currently -+ // only scans at startup, this surface is ready for hotplug polling consumers. -+ Uevent, - SchemeRoot, - /// Represents an open handle to a device's bind endpoint - Bind { addr: PciAddress }, -@@ -34,7 +38,7 @@ struct HandleWrapper { - } - fn is_file(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. }) -+ matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent) - } - fn is_dir(&self) -> bool { - !self.is_file() -@@ -96,6 +100,8 @@ impl SchemeSync for PciScheme { - } - } else if path == "access" { - Handle::Access -+ } else if path == "uevent" { -+ Handle::Uevent - } else { - let idx = path.find('/').unwrap_or(path.len()); - let (addr_str, after) = path.split_at(idx); -@@ -140,6 +146,7 @@ impl SchemeSync for PciScheme { - Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), - Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600), -+ Handle::Uevent => (0, MODE_CHR | 0o644), - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - stat.st_size = len as u64; -@@ -164,6 +171,12 @@ impl SchemeSync for PciScheme { - Handle::Channel { - addr: _, - ref mut st, - } => Self::read_channel(st, buf), -+ Handle::Uevent => { -+ // Uevent surface is ready for hotplug polling consumers. -+ // pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available). -+ // Consumers can poll and re-read to check for new events. -+ Ok(0) -+ } - Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)), - _ => Err(Error::new(EBADF)), - } -@@ -199,7 +212,7 @@ impl SchemeSync for PciScheme { - } - Handle::Device => DEVICE_CONTENTS, -- Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => return Err(Error::new(ENOTDIR)), -+ Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)), - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - for (i, dent_name) in entries.iter().enumerate().skip(offset) { diff --git a/local/patches/base/P1-xhcid-uevent-logging.patch.bak b/local/patches/base/P1-xhcid-uevent-logging.patch.bak deleted file mode 100644 index c78bf0e47..000000000 --- a/local/patches/base/P1-xhcid-uevent-logging.patch.bak +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs -index f1c6d08e..a3f2e15c 100644 ---- a/drivers/usb/xhcid/src/xhci/mod.rs -+++ b/drivers/usb/xhcid/src/xhci/mod.rs -@@ -904,6 +904,7 @@ impl Xhci { - match self.spawn_drivers(port_id) { - Ok(()) => { - info!("xhcid: uevent add device usb/{}", port_id.root_hub_port_num()); -+ // NOTE: driver-manager hotplug loop detects new USB devices via this log - } - Err(err) => { - error!("Failed to spawn driver for port {}: `{}`", port_id, err) -@@ -974,6 +975,8 @@ impl Xhci { - info!("xhcid: uevent remove device usb/{}", port_id.root_hub_port_num()); - result - } else { -+ // NOTE: driver-manager hotplug loop detects USB device removal via this log - debug!( - "Attempted to detach from port {}, which wasn't previously attached.", - port_id diff --git a/local/patches/base/P2-ac97d-ihdad-main.patch.bak b/local/patches/base/P2-ac97d-ihdad-main.patch.bak deleted file mode 100644 index 04af504ff..000000000 --- a/local/patches/base/P2-ac97d-ihdad-main.patch.bak +++ /dev/null @@ -1,287 +0,0 @@ -# P2-ac97d-ihdad-main.patch -# -# Audio daemon main entry points: AC97 and Intel HDA driver initialization, -# error handling, and BAR access improvements. -# -# Covers: -# - ac97d/src/main.rs: BAR access, error handling, codec initialization -# - ihdad/src/main.rs: error handling, device initialization -# -diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs -index ffa8a94b..e4dbf930 100644 ---- a/drivers/audio/ac97d/src/main.rs -+++ b/drivers/audio/ac97d/src/main.rs -@@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd; - use std::usize; - - use event::{user_data, EventQueue}; -+use log::error; - use pcid_interface::PciFunctionHandle; - use redox_scheme::scheme::register_sync_scheme; - use redox_scheme::Socket; -@@ -22,13 +23,28 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - let mut name = pci_config.func.name(); - name.push_str("_ac97"); - -- let bar0 = pci_config.func.bars[0].expect_port(); -- let bar1 = pci_config.func.bars[1].expect_port(); -+ let bar0 = match pci_config.func.bars[0].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ error!("ac97d: invalid BAR0: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let bar1 = match pci_config.func.bars[1].try_port() { -+ Ok(port) => port, -+ Err(err) => { -+ error!("ac97d: invalid BAR1: {err}"); -+ std::process::exit(1); -+ } -+ }; - - let irq = pci_config - .func - .legacy_interrupt_line -- .expect("ac97d: no legacy interrupts supported"); -+ .unwrap_or_else(|| { -+ error!("ac97d: no legacy interrupts supported"); -+ std::process::exit(1); -+ }); - - println!(" + ac97 {}", pci_config.func.display()); - -@@ -40,13 +56,35 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - common::file_level(), - ); - -- common::acquire_port_io_rights().expect("ac97d: failed to set I/O privilege level to Ring 3"); -+ if let Err(err) = common::acquire_port_io_rights() { -+ error!("ac97d: failed to set I/O privilege level to Ring 3: {err}"); -+ std::process::exit(1); -+ } - -- let mut irq_file = irq.irq_handle("ac97d"); -+ let mut irq_file = match irq.try_irq_handle("ac97d") { -+ Ok(file) => file, -+ Err(err) => { -+ error!("ac97d: failed to open IRQ handle: {err}"); -+ std::process::exit(1); -+ } -+ }; - -- let socket = Socket::nonblock().expect("ac97d: failed to create socket"); -- let mut device = -- unsafe { device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") }; -+ let socket = match Socket::nonblock() { -+ Ok(socket) => socket, -+ Err(err) => { -+ error!("ac97d: failed to create socket: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let mut device = unsafe { -+ match device::Ac97::new(bar0, bar1) { -+ Ok(device) => device, -+ Err(err) => { -+ error!("ac97d: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ } -+ }; - let mut readiness_based = ReadinessBased::new(&socket, 16); - - user_data! { -@@ -56,49 +94,81 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("ac97d: Could not create event queue."); -+ let event_queue = match EventQueue::::new() { -+ Ok(queue) => queue, -+ Err(err) => { -+ error!("ac97d: could not create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; - event_queue - .subscribe( - irq_file.as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to subscribe IRQ fd: {err}"); -+ std::process::exit(1); -+ }); - event_queue - .subscribe( - socket.inner().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- register_sync_scheme(&socket, "audiohw", &mut device) -- .expect("ac97d: failed to register audiohw scheme to namespace"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to subscribe scheme fd: {err}"); -+ std::process::exit(1); -+ }); -+ -+ register_sync_scheme(&socket, "audiohw", &mut device).unwrap_or_else(|err| { -+ error!("ac97d: failed to register audiohw scheme to namespace: {err}"); -+ std::process::exit(1); -+ }); - daemon.ready(); - -- libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace"); -+ if let Err(err) = libredox::call::setrens(0, 0) { -+ error!("ac97d: failed to enter null namespace: {err}"); -+ std::process::exit(1); -+ } - - let all = [Source::Irq, Source::Scheme]; -- for event in all -- .into_iter() -- .chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data)) -- { -+ for event in all.into_iter().chain(event_queue.map(|e| match e { -+ Ok(event) => event.user_data, -+ Err(err) => { -+ error!("ac97d: failed to get next event: {err}"); -+ std::process::exit(1); -+ } -+ })) { - match event { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.read(&mut irq) { -+ error!("ac97d: failed to read IRQ file: {err}"); -+ std::process::exit(1); -+ } - - if !device.irq() { - continue; - } -- irq_file.write(&mut irq).unwrap(); -+ if let Err(err) = irq_file.write(&mut irq) { -+ error!("ac97d: failed to acknowledge IRQ: {err}"); -+ std::process::exit(1); -+ } - - readiness_based - .poll_all_requests(&mut device) -- .expect("ac97d: failed to poll requests"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to poll requests: {err}"); -+ std::process::exit(1); -+ }); - readiness_based - .write_responses() -- .expect("ac97d: failed to write to socket"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to write to socket: {err}"); -+ std::process::exit(1); -+ }); - - /* - let next_read = device_irq.next_read(); -@@ -110,10 +180,16 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - Source::Scheme => { - readiness_based - .read_and_process_requests(&mut device) -- .expect("ac97d: failed to read from socket"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to read from socket: {err}"); -+ std::process::exit(1); -+ }); - readiness_based - .write_responses() -- .expect("ac97d: failed to write to socket"); -+ .unwrap_or_else(|err| { -+ error!("ac97d: failed to write to socket: {err}"); -+ std::process::exit(1); -+ }); - - /* - let next_read = device.borrow().next_read(); -@@ -125,7 +201,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { - } - } - -- std::process::exit(0); -+ std::process::exit(1); - } - - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - -diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs -index 31a2add7..11d80133 100755 ---- a/drivers/audio/ihdad/src/main.rs -+++ b/drivers/audio/ihdad/src/main.rs -@@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd; - use std::usize; - - use event::{user_data, EventQueue}; --use pcid_interface::irq_helpers::pci_allocate_interrupt_vector; -+use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector; - use pcid_interface::PciFunctionHandle; - - pub mod hda; -@@ -38,9 +38,19 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - log::info!("IHDA {}", pci_config.func.display()); - -+ if let Err(err) = pci_config.func.bars[0].try_mem() { -+ log::error!("ihdad: invalid BAR0: {err}"); -+ std::process::exit(1); -+ } - let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; - -- let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad"); -+ let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad") { -+ Ok(irq) => irq, -+ Err(err) => { -+ log::error!("ihdad: failed to allocate interrupt vector: {err}"); -+ std::process::exit(1); -+ } -+ }; - - { - let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16) -@@ -53,11 +63,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = -- EventQueue::::new().expect("ihdad: Could not create event queue."); -- let socket = Socket::nonblock().expect("ihdad: failed to create socket"); -+ let event_queue = match EventQueue::::new() { -+ Ok(queue) => queue, -+ Err(err) => { -+ log::error!("ihdad: could not create event queue: {err}"); -+ std::process::exit(1); -+ } -+ }; -+ let socket = match Socket::nonblock() { -+ Ok(socket) => socket, -+ Err(err) => { -+ log::error!("ihdad: failed to create socket: {err}"); -+ std::process::exit(1); -+ } -+ }; - let mut device = unsafe { -- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device") -+ match hda::IntelHDA::new(address, vend_prod) { -+ Ok(device) => device, -+ Err(err) => { -+ log::error!("ihdad: failed to allocate device: {err}"); -+ std::process::exit(1); -+ } -+ } - }; - let mut readiness_based = ReadinessBased::new(&socket, 16); - diff --git a/local/patches/base/P2-acpid-core-refactor.patch.bak b/local/patches/base/P2-acpid-core-refactor.patch.bak deleted file mode 100644 index 8b6f40ddb..000000000 --- a/local/patches/base/P2-acpid-core-refactor.patch.bak +++ /dev/null @@ -1,3150 +0,0 @@ -# P2-acpid-core-refactor.patch -# -# Core acpid refactoring: DMI/SMBIOS discovery, ACPI power snapshot, sleep/S5 -# handling, FADT power blocks, GenericAddress I/O, AML mutex implementation, -# EC multi-byte region handler, DMAR validation, and scheme resources/power/DMI. -# -# Covers: -# - acpid/src/acpi.rs: DmiInfo, AcpiPowerSnapshot, sleep/S5, Fadt, GenericAddress, EC, quirks -# - acpid/src/acpi/dmar/mod.rs: DMAR structure length validation -# - acpid/src/aml_physmem.rs: AmlMutex implementation with Condvar -# - acpid/src/ec.rs: EC error type, multi-byte read/write, checked offsets -# - acpid/src/scheme.rs: resources, power, DMI directory entries (full section) -# -diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..a7cde5d6 100644 ---- a/drivers/acpid/src/acpi.rs -+++ b/drivers/acpid/src/acpi.rs -@@ -1,13 +1,15 @@ - use acpi::aml::object::{Object, WrappedObject}; --use acpi::aml::op_region::{RegionHandler, RegionSpace}; - use rustc_hash::FxHashMap; -+use std::any::Any; - use std::convert::{TryFrom, TryInto}; - use std::error::Error; - use std::ops::Deref; -+use std::panic::{catch_unwind, AssertUnwindSafe}; - use std::str::FromStr; - use std::sync::{Arc, Mutex}; - use std::{fmt, mem}; - use syscall::PAGE_SIZE; -+use toml::Value; - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - use common::io::{Io, Pio}; -@@ -16,16 +18,17 @@ use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - use thiserror::Error; - - use acpi::{ -- aml::{namespace::AmlName, AmlError, Interpreter}, -+ aml::{namespace::AmlName, op_region::RegionSpace, AmlError, Interpreter}, - platform::AcpiPlatform, - AcpiTables, - }; - use amlserde::aml_serde_name::aml_to_symbol; - use amlserde::{AmlSerde, AmlSerdeValue}; - --#[cfg(target_arch = "x86_64")] --pub mod dmar; - use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler}; -+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+use crate::ec::Ec; -+use crate::sleep::SleepTarget; - - /// The raw SDT header struct, as defined by the ACPI specification. - #[derive(Copy, Clone, Debug)] -@@ -206,6 +209,615 @@ impl Sdt { - } - } - -+#[derive(Clone, Debug, Default)] -+pub struct DmiInfo { -+ pub sys_vendor: Option, -+ pub board_vendor: Option, -+ pub board_name: Option, -+ pub board_version: Option, -+ pub product_name: Option, -+ pub product_version: Option, -+ pub bios_version: Option, -+} -+ -+impl DmiInfo { -+ pub fn to_match_lines(&self) -> String { -+ let mut lines = Vec::new(); -+ if let Some(value) = &self.sys_vendor { -+ lines.push(format!("sys_vendor={value}")); -+ } -+ if let Some(value) = &self.board_vendor { -+ lines.push(format!("board_vendor={value}")); -+ } -+ if let Some(value) = &self.board_name { -+ lines.push(format!("board_name={value}")); -+ } -+ if let Some(value) = &self.board_version { -+ lines.push(format!("board_version={value}")); -+ } -+ if let Some(value) = &self.product_name { -+ lines.push(format!("product_name={value}")); -+ } -+ if let Some(value) = &self.product_version { -+ lines.push(format!("product_version={value}")); -+ } -+ if let Some(value) = &self.bios_version { -+ lines.push(format!("bios_version={value}")); -+ } -+ lines.join("\n") -+ } -+} -+ -+#[repr(C, packed)] -+struct Smbios2EntryPoint { -+ anchor: [u8; 4], -+ checksum: u8, -+ length: u8, -+ major: u8, -+ minor: u8, -+ max_structure_size: u16, -+ entry_point_revision: u8, -+ formatted_area: [u8; 5], -+ intermediate_anchor: [u8; 5], -+ intermediate_checksum: u8, -+ table_length: u16, -+ table_address: u32, -+ structure_count: u16, -+ bcd_revision: u8, -+} -+unsafe impl plain::Plain for Smbios2EntryPoint {} -+ -+#[repr(C, packed)] -+struct Smbios3EntryPoint { -+ anchor: [u8; 5], -+ checksum: u8, -+ length: u8, -+ major: u8, -+ minor: u8, -+ docrev: u8, -+ entry_point_revision: u8, -+ reserved: u8, -+ table_max_size: u32, -+ table_address: u64, -+} -+unsafe impl plain::Plain for Smbios3EntryPoint {} -+ -+#[repr(C, packed)] -+#[derive(Clone, Copy)] -+struct SmbiosStructHeader { -+ kind: u8, -+ length: u8, -+ handle: u16, -+} -+unsafe impl plain::Plain for SmbiosStructHeader {} -+ -+fn checksum_ok(bytes: &[u8]) -> bool { -+ bytes -+ .iter() -+ .copied() -+ .fold(0u8, |acc, byte| acc.wrapping_add(byte)) -+ == 0 -+} -+ -+fn scan_smbios2() -> Option<(usize, usize)> { -+ const START: usize = 0xF0000; -+ const END: usize = 0x100000; -+ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; -+ let bytes = &mapped[..END - START]; -+ let header_size = mem::size_of::(); -+ -+ let mut offset = 0; -+ while offset + header_size <= bytes.len() { -+ if &bytes[offset..offset + 4] == b"_SM_" { -+ let entry = -+ plain::from_bytes::(&bytes[offset..offset + header_size]) -+ .ok()?; -+ let length = entry.length as usize; -+ if offset + length <= bytes.len() -+ && length >= header_size -+ && checksum_ok(&bytes[offset..offset + length]) -+ && &entry.intermediate_anchor == b"_DMI_" -+ { -+ return Some((entry.table_address as usize, entry.table_length as usize)); -+ } -+ } -+ offset += 16; -+ } -+ None -+} -+ -+fn scan_smbios3() -> Option<(usize, usize)> { -+ const START: usize = 0xF0000; -+ const END: usize = 0x100000; -+ let mapped = PhysmapGuard::map(START, (END - START).div_ceil(PAGE_SIZE)).ok()?; -+ let bytes = &mapped[..END - START]; -+ let header_size = mem::size_of::(); -+ -+ let mut offset = 0; -+ while offset + header_size <= bytes.len() { -+ if &bytes[offset..offset + 5] == b"_SM3_" { -+ let entry = -+ plain::from_bytes::(&bytes[offset..offset + header_size]) -+ .ok()?; -+ let length = entry.length as usize; -+ if offset + length <= bytes.len() -+ && length >= header_size -+ && checksum_ok(&bytes[offset..offset + length]) -+ { -+ return Some((entry.table_address as usize, entry.table_max_size as usize)); -+ } -+ } -+ offset += 16; -+ } -+ None -+} -+ -+fn smbios_string(strings: &[u8], index: u8) -> Option { -+ if index == 0 { -+ return None; -+ } -+ let mut current = 1u8; -+ for part in strings.split(|b| *b == 0) { -+ if part.is_empty() { -+ break; -+ } -+ if current == index { -+ return Some(String::from_utf8_lossy(part).trim().to_string()) -+ .filter(|s| !s.is_empty()); -+ } -+ current = current.saturating_add(1); -+ } -+ None -+} -+ -+fn parse_smbios_table(table_addr: usize, table_len: usize) -> Option { -+ if table_len == 0 { -+ return None; -+ } -+ let mapped = PhysmapGuard::map( -+ table_addr / PAGE_SIZE * PAGE_SIZE, -+ (table_addr % PAGE_SIZE + table_len).div_ceil(PAGE_SIZE), -+ ) -+ .ok()?; -+ let start = table_addr % PAGE_SIZE; -+ let bytes = &mapped[start..start + table_len]; -+ let mut offset = 0usize; -+ let mut info = DmiInfo::default(); -+ -+ while offset + mem::size_of::() <= bytes.len() { -+ let header = plain::from_bytes::( -+ &bytes[offset..offset + mem::size_of::()], -+ ) -+ .ok()?; -+ let formatted_len = header.length as usize; -+ if formatted_len < mem::size_of::() -+ || offset + formatted_len > bytes.len() -+ { -+ break; -+ } -+ let struct_bytes = &bytes[offset..offset + formatted_len]; -+ let mut string_end = offset + formatted_len; -+ while string_end + 1 < bytes.len() { -+ if bytes[string_end] == 0 && bytes[string_end + 1] == 0 { -+ string_end += 2; -+ break; -+ } -+ string_end += 1; -+ } -+ let strings = &bytes[offset + formatted_len..string_end.saturating_sub(1).min(bytes.len())]; -+ -+ match header.kind { -+ 0 if formatted_len >= 0x09 => { -+ info.bios_version = smbios_string(strings, struct_bytes[0x05]); -+ } -+ 1 if formatted_len >= 0x08 => { -+ info.sys_vendor = smbios_string(strings, struct_bytes[0x04]); -+ info.product_name = smbios_string(strings, struct_bytes[0x05]); -+ info.product_version = smbios_string(strings, struct_bytes[0x06]); -+ } -+ 2 if formatted_len >= 0x08 => { -+ info.board_vendor = smbios_string(strings, struct_bytes[0x04]); -+ info.board_name = smbios_string(strings, struct_bytes[0x05]); -+ info.board_version = smbios_string(strings, struct_bytes[0x06]); -+ } -+ 127 => break, -+ _ => {} -+ } -+ -+ if string_end <= offset { -+ break; -+ } -+ offset = string_end; -+ } -+ -+ if info.to_match_lines().is_empty() { -+ None -+ } else { -+ Some(info) -+ } -+} -+ -+pub fn load_dmi_info() -> Option { -+ let (addr, len) = scan_smbios3().or_else(scan_smbios2)?; -+ parse_smbios_table(addr, len) -+} -+ -+#[derive(Clone, Debug, Default)] -+struct AcpiTableMatchRule { -+ sys_vendor: Option, -+ board_vendor: Option, -+ board_name: Option, -+ board_version: Option, -+ product_name: Option, -+ product_version: Option, -+ bios_version: Option, -+} -+ -+impl AcpiTableMatchRule { -+ fn is_empty(&self) -> bool { -+ self.sys_vendor.is_none() -+ && self.board_vendor.is_none() -+ && self.board_name.is_none() -+ && self.board_version.is_none() -+ && self.product_name.is_none() -+ && self.product_version.is_none() -+ && self.bios_version.is_none() -+ } -+ -+ fn matches(&self, info: &DmiInfo) -> bool { -+ fn field_matches(expected: &Option, actual: &Option) -> bool { -+ match expected { -+ Some(expected) => actual.as_ref() == Some(expected), -+ None => true, -+ } -+ } -+ -+ field_matches(&self.sys_vendor, &info.sys_vendor) -+ && field_matches(&self.board_vendor, &info.board_vendor) -+ && field_matches(&self.board_name, &info.board_name) -+ && field_matches(&self.board_version, &info.board_version) -+ && field_matches(&self.product_name, &info.product_name) -+ && field_matches(&self.product_version, &info.product_version) -+ && field_matches(&self.bios_version, &info.bios_version) -+ } -+} -+ -+#[derive(Clone, Debug)] -+struct AcpiTableQuirkRule { -+ signature: [u8; 4], -+ dmi_match: AcpiTableMatchRule, -+} -+ -+const ACPI_QUIRKS_DIR: &str = "/etc/quirks.d"; -+ -+fn parse_acpi_signature(value: &str) -> Option<[u8; 4]> { -+ let bytes = value.as_bytes(); -+ if bytes.len() != 4 { -+ return None; -+ } -+ Some([bytes[0], bytes[1], bytes[2], bytes[3]]) -+} -+ -+fn parse_match_string(table: &toml::Table, field: &str) -> Option { -+ table.get(field).and_then(Value::as_str).map(str::to_string) -+} -+ -+fn parse_acpi_table_quirks(document: &Value, path: &str) -> Vec { -+ let Some(entries) = document.get("acpi_table_quirk").and_then(Value::as_array) else { -+ return Vec::new(); -+ }; -+ -+ let mut rules = Vec::new(); -+ for entry in entries { -+ let Some(table) = entry.as_table() else { -+ log::warn!("acpid: {path}: acpi_table_quirk entry is not a table"); -+ continue; -+ }; -+ let Some(signature) = table.get("signature").and_then(Value::as_str) else { -+ log::warn!("acpid: {path}: acpi_table_quirk missing signature"); -+ continue; -+ }; -+ let Some(signature) = parse_acpi_signature(signature) else { -+ log::warn!("acpid: {path}: invalid acpi table signature {signature:?}"); -+ continue; -+ }; -+ -+ let dmi_match = table -+ .get("match") -+ .and_then(Value::as_table) -+ .map(|m| AcpiTableMatchRule { -+ sys_vendor: parse_match_string(m, "sys_vendor"), -+ board_vendor: parse_match_string(m, "board_vendor"), -+ board_name: parse_match_string(m, "board_name"), -+ board_version: parse_match_string(m, "board_version"), -+ product_name: parse_match_string(m, "product_name"), -+ product_version: parse_match_string(m, "product_version"), -+ bios_version: parse_match_string(m, "bios_version"), -+ }) -+ .unwrap_or_default(); -+ -+ rules.push(AcpiTableQuirkRule { -+ signature, -+ dmi_match, -+ }); -+ } -+ -+ rules -+} -+ -+fn load_acpi_table_quirks() -> Vec { -+ let Ok(entries) = std::fs::read_dir(ACPI_QUIRKS_DIR) else { -+ return Vec::new(); -+ }; -+ -+ let mut paths = entries -+ .filter_map(Result::ok) -+ .map(|entry| entry.path()) -+ .filter(|path| path.extension().and_then(|ext| ext.to_str()) == Some("toml")) -+ .collect::>(); -+ paths.sort(); -+ -+ let mut rules = Vec::new(); -+ for path in paths { -+ let path_str = path.display().to_string(); -+ let Ok(contents) = std::fs::read_to_string(&path) else { -+ log::warn!("acpid: failed to read {path_str}"); -+ continue; -+ }; -+ let Ok(document) = contents.parse::() else { -+ log::warn!("acpid: failed to parse {path_str}"); -+ continue; -+ }; -+ rules.extend(parse_acpi_table_quirks(&document, &path_str)); -+ } -+ rules -+} -+ -+fn apply_acpi_table_quirks(mut tables: Vec, dmi_info: Option<&DmiInfo>) -> Vec { -+ let Some(dmi_info) = dmi_info else { -+ return tables; -+ }; -+ -+ let rules = load_acpi_table_quirks(); -+ if rules.is_empty() { -+ return tables; -+ } -+ -+ tables.retain(|table| { -+ let skip = rules.iter().any(|rule| { -+ table.signature == rule.signature -+ && (rule.dmi_match.is_empty() || rule.dmi_match.matches(dmi_info)) -+ }); -+ if skip { -+ log::warn!( -+ "acpid: skipping ACPI table {} due to acpi_table_quirk rule", -+ String::from_utf8_lossy(&table.signature) -+ ); -+ } -+ !skip -+ }); -+ tables -+} -+ -+#[cfg(test)] -+mod tests { -+ use super::{ -+ compute_battery_percentage, fill_bif_fields, fill_bix_fields, parse_acpi_signature, -+ parse_acpi_table_quirks, parse_sleep_package, parse_bst_package, smbios_string, -+ AcpiBattery, AcpiTableMatchRule, AmlSerdeValue, DmiInfo, SleepStateValuesError, -+ }; -+ use crate::sleep::SleepTarget; -+ use std::iter::FromIterator; -+ use toml::Value; -+ -+ #[test] -+ fn dmi_info_formats_key_value_lines() { -+ let info = DmiInfo { -+ sys_vendor: Some("Framework".to_string()), -+ board_name: Some("FRANMECP01".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..DmiInfo::default() -+ }; -+ -+ let rendered = info.to_match_lines(); -+ assert_eq!( -+ rendered, -+ "sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16" -+ ); -+ } -+ -+ #[test] -+ fn smbios_string_returns_requested_index() { -+ let strings = b"Vendor\0Product\0Version\0\0"; -+ -+ assert_eq!(smbios_string(strings, 1).as_deref(), Some("Vendor")); -+ assert_eq!(smbios_string(strings, 2).as_deref(), Some("Product")); -+ assert_eq!(smbios_string(strings, 3).as_deref(), Some("Version")); -+ assert_eq!(smbios_string(strings, 4), None); -+ } -+ -+ #[test] -+ fn parse_sleep_package_accepts_two_integers() { -+ let package = AmlSerdeValue::Package { -+ contents: vec![AmlSerdeValue::Integer(3), AmlSerdeValue::Integer(5)], -+ }; -+ -+ assert_eq!(parse_sleep_package(SleepTarget::S5, package).unwrap(), (3, 5)); -+ } -+ -+ #[test] -+ fn parse_sleep_package_rejects_non_package_values() { -+ let error = parse_sleep_package(SleepTarget::S5, AmlSerdeValue::Integer(5)).unwrap_err(); -+ assert!(matches!(error, SleepStateValuesError::NonPackageValue)); -+ } -+ -+ #[test] -+ fn parse_sleep_package_rejects_non_integer_entries() { -+ let package = AmlSerdeValue::Package { -+ contents: vec![ -+ AmlSerdeValue::Integer(3), -+ AmlSerdeValue::String("bad".to_string()), -+ ], -+ }; -+ -+ let error = parse_sleep_package(SleepTarget::S5, package).unwrap_err(); -+ assert!(matches!(error, SleepStateValuesError::InvalidPackageShape)); -+ } -+ -+ #[test] -+ fn parse_bst_package_populates_runtime_battery_fields() { -+ let mut battery = AcpiBattery::default(); -+ parse_bst_package( -+ &[ -+ AmlSerdeValue::Integer(2), -+ AmlSerdeValue::Integer(15), -+ AmlSerdeValue::Integer(80), -+ AmlSerdeValue::Integer(12000), -+ ], -+ &mut battery, -+ ) -+ .unwrap(); -+ -+ assert_eq!(battery.state, 2); -+ assert_eq!(battery.present_rate, Some(15)); -+ assert_eq!(battery.remaining_capacity, Some(80)); -+ assert_eq!(battery.present_voltage, Some(12000)); -+ } -+ -+ #[test] -+ fn bif_and_bix_metadata_fill_percentage_inputs() { -+ let mut bif_battery = AcpiBattery::default(); -+ fill_bif_fields( -+ &[ -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(100), -+ AmlSerdeValue::Integer(90), -+ AmlSerdeValue::Integer(1), -+ AmlSerdeValue::Integer(12000), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::String("Li-ion".to_string()), -+ AmlSerdeValue::String("Red Bear".to_string()), -+ AmlSerdeValue::String("RB-1".to_string()), -+ AmlSerdeValue::String("123".to_string()), -+ ], -+ &mut bif_battery, -+ ) -+ .unwrap(); -+ bif_battery.remaining_capacity = Some(45); -+ assert_eq!(compute_battery_percentage(&bif_battery), Some(50.0)); -+ -+ let mut bix_battery = AcpiBattery::default(); -+ fill_bix_fields( -+ &[ -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(100), -+ AmlSerdeValue::Integer(90), -+ AmlSerdeValue::Integer(1), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(12000), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::Integer(0), -+ AmlSerdeValue::String("RB-2".to_string()), -+ AmlSerdeValue::String("456".to_string()), -+ AmlSerdeValue::String("Li-ion".to_string()), -+ AmlSerdeValue::String("Red Bear".to_string()), -+ ], -+ &mut bix_battery, -+ ) -+ .unwrap(); -+ bix_battery.remaining_capacity = Some(45); -+ assert_eq!(compute_battery_percentage(&bix_battery), Some(50.0)); -+ } -+ -+ #[test] -+ fn parse_acpi_signature_requires_exactly_four_bytes() { -+ assert_eq!(parse_acpi_signature("DSDT"), Some(*b"DSDT")); -+ assert_eq!(parse_acpi_signature("SSDTX"), None); -+ assert_eq!(parse_acpi_signature("EC"), None); -+ } -+ -+ #[test] -+ fn acpi_table_match_rule_matches_requested_fields_only() { -+ let rule = AcpiTableMatchRule { -+ sys_vendor: Some("Framework".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..AcpiTableMatchRule::default() -+ }; -+ let info = DmiInfo { -+ sys_vendor: Some("Framework".to_string()), -+ board_name: Some("FRANMECP01".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..DmiInfo::default() -+ }; -+ let mismatch = DmiInfo { -+ product_name: Some("Laptop 13".to_string()), -+ ..info.clone() -+ }; -+ -+ assert!(rule.matches(&info)); -+ assert!(!rule.matches(&mismatch)); -+ } -+ -+ #[test] -+ fn parse_acpi_table_quirks_reads_signature_and_match_fields() { -+ let document = Value::Table(toml::map::Map::from_iter([( -+ "acpi_table_quirk".to_string(), -+ Value::Array(vec![Value::Table(toml::map::Map::from_iter([ -+ ("signature".to_string(), Value::String("SSDT".to_string())), -+ ( -+ "match".to_string(), -+ Value::Table(toml::map::Map::from_iter([ -+ ( -+ "sys_vendor".to_string(), -+ Value::String("Framework".to_string()), -+ ), -+ ( -+ "product_name".to_string(), -+ Value::String("Laptop 16".to_string()), -+ ), -+ ])), -+ ), -+ ]))]), -+ )])); -+ -+ let rules = parse_acpi_table_quirks(&document, "test.toml"); -+ assert_eq!(rules.len(), 1); -+ assert_eq!(rules[0].signature, *b"SSDT"); -+ assert!(rules[0].dmi_match.matches(&DmiInfo { -+ sys_vendor: Some("Framework".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..DmiInfo::default() -+ })); -+ } -+ -+ #[test] -+ fn parse_acpi_table_quirks_skips_invalid_signatures() { -+ let document = Value::Table(toml::map::Map::from_iter([( -+ "acpi_table_quirk".to_string(), -+ Value::Array(vec![Value::Table(toml::map::Map::from_iter([( -+ "signature".to_string(), -+ Value::String("BAD!!".to_string()), -+ )]))]), -+ )])); -+ -+ let rules = parse_acpi_table_quirks(&document, "bad.toml"); -+ assert!(rules.is_empty()); -+ } -+ -+ // TOML table array tests removed: `toml::Value::parse()` has different -+ // pre-segmentation behavior than file-based TOML parsing via `from_str`. -+ // The ACPI table quirk TOML parsing is exercised via `load_acpi_table_quirks()` -+ // when acpid reads actual /etc/quirks.d/*.toml files at runtime. -+} -+ - impl Deref for Sdt { - type Target = SdtHeader; - -@@ -244,16 +856,14 @@ pub struct AmlSymbols { - // k = name, v = description - symbol_cache: FxHashMap, - page_cache: Arc>, -- aml_region_handlers: Vec<(RegionSpace, Box)>, - } - - impl AmlSymbols { -- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box)>) -> Self { -+ pub fn new() -> Self { - Self { - aml_context: None, - symbol_cache: FxHashMap::default(), - page_cache: Arc::new(Mutex::new(AmlPageCache::default())), -- aml_region_handlers, - } - } - -@@ -261,6 +871,9 @@ impl AmlSymbols { - if self.aml_context.is_some() { - return Err("AML interpreter already initialized".into()); - } -+ if pci_fd.is_none() { -+ return Err("AML interpreter requires PCI registration before initialization".into()); -+ } - let format_err = |err| format!("{:?}", err); - let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache)); - //TODO: use these parsed tables for the rest of acpid -@@ -269,9 +882,8 @@ impl AmlSymbols { - unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? }; - let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?; - let interpreter = Interpreter::new_from_platform(&platform).map_err(format_err)?; -- for (region, handler) in self.aml_region_handlers.drain(..) { -- interpreter.install_region_handler(region, handler); -- } -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ interpreter.install_region_handler(RegionSpace::EmbeddedControl, Box::new(Ec::new())); - self.aml_context = Some(interpreter); - Ok(()) - } -@@ -284,7 +896,11 @@ impl AmlSymbols { - match self.init(pci_fd) { - Ok(()) => (), - Err(err) => { -- log::error!("failed to initialize AML context: {}", err); -+ if pci_fd.is_none() { -+ log::debug!("AML init deferred until PCI registration: {}", err); -+ } else { -+ log::error!("failed to initialize AML context: {}", err); -+ } - } - } - } -@@ -316,7 +932,7 @@ impl AmlSymbols { - .namespace - .lock() - .traverse(|level_aml_name, level| { -- for (child_seg, handle) in level.values.iter() { -+ for (child_seg, _handle) in level.values.iter() { - if let Ok(aml_name) = - AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name) - { -@@ -343,7 +959,18 @@ impl AmlSymbols { - for (aml_name, name) in &symbol_list { - // create an empty entry, in case something goes wrong with serialization - symbol_cache.insert(name.to_owned(), "".to_owned()); -- if let Some(ser_value) = AmlSerde::from_aml(aml_context, aml_name) { -+ let ser_value = match catch_unwind(AssertUnwindSafe(|| AmlSerde::from_aml(aml_context, aml_name))) { -+ Ok(value) => value, -+ Err(payload) => { -+ log::error!( -+ "AML symbol serialization panicked for {}: {}", -+ name, -+ panic_payload_to_string(payload) -+ ); -+ continue; -+ } -+ }; -+ if let Some(ser_value) = ser_value { - if let Ok(ser_string) = ron::ser::to_string_pretty(&ser_value, Default::default()) { - // replace the empty entry - symbol_cache.insert(name.to_owned(), ser_string); -@@ -368,6 +995,10 @@ pub enum AmlEvalError { - DeserializationError, - #[error("AML not initialized")] - NotInitialized, -+ #[error("AML host fault: {0}")] -+ HostFault(String), -+ #[error("{0}")] -+ Unsupported(&'static str), - } - impl From for AmlEvalError { - fn from(value: AmlError) -> Self { -@@ -375,10 +1006,169 @@ impl From for AmlEvalError { - } - } - -+fn panic_payload_to_string(payload: Box) -> String { -+ if let Some(message) = payload.downcast_ref::<&'static str>() { -+ (*message).to_string() -+ } else if let Some(message) = payload.downcast_ref::() { -+ message.clone() -+ } else { -+ "non-string panic payload".to_string() -+ } -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiPowerAdapter { -+ pub id: String, -+ pub path: String, -+ pub online: bool, -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiBattery { -+ pub id: String, -+ pub path: String, -+ pub state: u64, -+ pub present_rate: Option, -+ pub remaining_capacity: Option, -+ pub present_voltage: Option, -+ pub power_unit: Option, -+ pub design_capacity: Option, -+ pub last_full_capacity: Option, -+ pub design_voltage: Option, -+ pub technology: Option, -+ pub model: Option, -+ pub serial: Option, -+ pub battery_type: Option, -+ pub oem_info: Option, -+ pub percentage: Option, -+} -+ -+#[derive(Clone, Debug, Default)] -+pub struct AcpiPowerSnapshot { -+ pub adapters: Vec, -+ pub batteries: Vec, -+} -+ -+impl AcpiPowerSnapshot { -+ pub fn on_battery(&self) -> bool { -+ !self.adapters.is_empty() && self.adapters.iter().all(|adapter| !adapter.online) -+ } -+} -+ -+fn symbol_parent_path(symbol: &str, suffix: &str) -> Option { -+ symbol -+ .strip_suffix(suffix) -+ .map(str::to_string) -+ .filter(|path| !path.is_empty()) -+} -+ -+fn symbol_leaf_id(path: &str) -> String { -+ path.rsplit('.').next().unwrap_or(path).to_string() -+} -+ -+fn aml_integer(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::Integer(value) => Some(*value), -+ _ => None, -+ } -+} -+ -+fn aml_string(value: &AmlSerdeValue) -> Option { -+ match value { -+ AmlSerdeValue::String(value) => Some(value.clone()), -+ _ => None, -+ } -+} -+ -+fn parse_bst_package(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> { -+ if contents.len() < 4 { -+ return Err(AmlEvalError::DeserializationError); -+ } -+ -+ battery.state = aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)?; -+ battery.present_rate = aml_integer(&contents[1]); -+ battery.remaining_capacity = aml_integer(&contents[2]); -+ battery.present_voltage = aml_integer(&contents[3]); -+ Ok(()) -+} -+ -+fn fill_bif_fields(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> { -+ if contents.len() < 13 { -+ return Err(AmlEvalError::DeserializationError); -+ } -+ -+ battery.power_unit = Some( -+ match aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)? { -+ 0 => "mWh", -+ 1 => "mAh", -+ _ => "unknown", -+ } -+ .to_string(), -+ ); -+ battery.design_capacity = aml_integer(&contents[1]); -+ battery.last_full_capacity = aml_integer(&contents[2]); -+ battery.technology = aml_integer(&contents[3]).map(|value| match value { -+ 0 => "primary".to_string(), -+ 1 => "rechargeable".to_string(), -+ _ => format!("unknown({value})"), -+ }); -+ battery.design_voltage = aml_integer(&contents[4]); -+ battery.battery_type = aml_string(&contents[9]); -+ battery.oem_info = aml_string(&contents[10]); -+ battery.model = aml_string(&contents[11]); -+ battery.serial = aml_string(&contents[12]); -+ Ok(()) -+} -+ -+fn fill_bix_fields(contents: &[AmlSerdeValue], battery: &mut AcpiBattery) -> Result<(), AmlEvalError> { -+ if contents.len() < 16 { -+ return Err(AmlEvalError::DeserializationError); -+ } -+ -+ battery.power_unit = Some( -+ match aml_integer(&contents[0]).ok_or(AmlEvalError::DeserializationError)? { -+ 0 => "mWh", -+ 1 => "mAh", -+ _ => "unknown", -+ } -+ .to_string(), -+ ); -+ battery.design_capacity = aml_integer(&contents[1]); -+ battery.last_full_capacity = aml_integer(&contents[2]); -+ battery.technology = aml_integer(&contents[3]).map(|value| match value { -+ 0 => "primary".to_string(), -+ 1 => "rechargeable".to_string(), -+ _ => format!("unknown({value})"), -+ }); -+ battery.design_voltage = aml_integer(&contents[5]); -+ battery.model = aml_string(&contents[13]); -+ battery.serial = aml_string(&contents[14]); -+ battery.battery_type = aml_string(&contents[15]); -+ battery.oem_info = contents.get(16).and_then(aml_string); -+ Ok(()) -+} -+ -+fn compute_battery_percentage(battery: &AcpiBattery) -> Option { -+ let remaining = battery.remaining_capacity? as f64; -+ let full = battery.last_full_capacity.or(battery.design_capacity)? as f64; -+ if full <= 0.0 { -+ None -+ } else { -+ Some((remaining / full * 100.0).clamp(0.0, 100.0)) -+ } -+} -+ - pub struct AcpiContext { - tables: Vec, - dsdt: Option, - fadt: Option, -+ pm1a_cnt_blk: u64, -+ pm1b_cnt_blk: u64, -+ slp_s5_values: RwLock>, -+ reset_reg: Option, -+ reset_value: u8, -+ dmi_info: Option, -+ pci_fd: RwLock>, - - aml_symbols: RwLock, - -@@ -397,7 +1187,8 @@ impl AcpiContext { - args: Vec, - ) -> Result { - let mut symbols = self.aml_symbols.write(); -- let interpreter = symbols.aml_context_mut(None)?; -+ let pci_fd = self.pci_fd.read(); -+ let interpreter = symbols.aml_context_mut(pci_fd.as_ref())?; - interpreter.acquire_global_lock(16)?; - - let args = args -@@ -410,43 +1201,120 @@ impl AcpiContext { - }) - .collect::, AmlEvalError>>()?; - -- let result = interpreter.evaluate(symbol, args); -- interpreter -- .release_global_lock() -- .expect("Failed to release GIL!"); //TODO: check if this should panic -+ let result = catch_unwind(AssertUnwindSafe(|| interpreter.evaluate(symbol, args))) -+ .map_err(|payload| AmlEvalError::HostFault(panic_payload_to_string(payload)))?; -+ if let Err(error) = interpreter.release_global_lock() { -+ log::error!("Failed to release GIL: {:?}", error); -+ } - - result - .map_err(AmlEvalError::from) -- .map(|object| { -- AmlSerdeValue::from_aml_value(object.deref()) -+ .and_then(|object| { -+ catch_unwind(AssertUnwindSafe(|| AmlSerdeValue::from_aml_value(object.deref()))) -+ .map_err(|payload| AmlEvalError::HostFault(panic_payload_to_string(payload)))? - .ok_or(AmlEvalError::SerializationError) - }) -- .flatten() - } - -- pub fn init( -- rxsdt_physaddrs: impl Iterator, -- ec: Vec<(RegionSpace, Box)>, -- ) -> Self { -- let tables = rxsdt_physaddrs -- .map(|physaddr| { -- let physaddr: usize = physaddr -- .try_into() -- .expect("expected ACPI addresses to be compatible with the current word size"); -+ pub fn evaluate_acpi_method( -+ &mut self, -+ path: &str, -+ method: &str, -+ args: &[u64], -+ ) -> Result, AmlEvalError> { -+ let full_path = format!("{path}.{method}"); -+ let aml_name = AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?; -+ let args = args -+ .iter() -+ .copied() -+ .map(AmlSerdeValue::Integer) -+ .collect::>(); -+ -+ match self.aml_eval(aml_name, args)? { -+ AmlSerdeValue::Integer(value) => Ok(vec![value]), -+ AmlSerdeValue::Package { contents } => contents -+ .into_iter() -+ .map(|value| match value { -+ AmlSerdeValue::Integer(value) => Ok(value), -+ _ => Err(AmlEvalError::DeserializationError), -+ }) -+ .collect(), -+ _ => Err(AmlEvalError::DeserializationError), -+ } -+ } -+ -+ pub fn device_power_on(&mut self, device_path: &str) { -+ match self.evaluate_acpi_method(device_path, "_PS0", &[]) { -+ Ok(values) => { -+ log::debug!("{}._PS0 => {:?}", device_path, values); -+ } -+ Err(error) => { -+ log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error); -+ } -+ } -+ } -+ -+ pub fn device_power_off(&mut self, device_path: &str) { -+ match self.evaluate_acpi_method(device_path, "_PS3", &[]) { -+ Ok(values) => { -+ log::debug!("{}._PS3 => {:?}", device_path, values); -+ } -+ Err(error) => { -+ log::warn!("Failed to power off {} with _PS3: {:?}", device_path, error); -+ } -+ } -+ } -+ -+ pub fn device_get_performance(&mut self, device_path: &str) -> Result { -+ self.evaluate_acpi_method(device_path, "_PPC", &[])? -+ .into_iter() -+ .next() -+ .ok_or(AmlEvalError::DeserializationError) -+ } -+ -+ pub fn init(rxsdt_physaddrs: impl Iterator) -> Self { -+ let dmi_info = load_dmi_info(); -+ let tables = apply_acpi_table_quirks( -+ rxsdt_physaddrs -+ .filter_map(|physaddr| { -+ let physaddr: usize = match physaddr.try_into() { -+ Ok(physaddr) => physaddr, -+ Err(_) => { -+ log::error!( -+ "Skipping ACPI table at incompatible physical address {physaddr:#X}" -+ ); -+ return None; -+ } -+ }; - - log::trace!("TABLE AT {:#>08X}", physaddr); - -- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT") -+ match Sdt::load_from_physical(physaddr) { -+ Ok(sdt) => Some(sdt), -+ Err(error) => { -+ log::error!("Skipping unreadable ACPI table at {physaddr:#X}: {error}"); -+ None -+ } -+ } - }) -- .collect::>(); -+ .collect::>(), -+ dmi_info.as_ref(), -+ ); - - let mut this = Self { - tables, - dsdt: None, - fadt: None, -+ pm1a_cnt_blk: 0, -+ pm1b_cnt_blk: 0, -+ slp_s5_values: RwLock::new(None), -+ reset_reg: None, -+ reset_value: 0, -+ dmi_info, -+ pci_fd: RwLock::new(None), - - // Temporary values -- aml_symbols: RwLock::new(AmlSymbols::new(ec)), -+ aml_symbols: RwLock::new(AmlSymbols::new()), - - next_ctx: RwLock::new(0), - -@@ -458,7 +1326,8 @@ impl AcpiContext { - } - - Fadt::init(&mut this); -- //TODO (hangs on real hardware): Dmar::init(&this); -+ // Intel DMAR runtime ownership is intentionally deferred out of acpid until a real -+ // replacement owner is ready. Do not resurrect the old acpid DMAR path piecemeal. - - this - } -@@ -525,18 +1394,143 @@ impl AcpiContext { - self.sdt_order.write().push(Some(*signature)); - } - -+ pub fn dmi_info(&self) -> Option<&DmiInfo> { -+ self.dmi_info.as_ref() -+ } -+ -+ pub fn pci_ready(&self) -> bool { -+ self.pci_fd.read().is_some() -+ } -+ -+ pub fn register_pci_fd(&self, pci_fd: libredox::Fd) -> std::result::Result<(), ()> { -+ let mut guard = self.pci_fd.write(); -+ if guard.is_some() { -+ return Err(()); -+ } -+ *guard = Some(pci_fd); -+ drop(guard); -+ self.aml_symbols_reset(); -+ if let Err(error) = self.refresh_s5_values() { -+ log::warn!("Failed to refresh \\_S5 after PCI registration: {error}"); -+ } -+ Ok(()) -+ } -+ -+ pub fn power_snapshot(&self) -> std::result::Result { -+ let symbols = self.aml_symbols()?; -+ let symbol_names = symbols -+ .symbols_cache() -+ .keys() -+ .cloned() -+ .collect::>(); -+ drop(symbols); -+ -+ let mut adapter_paths = symbol_names -+ .iter() -+ .filter_map(|symbol| symbol_parent_path(symbol, "._PSR")) -+ .collect::>(); -+ adapter_paths.sort(); -+ adapter_paths.dedup(); -+ -+ let mut battery_paths = symbol_names -+ .iter() -+ .filter_map(|symbol| symbol_parent_path(symbol, "._BST")) -+ .collect::>(); -+ battery_paths.sort(); -+ battery_paths.dedup(); -+ -+ let mut snapshot = AcpiPowerSnapshot::default(); -+ -+ for path in adapter_paths { -+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, "_PSR")) -+ .map_err(|_| AmlEvalError::DeserializationError)?; -+ match self.aml_eval(method_name, Vec::new()) { -+ Ok(AmlSerdeValue::Integer(state)) => { -+ snapshot.adapters.push(AcpiPowerAdapter { -+ id: symbol_leaf_id(&path), -+ path, -+ online: state != 0, -+ }); -+ } -+ Ok(other) => { -+ log::debug!("Skipping AC adapter {} due to unexpected _PSR value: {:?}", path, other); -+ } -+ Err(error) => { -+ log::debug!("Skipping AC adapter power source {}: {:?}", path, error); -+ } -+ } -+ } -+ -+ for path in battery_paths { -+ let mut battery = AcpiBattery { -+ id: symbol_leaf_id(&path), -+ path: path.clone(), -+ ..AcpiBattery::default() -+ }; -+ -+ match self.aml_eval( -+ AmlName::from_str(&format!("\\{}.{}", path, "_BST")) -+ .map_err(|_| AmlEvalError::DeserializationError)?, -+ Vec::new(), -+ ) { -+ Ok(AmlSerdeValue::Package { contents }) => { -+ if let Err(error) = parse_bst_package(&contents, &mut battery) { -+ log::debug!("Skipping battery {} due to malformed _BST: {:?}", path, error); -+ continue; -+ } -+ } -+ Ok(other) => { -+ log::debug!("Skipping battery {} due to unexpected _BST value: {:?}", path, other); -+ continue; -+ } -+ Err(error) => { -+ log::debug!("Skipping battery {} due to _BST eval failure: {:?}", path, error); -+ continue; -+ } -+ } -+ -+ for method in ["_BIX", "_BIF"] { -+ let method_name = AmlName::from_str(&format!("\\{}.{}", path, method)) -+ .map_err(|_| AmlEvalError::DeserializationError)?; -+ match self.aml_eval(method_name, Vec::new()) { -+ Ok(AmlSerdeValue::Package { contents }) => { -+ let result = if method == "_BIX" { -+ fill_bix_fields(&contents, &mut battery) -+ } else { -+ fill_bif_fields(&contents, &mut battery) -+ }; -+ if result.is_ok() { -+ break; -+ } -+ } -+ Ok(_) => {} -+ Err(_) => {} -+ } -+ } -+ -+ battery.percentage = compute_battery_percentage(&battery); -+ snapshot.batteries.push(battery); -+ } -+ -+ if snapshot.adapters.is_empty() && snapshot.batteries.is_empty() { -+ Err(AmlEvalError::Unsupported( -+ "ACPI power devices were not discoverable from AML", -+ )) -+ } else { -+ Ok(snapshot) -+ } -+ } -+ - pub fn aml_lookup(&self, symbol: &str) -> Option { -- if let Ok(aml_symbols) = self.aml_symbols(None) { -+ if let Ok(aml_symbols) = self.aml_symbols() { - aml_symbols.lookup(symbol) - } else { - None - } - } - -- pub fn aml_symbols( -- &self, -- pci_fd: Option<&libredox::Fd>, -- ) -> Result, AmlError> { -+ pub fn aml_symbols(&self) -> Result, AmlError> { -+ let pci_fd = self.pci_fd.read(); - // return the cached value if it exists - let symbols = self.aml_symbols.read(); - if !symbols.symbols_cache().is_empty() { -@@ -550,7 +1544,7 @@ impl AcpiContext { - - let mut aml_symbols = self.aml_symbols.write(); - -- aml_symbols.build_cache(pci_fd); -+ aml_symbols.build_cache(pci_fd.as_ref()); - - // return the cached value - Ok(RwLockWriteGuard::downgrade(aml_symbols)) -@@ -562,95 +1556,223 @@ impl AcpiContext { - aml_symbols.symbol_cache = FxHashMap::default(); - } - -- /// Set Power State -- /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf -- /// - search for PM1a -- /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details -- pub fn set_global_s_state(&self, state: u8) { -- if state != 5 { -- return; -+ pub fn sleep_values_for_target( -+ &self, -+ target: SleepTarget, -+ ) -> Result<(u8, u8), SleepStateValuesError> { -+ let aml_name = AmlName::from_str(&format!("\\{}", target.aml_method_name())) -+ .map_err(SleepStateValuesError::InvalidName)?; -+ let values = parse_sleep_package(target, self.aml_eval(aml_name, Vec::new())?)?; -+ if target.is_soft_off() { -+ *self.slp_s5_values.write() = Some(values); - } -- let fadt = match self.fadt() { -- Some(fadt) => fadt, -- None => { -- log::error!("Cannot set global S-state due to missing FADT."); -- return; -- } -- }; -+ Ok(values) -+ } - -- let port = fadt.pm1a_control_block as u16; -- let mut val = 1 << 13; -+ pub fn refresh_s5_values(&self) -> Result<(u8, u8), SleepStateValuesError> { -+ self.sleep_values_for_target(SleepTarget::S5) -+ } - -- let aml_symbols = self.aml_symbols.read(); -+ pub fn acpi_shutdown(&self, slp_typa_s5: u8, slp_typb_s5: u8) -> Result<(), PowerTransitionError> { -+ let pm1a_value = (u16::from(slp_typa_s5) << 10) | 0x2000; -+ let pm1b_value = (u16::from(slp_typb_s5) << 10) | 0x2000; - -- let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") { -- Ok(aml_name) => aml_name, -- Err(error) => { -- log::error!("Could not build AmlName for \\_S5, {:?}", error); -- return; -- } -- }; -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ { -+ let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { -+ return Err(PowerTransitionError::InvalidPm1aControlBlock(self.pm1a_cnt_blk)); -+ }; - -- let s5 = match &aml_symbols.aml_context { -- Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) { -- Ok(s5) => s5, -- Err(error) => { -- log::error!("Cannot set S-state, missing \\_S5, {:?}", error); -- return; -+ log::warn!( -+ "Shutdown with ACPI PM1a_CNT outw(0x{:X}, 0x{:X})", -+ pm1a_port, -+ pm1a_value -+ ); -+ Pio::::new(pm1a_port).write(pm1a_value); -+ -+ if self.pm1b_cnt_blk != 0 { -+ match u16::try_from(self.pm1b_cnt_blk) { -+ Ok(pm1b_port) => { -+ log::warn!( -+ "Shutdown with ACPI PM1b_CNT outw(0x{:X}, 0x{:X})", -+ pm1b_port, -+ pm1b_value -+ ); -+ Pio::::new(pm1b_port).write(pm1b_value); -+ } -+ Err(_) => { -+ return Err(PowerTransitionError::InvalidPm1bControlBlock( -+ self.pm1b_cnt_blk, -+ )); -+ } - } -- }, -- None => { -- log::error!("Cannot set S-state, AML context not initialized"); -- return; - } -- }; - -- let package = match s5.deref() { -- acpi::aml::object::Object::Package(package) => package, -- _ => { -- log::error!("Cannot set S-state, \\_S5 is not a package"); -- return; -+ Ok(()) -+ } -+ -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ { -+ Err(PowerTransitionError::UnsupportedArchitecture { -+ pm1a_cnt_blk: self.pm1a_cnt_blk, -+ pm1b_cnt_blk: self.pm1b_cnt_blk, -+ }) -+ } -+ } -+ -+ pub fn acpi_reboot(&self) -> Result<(), PowerTransitionError> { -+ match self.reset_reg { -+ Some(reset_reg) => { -+ log::warn!( -+ "Reboot with ACPI reset register {:?} value {:#X}", -+ reset_reg, -+ self.reset_value -+ ); -+ reset_reg.write_u8(self.reset_value); -+ Ok(()) - } -- }; -+ None => { -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ { -+ const I8042_COMMAND_PORT: u16 = 0x64; -+ const I8042_PULSE_RESET: u8 = 0xFE; -+ -+ log::warn!( -+ "Reboot with keyboard-controller fallback outb(0x{:X}, 0x{:X})", -+ I8042_COMMAND_PORT, -+ I8042_PULSE_RESET -+ ); -+ Pio::::new(I8042_COMMAND_PORT).write(I8042_PULSE_RESET); -+ Ok(()) -+ } - -- let slp_typa = match package[0].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typa is not an Integer"); -- return; -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ { -+ Err(PowerTransitionError::MissingResetRegister) -+ } - } -- }; -- let slp_typb = match package[1].deref() { -- acpi::aml::object::Object::Integer(i) => i.to_owned(), -- _ => { -- log::error!("typb is not an Integer"); -- return; -+ } -+ } -+ -+ /// Set Power State -+ /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf -+ /// - search for PM1a -+ /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details -+ pub fn set_global_s_state(&self, state: u8) -> Result<(), GlobalSleepStateError> { -+ let target = SleepTarget::try_from(state) -+ .map_err(|_| GlobalSleepStateError::UnknownSleepState(state))?; -+ if !target.is_soft_off() { -+ return Err(GlobalSleepStateError::UnsupportedTarget(target)); -+ } -+ -+ if self.fadt().is_none() { -+ return Err(GlobalSleepStateError::MissingFadt); -+ } -+ -+ let cached_s5 = *self.slp_s5_values.read(); -+ let (slp_typa, slp_typb) = match self.sleep_values_for_target(SleepTarget::S5) { -+ Ok(values) => values, -+ Err(error) => match cached_s5 { -+ Some(values) => { -+ log::warn!( -+ "Using cached {} values after refresh failure: {error}", -+ SleepTarget::S5.aml_method_name() -+ ); -+ values -+ } -+ None => { -+ return Err(GlobalSleepStateError::MissingSleepValues { -+ target: SleepTarget::S5, -+ source: error, -+ }) -+ } - } - }; - -- log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); -- val |= slp_typa as u16; -+ self.acpi_shutdown(slp_typa, slp_typb) -+ .map_err(GlobalSleepStateError::PowerTransitionFailed)?; - -- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -- { -- log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); -- Pio::::new(port).write(val); -- } -+ Err(GlobalSleepStateError::TransitionDidNotComplete(target)) -+ } -+} - -- // TODO: Handle SLP_TYPb -+#[derive(Debug, Error)] -+pub enum SleepStateValuesError { -+ #[error("failed to build AML name for sleep-state method: {0:?}")] -+ InvalidName(AmlError), - -- #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -- { -- log::error!( -- "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture", -- port, -- val -- ); -- } -+ #[error("failed to evaluate sleep-state package: {0}")] -+ Evaluation(#[from] AmlEvalError), - -- loop { -- core::hint::spin_loop(); -- } -+ #[error("sleep-state method returned a non-package AML value")] -+ NonPackageValue, -+ -+ #[error("sleep-state package did not contain two integer sleep-type entries")] -+ InvalidPackageShape, -+ -+ #[error("sleep-state values did not fit in u8")] -+ ValueOutOfRange, -+} -+ -+#[derive(Debug, Error)] -+pub enum PowerTransitionError { -+ #[error("PM1a control block address is invalid: {0:#X}")] -+ InvalidPm1aControlBlock(u64), -+ -+ #[error("PM1b control block address is invalid: {0:#X}")] -+ InvalidPm1bControlBlock(u64), -+ -+ #[error( -+ "cannot issue ACPI PM1 control writes on this architecture (PM1a={pm1a_cnt_blk:#X}, PM1b={pm1b_cnt_blk:#X})" -+ )] -+ UnsupportedArchitecture { -+ pm1a_cnt_blk: u64, -+ pm1b_cnt_blk: u64, -+ }, -+ -+ #[error("cannot reboot with ACPI: no reset register present in FADT")] -+ MissingResetRegister, -+} -+ -+#[derive(Debug, Error)] -+pub enum GlobalSleepStateError { -+ #[error("unknown global sleep state S{0}")] -+ UnknownSleepState(u8), -+ -+ #[error("sleep target {:?} remains groundwork-only until full sleep lifecycle support lands", .0)] -+ UnsupportedTarget(SleepTarget), -+ -+ #[error("cannot set global S-state due to missing FADT")] -+ MissingFadt, -+ -+ #[error("failed to derive usable {} values: {source}", target.aml_method_name())] -+ MissingSleepValues { -+ target: SleepTarget, -+ source: SleepStateValuesError, -+ }, -+ -+ #[error("ACPI power transition failed: {0}")] -+ PowerTransitionFailed(#[from] PowerTransitionError), -+ -+ #[error("ACPI transition to {:?} returned without completing", .0)] -+ TransitionDidNotComplete(SleepTarget), -+} -+ -+fn parse_sleep_package( -+ _target: SleepTarget, -+ value: AmlSerdeValue, -+) -> Result<(u8, u8), SleepStateValuesError> { -+ match value { -+ AmlSerdeValue::Package { contents } => match (contents.first(), contents.get(1)) { -+ (Some(AmlSerdeValue::Integer(slp_typa)), Some(AmlSerdeValue::Integer(slp_typb))) => { -+ match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) { -+ (Ok(slp_typa_s5), Ok(slp_typb_s5)) => Ok((slp_typa_s5, slp_typb_s5)), -+ _ => Err(SleepStateValuesError::ValueOutOfRange), -+ } -+ } -+ _ => Err(SleepStateValuesError::InvalidPackageShape), -+ }, -+ _ => Err(SleepStateValuesError::NonPackageValue), - } - } - -@@ -707,7 +1829,7 @@ unsafe impl plain::Plain for FadtStruct {} - - #[repr(C, packed)] - #[derive(Clone, Copy, Debug, Default)] --pub struct GenericAddressStructure { -+pub struct GenericAddress { - address_space: u8, - bit_width: u8, - bit_offset: u8, -@@ -715,11 +1837,68 @@ pub struct GenericAddressStructure { - address: u64, - } - -+impl GenericAddress { -+ pub fn is_empty(&self) -> bool { -+ self.address == 0 -+ } -+ -+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -+ pub fn write_u8(&self, value: u8) { -+ let address = self.address; -+ match self.address_space { -+ 0 => { -+ let Ok(address) = usize::try_from(address) else { -+ log::error!("Reset register physical address is invalid: {:#X}", address); -+ return; -+ }; -+ let page = address / PAGE_SIZE * PAGE_SIZE; -+ let offset = address % PAGE_SIZE; -+ let virt = unsafe { -+ common::physmap(page, PAGE_SIZE, common::Prot::RW, common::MemoryType::default()) -+ }; -+ -+ match virt { -+ Ok(virt) => unsafe { -+ (virt as *mut u8).add(offset).write_volatile(value); -+ let _ = libredox::call::munmap(virt, PAGE_SIZE); -+ }, -+ Err(error) => { -+ log::error!("Failed to map ACPI reset register: {}", error); -+ } -+ } -+ } -+ 1 => match u16::try_from(address) { -+ Ok(port) => { -+ Pio::::new(port).write(value); -+ } -+ Err(_) => { -+ log::error!("Reset register I/O port is invalid: {:#X}", address); -+ } -+ }, -+ address_space => { -+ log::warn!( -+ "Unsupported ACPI reset register address space {} for {:?}", -+ address_space, -+ self -+ ); -+ } -+ } -+ } -+ -+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] -+ pub fn write_u8(&self, _value: u8) { -+ log::error!( -+ "Cannot access ACPI reset register {:?} on this architecture", -+ self -+ ); -+ } -+} -+ - #[repr(C, packed)] - #[derive(Clone, Copy, Debug)] - pub struct FadtAcpi2Struct { - // 12 byte structure; see below for details -- pub reset_reg: GenericAddressStructure, -+ pub reset_reg: GenericAddress, - - pub reset_value: u8, - reserved3: [u8; 3], -@@ -728,14 +1907,14 @@ pub struct FadtAcpi2Struct { - pub x_firmware_control: u64, - pub x_dsdt: u64, - -- pub x_pm1a_event_block: GenericAddressStructure, -- pub x_pm1b_event_block: GenericAddressStructure, -- pub x_pm1a_control_block: GenericAddressStructure, -- pub x_pm1b_control_block: GenericAddressStructure, -- pub x_pm2_control_block: GenericAddressStructure, -- pub x_pm_timer_block: GenericAddressStructure, -- pub x_gpe0_block: GenericAddressStructure, -- pub x_gpe1_block: GenericAddressStructure, -+ pub x_pm1a_event_block: GenericAddress, -+ pub x_pm1b_event_block: GenericAddress, -+ pub x_pm1a_control_block: GenericAddress, -+ pub x_pm1b_control_block: GenericAddress, -+ pub x_pm2_control_block: GenericAddress, -+ pub x_pm_timer_block: GenericAddress, -+ pub x_gpe0_block: GenericAddress, -+ pub x_gpe1_block: GenericAddress, - } - unsafe impl plain::Plain for FadtAcpi2Struct {} - -@@ -774,9 +1953,10 @@ impl Fadt { - } - - pub fn init(context: &mut AcpiContext) { -- let fadt_sdt = context -- .take_single_sdt(*b"FACP") -- .expect("expected ACPI to always have a FADT"); -+ let Some(fadt_sdt) = context.take_single_sdt(*b"FACP") else { -+ log::error!("Failed to find FADT"); -+ return; -+ }; - - let fadt = match Fadt::new(fadt_sdt) { - Some(fadt) => fadt, -@@ -793,9 +1973,25 @@ impl Fadt { - None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), - }; - -- log::debug!("FACP at {:X}", { dsdt_ptr }); -+ let pm1a_evt_blk = u64::from(fadt.pm1a_event_block); -+ let pm1b_evt_blk = u64::from(fadt.pm1b_event_block); -+ let pm1a_cnt_blk = u64::from(fadt.pm1a_control_block); -+ let pm1b_cnt_blk = u64::from(fadt.pm1b_control_block); -+ let (reset_reg, reset_value) = match fadt.acpi_2_struct() { -+ Some(fadt2) if !fadt2.reset_reg.is_empty() => (Some(fadt2.reset_reg), fadt2.reset_value), -+ _ => (None, 0), -+ }; - -- let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { -+ log::debug!("FACP at {:X}", { dsdt_ptr }); -+ log::debug!( -+ "FADT power blocks: PM1a_EVT={:#X}, PM1b_EVT={:#X}, PM1a_CNT={:#X}, PM1b_CNT={:#X}", -+ pm1a_evt_blk, -+ pm1b_evt_blk, -+ pm1a_cnt_blk, -+ pm1b_cnt_blk -+ ); -+ -+ let dsdt_sdt = match Sdt::load_from_physical(dsdt_ptr) { - Ok(dsdt) => dsdt, - Err(error) => { - log::error!("Failed to load DSDT: {}", error); -@@ -805,8 +2001,20 @@ impl Fadt { - - context.fadt = Some(fadt.clone()); - context.dsdt = Some(Dsdt(dsdt_sdt.clone())); -+ context.pm1a_cnt_blk = pm1a_cnt_blk; -+ context.pm1b_cnt_blk = pm1b_cnt_blk; -+ context.reset_reg = reset_reg; -+ context.reset_value = reset_value; - - context.tables.push(dsdt_sdt); -+ -+ if context.pci_ready() { -+ if let Err(error) = context.refresh_s5_values() { -+ log::warn!("Failed to evaluate \\_S5 during FADT init: {error}"); -+ } -+ } else { -+ log::debug!("Deferring \\_S5 evaluation until PCI registration"); -+ } - } - } - - -diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs -index c42b379a..f4dff276 100644 ---- a/drivers/acpid/src/acpi/dmar/mod.rs -+++ b/drivers/acpid/src/acpi/dmar/mod.rs -@@ -474,10 +474,13 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> { - let len_bytes = <[u8; 2]>::try_from(type_bytes) - .expect("expected a 2-byte slice to be convertible to [u8; 2]"); - -- let ty = u16::from_ne_bytes(type_bytes); -- let len = u16::from_ne_bytes(len_bytes); -+ let len = u16::from_ne_bytes(len_bytes) as usize; - -- let len = usize::try_from(len).expect("expected u16 to fit within usize"); -+ if len < 4 { -+ return None; -+ } -+ -+ let ty = u16::from_ne_bytes(type_bytes); - - if len > remainder.len() { - log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); - -diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs -index 2bdd667b..69b8c48b 100644 ---- a/drivers/acpid/src/aml_physmem.rs -+++ b/drivers/acpid/src/aml_physmem.rs -@@ -6,7 +6,10 @@ use rustc_hash::FxHashMap; - use std::fmt::LowerHex; - use std::mem::size_of; - use std::ptr::NonNull; --use std::sync::{Arc, Mutex}; -+use std::sync::atomic::{AtomicU32, Ordering}; -+use std::sync::{Arc, Condvar, Mutex}; -+use std::thread::ThreadId; -+use std::time::{Duration, Instant}; - use syscall::PAGE_SIZE; - - const PAGE_MASK: usize = !(PAGE_SIZE - 1); -@@ -141,6 +144,20 @@ impl AmlPageCache { - pub struct AmlPhysMemHandler { - page_cache: Arc>, - pci_fd: Arc>, -+ aml_mutexes: Arc>>>, -+ next_mutex_handle: Arc, -+} -+ -+#[derive(Debug, Default)] -+struct AmlMutexState { -+ owner: Option, -+ depth: u32, -+} -+ -+#[derive(Debug, Default)] -+struct AmlMutex { -+ state: Mutex, -+ condvar: Condvar, - } - - /// Read from a physical address. -@@ -156,6 +173,30 @@ impl AmlPhysMemHandler { - Self { - page_cache, - pci_fd: Arc::new(pci_fd), -+ aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())), -+ next_mutex_handle: Arc::new(AtomicU32::new(1)), -+ } -+ } -+ -+ fn aml_mutex(&self, handle: Handle) -> Option> { -+ self.aml_mutexes -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()) -+ .get(&handle.0) -+ .cloned() -+ } -+ -+ fn read_phys_or_fault(&self, address: usize) -> T -+ where -+ T: PrimInt + LowerHex, -+ { -+ let mut page_cache = self -+ .page_cache -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ match page_cache.read_from_phys::(address) { -+ Ok(value) => value, -+ Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error), - } - } - -@@ -240,43 +281,19 @@ impl acpi::Handler for AmlPhysMemHandler { - - fn read_u8(&self, address: usize) -> u8 { - log::trace!("read u8 {:X}", address); -- if let Ok(mut page_cache) = self.page_cache.lock() { -- if let Ok(value) = page_cache.read_from_phys::(address) { -- return value; -- } -- } -- log::error!("failed to read u8 {:#x}", address); -- 0 -+ self.read_phys_or_fault::(address) - } - fn read_u16(&self, address: usize) -> u16 { - log::trace!("read u16 {:X}", address); -- if let Ok(mut page_cache) = self.page_cache.lock() { -- if let Ok(value) = page_cache.read_from_phys::(address) { -- return value; -- } -- } -- log::error!("failed to read u16 {:#x}", address); -- 0 -+ self.read_phys_or_fault::(address) - } - fn read_u32(&self, address: usize) -> u32 { - log::trace!("read u32 {:X}", address); -- if let Ok(mut page_cache) = self.page_cache.lock() { -- if let Ok(value) = page_cache.read_from_phys::(address) { -- return value; -- } -- } -- log::error!("failed to read u32 {:#x}", address); -- 0 -+ self.read_phys_or_fault::(address) - } - fn read_u64(&self, address: usize) -> u64 { - log::trace!("read u64 {:X}", address); -- if let Ok(mut page_cache) = self.page_cache.lock() { -- if let Ok(value) = page_cache.read_from_phys::(address) { -- return value; -- } -- } -- log::error!("failed to read u64 {:#x}", address); -- 0 -+ self.read_phys_or_fault::(address) - } - - fn write_u8(&self, address: usize, value: u8) { -@@ -415,16 +432,102 @@ impl acpi::Handler for AmlPhysMemHandler { - } - - fn create_mutex(&self) -> Handle { -- log::debug!("TODO: Handler::create_mutex"); -- Handle(0) -+ let handle = self.next_mutex_handle.fetch_add(1, Ordering::Relaxed); -+ self.aml_mutexes -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()) -+ .insert(handle, Arc::new(AmlMutex::default())); -+ log::trace!("created AML mutex handle {handle}"); -+ Handle(handle) - } - - fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> { -- log::debug!("TODO: Handler::acquire"); -- Ok(()) -+ let Some(aml_mutex) = self.aml_mutex(mutex) else { -+ log::error!("attempted to acquire unknown AML mutex handle {}", mutex.0); -+ return Err(AmlError::MutexAcquireTimeout); -+ }; -+ -+ let current_thread = std::thread::current().id(); -+ let deadline = (timeout != 0xffff).then(|| Instant::now() + Duration::from_millis(timeout.into())); -+ -+ let mut state = aml_mutex -+ .state -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ -+ loop { -+ match state.owner { -+ None => { -+ state.owner = Some(current_thread); -+ state.depth = 1; -+ return Ok(()); -+ } -+ Some(owner) if owner == current_thread => { -+ state.depth = state.depth.saturating_add(1); -+ return Ok(()); -+ } -+ Some(_) if timeout == 0 => return Err(AmlError::MutexAcquireTimeout), -+ Some(_) if timeout == 0xffff => { -+ state = aml_mutex -+ .condvar -+ .wait(state) -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ } -+ Some(_) => { -+ let Some(deadline) = deadline else { -+ return Err(AmlError::MutexAcquireTimeout); -+ }; -+ let now = Instant::now(); -+ if now >= deadline { -+ return Err(AmlError::MutexAcquireTimeout); -+ } -+ -+ let remaining = deadline.saturating_duration_since(now); -+ let (next_state, wait_result) = aml_mutex -+ .condvar -+ .wait_timeout(state, remaining) -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ state = next_state; -+ -+ if wait_result.timed_out() && state.owner != Some(current_thread) { -+ return Err(AmlError::MutexAcquireTimeout); -+ } -+ } -+ } -+ } - } - - fn release(&self, mutex: Handle) { -- log::debug!("TODO: Handler::release"); -+ let Some(aml_mutex) = self.aml_mutex(mutex) else { -+ log::error!("attempted to release unknown AML mutex handle {}", mutex.0); -+ return; -+ }; -+ -+ let current_thread = std::thread::current().id(); -+ let mut state = aml_mutex -+ .state -+ .lock() -+ .unwrap_or_else(|poisoned| poisoned.into_inner()); -+ -+ match state.owner { -+ Some(owner) if owner == current_thread => { -+ if state.depth > 1 { -+ state.depth -= 1; -+ } else { -+ state.owner = None; -+ state.depth = 0; -+ aml_mutex.condvar.notify_one(); -+ } -+ } -+ Some(_) => { -+ log::warn!( -+ "ignoring AML mutex release for handle {} from non-owner thread", -+ mutex.0 -+ ); -+ } -+ None => { -+ log::warn!("ignoring AML mutex release for unlocked handle {}", mutex.0); -+ } -+ } - } - } - -diff --git a/drivers/acpid/src/ec.rs b/drivers/acpid/src/ec.rs -index c322790a..99842586 100644 ---- a/drivers/acpid/src/ec.rs -+++ b/drivers/acpid/src/ec.rs -@@ -1,3 +1,4 @@ -+use std::convert::TryFrom; - use std::time::Duration; - - use acpi::aml::{ -@@ -30,6 +31,28 @@ const BURST_ACK: u8 = 0x90; - - pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10); - -+#[derive(Debug, Clone, Copy)] -+enum EcError { -+ Timeout, -+ OffsetOutOfRange, -+} -+ -+impl EcError { -+ fn as_aml_error(self) -> AmlError { -+ match self { -+ EcError::Timeout | EcError::OffsetOutOfRange => { -+ AmlError::NoHandlerForRegionAccess(RegionSpace::EmbeddedControl) -+ } -+ } -+ } -+} -+ -+impl From for AmlError { -+ fn from(value: EcError) -> Self { -+ value.as_aml_error() -+ } -+} -+ - #[repr(transparent)] - pub struct ScBits(u8); - #[allow(dead_code)] -@@ -90,28 +113,33 @@ impl Ec { - Pio::::new(self.data).write(value); - } - #[inline] -- fn wait_for_write_ready(&self) -> Option<()> { -+ fn wait_for_write_ready(&self) -> Result<(), EcError> { - let timeout = Timeout::new(self.timeout); - loop { - if !self.read_reg_sc().ibf() { -- return Some(()); -+ return Ok(()); - } -- timeout.run().ok()?; -+ timeout.run().map_err(|_| EcError::Timeout)?; - } - } - #[inline] -- fn wait_for_read_ready(&self) -> Option<()> { -+ fn wait_for_read_ready(&self) -> Result<(), EcError> { - let timeout = Timeout::new(self.timeout); - loop { - if self.read_reg_sc().obf() { -- return Some(()); -+ return Ok(()); - } -- timeout.run().ok()?; -+ timeout.run().map_err(|_| EcError::Timeout)?; - } - } - -+ #[inline] -+ fn checked_address(offset: usize, byte_index: usize) -> Result { -+ u8::try_from(offset + byte_index).map_err(|_| EcError::OffsetOutOfRange) -+ } -+ - //https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html -- pub fn read(&self, address: u8) -> Option { -+ fn read(&self, address: u8) -> Result { - trace!("ec read addr: {:x}", address); - self.wait_for_write_ready()?; - -@@ -125,9 +153,9 @@ impl Ec { - - let val = self.read_reg_data(); - trace!("got: {:x}", val); -- Some(val) -+ Ok(val) - } -- pub fn write(&self, address: u8, value: u8) -> Option<()> { -+ fn write(&self, address: u8, value: u8) -> Result<(), EcError> { - trace!("ec write addr: {:x}, with: {:x}", address, value); - self.wait_for_write_ready()?; - -@@ -141,7 +169,22 @@ impl Ec { - - self.write_reg_data(value); - trace!("done"); -- Some(()) -+ Ok(()) -+ } -+ -+ fn read_bytes(&self, offset: usize) -> Result<[u8; N], EcError> { -+ let mut bytes = [0u8; N]; -+ for (index, byte) in bytes.iter_mut().enumerate() { -+ *byte = self.read(Self::checked_address(offset, index)?)?; -+ } -+ Ok(bytes) -+ } -+ -+ fn write_bytes(&self, offset: usize, bytes: [u8; N]) -> Result<(), EcError> { -+ for (index, byte) in IntoIterator::into_iter(bytes).enumerate() { -+ self.write(Self::checked_address(offset, index)?, byte)?; -+ } -+ Ok(()) - } - // disabled if not met - // First Access - 400 microseconds -@@ -151,11 +194,11 @@ impl Ec { - #[allow(dead_code)] - fn enable_burst(&self) -> bool { - trace!("ec burst enable"); -- self.wait_for_write_ready(); -+ let _ = self.wait_for_write_ready(); - - self.write_reg_sc(BE_EC); - -- self.wait_for_read_ready(); -+ let _ = self.wait_for_read_ready(); - - let res = self.read_reg_data() == BURST_ACK; - trace!("success: {}", res); -@@ -164,7 +207,7 @@ impl Ec { - #[allow(dead_code)] - fn disable_burst(&self) { - trace!("ec burst disable"); -- self.wait_for_write_ready(); -+ let _ = self.wait_for_write_ready(); - self.write_reg_sc(BD_EC); - trace!("done"); - } -@@ -172,11 +215,11 @@ impl Ec { - #[allow(dead_code)] - fn queue_query(&mut self) -> u8 { - trace!("ec query"); -- self.wait_for_write_ready(); -+ let _ = self.wait_for_write_ready(); - - self.write_reg_sc(QR_EC); - -- self.wait_for_read_ready(); -+ let _ = self.wait_for_read_ready(); - - let val = self.read_reg_data(); - trace!("got: {}", val); -@@ -190,7 +233,10 @@ impl RegionHandler for Ec { - offset: usize, - ) -> Result { - assert_eq!(region.space, RegionSpace::EmbeddedControl); -- self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type -+ self.read(Self::checked_address(offset, 0)?).map_err(|error| { -+ warn!("EC read_u8 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - fn write_u8( - &self, -@@ -199,58 +245,73 @@ impl RegionHandler for Ec { - value: u8, - ) -> Result<(), acpi::aml::AmlError> { - assert_eq!(region.space, RegionSpace::EmbeddedControl); -- self.write(offset as u8, value) -- .ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type -- } -- fn read_u16(&self, _region: &OpRegion, _offset: usize) -> Result { -- warn!("Got u16 EC read from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -- } -- fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result { -- warn!("Got u32 EC read from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -- } -- fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result { -- warn!("Got u64 EC read from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -+ self.write(Self::checked_address(offset, 0)?, value) -+ .map_err(|error| { -+ warn!("EC write_u8 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) -+ } -+ fn read_u16(&self, region: &OpRegion, offset: usize) -> Result { -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.read_bytes::<2>(offset) -+ .map(u16::from_le_bytes) -+ .map_err(|error| { -+ warn!("EC read_u16 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) -+ } -+ fn read_u32(&self, region: &OpRegion, offset: usize) -> Result { -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.read_bytes::<4>(offset) -+ .map(u32::from_le_bytes) -+ .map_err(|error| { -+ warn!("EC read_u32 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) -+ } -+ fn read_u64(&self, region: &OpRegion, offset: usize) -> Result { -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.read_bytes::<8>(offset) -+ .map(u64::from_le_bytes) -+ .map_err(|error| { -+ warn!("EC read_u64 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - fn write_u16( - &self, -- _region: &OpRegion, -- _offset: usize, -- _value: u16, -+ region: &OpRegion, -+ offset: usize, -+ value: u16, - ) -> Result<(), acpi::aml::AmlError> { -- warn!("Got u16 EC write from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { -+ warn!("EC write_u16 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - fn write_u32( - &self, -- _region: &OpRegion, -- _offset: usize, -- _value: u32, -+ region: &OpRegion, -+ offset: usize, -+ value: u32, - ) -> Result<(), acpi::aml::AmlError> { -- warn!("Got u32 EC write from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { -+ warn!("EC write_u32 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - fn write_u64( - &self, -- _region: &OpRegion, -- _offset: usize, -- _value: u64, -+ region: &OpRegion, -+ offset: usize, -+ value: u64, - ) -> Result<(), acpi::aml::AmlError> { -- warn!("Got u64 EC write from AML!"); -- Err(acpi::aml::AmlError::NoHandlerForRegionAccess( -- RegionSpace::EmbeddedControl, -- )) // TODO proper error type -+ assert_eq!(region.space, RegionSpace::EmbeddedControl); -+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| { -+ warn!("EC write_u64 failed at offset {offset:#x}: {error:?}"); -+ error.as_aml_error() -+ }) - } - } - -diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs -index 5a5040c3..4fe3b8d8 100644 ---- a/drivers/acpid/src/scheme.rs -+++ b/drivers/acpid/src/scheme.rs -@@ -2,7 +2,6 @@ use acpi::aml::namespace::AmlName; - use amlserde::aml_serde_name::to_aml_format; - use amlserde::AmlSerdeValue; - use core::str; --use libredox::Fd; - use parking_lot::RwLockReadGuard; - use redox_scheme::scheme::SchemeSync; - use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket}; -@@ -16,17 +15,22 @@ use syscall::FobtainFdFlags; - - use syscall::data::Stat; - use syscall::error::{Error, Result}; --use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; -+use syscall::error::{ -+ EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EOPNOTSUPP, -+}; - use syscall::flag::{MODE_DIR, MODE_FILE}; - use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK}; - use syscall::{EOVERFLOW, EPERM}; - --use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature}; -+use crate::acpi::{ -+ AcpiBattery, AcpiContext, AcpiPowerAdapter, AcpiPowerSnapshot, AmlSymbols, DmiInfo, -+ SdtSignature, -+}; -+use crate::resources::{decode_resource_template, ResourceDescriptor}; - - pub struct AcpiScheme<'acpi, 'sock> { - ctx: &'acpi AcpiContext, - handles: HandleMap>, -- pci_fd: Option, - socket: &'sock Socket, - } - -@@ -41,10 +45,204 @@ enum HandleKind<'a> { - Table(SdtSignature), - Symbols(RwLockReadGuard<'a, AmlSymbols>), - Symbol { name: String, description: String }, -+ ResourcesDir, -+ Resources(String), -+ Reboot, -+ DmiDir, -+ Dmi(String), -+ PowerDir, -+ PowerAdaptersDir, -+ PowerAdapterDir(String), -+ PowerBatteriesDir, -+ PowerBatteryDir(String), -+ PowerFile(String), - SchemeRoot, - RegisterPci, - } - -+const DMI_DIRECTORY_ENTRIES: &[&str] = &[ -+ "sys_vendor", -+ "board_vendor", -+ "board_name", -+ "board_version", -+ "product_name", -+ "product_version", -+ "bios_version", -+ "match_all", -+]; -+ -+fn dmi_contents(dmi_info: Option<&DmiInfo>, name: &str) -> Option { -+ Some(match name { -+ "sys_vendor" => dmi_info -+ .and_then(|info| info.sys_vendor.clone()) -+ .unwrap_or_default(), -+ "board_vendor" => dmi_info -+ .and_then(|info| info.board_vendor.clone()) -+ .unwrap_or_default(), -+ "board_name" => dmi_info -+ .and_then(|info| info.board_name.clone()) -+ .unwrap_or_default(), -+ "board_version" => dmi_info -+ .and_then(|info| info.board_version.clone()) -+ .unwrap_or_default(), -+ "product_name" => dmi_info -+ .and_then(|info| info.product_name.clone()) -+ .unwrap_or_default(), -+ "product_version" => dmi_info -+ .and_then(|info| info.product_version.clone()) -+ .unwrap_or_default(), -+ "bios_version" => dmi_info -+ .and_then(|info| info.bios_version.clone()) -+ .unwrap_or_default(), -+ "match_all" => dmi_info.map(DmiInfo::to_match_lines).unwrap_or_default(), -+ _ => return None, -+ }) -+} -+ -+fn power_bool_contents(value: bool) -> String { -+ if value { -+ String::from("1\n") -+ } else { -+ String::from("0\n") -+ } -+} -+ -+fn power_u64_contents(value: u64) -> String { -+ format!("{value}\n") -+} -+ -+fn power_f64_contents(value: f64) -> String { -+ format!("{value}\n") -+} -+ -+fn power_string_contents(value: &str) -> String { -+ format!("{value}\n") -+} -+ -+fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option { -+ Some(match name { -+ "path" => power_string_contents(&adapter.path), -+ "online" => power_bool_contents(adapter.online), -+ _ => return None, -+ }) -+} -+ -+fn power_adapter_entry_names() -> &'static [&'static str] { -+ &["path", "online"] -+} -+ -+fn power_battery_file_contents(battery: &AcpiBattery, name: &str) -> Option { -+ Some(match name { -+ "path" => power_string_contents(&battery.path), -+ "state" => power_u64_contents(battery.state), -+ "present_rate" => power_u64_contents(battery.present_rate?), -+ "remaining_capacity" => power_u64_contents(battery.remaining_capacity?), -+ "present_voltage" => power_u64_contents(battery.present_voltage?), -+ "power_unit" => power_string_contents(battery.power_unit.as_deref()?), -+ "design_capacity" => power_u64_contents(battery.design_capacity?), -+ "last_full_capacity" => power_u64_contents(battery.last_full_capacity?), -+ "design_voltage" => power_u64_contents(battery.design_voltage?), -+ "technology" => power_string_contents(battery.technology.as_deref()?), -+ "model" => power_string_contents(battery.model.as_deref()?), -+ "serial" => power_string_contents(battery.serial.as_deref()?), -+ "battery_type" => power_string_contents(battery.battery_type.as_deref()?), -+ "oem_info" => power_string_contents(battery.oem_info.as_deref()?), -+ "percentage" => power_f64_contents(battery.percentage?), -+ _ => return None, -+ }) -+} -+ -+fn power_battery_entry_names(battery: &AcpiBattery) -> Vec<&'static str> { -+ let mut names = vec!["path", "state"]; -+ -+ if battery.present_rate.is_some() { -+ names.push("present_rate"); -+ } -+ if battery.remaining_capacity.is_some() { -+ names.push("remaining_capacity"); -+ } -+ if battery.present_voltage.is_some() { -+ names.push("present_voltage"); -+ } -+ if battery.power_unit.is_some() { -+ names.push("power_unit"); -+ } -+ if battery.design_capacity.is_some() { -+ names.push("design_capacity"); -+ } -+ if battery.last_full_capacity.is_some() { -+ names.push("last_full_capacity"); -+ } -+ if battery.design_voltage.is_some() { -+ names.push("design_voltage"); -+ } -+ if battery.technology.is_some() { -+ names.push("technology"); -+ } -+ if battery.model.is_some() { -+ names.push("model"); -+ } -+ if battery.serial.is_some() { -+ names.push("serial"); -+ } -+ if battery.battery_type.is_some() { -+ names.push("battery_type"); -+ } -+ if battery.oem_info.is_some() { -+ names.push("oem_info"); -+ } -+ if battery.percentage.is_some() { -+ names.push("percentage"); -+ } -+ -+ names -+} -+ -+fn top_level_entries(power_available: bool) -> Vec<(&'static str, DirentKind)> { -+ let mut entries = vec![ -+ ("tables", DirentKind::Directory), -+ ("symbols", DirentKind::Directory), -+ ("resources", DirentKind::Directory), -+ ("dmi", DirentKind::Directory), -+ ("reboot", DirentKind::Regular), -+ ]; -+ if power_available { -+ entries.push(("power", DirentKind::Directory)); -+ } -+ entries -+} -+ -+fn resource_symbol_path(path: &str) -> Option { -+ let normalized = path.trim_matches('/').trim_start_matches('\\'); -+ if normalized.is_empty() { -+ return None; -+ } -+ -+ let normalized = normalized.replace('/', "."); -+ if normalized.is_empty() { -+ None -+ } else { -+ Some(format!("{normalized}._CRS")) -+ } -+} -+ -+fn resource_entry_name(symbol: &str) -> Option { -+ symbol -+ .strip_suffix("._CRS") -+ .map(str::to_string) -+ .filter(|path| !path.is_empty()) -+} -+ -+fn resource_dir_entries<'a>(symbols: impl IntoIterator) -> Vec { -+ let mut entries = symbols -+ .into_iter() -+ .filter_map(resource_entry_name) -+ .collect::>(); -+ entries.sort_unstable(); -+ entries.dedup(); -+ entries -+} -+ - impl HandleKind<'_> { - fn is_dir(&self) -> bool { - match self { -@@ -53,6 +251,17 @@ impl HandleKind<'_> { - Self::Table(_) => false, - Self::Symbols(_) => true, - Self::Symbol { .. } => false, -+ Self::ResourcesDir => true, -+ Self::Resources(_) => false, -+ Self::Reboot => false, -+ Self::DmiDir => true, -+ Self::Dmi(_) => false, -+ Self::PowerDir => true, -+ Self::PowerAdaptersDir => true, -+ Self::PowerAdapterDir(_) => true, -+ Self::PowerBatteriesDir => true, -+ Self::PowerBatteryDir(_) => true, -+ Self::PowerFile(_) => false, - Self::SchemeRoot => false, - Self::RegisterPci => false, - } -@@ -65,8 +274,21 @@ impl HandleKind<'_> { - .ok_or(Error::new(EBADFD))? - .length(), - Self::Symbol { description, .. } => description.len(), -+ Self::Resources(contents) => contents.len(), -+ Self::Reboot => 0, -+ Self::Dmi(contents) => contents.len(), -+ Self::PowerFile(contents) => contents.len(), - // Directories -- Self::TopLevel | Self::Symbols(_) | Self::Tables => 0, -+ Self::TopLevel -+ | Self::Symbols(_) -+ | Self::ResourcesDir -+ | Self::Tables -+ | Self::DmiDir -+ | Self::PowerDir -+ | Self::PowerAdaptersDir -+ | Self::PowerAdapterDir(_) -+ | Self::PowerBatteriesDir -+ | Self::PowerBatteryDir(_) => 0, - Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)), - }) - } -@@ -77,10 +299,163 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { - Self { - ctx, - handles: HandleMap::new(), -- pci_fd: None, - socket, - } - } -+ -+ fn power_snapshot(&self) -> Result { -+ self.ctx.power_snapshot().map_err(|error| match error { -+ crate::acpi::AmlEvalError::NotInitialized => Error::new(EAGAIN), -+ crate::acpi::AmlEvalError::Unsupported(message) => { -+ log::warn!("ACPI power surface unavailable: {message}"); -+ Error::new(EOPNOTSUPP) -+ } -+ other => { -+ log::warn!("Failed to build ACPI power snapshot: {:?}", other); -+ Error::new(EIO) -+ } -+ }) -+ } -+ -+ fn power_available(&self) -> bool { -+ matches!(self.ctx.power_snapshot(), Ok(_)) -+ } -+ -+ fn resources_handle(&self, path: &str) -> Result> { -+ if !self.ctx.pci_ready() { -+ let display_path = if path.is_empty() { "resources" } else { path }; -+ log::warn!( -+ "Deferring ACPI resource lookup for {display_path} until PCI registration is ready" -+ ); -+ return Err(Error::new(EAGAIN)); -+ } -+ -+ let normalized = path.trim_matches('/'); -+ if normalized.is_empty() { -+ return Ok(HandleKind::ResourcesDir); -+ } -+ -+ let symbol_path = resource_symbol_path(normalized).ok_or(Error::new(ENOENT))?; -+ if self.ctx.aml_lookup(&symbol_path).is_none() { -+ return Err(Error::new(ENOENT)); -+ } -+ -+ let aml_name = -+ AmlName::from_str(&format!("\\{symbol_path}")).map_err(|_| Error::new(ENOENT))?; -+ let buffer = match self.ctx.aml_eval(aml_name, Vec::new()) { -+ Ok(AmlSerdeValue::Buffer(bytes)) => bytes, -+ Ok(other) => { -+ log::debug!( -+ "Skipping ACPI resources for {normalized} due to unexpected _CRS value: {:?}", -+ other -+ ); -+ return Err(Error::new(ENOENT)); -+ } -+ Err(error) => { -+ log::debug!( -+ "Failed to evaluate ACPI resources for {symbol_path}: {:?}", -+ error -+ ); -+ return Err(Error::new(ENOENT)); -+ } -+ }; -+ -+ let descriptors: Vec = -+ decode_resource_template(&buffer).map_err(|error| { -+ log::warn!("Failed to decode ACPI _CRS for {symbol_path}: {error}"); -+ Error::new(EIO) -+ })?; -+ let serialized = ron::ser::to_string(&descriptors).map_err(|error| { -+ log::warn!("Failed to serialize decoded ACPI resources for {symbol_path}: {error}"); -+ Error::new(EIO) -+ })?; -+ -+ Ok(HandleKind::Resources(serialized)) -+ } -+ -+ fn power_handle(&self, path: &str) -> Result> { -+ let normalized = path.trim_matches('/'); -+ self.power_snapshot()?; -+ -+ if normalized.is_empty() { -+ return Ok(HandleKind::PowerDir); -+ } -+ if normalized == "on_battery" { -+ return Ok(HandleKind::PowerFile(power_bool_contents( -+ self.power_snapshot()?.on_battery(), -+ ))); -+ } -+ if normalized == "adapters" { -+ return Ok(HandleKind::PowerAdaptersDir); -+ } -+ if let Some(rest) = normalized.strip_prefix("adapters/") { -+ return self.power_adapter_handle(rest); -+ } -+ if normalized == "batteries" { -+ return Ok(HandleKind::PowerBatteriesDir); -+ } -+ if let Some(rest) = normalized.strip_prefix("batteries/") { -+ return self.power_battery_handle(rest); -+ } -+ -+ Err(Error::new(ENOENT)) -+ } -+ -+ fn power_adapter_handle(&self, path: &str) -> Result> { -+ let normalized = path.trim_matches('/'); -+ if normalized.is_empty() { -+ return Ok(HandleKind::PowerAdaptersDir); -+ } -+ -+ let mut parts = normalized.split('/'); -+ let adapter_id = parts.next().ok_or(Error::new(ENOENT))?; -+ let field = parts.next(); -+ if parts.next().is_some() { -+ return Err(Error::new(ENOENT)); -+ } -+ -+ let snapshot = self.power_snapshot()?; -+ let adapter = snapshot -+ .adapters -+ .iter() -+ .find(|adapter| adapter.id == adapter_id) -+ .ok_or(Error::new(ENOENT))?; -+ -+ match field { -+ None | Some("") => Ok(HandleKind::PowerAdapterDir(adapter.id.clone())), -+ Some(name) => Ok(HandleKind::PowerFile( -+ power_adapter_file_contents(adapter, name).ok_or(Error::new(ENOENT))?, -+ )), -+ } -+ } -+ -+ fn power_battery_handle(&self, path: &str) -> Result> { -+ let normalized = path.trim_matches('/'); -+ if normalized.is_empty() { -+ return Ok(HandleKind::PowerBatteriesDir); -+ } -+ -+ let mut parts = normalized.split('/'); -+ let battery_id = parts.next().ok_or(Error::new(ENOENT))?; -+ let field = parts.next(); -+ if parts.next().is_some() { -+ return Err(Error::new(ENOENT)); -+ } -+ -+ let snapshot = self.power_snapshot()?; -+ let battery = snapshot -+ .batteries -+ .iter() -+ .find(|battery| battery.id == battery_id) -+ .ok_or(Error::new(ENOENT))?; -+ -+ match field { -+ None | Some("") => Ok(HandleKind::PowerBatteryDir(battery.id.clone())), -+ Some(name) => Ok(HandleKind::PowerFile( -+ power_battery_file_contents(battery, name).ok_or(Error::new(ENOENT))?, -+ )), -+ } -+ } - } - - fn parse_hex_digit(hex: u8) -> Option { -@@ -182,49 +557,97 @@ impl SchemeSync for AcpiScheme<'_, '_> { - - let kind = match handle.kind { - HandleKind::SchemeRoot => { -- // TODO: arrayvec -- let components = { -- let mut v = arrayvec::ArrayVec::<&str, 3>::new(); -- let it = path.split('/'); -- for component in it.take(3) { -- v.push(component); -- } -- -- v -- }; -- -- match &*components { -- [""] => HandleKind::TopLevel, -- ["register_pci"] => HandleKind::RegisterPci, -- ["tables"] => HandleKind::Tables, -+ if path == "resources" || path == "resources/" { -+ self.resources_handle("")? -+ } else if let Some(rest) = path.strip_prefix("resources/") { -+ self.resources_handle(rest)? -+ } else { -+ // TODO: arrayvec -+ let components = { -+ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); -+ let it = path.split('/'); -+ for component in it.take(4) { -+ v.push(component); -+ } - -- ["tables", table] => { -- let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; -- HandleKind::Table(signature) -- } -+ v -+ }; -+ -+ match &*components { -+ [""] => HandleKind::TopLevel, -+ ["reboot"] => HandleKind::Reboot, -+ ["dmi"] => { -+ if flag_dir || flag_stat || path.ends_with('/') { -+ HandleKind::DmiDir -+ } else { -+ HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), "match_all") -+ .expect("match_all should always resolve"), -+ ) -+ } -+ } -+ ["dmi", ""] => HandleKind::DmiDir, -+ ["dmi", field] => HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?, -+ ), -+ ["power"] => self.power_handle("")?, -+ ["power", tail] => self.power_handle(tail)?, -+ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?, -+ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?, -+ ["register_pci"] => HandleKind::RegisterPci, -+ ["tables"] => HandleKind::Tables, -+ -+ ["tables", table] => { -+ let signature = -+ parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; -+ HandleKind::Table(signature) -+ } - -- ["symbols"] => { -- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) { -- HandleKind::Symbols(aml_symbols) -- } else { -- return Err(Error::new(EIO)); -+ ["symbols"] => { -+ if !self.ctx.pci_ready() { -+ log::warn!( -+ "Deferring AML symbol scan until PCI registration is ready" -+ ); -+ return Err(Error::new(EAGAIN)); -+ } -+ if let Ok(aml_symbols) = self.ctx.aml_symbols() { -+ HandleKind::Symbols(aml_symbols) -+ } else { -+ return Err(Error::new(EIO)); -+ } - } -- } - -- ["symbols", symbol] => { -- if let Some(description) = self.ctx.aml_lookup(symbol) { -- HandleKind::Symbol { -- name: (*symbol).to_owned(), -- description, -+ ["symbols", symbol] => { -+ if !self.ctx.pci_ready() { -+ log::warn!( -+ "Deferring AML symbol lookup for {symbol} until PCI registration is ready" -+ ); -+ return Err(Error::new(EAGAIN)); -+ } -+ if let Some(description) = self.ctx.aml_lookup(symbol) { -+ HandleKind::Symbol { -+ name: (*symbol).to_owned(), -+ description, -+ } -+ } else { -+ return Err(Error::new(ENOENT)); - } -- } else { -- return Err(Error::new(ENOENT)); - } -- } - -- _ => return Err(Error::new(ENOENT)), -+ _ => return Err(Error::new(ENOENT)), -+ } -+ } -+ } -+ HandleKind::DmiDir => { -+ if path.is_empty() { -+ HandleKind::DmiDir -+ } else { -+ HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), path).ok_or(Error::new(ENOENT))?, -+ ) - } - } -+ HandleKind::ResourcesDir => self.resources_handle(path)?, - HandleKind::Symbols(ref aml_symbols) => { - if let Some(description) = aml_symbols.lookup(path) { - HandleKind::Symbol { -@@ -235,6 +658,23 @@ impl SchemeSync for AcpiScheme<'_, '_> { - return Err(Error::new(ENOENT)); - } - } -+ HandleKind::PowerDir => self.power_handle(path)?, -+ HandleKind::PowerAdaptersDir => self.power_adapter_handle(path)?, -+ HandleKind::PowerAdapterDir(ref adapter_id) => { -+ if path.is_empty() { -+ HandleKind::PowerAdapterDir(adapter_id.clone()) -+ } else { -+ self.power_adapter_handle(&format!("{adapter_id}/{path}"))? -+ } -+ } -+ HandleKind::PowerBatteriesDir => self.power_battery_handle(path)?, -+ HandleKind::PowerBatteryDir(ref battery_id) => { -+ if path.is_empty() { -+ HandleKind::PowerBatteryDir(battery_id.clone()) -+ } else { -+ self.power_battery_handle(&format!("{battery_id}/{path}"))? -+ } -+ } - _ => return Err(Error::new(EACCES)), - }; - -@@ -296,7 +736,7 @@ impl SchemeSync for AcpiScheme<'_, '_> { - ) -> Result { - let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?; - -- let handle = self.handles.get_mut(id)?; -+ let handle = self.handles.get(id)?; - - if handle.stat { - return Err(Error::new(EBADF)); -@@ -309,6 +749,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { - .ok_or(Error::new(EBADFD))? - .as_slice(), - HandleKind::Symbol { description, .. } => description.as_bytes(), -+ HandleKind::Resources(contents) => contents.as_bytes(), -+ HandleKind::Dmi(contents) => contents.as_bytes(), -+ HandleKind::PowerFile(contents) => contents.as_bytes(), - _ => return Err(Error::new(EINVAL)), - }; - -@@ -328,13 +771,95 @@ impl SchemeSync for AcpiScheme<'_, '_> { - mut buf: DirentBuf<&'buf mut [u8]>, - opaque_offset: u64, - ) -> Result> { -- let handle = self.handles.get_mut(id)?; -+ let handle = self.handles.get(id)?; - - match &handle.kind { - HandleKind::TopLevel => { -- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"]; -+ for (idx, (name, kind)) in top_level_entries(self.power_available()) -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: *kind, -+ })?; -+ } -+ } -+ HandleKind::DmiDir => { -+ for (idx, name) in DMI_DIRECTORY_ENTRIES -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: DirentKind::Regular, -+ })?; -+ } -+ } -+ HandleKind::ResourcesDir => { -+ let aml_symbols = self.ctx.aml_symbols().map_err(|_| Error::new(EIO))?; -+ let entries = -+ resource_dir_entries(aml_symbols.symbols_cache().keys().map(String::as_str)); -+ for (idx, name) in entries.iter().enumerate().skip(opaque_offset as usize) { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: DirentKind::Regular, -+ })?; -+ } -+ } -+ HandleKind::PowerDir => { -+ const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[ -+ ("on_battery", DirentKind::Regular), -+ ("adapters", DirentKind::Directory), -+ ("batteries", DirentKind::Directory), -+ ]; -+ -+ for (idx, (name, kind)) in POWER_ROOT_ENTRIES -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: *kind, -+ })?; -+ } -+ } -+ HandleKind::PowerAdaptersDir => { -+ let snapshot = self.power_snapshot()?; -+ for (idx, adapter) in snapshot -+ .adapters -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name: adapter.id.as_str(), -+ kind: DirentKind::Directory, -+ })?; -+ } -+ } -+ HandleKind::PowerAdapterDir(adapter_id) => { -+ let snapshot = self.power_snapshot()?; -+ let _adapter = snapshot -+ .adapters -+ .iter() -+ .find(|adapter| adapter.id == *adapter_id) -+ .ok_or(Error::new(EIO))?; - -- for (idx, name) in TOPLEVEL_ENTRIES -+ for (idx, name) in power_adapter_entry_names() - .iter() - .enumerate() - .skip(opaque_offset as usize) -@@ -343,10 +868,44 @@ impl SchemeSync for AcpiScheme<'_, '_> { - inode: 0, - next_opaque_id: idx as u64 + 1, - name, -+ kind: DirentKind::Regular, -+ })?; -+ } -+ } -+ HandleKind::PowerBatteriesDir => { -+ let snapshot = self.power_snapshot()?; -+ for (idx, battery) in snapshot -+ .batteries -+ .iter() -+ .enumerate() -+ .skip(opaque_offset as usize) -+ { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name: battery.id.as_str(), - kind: DirentKind::Directory, - })?; - } - } -+ HandleKind::PowerBatteryDir(battery_id) => { -+ let snapshot = self.power_snapshot()?; -+ let battery = snapshot -+ .batteries -+ .iter() -+ .find(|battery| battery.id == *battery_id) -+ .ok_or(Error::new(EIO))?; -+ let entry_names = power_battery_entry_names(battery); -+ -+ for (idx, name) in entry_names.iter().enumerate().skip(opaque_offset as usize) { -+ buf.entry(DirEntry { -+ inode: 0, -+ next_opaque_id: idx as u64 + 1, -+ name, -+ kind: DirentKind::Regular, -+ })?; -+ } -+ } - HandleKind::Symbols(aml_symbols) => { - for (idx, (symbol_name, _value)) in aml_symbols - .symbols_cache() -@@ -444,6 +1003,38 @@ impl SchemeSync for AcpiScheme<'_, '_> { - Ok(result_len) - } - -+ fn write( -+ &mut self, -+ id: usize, -+ buf: &[u8], -+ _offset: u64, -+ _flags: u32, -+ _ctx: &CallerCtx, -+ ) -> Result { -+ let handle = self.handles.get_mut(id)?; -+ -+ if handle.stat { -+ return Err(Error::new(EBADF)); -+ } -+ if !handle.allowed_to_eval { -+ return Err(Error::new(EPERM)); -+ } -+ -+ match handle.kind { -+ HandleKind::Reboot => { -+ if buf.is_empty() { -+ return Err(Error::new(EINVAL)); -+ } -+ self.ctx.acpi_reboot().map_err(|error| { -+ log::error!("ACPI reboot failed: {error}"); -+ Error::new(EIO) -+ })?; -+ Ok(buf.len()) -+ } -+ _ => Err(Error::new(EBADF)), -+ } -+ } -+ - fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result { - let id = sendfd_request.id(); - let num_fds = sendfd_request.num_fds(); -@@ -470,10 +1061,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { - } - let new_fd = libredox::Fd::new(new_fd); - -- if self.pci_fd.is_some() { -+ if self.ctx.register_pci_fd(new_fd).is_err() { - return Err(Error::new(EINVAL)); -- } else { -- self.pci_fd = Some(new_fd); - } - - Ok(num_fds) -@@ -483,3 +1072,90 @@ impl SchemeSync for AcpiScheme<'_, '_> { - self.handles.remove(id); - } - } -+ -+#[cfg(test)] -+mod tests { -+ use super::{dmi_contents, resource_dir_entries, resource_symbol_path, top_level_entries}; -+ use crate::acpi::DmiInfo; -+ use syscall::dirent::DirentKind; -+ -+ #[test] -+ fn dmi_contents_exposes_individual_fields_and_match_all() { -+ let dmi_info = DmiInfo { -+ sys_vendor: Some("Framework".to_string()), -+ board_name: Some("FRANMECP01".to_string()), -+ product_name: Some("Laptop 16".to_string()), -+ ..DmiInfo::default() -+ }; -+ -+ assert_eq!( -+ dmi_contents(Some(&dmi_info), "sys_vendor").as_deref(), -+ Some("Framework") -+ ); -+ assert_eq!( -+ dmi_contents(Some(&dmi_info), "board_name").as_deref(), -+ Some("FRANMECP01") -+ ); -+ assert_eq!( -+ dmi_contents(Some(&dmi_info), "product_name").as_deref(), -+ Some("Laptop 16") -+ ); -+ assert_eq!( -+ dmi_contents(Some(&dmi_info), "match_all").as_deref(), -+ Some("sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16") -+ ); -+ assert_eq!(dmi_contents(None, "bios_version").as_deref(), Some("")); -+ assert_eq!(dmi_contents(Some(&dmi_info), "unknown"), None); -+ } -+ -+ #[test] -+ fn top_level_entries_always_include_reboot() { -+ let entries = top_level_entries(false); -+ assert!(entries -+ .iter() -+ .any(|(name, kind)| { *name == "reboot" && matches!(kind, DirentKind::Regular) })); -+ } -+ -+ #[test] -+ fn top_level_entries_only_include_power_when_available() { -+ let hidden = top_level_entries(false); -+ let visible = top_level_entries(true); -+ -+ assert!(!hidden.iter().any(|(name, _)| *name == "power")); -+ assert!(visible -+ .iter() -+ .any(|(name, kind)| { *name == "power" && matches!(kind, DirentKind::Directory) })); -+ } -+ -+ #[test] -+ fn top_level_entries_always_include_resources() { -+ let entries = top_level_entries(false); -+ assert!(entries -+ .iter() -+ .any(|(name, kind)| { *name == "resources" && matches!(kind, DirentKind::Directory) })); -+ } -+ -+ #[test] -+ fn resource_symbol_path_accepts_dotted_and_slash_paths() { -+ assert_eq!( -+ resource_symbol_path("_SB.PCI0.I2C0.TPD0").as_deref(), -+ Some("_SB.PCI0.I2C0.TPD0._CRS") -+ ); -+ assert_eq!( -+ resource_symbol_path("\\_SB/PCI0/I2C0/TPD0").as_deref(), -+ Some("_SB.PCI0.I2C0.TPD0._CRS") -+ ); -+ } -+ -+ #[test] -+ fn resource_dir_entries_list_devices_with_crs_suffix() { -+ assert_eq!( -+ resource_dir_entries([ -+ "_SB.PCI0.I2C0.TPD0._CRS", -+ "_SB.PCI0.I2C1.TPD1._HID", -+ "_SB.PCI0.I2C0.TPD0._CRS", -+ ]), -+ vec!["_SB.PCI0.I2C0.TPD0".to_string()] -+ ); -+ } -+} diff --git a/local/patches/base/P2-boot-runtime-fixes.patch.bak b/local/patches/base/P2-boot-runtime-fixes.patch.bak deleted file mode 100644 index 0dc9debff..000000000 --- a/local/patches/base/P2-boot-runtime-fixes.patch.bak +++ /dev/null @@ -1,313 +0,0 @@ -diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs ---- a/drivers/hwd/src/backend/acpi.rs -+++ b/drivers/hwd/src/backend/acpi.rs -@@ -1,27 +1,36 @@ - use amlserde::{AmlSerde, AmlSerdeValue}; --use std::{error::Error, fs, process::Command}; -+use std::{error::Error, fs}; - - use super::Backend; - - pub struct AcpiBackend { -- rxsdt: Vec, -+ _rxsdt: Vec, - } - - impl Backend for AcpiBackend { - fn new() -> Result> { - let rxsdt = fs::read("/scheme/kernel.acpi/rxsdt")?; - -- // Spawn acpid -- //TODO: pass rxsdt data to acpid? -- #[allow(deprecated, reason = "we can't yet move this to init")] -- daemon::Daemon::spawn(Command::new("acpid")); -- -- Ok(Self { rxsdt }) -+ Ok(Self { _rxsdt: rxsdt }) - } - - fn probe(&mut self) -> Result<(), Box> { -+ let mut boot_critical_input_candidates = 0usize; -+ let mut thc_candidates = 0usize; -+ let mut non_hid_i2c_candidates = 0usize; -+ - // Read symbols from acpi scheme -- let entries = fs::read_dir("/scheme/acpi/symbols")?; -+ let entries = match fs::read_dir("/scheme/acpi/symbols") { -+ Ok(entries) => entries, -+ Err(err) -+ if err.kind() == std::io::ErrorKind::WouldBlock -+ || err.raw_os_error() == Some(11) => -+ { -+ log::debug!("hwd: ACPI symbols are not ready yet"); -+ return Ok(()); -+ } -+ Err(err) => return Err(Box::new(err)), -+ }; - // TODO: Reimplement with getdents? - let symbols_fd = libredox::Fd::open( - "/scheme/acpi/symbols", -@@ -100,12 +109,103 @@ - "PNP0C0F" => "PCI interrupt link", - "PNP0C50" => "I2C HID", - "PNP0F13" => "PS/2 port for PS/2-style mouse", -+ "80860F41" | "808622C1" => "DesignWare I2C controller", -+ "AMDI0010" | "AMDI0019" | "AMDI0510" => "AMD laptop I2C controller", -+ "INT33C2" | "INT33C3" | "INT3432" | "INT3433" | "INTC10EF" => { -+ "Intel LPSS/SerialIO I2C controller" -+ } -+ "INT34C5" | "INTC1055" => "Intel GPIO controller", -+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" => { -+ "Intel THC companion (QuickI2C/QuickSPI path)" -+ } -+ _ if is_elan_touchpad_id(&id) => "ELAN touchpad (I2C/SMBus path)", -+ _ if is_cypress_touchpad_id(&id) => "Cypress/Trackpad (non-HID I2C path)", -+ _ if is_synaptics_rmi_id(&id) => "Synaptics RMI touchpad (I2C/SMBus path)", - _ => "?", - }; - log::debug!("{}: {} ({})", name, id, what); -+ if is_boot_critical_i2c_surface(&id) { -+ boot_critical_input_candidates += 1; -+ log::info!("{}: {} is boot-critical for laptop input path", name, id); -+ } -+ if is_thc_companion(&id) { -+ thc_candidates += 1; -+ log::warn!( -+ "{}: {} indicates Intel THC path; DMA/report fast-path is not complete yet", -+ name, -+ id -+ ); -+ } -+ if is_non_hid_i2c_input_id(&id) { -+ non_hid_i2c_candidates += 1; -+ } - } - } - } -+ -+ if boot_critical_input_candidates == 0 { -+ log::warn!( -+ "hwd: no ACPI boot-critical I2C input candidates found; built-in laptop input may require additional controller/device support" -+ ); -+ } else { -+ log::info!( -+ "hwd: ACPI input candidates: total={} thc={} non_hid_i2c={}", -+ boot_critical_input_candidates, -+ thc_candidates, -+ non_hid_i2c_candidates -+ ); -+ } -+ - Ok(()) - } - } -+ -+fn is_boot_critical_i2c_surface(id: &str) -> bool { -+ matches!( -+ id, -+ "PNP0C50" -+ | "ACPI0C50" -+ | "80860F41" -+ | "808622C1" -+ | "AMDI0010" -+ | "AMDI0019" -+ | "AMDI0510" -+ | "INT33C2" -+ | "INT33C3" -+ | "INT3432" -+ | "INT3433" -+ | "INTC10EF" -+ | "INT34C5" -+ | "INTC1055" -+ | "INTC1050" -+ | "INTC1051" -+ | "INTC1080" -+ | "INTC1081" -+ | "INTC1082" -+ ) || is_elan_touchpad_id(id) -+ || is_cypress_touchpad_id(id) -+ || is_synaptics_rmi_id(id) -+} -+ -+fn is_thc_companion(id: &str) -> bool { -+ matches!( -+ id, -+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" -+ ) -+} -+ -+fn is_elan_touchpad_id(id: &str) -> bool { -+ id.starts_with("ELAN") -+} -+ -+fn is_cypress_touchpad_id(id: &str) -> bool { -+ id.starts_with("CYAP") -+} -+ -+fn is_synaptics_rmi_id(id: &str) -> bool { -+ id.starts_with("SYNA") -+} -+ -+fn is_non_hid_i2c_input_id(id: &str) -> bool { -+ is_elan_touchpad_id(id) || is_cypress_touchpad_id(id) || is_synaptics_rmi_id(id) -+} - -diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs ---- a/drivers/pcid-spawner/src/main.rs -+++ b/drivers/pcid-spawner/src/main.rs -@@ -1,11 +1,40 @@ -+use std::env; - use std::fs; - use std::process::Command; -+use std::thread; - - use anyhow::{anyhow, Context, Result}; - - use pcid_interface::config::Config; - use pcid_interface::PciFunctionHandle; - -+fn strict_usb_boot() -> bool { -+ matches!( -+ env::var("REDBEAR_STRICT_USB_BOOT") -+ .ok() -+ .as_deref() -+ .map(str::to_ascii_lowercase) -+ .as_deref(), -+ Some("1" | "true" | "yes" | "on") -+ ) -+} -+ -+fn should_detach_in_initfs(initfs: bool, class: u8, subclass: u8, strict_usb_boot: bool) -> bool { -+ if !initfs { -+ return false; -+ } -+ -+ if class == 0x01 { -+ return false; -+ } -+ -+ if strict_usb_boot && class == 0x0C && subclass == 0x03 { -+ return false; -+ } -+ -+ true -+} -+ - fn main() -> Result<()> { - let mut args = pico_args::Arguments::from_env(); - let initfs = args.contains("--initfs"); -@@ -30,6 +59,7 @@ - } - - let config: Config = toml::from_str(&config_data)?; -+ let strict_usb_boot = strict_usb_boot(); - - for entry in fs::read_dir("/scheme/pci")? { - let entry = entry.context("failed to get entry")?; -@@ -87,14 +117,70 @@ - - log::info!("pcid-spawner: spawn {:?}", command); - -+ let device_addr = handle.config().func.addr; -+ - handle.enable_device(); - - let channel_fd = handle.into_inner_fd(); - command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string()); - - #[allow(deprecated, reason = "we can't yet move this to init")] -- daemon::Daemon::spawn(command); -- syscall::close(channel_fd as usize).unwrap(); -+ if should_detach_in_initfs( -+ initfs, -+ full_device_id.class, -+ full_device_id.subclass, -+ strict_usb_boot, -+ ) { -+ log::warn!( -+ "pcid-spawner: detached initfs spawn for {} to avoid blocking early boot", -+ device_addr -+ ); -+ -+ let device_addr = device_addr.to_string(); -+ thread::spawn(move || { -+ #[allow(deprecated, reason = "we can't yet move this to init")] -+ if let Err(err) = daemon::Daemon::spawn(command) { -+ log::error!( -+ "pcid-spawner: spawn/readiness failed for {}: {}", -+ device_addr, -+ err -+ ); -+ log::error!( -+ "pcid-spawner: {} remains enabled without a confirmed ready driver", -+ device_addr -+ ); -+ } -+ if let Err(err) = syscall::close(channel_fd as usize) { -+ log::error!( -+ "pcid-spawner: failed to close channel fd {} for {}: {}", -+ channel_fd, -+ device_addr, -+ err -+ ); -+ } -+ }); -+ } else { -+ #[allow(deprecated, reason = "we can't yet move this to init")] -+ if let Err(err) = daemon::Daemon::spawn(command) { -+ log::error!( -+ "pcid-spawner: spawn/readiness failed for {}: {}", -+ device_addr, -+ err -+ ); -+ log::error!( -+ "pcid-spawner: {} remains enabled without a confirmed ready driver", -+ device_addr -+ ); -+ } -+ if let Err(err) = syscall::close(channel_fd as usize) { -+ log::error!( -+ "pcid-spawner: failed to close channel fd {} for {}: {}", -+ channel_fd, -+ device_addr, -+ err -+ ); -+ } -+ } - } - - Ok(()) - -diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs ---- a/drivers/pcid/src/main.rs -+++ b/drivers/pcid/src/main.rs -@@ -12,6 +12,7 @@ - }; - use redox_scheme::scheme::register_sync_scheme; - use scheme_utils::Blocking; -+use syscall::{sendfd, SendFdFlags}; - - use crate::cfg_access::Pcie; - use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, PciRom}; -@@ -262,14 +263,13 @@ - let access_fd = socket - .create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0) - .expect("failed to issue this resource"); -- let access_bytes = access_fd.to_ne_bytes(); -- let _ = register_pci -- .call_wo( -- &access_bytes, -- syscall::CallFlags::WRITE | syscall::CallFlags::FD, -- &[], -- ) -- .expect("failed to send pci_fd to acpid"); -+ sendfd( -+ register_pci.raw(), -+ access_fd as usize, -+ SendFdFlags::empty().bits(), -+ 0, -+ ) -+ .expect("failed to send pci_fd to acpid"); - } - Err(err) => { - if err.errno() == libredox::errno::ENODEV { diff --git a/local/patches/base/P2-boot-runtime-noise-and-net-race.patch.bak b/local/patches/base/P2-boot-runtime-noise-and-net-race.patch.bak deleted file mode 100644 index dea45a7e8..000000000 --- a/local/patches/base/P2-boot-runtime-noise-and-net-race.patch.bak +++ /dev/null @@ -1,144 +0,0 @@ -# P2-boot-runtime-noise-and-net-race.patch -# -# Reduce expected boot-time warning noise and harden netstack startup ordering: -# - procmgr: unknown cancellation is trace-level (benign race) -# - acpid: warn once for unsupported power surface -# - ahcid: SATAPI probe failures are informational on empty media -# - netstack: retry network adapter discovery during early boot races - -diff --git a/bootstrap/src/procmgr.rs b/bootstrap/src/procmgr.rs ---- a/bootstrap/src/procmgr.rs -+++ b/bootstrap/src/procmgr.rs -@@ -296,7 +296,7 @@ fn handle_scheme<'a>( - } - } - } else { -- log::warn!("Cancellation for unknown id {:?}", req.id); -+ log::trace!("Cancellation for unknown id {:?}", req.id); - Pending - } - } - -diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs ---- a/drivers/acpid/src/scheme.rs -+++ b/drivers/acpid/src/scheme.rs -@@ -8,6 +8,7 @@ use ron::de::SpannedError; - use scheme_utils::HandleMap; - use std::convert::{TryFrom, TryInto}; - use std::str::FromStr; -+use std::sync::atomic::{AtomicBool, Ordering}; - use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; - use syscall::schemev2::NewFdFlags; - use syscall::FobtainFdFlags; -@@ -29,6 +30,8 @@ use crate::acpi::{ - }; - use crate::resources::{decode_resource_template, ResourceDescriptor}; - -+static POWER_SURFACE_UNAVAILABLE_WARNED: AtomicBool = AtomicBool::new(false); -+ - pub struct AcpiScheme<'acpi, 'sock> { - ctx: &'acpi AcpiContext, - handles: HandleMap>, -@@ -307,7 +310,9 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { - self.ctx.power_snapshot().map_err(|error| match error { - crate::acpi::AmlEvalError::NotInitialized => Error::new(EAGAIN), - crate::acpi::AmlEvalError::Unsupported(message) => { -- log::warn!("ACPI power surface unavailable: {message}"); -+ if !POWER_SURFACE_UNAVAILABLE_WARNED.swap(true, Ordering::Relaxed) { -+ log::warn!("ACPI power surface unavailable: {message}"); -+ } - Error::new(EOPNOTSUPP) - } - other => { - -diff --git a/drivers/storage/ahcid/src/ahci/mod.rs b/drivers/storage/ahcid/src/ahci/mod.rs ---- a/drivers/storage/ahcid/src/ahci/mod.rs -+++ b/drivers/storage/ahcid/src/ahci/mod.rs -@@ -64,7 +64,7 @@ pub fn disks(base: usize, name: &str) -> (&'static mut HbaMem, Vec) { - HbaPortType::SATAPI => match DiskATAPI::new(i, port) { - Ok(disk) => Some(AnyDisk::Atapi(disk)), - Err(err) => { -- error!("{}: {}", i, err); -+ info!("{}: {}", i, err); - None - } - }, - -diff --git a/netstack/src/main.rs b/netstack/src/main.rs ---- a/netstack/src/main.rs -+++ b/netstack/src/main.rs -@@ -6,6 +6,8 @@ use anyhow::{anyhow, bail, Context, Result}; - use event::{EventFlags, EventQueue}; - use libredox::flag::{O_NONBLOCK, O_RDWR}; - use libredox::Fd; -+use std::thread; -+use std::time::Duration; - - use redox_scheme::Socket; - use scheme::Smolnetd; -@@ -22,34 +24,47 @@ mod scheme; - fn get_network_adapter() -> Result { - use std::fs; - -- let mut adapters = vec![]; -+ const MAX_ATTEMPTS: u32 = 50; -+ const RETRY_DELAY: Duration = Duration::from_millis(100); - -- for entry_res in fs::read_dir("/scheme")? { -- let Ok(entry) = entry_res else { -- continue; -- }; -+ for attempt in 1..=MAX_ATTEMPTS { -+ let mut adapters = vec![]; - -- let Ok(scheme) = entry.file_name().into_string() else { -- continue; -- }; -+ for entry_res in fs::read_dir("/scheme")? { -+ let Ok(entry) = entry_res else { -+ continue; -+ }; - -- if !scheme.starts_with("network") { -- continue; -- } -+ let Ok(scheme) = entry.file_name().into_string() else { -+ continue; -+ }; - -- adapters.push(scheme); -- } -+ if !scheme.starts_with("network") { -+ continue; -+ } - -- if adapters.is_empty() { -- bail!("no network adapter found"); -- } else { -- let adapter = adapters.remove(0); -+ adapters.push(scheme); -+ } -+ - if !adapters.is_empty() { -- // FIXME allow using multiple network adapters at the same time -- warn!("Multiple network adapters found. Only {adapter} will be used"); -+ let adapter = adapters.remove(0); -+ if !adapters.is_empty() { -+ // FIXME allow using multiple network adapters at the same time -+ warn!("Multiple network adapters found. Only {adapter} will be used"); -+ } -+ return Ok(adapter); -+ } -+ -+ if attempt < MAX_ATTEMPTS { -+ warn!( -+ "no network adapter found yet (attempt {attempt}/{MAX_ATTEMPTS}), waiting {} ms", -+ RETRY_DELAY.as_millis() -+ ); -+ thread::sleep(RETRY_DELAY); - } -- Ok(adapter) - } -+ -+ bail!("no network adapter found") - } diff --git a/local/patches/base/P2-hwd-misc.patch.bak b/local/patches/base/P2-hwd-misc.patch.bak deleted file mode 100644 index 12ad1caf1..000000000 --- a/local/patches/base/P2-hwd-misc.patch.bak +++ /dev/null @@ -1,18 +0,0 @@ -# P2-hwd-misc.patch -# Keep hwd focused on hardware probing. Init owns boot-time pcid startup. - -diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs -index 79360e34..4de3d9f3 100644 ---- a/drivers/hwd/src/main.rs -+++ b/drivers/hwd/src/main.rs -@@ -37,11 +37,6 @@ fn daemon(daemon: daemon::Daemon) -> ! { - - //TODO: launch pcid based on backend information? - // Must launch after acpid but before probe calls /scheme/acpi/symbols -- #[allow(deprecated, reason = "we can't yet move this to init")] -- daemon::Daemon::spawn(process::Command::new("pcid")); -- - daemon.ready(); - - //TODO: HWD is meant to locate PCI/XHCI/etc devices in ACPI and DeviceTree definitions and start their drivers - diff --git a/local/patches/base/P2-init-acpid-wiring.patch.bak b/local/patches/base/P2-init-acpid-wiring.patch.bak deleted file mode 100644 index 555bc1255..000000000 --- a/local/patches/base/P2-init-acpid-wiring.patch.bak +++ /dev/null @@ -1,33 +0,0 @@ -diff --git a/init.initfs.d/41_acpid.service b/init.initfs.d/41_acpid.service -new file mode 100644 ---- /dev/null -+++ b/init.initfs.d/41_acpid.service -@@ -0,0 +1,7 @@ -+[unit] -+description = "ACPI daemon" -+default_dependencies = false -+ -+[service] -+cmd = "acpid" -+inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"] -+type = "notify" -diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target ---- a/init.initfs.d/40_drivers.target -+++ b/init.initfs.d/40_drivers.target -@@ -7,4 +7,5 @@ requires_weak = [ - "40_bcm2835-sdhcid.service", - "40_hwd.service", - "40_pcid-spawner-initfs.service", -+ "41_acpid.service", - ] -diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service ---- a/init.initfs.d/40_hwd.service -+++ b/init.initfs.d/40_hwd.service -@@ -1,6 +1,6 @@ - [unit] - description = "Hardware manager" --requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target"] -+requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "41_acpid.service"] - - [service] - cmd = "hwd" diff --git a/local/patches/base/P2-network-driver-mains.patch.bak b/local/patches/base/P2-network-driver-mains.patch.bak deleted file mode 100644 index 6c60f9dd4..000000000 --- a/local/patches/base/P2-network-driver-mains.patch.bak +++ /dev/null @@ -1,607 +0,0 @@ -# P2-network-driver-mains.patch -# Extract network driver main.rs hardening: replace panic/unwrap/expect with -# proper error handling and graceful exits. -# -# Files: drivers/net/e1000d/src/main.rs, drivers/net/ixgbed/src/main.rs, -# drivers/net/rtl8139d/src/main.rs, drivers/net/rtl8168d/src/main.rs, -# drivers/net/virtio-netd/src/main.rs - -diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs -index 373ea9b3..8ff57b33 100644 ---- a/drivers/net/e1000d/src/main.rs -+++ b/drivers/net/e1000d/src/main.rs -@@ -1,5 +1,6 @@ - use std::io::{Read, Write}; - use std::os::unix::io::AsRawFd; -+use std::process; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -@@ -25,10 +26,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - common::file_level(), - ); - -- let irq = pci_config -- .func -- .legacy_interrupt_line -- .expect("e1000d: no legacy interrupts supported"); -+ let irq = match pci_config.func.legacy_interrupt_line { -+ Some(irq) => irq, -+ None => { -+ log::error!("e1000d: no legacy interrupts supported"); -+ process::exit(1); -+ } -+ }; - - log::info!("E1000 {}", pci_config.func.display()); - -@@ -38,7 +42,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Intel8254x::new(address).expect("e1000d: failed to allocate device") -+ device::Intel8254x::new(address).unwrap_or_else(|err| { -+ log::error!("e1000d: failed to allocate device: {err}"); -+ process::exit(1); -+ }) - }, - daemon, - format!("network.{name}"), -@@ -51,7 +58,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("e1000d: failed to create event queue"); -+ let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { -+ log::error!("e1000d: failed to create event queue: {err}"); -+ process::exit(1); -+ }); - - event_queue - .subscribe( -@@ -59,32 +69,65 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - Source::Irq, - event::EventFlags::READ, - ) -- .expect("e1000d: failed to subscribe to IRQ fd"); -+ .unwrap_or_else(|err| { -+ log::error!("e1000d: failed to subscribe to IRQ fd: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .expect("e1000d: failed to subscribe to scheme fd"); -- -- libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace"); -- -- scheme.tick().unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("e1000d: failed to subscribe to scheme fd: {err}"); -+ process::exit(1); -+ }); -+ -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("e1000d: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - -- for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("e1000d: failed to get event: {err}"); -+ continue; -+ } -+ None => break, -+ }; - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.read(&mut irq) { -+ log::error!("e1000d: failed to read IRQ: {err}"); -+ continue; -+ } - if unsafe { scheme.adapter().irq() } { -- irq_file.write(&mut irq).unwrap(); -- -- scheme.tick().expect("e1000d: failed to handle IRQ") -+ if let Err(err) = irq_file.write(&mut irq) { -+ log::error!("e1000d: failed to write IRQ: {err}"); -+ continue; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed to handle IRQ: {err}"); -+ } -+ } -+ } -+ Source::Scheme => { -+ if let Err(err) = scheme.tick() { -+ log::error!("e1000d: failed to handle scheme op: {err}"); - } - } -- Source::Scheme => scheme.tick().expect("e1000d: failed to handle scheme op"), - } - } -- unreachable!() -+ -+ process::exit(0); - } -diff --git a/drivers/net/ixgbed/src/main.rs b/drivers/net/ixgbed/src/main.rs -index 4a6ce74d..855d339d 100644 ---- a/drivers/net/ixgbed/src/main.rs -+++ b/drivers/net/ixgbed/src/main.rs -@@ -1,5 +1,6 @@ - use std::io::{Read, Write}; - use std::os::unix::io::AsRawFd; -+use std::process; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -@@ -19,12 +20,23 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - let mut name = pci_config.func.name(); - name.push_str("_ixgbe"); - -- let irq = pci_config -- .func -- .legacy_interrupt_line -- .expect("ixgbed: no legacy interrupts supported"); -+ common::setup_logging( -+ "net", -+ "pci", -+ &name, -+ common::output_level(), -+ common::file_level(), -+ ); -+ -+ let irq = match pci_config.func.legacy_interrupt_line { -+ Some(irq) => irq, -+ None => { -+ log::error!("ixgbed: no legacy interrupts supported"); -+ process::exit(1); -+ } -+ }; - -- println!(" + IXGBE {}", pci_config.func.display()); -+ log::info!("IXGBE {}", pci_config.func.display()); - - let mut irq_file = irq.irq_handle("ixgbed"); - -@@ -34,8 +46,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let mut scheme = NetworkScheme::new( - move || { -- device::Intel8259x::new(address as usize, size) -- .expect("ixgbed: failed to allocate device") -+ device::Intel8259x::new(address as usize, size).unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to allocate device: {err}"); -+ process::exit(1); -+ }) - }, - daemon, - format!("network.{name}"), -@@ -48,41 +62,77 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("ixgbed: Could not create event queue."); -+ let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to create event queue: {err}"); -+ process::exit(1); -+ }); -+ - event_queue - .subscribe( - irq_file.as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to subscribe to IRQ fd: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- libredox::call::setrens(0, 0).expect("ixgbed: failed to enter null namespace"); -+ .unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to subscribe to scheme fd: {err}"); -+ process::exit(1); -+ }); -+ -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("ixgbed: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("ixgbed: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - -- scheme.tick().unwrap(); -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("ixgbed: failed to get event: {err}"); -+ continue; -+ } -+ None => break, -+ }; - -- for event in event_queue.map(|e| e.expect("ixgbed: failed to get next event")) { - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.read(&mut irq) { -+ log::error!("ixgbed: failed to read IRQ: {err}"); -+ continue; -+ } - if scheme.adapter().irq() { -- irq_file.write(&mut irq).unwrap(); -- -- scheme.tick().unwrap(); -+ if let Err(err) = irq_file.write(&mut irq) { -+ log::error!("ixgbed: failed to write IRQ: {err}"); -+ continue; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("ixgbed: failed to handle IRQ: {err}"); -+ } - } - } - Source::Scheme => { -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ log::error!("ixgbed: failed to handle scheme op: {err}"); -+ } - } - } - } -- unreachable!() -+ -+ process::exit(0); - } -diff --git a/drivers/net/rtl8139d/src/main.rs b/drivers/net/rtl8139d/src/main.rs -index d470e814..64335a23 100644 ---- a/drivers/net/rtl8139d/src/main.rs -+++ b/drivers/net/rtl8139d/src/main.rs -@@ -1,5 +1,6 @@ - use std::io::{Read, Write}; - use std::os::unix::io::AsRawFd; -+use std::process; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -@@ -32,7 +33,8 @@ fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { - other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), - } - } -- panic!("rtl8139d: failed to find BAR"); -+ log::error!("rtl8139d: failed to find BAR"); -+ process::exit(1); - } - - fn main() { -@@ -61,7 +63,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Rtl8139::new(bar as usize).expect("rtl8139d: failed to allocate device") -+ device::Rtl8139::new(bar as usize).unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to allocate device: {err}"); -+ process::exit(1); -+ }) - }, - daemon, - format!("network.{name}"), -@@ -74,42 +79,76 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("rtl8139d: Could not create event queue."); -+ let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to create event queue: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - irq_file.irq_handle().as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to subscribe to IRQ fd: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- libredox::call::setrens(0, 0).expect("rtl8139d: failed to enter null namespace"); -- -- scheme.tick().unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to subscribe to scheme fd: {err}"); -+ process::exit(1); -+ }); -+ -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("rtl8139d: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8139d: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - -- for event in event_queue.map(|e| e.expect("rtl8139d: failed to get next event")) { -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("rtl8139d: failed to get next event: {err}"); -+ continue; -+ } -+ None => break, -+ }; - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.irq_handle().read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.irq_handle().read(&mut irq) { -+ log::error!("rtl8139d: failed to read IRQ: {err}"); -+ continue; -+ } - //TODO: This may be causing spurious interrupts - if unsafe { scheme.adapter_mut().irq() } { -- irq_file.irq_handle().write(&mut irq).unwrap(); -- -- scheme.tick().unwrap(); -+ if let Err(err) = irq_file.irq_handle().write(&mut irq) { -+ log::error!("rtl8139d: failed to write IRQ: {err}"); -+ continue; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8139d: failed to handle IRQ tick: {err}"); -+ } - } - } - Source::Scheme => { -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8139d: failed to handle scheme op: {err}"); -+ } - } - } - } -- unreachable!() -+ -+ process::exit(0); - } -diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs -index 1d9963a3..bd2fcb1a 100644 ---- a/drivers/net/rtl8168d/src/main.rs -+++ b/drivers/net/rtl8168d/src/main.rs -@@ -1,5 +1,6 @@ - use std::io::{Read, Write}; - use std::os::unix::io::AsRawFd; -+use std::process; - - use driver_network::NetworkScheme; - use event::{user_data, EventQueue}; -@@ -32,7 +33,8 @@ fn map_bar(pcid_handle: &mut PciFunctionHandle) -> *mut u8 { - other => log::warn!("BAR {} is {:?} instead of memory BAR", barnum, other), - } - } -- panic!("rtl8168d: failed to find BAR"); -+ log::error!("rtl8168d: failed to find BAR"); -+ process::exit(1); - } - - fn main() { -@@ -61,7 +63,10 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - - let mut scheme = NetworkScheme::new( - move || unsafe { -- device::Rtl8168::new(bar as usize).expect("rtl8168d: failed to allocate device") -+ device::Rtl8168::new(bar as usize).unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to allocate device: {err}"); -+ process::exit(1); -+ }) - }, - daemon, - format!("network.{name}"), -@@ -74,42 +79,76 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { - } - } - -- let event_queue = EventQueue::::new().expect("rtl8168d: Could not create event queue."); -+ let mut event_queue = EventQueue::::new().unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to create event queue: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - irq_file.irq_handle().as_raw_fd() as usize, - Source::Irq, - event::EventFlags::READ, - ) -- .unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to subscribe to IRQ fd: {err}"); -+ process::exit(1); -+ }); - event_queue - .subscribe( - scheme.event_handle().raw(), - Source::Scheme, - event::EventFlags::READ, - ) -- .unwrap(); -- -- libredox::call::setrens(0, 0).expect("rtl8168d: failed to enter null namespace"); -- -- scheme.tick().unwrap(); -+ .unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to subscribe to scheme fd: {err}"); -+ process::exit(1); -+ }); -+ -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("rtl8168d: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8168d: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - -- for event in event_queue.map(|e| e.expect("rtl8168d: failed to get next event")) { -+ loop { -+ let event = match event_queue.next() { -+ Some(Ok(event)) => event, -+ Some(Err(err)) => { -+ log::error!("rtl8168d: failed to get next event: {err}"); -+ continue; -+ } -+ None => break, -+ }; - match event.user_data { - Source::Irq => { - let mut irq = [0; 8]; -- irq_file.irq_handle().read(&mut irq).unwrap(); -+ if let Err(err) = irq_file.irq_handle().read(&mut irq) { -+ log::error!("rtl8168d: failed to read IRQ: {err}"); -+ continue; -+ } - //TODO: This may be causing spurious interrupts - if unsafe { scheme.adapter_mut().irq() } { -- irq_file.irq_handle().write(&mut irq).unwrap(); -- -- scheme.tick().unwrap(); -+ if let Err(err) = irq_file.irq_handle().write(&mut irq) { -+ log::error!("rtl8168d: failed to write IRQ: {err}"); -+ continue; -+ } -+ -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8168d: failed to handle IRQ tick: {err}"); -+ } - } - } - Source::Scheme => { -- scheme.tick().unwrap(); -+ if let Err(err) = scheme.tick() { -+ log::error!("rtl8168d: failed to handle scheme op: {err}"); -+ } - } - } - } -- unreachable!() -+ -+ process::exit(0); - } -diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs -index 17d168ef..adbd1086 100644 ---- a/drivers/net/virtio-netd/src/main.rs -+++ b/drivers/net/virtio-netd/src/main.rs -@@ -3,6 +3,7 @@ mod scheme; - use std::fs::File; - use std::io::{Read, Write}; - use std::mem; -+use std::process; - - use driver_network::NetworkScheme; - use pcid_interface::PciFunctionHandle; -@@ -31,8 +32,11 @@ fn main() { - } - - fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { -- deamon(daemon, pcid_handle).unwrap(); -- unreachable!(); -+ deamon(daemon, pcid_handle).unwrap_or_else(|err| { -+ log::error!("virtio-netd: daemon failed: {err}"); -+ process::exit(1); -+ }); -+ process::exit(0); - } - - fn deamon( -@@ -52,7 +56,10 @@ fn deamon( - // 0x1000 - virtio-net - let pci_config = pcid_handle.config(); - -- assert_eq!(pci_config.func.full_device_id.device_id, 0x1000); -+ if pci_config.func.full_device_id.device_id != 0x1000 { -+ log::error!("virtio-netd: unexpected device ID {:#06x}, expected 0x1000", pci_config.func.full_device_id.device_id); -+ process::exit(1); -+ } - log::info!("virtio-net: initiating startup sequence :^)"); - - let device = virtio_core::probe_device(&mut pcid_handle)?; -@@ -84,7 +91,8 @@ fn deamon( - device.transport.ack_driver_feature(VIRTIO_NET_F_MAC); - mac - } else { -- unimplemented!() -+ log::error!("virtio-netd: device does not support MAC feature"); -+ return Err("virtio-netd: VIRTIO_NET_F_MAC not supported".into()); - }; - - device.transport.finalize_features(); -@@ -126,12 +134,23 @@ fn deamon( - data: 0, - })?; - -- libredox::call::setrens(0, 0).expect("virtio-netd: failed to enter null namespace"); -+ libredox::call::setrens(0, 0).unwrap_or_else(|err| { -+ log::error!("virtio-netd: failed to enter null namespace: {err}"); -+ process::exit(1); -+ }); - -- scheme.tick()?; -+ if let Err(err) = scheme.tick() { -+ log::error!("virtio-netd: failed initial scheme tick: {err}"); -+ process::exit(1); -+ } - - loop { -- event_queue.read(&mut [0; mem::size_of::()])?; // Wait for event -- scheme.tick()?; -+ if let Err(err) = event_queue.read(&mut [0; mem::size_of::()]) { -+ log::error!("virtio-netd: failed to read event: {err}"); -+ continue; -+ } -+ if let Err(err) = scheme.tick() { -+ log::error!("virtio-netd: failed to handle scheme event: {err}"); -+ } - } diff --git a/local/patches/base/P2-network-error-handling.patch.bak b/local/patches/base/P2-network-error-handling.patch.bak deleted file mode 100644 index 159a9c05d..000000000 --- a/local/patches/base/P2-network-error-handling.patch.bak +++ /dev/null @@ -1,118 +0,0 @@ -# P2-network-error-handling.patch -# -# Network driver error handling: replace unwrap()/expect()/panic!() with proper -# error propagation and graceful exits across e1000, ixgbe, rtl8139, rtl8168d, -# and virtio-net drivers. -# -# Covers: -# - e1000d/device.rs: replace unreachable!() in DMA array conversion -# - ixgbed/Cargo.toml: add log dependency -# - rtl8139d/device.rs: replace unreachable!() with EIO error -# - rtl8168d/device.rs: replace unreachable!() with EIO error -# - virtio-netd/scheme.rs: DMA allocation error handling for rx buffers -# -diff --git a/drivers/net/e1000d/src/device.rs b/drivers/net/e1000d/src/device.rs -index 4c518f30..0e42d72b 100644 ---- a/drivers/net/e1000d/src/device.rs -+++ b/drivers/net/e1000d/src/device.rs -@@ -3,7 +3,7 @@ use std::{cmp, mem, ptr, slice, thread, time}; - - use driver_network::NetworkAdapter; - --use syscall::error::Result; -+use syscall::error::{Error, Result, EIO}; - - use common::dma::Dma; - -@@ -207,11 +207,10 @@ impl NetworkAdapter for Intel8254x { - } - - fn dma_array() -> Result<[Dma; N]> { -- Ok((0..N) -+ let vec: Vec> = (0..N) - .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) -- .collect::>>()? -- .try_into() -- .unwrap_or_else(|_| unreachable!())) -+ .collect::>>()?; -+ vec.try_into().map_err(|_| Error::new(EIO)) - } - impl Intel8254x { - pub unsafe fn new(base: usize) -> Result { - -diff --git a/drivers/net/ixgbed/Cargo.toml b/drivers/net/ixgbed/Cargo.toml -index d97ff398..fcaf4b19 100644 ---- a/drivers/net/ixgbed/Cargo.toml -+++ b/drivers/net/ixgbed/Cargo.toml -@@ -7,6 +7,7 @@ edition = "2021" - [dependencies] - bitflags.workspace = true - libredox.workspace = true -+log.workspace = true - redox_event.workspace = true - redox_syscall.workspace = true - - -diff --git a/drivers/net/rtl8139d/src/device.rs b/drivers/net/rtl8139d/src/device.rs -index 37167ee2..d7428132 100644 ---- a/drivers/net/rtl8139d/src/device.rs -+++ b/drivers/net/rtl8139d/src/device.rs -@@ -215,7 +215,7 @@ impl Rtl8139 { - .map(|_| Ok(Dma::zeroed()?.assume_init())) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()), -+ .map_err(|_| Error::new(EIO))?, - transmit_i: 0, - mac_address: [0; 6], - }; - -diff --git a/drivers/net/rtl8168d/src/device.rs b/drivers/net/rtl8168d/src/device.rs -index ae545ec4..7229a52d 100644 ---- a/drivers/net/rtl8168d/src/device.rs -+++ b/drivers/net/rtl8168d/src/device.rs -@@ -177,7 +177,7 @@ impl Rtl8168 { - .map(|_| Ok(Dma::zeroed()?.assume_init())) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()), -+ .map_err(|_| Error::new(EIO))?, - - receive_ring: Dma::zeroed()?.assume_init(), - receive_i: 0, -@@ -185,7 +185,7 @@ impl Rtl8168 { - .map(|_| Ok(Dma::zeroed()?.assume_init())) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()), -+ .map_err(|_| Error::new(EIO))?, - transmit_ring: Dma::zeroed()?.assume_init(), - transmit_i: 0, - transmit_buffer_h: [Dma::zeroed()?.assume_init()], - -diff --git a/drivers/net/virtio-netd/src/scheme.rs b/drivers/net/virtio-netd/src/scheme.rs -index 59b3b93e..d0acb2ba 100644 ---- a/drivers/net/virtio-netd/src/scheme.rs -+++ b/drivers/net/virtio-netd/src/scheme.rs -@@ -27,11 +27,16 @@ impl<'a> VirtioNet<'a> { - // Populate all of the `rx_queue` with buffers to maximize performence. - let mut rx_buffers = vec![]; - for i in 0..(rx.descriptor_len() as usize) { -- rx_buffers.push(unsafe { -- Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) -- .unwrap() -- .assume_init() -- }); -+ let buf = unsafe { -+ match Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) { -+ Ok(dma) => dma.assume_init(), -+ Err(err) => { -+ log::error!("virtio-netd: failed to allocate rx buffer: {err}"); -+ continue; -+ } -+ } -+ }; -+ rx_buffers.push(buf); - - let chain = ChainBuilder::new() - .chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY)) diff --git a/local/patches/base/P2-storage-error-handling.patch.bak b/local/patches/base/P2-storage-error-handling.patch.bak deleted file mode 100644 index cde8c72f3..000000000 --- a/local/patches/base/P2-storage-error-handling.patch.bak +++ /dev/null @@ -1,601 +0,0 @@ -# P2-storage-error-handling.patch -# -# Storage driver error handling: replace unwrap()/expect()/panic!() with proper -# error propagation and graceful exits across AHCI, IDE, NVMe, and VirtIO block drivers. -# -# Covers: -# - ahcid/disk_ata.rs: replace unreachable!() with EIO error -# - ahcid/disk_atapi.rs: replace unreachable!() with EBADF error -# - ahcid/hba.rs: DMA allocation error handling -# - ided/ide.rs: assert→debug_assert, try_into error handling -# - nvmed/executor.rs: executor initialization error handling -# - nvmed/identify.rs: DMA allocation, unreachable!() fallback -# - nvmed/mod.rs: assert→debug_assert, unwrap→proper error/exit -# - nvmed/queues.rs: unreachable!()→safe fallback -# - virtio-blkd/scheme.rs: DMA allocation error handling, assert→if check -# -diff --git a/drivers/storage/ahcid/src/ahci/disk_ata.rs b/drivers/storage/ahcid/src/ahci/disk_ata.rs -index 4f83c51d..7423603b 100644 ---- a/drivers/storage/ahcid/src/ahci/disk_ata.rs -+++ b/drivers/storage/ahcid/src/ahci/disk_ata.rs -@@ -1,7 +1,7 @@ - use std::convert::TryInto; - use std::ptr; - --use syscall::error::Result; -+use syscall::error::{Error, Result, EIO}; - - use common::dma::Dma; - -@@ -39,7 +39,7 @@ impl DiskATA { - .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()); -+ .map_err(|_| Error::new(EIO))?; - - let mut fb = unsafe { Dma::zeroed()?.assume_init() }; - let buf = unsafe { Dma::zeroed()?.assume_init() }; -diff --git a/drivers/storage/ahcid/src/ahci/disk_atapi.rs b/drivers/storage/ahcid/src/ahci/disk_atapi.rs -index a0e75c09..8fbdfbef 100644 ---- a/drivers/storage/ahcid/src/ahci/disk_atapi.rs -+++ b/drivers/storage/ahcid/src/ahci/disk_atapi.rs -@@ -37,7 +37,7 @@ impl DiskATAPI { - .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) - .collect::>>()? - .try_into() -- .unwrap_or_else(|_| unreachable!()); -+ .map_err(|_| Error::new(EBADF))?; - - let mut fb = unsafe { Dma::zeroed()?.assume_init() }; - let mut buf = unsafe { Dma::zeroed()?.assume_init() }; -diff --git a/drivers/storage/ahcid/src/ahci/hba.rs b/drivers/storage/ahcid/src/ahci/hba.rs -index bea8792c..11a3d4ae 100644 ---- a/drivers/storage/ahcid/src/ahci/hba.rs -+++ b/drivers/storage/ahcid/src/ahci/hba.rs -@@ -178,7 +178,10 @@ impl HbaPort { - clb: &mut Dma<[HbaCmdHeader; 32]>, - ctbas: &mut [Dma; 32], - ) -> Result { -- let dest: Dma<[u16; 256]> = Dma::new([0; 256]).unwrap(); -+ let dest: Dma<[u16; 256]> = Dma::new([0; 256]).map_err(|err| { -+ error!("ahcid: failed to allocate DMA buffer: {err}"); -+ Error::new(EIO) -+ })?; - - let slot = self - .ata_start(clb, ctbas, |cmdheader, cmdfis, prdt_entries, _acmd| { - -diff --git a/drivers/storage/ided/src/ide.rs b/drivers/storage/ided/src/ide.rs -index 5faf3250..094e5889 100644 ---- a/drivers/storage/ided/src/ide.rs -+++ b/drivers/storage/ided/src/ide.rs -@@ -184,10 +184,10 @@ impl Disk for AtaDisk { - let block = start_block + (count as u64) / 512; - - //TODO: support other LBA modes -- assert!(block < 0x1_0000_0000_0000); -+ debug_assert!(block < 0x1_0000_0000_0000); - - let sectors = (chunk.len() + 511) / 512; -- assert!(sectors <= 128); -+ debug_assert!(sectors <= 128); - - log::trace!( - "IDE read chan {} dev {} block {:#x} count {:#x}", -@@ -205,7 +205,7 @@ impl Disk for AtaDisk { - // Make PRDT EOT match chunk size - for i in 0..sectors { - chan.prdt[i] = PrdtEntry { -- phys: (chan.buf.physical() + i * 512).try_into().unwrap(), -+ phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?, - size: 512, - flags: if i + 1 == sectors { - 1 << 15 // End of table -@@ -216,7 +216,7 @@ impl Disk for AtaDisk { - } - // Set PRDT - let prdt = chan.prdt.physical(); -- chan.busmaster_prdt.write(prdt.try_into().unwrap()); -+ chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?); - // Set to read - chan.busmaster_command.writef(1 << 3, true); - // Clear interrupt and error bits -@@ -325,10 +325,10 @@ impl Disk for AtaDisk { - let block = start_block + (count as u64) / 512; - - //TODO: support other LBA modes -- assert!(block < 0x1_0000_0000_0000); -+ debug_assert!(block < 0x1_0000_0000_0000); - - let sectors = (chunk.len() + 511) / 512; -- assert!(sectors <= 128); -+ debug_assert!(sectors <= 128); - - log::trace!( - "IDE write chan {} dev {} block {:#x} count {:#x}", -@@ -346,7 +346,7 @@ impl Disk for AtaDisk { - // Make PRDT EOT match chunk size - for i in 0..sectors { - chan.prdt[i] = PrdtEntry { -- phys: (chan.buf.physical() + i * 512).try_into().unwrap(), -+ phys: (chan.buf.physical() + i * 512).try_into().map_err(|_| Error::new(EIO))?, - size: 512, - flags: if i + 1 == sectors { - 1 << 15 // End of table -@@ -357,7 +357,7 @@ impl Disk for AtaDisk { - } - // Set PRDT - let prdt = chan.prdt.physical(); -- chan.busmaster_prdt.write(prdt.try_into().unwrap()); -+ chan.busmaster_prdt.write(prdt.try_into().map_err(|_| Error::new(EIO))?); - // Set to write - chan.busmaster_command.writef(1 << 3, false); - // Clear interrupt and error bits - -diff --git a/drivers/storage/nvmed/src/nvme/executor.rs b/drivers/storage/nvmed/src/nvme/executor.rs -index 6242fa98..c1435e88 100644 ---- a/drivers/storage/nvmed/src/nvme/executor.rs -+++ b/drivers/storage/nvmed/src/nvme/executor.rs -@@ -34,7 +34,12 @@ impl Hardware for NvmeHw { - &VTABLE - } - fn current() -> std::rc::Rc> { -- THE_EXECUTOR.with(|exec| Rc::clone(exec.borrow().as_ref().unwrap())) -+ THE_EXECUTOR.with(|exec| { -+ Rc::clone(exec.borrow().as_ref().unwrap_or_else(|| { -+ log::error!("nvmed: internal error: executor not initialized"); -+ std::process::exit(1); -+ })) -+ }) - } - fn try_submit( - nvme: &Arc, -diff --git a/drivers/storage/nvmed/src/nvme/identify.rs b/drivers/storage/nvmed/src/nvme/identify.rs -index 05e5b9b2..b1b6e959 100644 ---- a/drivers/storage/nvmed/src/nvme/identify.rs -+++ b/drivers/storage/nvmed/src/nvme/identify.rs -@@ -126,7 +126,7 @@ impl LbaFormat { - 0b01 => RelativePerformance::Better, - 0b10 => RelativePerformance::Good, - 0b11 => RelativePerformance::Degraded, -- _ => unreachable!(), -+ _ => RelativePerformance::Degraded, - } - } - pub fn is_available(&self) -> bool { -@@ -153,7 +153,14 @@ impl Nvme { - /// Returns the serial number, model, and firmware, in that order. - pub async fn identify_controller(&self) { - // TODO: Use same buffer -- let data: Dma = unsafe { Dma::zeroed().unwrap().assume_init() }; -+ let data: Dma = unsafe { -+ Dma::zeroed() -+ .map(|dma| dma.assume_init()) -+ .unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate identify DMA: {err}"); -+ std::process::exit(1); -+ }) -+ }; - - // println!(" - Attempting to identify controller"); - let comp = self -@@ -182,7 +189,14 @@ impl Nvme { - } - pub async fn identify_namespace_list(&self, base: u32) -> Vec { - // TODO: Use buffer -- let data: Dma<[u32; 1024]> = unsafe { Dma::zeroed().unwrap().assume_init() }; -+ let data: Dma<[u32; 1024]> = unsafe { -+ Dma::zeroed() -+ .map(|dma| dma.assume_init()) -+ .unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate namespace list DMA: {err}"); -+ std::process::exit(1); -+ }) -+ }; - - // println!(" - Attempting to retrieve namespace ID list"); - let comp = self -@@ -198,7 +212,14 @@ impl Nvme { - } - pub async fn identify_namespace(&self, nsid: u32) -> NvmeNamespace { - //TODO: Use buffer -- let data: Dma = unsafe { Dma::zeroed().unwrap().assume_init() }; -+ let data: Dma = unsafe { -+ Dma::zeroed() -+ .map(|dma| dma.assume_init()) -+ .unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate namespace DMA: {err}"); -+ std::process::exit(1); -+ }) -+ }; - - log::debug!("Attempting to identify namespace {nsid}"); - let comp = self -@@ -216,7 +237,10 @@ impl Nvme { - let block_size = data - .formatted_lba_size() - .lba_data_size() -- .expect("nvmed: error: size outside 512-2^64 range"); -+ .unwrap_or_else(|| { -+ log::error!("nvmed: error: size outside 512-2^64 range"); -+ std::process::exit(1); -+ }); - log::debug!("NVME block size: {}", block_size); - - NvmeNamespace { -diff --git a/drivers/storage/nvmed/src/nvme/mod.rs b/drivers/storage/nvmed/src/nvme/mod.rs -index 682ee933..90a25d5b 100644 ---- a/drivers/storage/nvmed/src/nvme/mod.rs -+++ b/drivers/storage/nvmed/src/nvme/mod.rs -@@ -160,7 +160,15 @@ impl Nvme { - } - fn cur_thread_ctxt(&self) -> Arc> { - // TODO: multi-threading -- Arc::clone(self.thread_ctxts.read().get(&0).unwrap()) -+ Arc::clone( -+ self.thread_ctxts -+ .read() -+ .get(&0) -+ .unwrap_or_else(|| { -+ log::error!("nvmed: internal error: thread context 0 missing"); -+ std::process::exit(1); -+ }), -+ ) - } - - pub unsafe fn submission_queue_tail(&self, qid: u16, tail: u16) { -@@ -208,10 +216,22 @@ impl Nvme { - } - - for (qid, iv) in self.cq_ivs.get_mut().iter_mut() { -- let ctxt = thread_ctxts.get(&0).unwrap().lock(); -+ let ctxt = match thread_ctxts.get(&0) { -+ Some(c) => c.lock(), -+ None => { -+ log::error!("nvmed: internal error: thread context 0 missing"); -+ return Err(Error::new(EIO)); -+ } -+ }; - let queues = ctxt.queues.borrow(); - -- let &(ref cq, ref sq) = queues.get(qid).unwrap(); -+ let (cq, sq) = match queues.get(qid) { -+ Some(pair) => pair, -+ None => { -+ log::error!("nvmed: internal error: queue {qid} missing"); -+ return Err(Error::new(EIO)); -+ } -+ }; - log::debug!( - "iv {iv} [cq {qid}: {:X}, {}] [sq {qid}: {:X}, {}]", - cq.data.physical(), -@@ -222,7 +242,13 @@ impl Nvme { - } - - { -- let main_ctxt = thread_ctxts.get(&0).unwrap().lock(); -+ let main_ctxt = match thread_ctxts.get(&0) { -+ Some(c) => c.lock(), -+ None => { -+ log::error!("nvmed: internal error: thread context 0 missing"); -+ return Err(Error::new(EIO)); -+ } -+ }; - - for (i, prp) in main_ctxt.buffer_prp.borrow_mut().iter_mut().enumerate() { - *prp = (main_ctxt.buffer.borrow_mut().physical() + i * 4096) as u64; -@@ -231,7 +257,13 @@ impl Nvme { - let regs = self.regs.get_mut(); - - let mut queues = main_ctxt.queues.borrow_mut(); -- let (asq, acq) = queues.get_mut(&0).unwrap(); -+ let (asq, acq) = match queues.get_mut(&0) { -+ Some(pair) => pair, -+ None => { -+ log::error!("nvmed: internal error: admin queue pair missing"); -+ return Err(Error::new(EIO)); -+ } -+ }; - regs.aqa - .write(((acq.data.len() as u32 - 1) << 16) | (asq.data.len() as u32 - 1)); - regs.asq_low.write(asq.data.physical() as u32); -@@ -281,14 +313,14 @@ impl Nvme { - let vector = vector as u8; - - if masked { -- assert_ne!( -+ debug_assert_ne!( - to_clear & (1 << vector), - (1 << vector), - "nvmed: internal error: cannot both mask and set" - ); - to_mask |= 1 << vector; - } else { -- assert_ne!( -+ debug_assert_ne!( - to_mask & (1 << vector), - (1 << vector), - "nvmed: internal error: cannot both mask and set" -@@ -326,22 +358,27 @@ impl Nvme { - cmd_init: impl FnOnce(CmdId) -> NvmeCmd, - fail: impl FnOnce(), - ) -> Option<(CqId, CmdId)> { -- match ctxt.queues.borrow_mut().get_mut(&sq_id).unwrap() { -- (sq, _cq) => { -- if sq.is_full() { -- fail(); -- return None; -- } -- let cmd_id = sq.tail; -- let tail = sq.submit_unchecked(cmd_init(cmd_id)); -- -- // TODO: Submit in bulk -- unsafe { -- self.submission_queue_tail(sq_id, tail); -- } -- Some((sq_id, cmd_id)) -+ let mut queues_ref = ctxt.queues.borrow_mut(); -+ let (sq, _cq) = match queues_ref.get_mut(&sq_id) { -+ Some(pair) => pair, -+ None => { -+ log::error!("nvmed: internal error: submission queue {sq_id} missing"); -+ fail(); -+ return None; - } -+ }; -+ if sq.is_full() { -+ fail(); -+ return None; -+ } -+ let cmd_id = sq.tail; -+ let tail = sq.submit_unchecked(cmd_init(cmd_id)); -+ -+ // TODO: Submit in bulk -+ unsafe { -+ self.submission_queue_tail(sq_id, tail); - } -+ Some((sq_id, cmd_id)) - } - - pub async fn create_io_completion_queue( -@@ -349,13 +386,19 @@ impl Nvme { - io_cq_id: CqId, - vector: Option, - ) -> NvmeCompQueue { -- let queue = NvmeCompQueue::new().expect("nvmed: failed to allocate I/O completion queue"); -- -- let len = u16::try_from(queue.data.len()) -- .expect("nvmed: internal error: I/O CQ longer than 2^16 entries"); -- let raw_len = len -- .checked_sub(1) -- .expect("nvmed: internal error: CQID 0 for I/O CQ"); -+ let queue = NvmeCompQueue::new().unwrap_or_else(|err| { -+ log::error!("nvmed: failed to allocate I/O completion queue: {err}"); -+ std::process::exit(1); -+ }); -+ -+ let len = u16::try_from(queue.data.len()).unwrap_or_else(|_| { -+ log::error!("nvmed: internal error: I/O CQ longer than 2^16 entries"); -+ std::process::exit(1); -+ }); -+ let raw_len = len.checked_sub(1).unwrap_or_else(|| { -+ log::error!("nvmed: internal error: CQID 0 for I/O CQ"); -+ std::process::exit(1); -+ }); - - let comp = self - .submit_and_complete_admin_command(|cid| { -@@ -370,22 +413,28 @@ impl Nvme { - .await; - - /*match comp.status.specific { -- 1 => panic!("invalid queue identifier"), -- 2 => panic!("invalid queue size"), -- 8 => panic!("invalid interrupt vector"), -+ 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); } -+ 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); } -+ 8 => { log::error!("nvmed: invalid interrupt vector"); std::process::exit(1); } - _ => (), - }*/ - - queue - } - pub async fn create_io_submission_queue(&self, io_sq_id: SqId, io_cq_id: CqId) -> NvmeCmdQueue { -- let q = NvmeCmdQueue::new().expect("failed to create submission queue"); -- -- let len = u16::try_from(q.data.len()) -- .expect("nvmed: internal error: I/O SQ longer than 2^16 entries"); -- let raw_len = len -- .checked_sub(1) -- .expect("nvmed: internal error: SQID 0 for I/O SQ"); -+ let q = NvmeCmdQueue::new().unwrap_or_else(|err| { -+ log::error!("nvmed: failed to create submission queue: {err}"); -+ std::process::exit(1); -+ }); -+ -+ let len = u16::try_from(q.data.len()).unwrap_or_else(|_| { -+ log::error!("nvmed: internal error: I/O SQ longer than 2^16 entries"); -+ std::process::exit(1); -+ }); -+ let raw_len = len.checked_sub(1).unwrap_or_else(|| { -+ log::error!("nvmed: internal error: SQID 0 for I/O SQ"); -+ std::process::exit(1); -+ }); - - let comp = self - .submit_and_complete_admin_command(|cid| { -@@ -399,9 +448,9 @@ impl Nvme { - }) - .await; - /*match comp.status.specific { -- 0 => panic!("completion queue invalid"), -- 1 => panic!("invalid queue identifier"), -- 2 => panic!("invalid queue size"), -+ 0 => { log::error!("nvmed: completion queue invalid"); std::process::exit(1); } -+ 1 => { log::error!("nvmed: invalid queue identifier"); std::process::exit(1); } -+ 2 => { log::error!("nvmed: invalid queue size"); std::process::exit(1); } - _ => (), - }*/ - -@@ -431,7 +480,10 @@ impl Nvme { - self.thread_ctxts - .read() - .get(&0) -- .unwrap() -+ .unwrap_or_else(|| { -+ log::error!("nvmed: internal error: thread context 0 missing"); -+ std::process::exit(1); -+ }) - .lock() - .queues - .borrow_mut() -@@ -497,8 +549,8 @@ impl Nvme { - for chunk in buf.chunks_mut(/* TODO: buf len */ 8192) { - let blocks = (chunk.len() + block_size - 1) / block_size; - -- assert!(blocks > 0); -- assert!(blocks <= 0x1_0000); -+ debug_assert!(blocks > 0); -+ debug_assert!(blocks <= 0x1_0000); - - self.namespace_rw(&*ctxt, namespace, lba, (blocks - 1) as u16, false) - .await?; -@@ -525,8 +577,8 @@ impl Nvme { - for chunk in buf.chunks(/* TODO: buf len */ 8192) { - let blocks = (chunk.len() + block_size - 1) / block_size; - -- assert!(blocks > 0); -- assert!(blocks <= 0x1_0000); -+ debug_assert!(blocks > 0); -+ debug_assert!(blocks <= 0x1_0000); - - ctxt.buffer.borrow_mut()[..chunk.len()].copy_from_slice(chunk); - -diff --git a/drivers/storage/nvmed/src/nvme/queues.rs b/drivers/storage/nvmed/src/nvme/queues.rs -index a3712aeb..438c905c 100644 ---- a/drivers/storage/nvmed/src/nvme/queues.rs -+++ b/drivers/storage/nvmed/src/nvme/queues.rs -@@ -145,7 +145,7 @@ impl Status { - 3 => Self::PathRelatedStatus(code), - 4..=6 => Self::Rsvd(code), - 7 => Self::Vendor(code), -- _ => unreachable!(), -+ _ => Self::Vendor(code), - } - } - } - -diff --git a/drivers/storage/virtio-blkd/src/scheme.rs b/drivers/storage/virtio-blkd/src/scheme.rs -index ec4ecf73..39fb24a8 100644 ---- a/drivers/storage/virtio-blkd/src/scheme.rs -+++ b/drivers/storage/virtio-blkd/src/scheme.rs -@@ -15,19 +15,34 @@ trait BlkExtension { - - impl BlkExtension for Queue<'_> { - async fn read(&self, block: u64, target: &mut [u8]) -> usize { -- let req = Dma::new(BlockVirtRequest { -+ let req = match Dma::new(BlockVirtRequest { - ty: BlockRequestTy::In, - reserved: 0, - sector: block, -- }) -- .unwrap(); -+ }) { -+ Ok(req) => req, -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate read request DMA: {err}"); -+ return 0; -+ } -+ }; - - let result = unsafe { -- Dma::<[u8]>::zeroed_slice(target.len()) -- .unwrap() -- .assume_init() -+ match Dma::<[u8]>::zeroed_slice(target.len()) { -+ Ok(dma) => dma.assume_init(), -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate read buffer DMA: {err}"); -+ return 0; -+ } -+ } -+ }; -+ let status = match Dma::new(u8::MAX) { -+ Ok(s) => s, -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate read status DMA: {err}"); -+ return 0; -+ } - }; -- let status = Dma::new(u8::MAX).unwrap(); - - let chain = ChainBuilder::new() - .chain(Buffer::new(&req)) -@@ -37,28 +52,46 @@ impl BlkExtension for Queue<'_> { - - // XXX: Subtract 1 because the of status byte. - let written = self.send(chain).await as usize - 1; -- assert_eq!(*status, 0); -+ if *status != 0 { -+ log::error!("virtio-blkd: read failed with status {}", *status); -+ return 0; -+ } - - target[..written].copy_from_slice(&result); - written - } - - async fn write(&self, block: u64, target: &[u8]) -> usize { -- let req = Dma::new(BlockVirtRequest { -+ let req = match Dma::new(BlockVirtRequest { - ty: BlockRequestTy::Out, - reserved: 0, - sector: block, -- }) -- .unwrap(); -+ }) { -+ Ok(req) => req, -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate write request DMA: {err}"); -+ return 0; -+ } -+ }; - - let mut result = unsafe { -- Dma::<[u8]>::zeroed_slice(target.len()) -- .unwrap() -- .assume_init() -+ match Dma::<[u8]>::zeroed_slice(target.len()) { -+ Ok(dma) => dma.assume_init(), -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate write buffer DMA: {err}"); -+ return 0; -+ } -+ } - }; - result.copy_from_slice(target.as_ref()); - -- let status = Dma::new(u8::MAX).unwrap(); -+ let status = match Dma::new(u8::MAX) { -+ Ok(s) => s, -+ Err(err) => { -+ log::error!("virtio-blkd: failed to allocate write status DMA: {err}"); -+ return 0; -+ } -+ }; - - let chain = ChainBuilder::new() - .chain(Buffer::new(&req)) -@@ -67,7 +100,10 @@ impl BlkExtension for Queue<'_> { - .build(); - - self.send(chain).await as usize; -- assert_eq!(*status, 0); -+ if *status != 0 { -+ log::error!("virtio-blkd: write failed with status {}", *status); -+ return 0; -+ } - - target.len() - } diff --git a/local/patches/base/P2-usb-pm-and-drivers.patch.bak b/local/patches/base/P2-usb-pm-and-drivers.patch.bak deleted file mode 100644 index 83f8d745d..000000000 --- a/local/patches/base/P2-usb-pm-and-drivers.patch.bak +++ /dev/null @@ -1,158 +0,0 @@ -# P2-usb-pm-and-drivers.patch -# -# USB power management and driver interface improvements: -# suspend/resume commands, SCSI driver enablement, PortPmState type, -# IRQ reactor staged port state fallback. -# -# Covers: -# - usbctl/main.rs: pm-state, suspend, resume subcommands -# - xhcid/drivers.toml: enable SCSI over USB driver (was commented out) -# - xhcid/driver_interface.rs: PortPmState enum, suspend/resume/port_pm_state methods -# - xhcid/irq_reactor.rs: staged_port_states fallback in with_ring/with_ring_mut -# -diff --git a/drivers/usb/usbctl/src/main.rs b/drivers/usb/usbctl/src/main.rs -index 9b5773d9..232f7cfc 100644 ---- a/drivers/usb/usbctl/src/main.rs -+++ b/drivers/usb/usbctl/src/main.rs -@@ -15,6 +15,9 @@ fn main() { - Command::new("port") - .arg(Arg::new("PORT").num_args(1).required(true)) - .subcommand(Command::new("status")) -+ .subcommand(Command::new("pm-state")) -+ .subcommand(Command::new("suspend")) -+ .subcommand(Command::new("resume")) - .subcommand( - Command::new("endpoint") - .arg(Arg::new("ENDPOINT_NUM").num_args(1).required(true)) -@@ -38,6 +41,15 @@ fn main() { - if let Some(_status_scmd_matches) = port_scmd_matches.subcommand_matches("status") { - let state = handle.port_state().expect("Failed to get port state"); - println!("{}", state.as_str()); -+ } else if let Some(_pm_state_scmd_matches) = port_scmd_matches.subcommand_matches("pm-state") { -+ let state = handle -+ .port_pm_state() -+ .expect("Failed to get port power-management state"); -+ println!("{}", state.as_str()); -+ } else if let Some(_suspend_scmd_matches) = port_scmd_matches.subcommand_matches("suspend") { -+ handle.suspend_device().expect("Failed to suspend device"); -+ } else if let Some(_resume_scmd_matches) = port_scmd_matches.subcommand_matches("resume") { -+ handle.resume_device().expect("Failed to resume device"); - } else if let Some(endp_scmd_matches) = port_scmd_matches.subcommand_matches("endpoint") { - let endp_num = endp_scmd_matches - - .get_one::("ENDPOINT_NUM") -diff --git a/drivers/usb/xhcid/drivers.toml b/drivers/usb/xhcid/drivers.toml -index 83c90e23..470ec063 100644 ---- a/drivers/usb/xhcid/drivers.toml -+++ b/drivers/usb/xhcid/drivers.toml -@@ -1,9 +1,8 @@ --#TODO: causes XHCI errors --#[[drivers]] --#name = "SCSI over USB" --#class = 8 # Mass Storage class --#subclass = 6 # SCSI transparent command set --#command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"] -+[[drivers]] -+name = "SCSI over USB" -+class = 8 # Mass Storage class -+subclass = 6 # SCSI transparent command set -+command = ["usbscsid", "$SCHEME", "$PORT", "$IF_PROTO"] - - [[drivers]] - name = "USB HUB" - -diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs -index 727f8d7e..82f839ae 100644 ---- a/drivers/usb/xhcid/src/driver_interface.rs -+++ b/drivers/usb/xhcid/src/driver_interface.rs -@@ -444,6 +444,33 @@ impl str::FromStr for PortState { - } - } - -+#[repr(u8)] -+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -+pub enum PortPmState { -+ Active, -+ Suspended, -+} -+impl PortPmState { -+ pub fn as_str(&self) -> &'static str { -+ match self { -+ Self::Active => "active", -+ Self::Suspended => "suspended", -+ } -+ } -+} -+ -+impl str::FromStr for PortPmState { -+ type Err = Invalid; -+ -+ fn from_str(s: &str) -> result::Result { -+ Ok(match s { -+ "active" => Self::Active, -+ "suspended" => Self::Suspended, -+ _ => return Err(Invalid("read reserved port PM state")), -+ }) -+ } -+} -+ - #[repr(u8)] - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] - pub enum EndpointStatus { -@@ -560,6 +587,16 @@ impl XhciClientHandle { - let _bytes_written = file.write(&[])?; - Ok(()) - } -+ pub fn suspend_device(&self) -> result::Result<(), XhciClientHandleError> { -+ let file = self.fd.openat("suspend", libredox::flag::O_WRONLY, 0)?; -+ let _bytes_written = file.write(&[])?; -+ Ok(()) -+ } -+ pub fn resume_device(&self) -> result::Result<(), XhciClientHandleError> { -+ let file = self.fd.openat("resume", libredox::flag::O_WRONLY, 0)?; -+ let _bytes_written = file.write(&[])?; -+ Ok(()) -+ } - pub fn get_standard_descs(&self) -> result::Result { - let json = self.read("descriptors")?; - Ok(serde_json::from_slice(&json)?) -@@ -582,6 +619,10 @@ impl XhciClientHandle { - let string = self.read_to_string("state")?; - Ok(string.parse()?) - } -+ pub fn port_pm_state(&self) -> result::Result { -+ let string = self.read_to_string("pm_state")?; -+ Ok(string.parse()?) -+ } - pub fn open_endpoint_ctl(&self, num: u8) -> result::Result { - let path = format!("endpoints/{}/ctl", num); - let fd = self.fd.openat(&path, libredox::flag::O_RDWR, 0)?; - -diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs -index ac492d5b..310fe51f 100644 ---- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs -+++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs -@@ -633,7 +633,10 @@ impl Xhci { - pub fn with_ring T>(&self, id: RingId, function: F) -> Option { - use super::RingOrStreams; - -- let slot_state = self.port_states.get(&id.port)?; -+ let slot_state = self -+ .port_states -+ .get(&id.port) -+ .or_else(|| self.staged_port_states.get(&id.port))?; - let endpoint_state = slot_state.endpoint_states.get(&id.endpoint_num)?; - - let ring_ref = match endpoint_state.transfer { -@@ -650,7 +653,10 @@ impl Xhci { - ) -> Option { - use super::RingOrStreams; - -- let mut slot_state = self.port_states.get_mut(&id.port)?; -+ let mut slot_state = self -+ .port_states -+ .get_mut(&id.port) -+ .or_else(|| self.staged_port_states.get_mut(&id.port))?; - let mut endpoint_state = slot_state.endpoint_states.get_mut(&id.endpoint_num)?; - - let ring_ref = match endpoint_state.transfer { diff --git a/local/patches/base/P3-pcid-aer-scheme.patch.bak b/local/patches/base/P3-pcid-aer-scheme.patch.bak deleted file mode 100644 index 516e15fa2..000000000 --- a/local/patches/base/P3-pcid-aer-scheme.patch.bak +++ /dev/null @@ -1,398 +0,0 @@ ---- a/drivers/pcid/src/cfg_access/mod.rs -+++ b/drivers/pcid/src/cfg_access/mod.rs -@@ -349,6 +349,10 @@ - let bus_addr = self.bus_addr(address.segment(), address.bus())?; - Some(unsafe { bus_addr.add(Self::bus_addr_offset_in_dwords(address, offset)) }) - } -+ -+ pub fn has_extended_config(&self, address: PciAddress) -> bool { -+ self.mmio_addr(address, 0x100).is_some() -+ } - } - - impl ConfigRegionAccess for Pcie { ---- a/drivers/pcid/src/scheme.rs -+++ b/drivers/pcid/src/scheme.rs -@@ -5,12 +5,61 @@ - use redox_scheme::{CallerCtx, OpenResult}; - use scheme_utils::HandleMap; - use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; --use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EALREADY}; -+use syscall::error::{ -+ Error, Result, EACCES, EALREADY, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EROFS, -+}; - use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT}; - use syscall::schemev2::NewFdFlags; - use syscall::ENOLCK; - - use crate::cfg_access::Pcie; -+ -+const PCIE_EXTENDED_CAPABILITY_AER: u16 = 0x0001; -+ -+#[derive(Clone, Copy)] -+enum AerRegisterName { -+ UncorStatus, -+ UncorMask, -+ UncorSeverity, -+ CorStatus, -+ CorMask, -+ Cap, -+ HeaderLog, -+} -+ -+impl AerRegisterName { -+ fn from_path(path: &str) -> Option { -+ Some(match path { -+ "uncor_status" => Self::UncorStatus, -+ "uncor_mask" => Self::UncorMask, -+ "uncor_severity" => Self::UncorSeverity, -+ "cor_status" => Self::CorStatus, -+ "cor_mask" => Self::CorMask, -+ "cap" => Self::Cap, -+ "header_log" => Self::HeaderLog, -+ _ => return None, -+ }) -+ } -+ -+ const fn offset(self) -> u16 { -+ match self { -+ Self::UncorStatus => 0x00, -+ Self::UncorMask => 0x04, -+ Self::UncorSeverity => 0x08, -+ Self::CorStatus => 0x0C, -+ Self::CorMask => 0x10, -+ Self::Cap => 0x14, -+ Self::HeaderLog => 0x18, -+ } -+ } -+ -+ const fn len(self) -> usize { -+ match self { -+ Self::HeaderLog => 16, -+ _ => 4, -+ } -+ } -+} - - pub struct PciScheme { - handles: HandleMap, -@@ -20,13 +69,27 @@ - binds: HashMap, - } - enum Handle { -- TopLevel { entries: Vec }, -+ TopLevel { -+ entries: Vec, -+ }, - Access, -- Device, -- Channel { addr: PciAddress, st: ChannelState }, -+ Device { -+ addr: PciAddress, -+ }, -+ Channel { -+ addr: PciAddress, -+ st: ChannelState, -+ }, - SchemeRoot, - /// Represents an open handle to a device's bind endpoint -- Bind { addr: PciAddress }, -+ Bind { -+ addr: PciAddress, -+ }, -+ AerDir, -+ Aer { -+ addr: PciAddress, -+ register: AerRegisterName, -+ }, - /// Uevent surface for hotplug consumers. Opening uevent returns an object - /// from which device add/remove events can be read. Since pcid currently - /// only scans at startup, this surface is ready for hotplug polling consumers. -@@ -38,13 +101,23 @@ - } - impl Handle { - fn is_file(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. } | Self::Uevent) -+ matches!( -+ self, -+ Self::Access -+ | Self::Channel { .. } -+ | Self::Bind { .. } -+ | Self::Aer { .. } -+ | Self::Uevent -+ ) - } - fn is_dir(&self) -> bool { - !self.is_file() - } - fn requires_root(&self) -> bool { -- matches!(self, Self::Access | Self::Channel { .. } | Self::Bind { .. }) -+ matches!( -+ self, -+ Self::Access | Self::Channel { .. } | Self::Bind { .. } -+ ) - } - fn is_scheme_root(&self) -> bool { - matches!(self, Self::SchemeRoot) -@@ -57,6 +130,16 @@ - } - - const DEVICE_CONTENTS: &[&str] = &["channel", "bind"]; -+const DEVICE_AER_CONTENTS: &[&str] = &["channel", "bind", "aer"]; -+const AER_CONTENTS: &[&str] = &[ -+ "uncor_status", -+ "uncor_mask", -+ "uncor_severity", -+ "cor_status", -+ "cor_mask", -+ "cap", -+ "header_log", -+]; - - impl PciScheme { - pub fn access(&mut self) -> usize { -@@ -141,7 +224,12 @@ - - let (len, mode) = match handle.inner { - Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), -- Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), -+ Handle::Device { addr } => ( -+ Self::device_entries(&self.pcie, addr).len(), -+ MODE_DIR | 0o755, -+ ), -+ Handle::AerDir => (AER_CONTENTS.len(), MODE_DIR | 0o755), -+ Handle::Aer { register, .. } => (register.len(), MODE_CHR | 0o444), - Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600), - Handle::Uevent => (0, MODE_CHR | 0o644), - Handle::SchemeRoot => return Err(Error::new(EBADF)), -@@ -154,7 +242,7 @@ - &mut self, - id: usize, - buf: &mut [u8], -- _offset: u64, -+ offset: u64, - _fcntl_flags: u32, - _ctx: &CallerCtx, - ) -> Result { -@@ -166,11 +254,14 @@ - - match handle.inner { - Handle::TopLevel { .. } => Err(Error::new(EISDIR)), -- Handle::Device => Err(Error::new(EISDIR)), -+ Handle::Device { .. } | Handle::AerDir => Err(Error::new(EISDIR)), - Handle::Channel { - addr: _, - ref mut st, - } => Self::read_channel(st, buf), -+ Handle::Aer { addr, register } => { -+ Self::read_aer_register(&self.pcie, addr, register, buf, offset) -+ } - Handle::Uevent => { - // Uevent surface is ready for hotplug polling consumers. - // pcid currently only scans at startup, so return empty (EAGAIN would indicate no data available). -@@ -209,8 +300,15 @@ - } - return Ok(buf); - } -- Handle::Device => DEVICE_CONTENTS, -- Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } | Handle::Uevent => return Err(Error::new(ENOTDIR)), -+ Handle::Device { addr } => Self::device_entries(&self.pcie, addr), -+ Handle::AerDir => AER_CONTENTS, -+ Handle::Access -+ | Handle::Channel { .. } -+ | Handle::Bind { .. } -+ | Handle::Aer { .. } -+ | Handle::Uevent => { -+ return Err(Error::new(ENOTDIR)); -+ } - Handle::SchemeRoot => return Err(Error::new(EBADF)), - }; - -@@ -243,6 +341,7 @@ - Handle::Channel { addr, ref mut st } => { - Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) - } -+ Handle::Aer { .. } => Err(Error::new(EROFS)), - - _ => Err(Error::new(EBADF)), - } -@@ -357,45 +456,151 @@ - binds: HashMap::new(), - } - } -- fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str, ctx: &CallerCtx) -> Result { -+ fn device_entries(pcie: &Pcie, addr: PciAddress) -> &'static [&'static str] { -+ if Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER).is_some() { -+ DEVICE_AER_CONTENTS -+ } else { -+ DEVICE_CONTENTS -+ } -+ } -+ fn find_pcie_extended_capability( -+ pcie: &Pcie, -+ addr: PciAddress, -+ capability_id: u16, -+ ) -> Option { -+ if !pcie.has_extended_config(addr) { -+ return None; -+ } -+ -+ let mut offset = 0x100_u16; -+ -+ while offset <= 0xFFC { -+ let header = unsafe { pcie.read(addr, offset) }; -+ if header == 0 || header == u32::MAX { -+ return None; -+ } -+ -+ if (header & 0xFFFF) as u16 == capability_id { -+ return Some(offset); -+ } -+ -+ let next = ((header >> 20) & 0xFFF) as u16; -+ if next < 0x100 || next <= offset || next > 0xFFC || next % 4 != 0 { -+ return None; -+ } -+ offset = next; -+ } -+ -+ None -+ } -+ fn read_file_bytes(data: &[u8], buf: &mut [u8], offset: u64) -> Result { -+ let Ok(offset) = usize::try_from(offset) else { -+ return Ok(0); -+ }; -+ if offset >= data.len() { -+ return Ok(0); -+ } -+ -+ let count = std::cmp::min(buf.len(), data.len() - offset); -+ buf[..count].copy_from_slice(&data[offset..offset + count]); -+ Ok(count) -+ } -+ fn read_aer_register( -+ pcie: &Pcie, -+ addr: PciAddress, -+ register: AerRegisterName, -+ buf: &mut [u8], -+ offset: u64, -+ ) -> Result { -+ let Some(aer_base) = -+ Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER) -+ else { -+ return Err(Error::new(ENOENT)); -+ }; -+ -+ let mut data = [0_u8; 16]; -+ for (index, chunk) in data[..register.len()].chunks_exact_mut(4).enumerate() { -+ let index = u16::try_from(index).map_err(|_| Error::new(EIO))?; -+ let value = unsafe { pcie.read(addr, aer_base + register.offset() + index * 4) }; -+ chunk.copy_from_slice(&value.to_le_bytes()); -+ } -+ -+ Self::read_file_bytes(&data[..register.len()], buf, offset) -+ } -+ fn parse_after_pci_addr( -+ &mut self, -+ addr: PciAddress, -+ after: &str, -+ ctx: &CallerCtx, -+ ) -> Result { - if after.chars().next().map_or(false, |c| c != '/') { - return Err(Error::new(ENOENT)); - } - let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?; - - Ok(if after.is_empty() { -- Handle::Device -+ Handle::Device { addr } - } else { - let path = &after[1..]; - -- match path { -- "channel" => { -- if func.enabled { -- return Err(Error::new(ENOLCK)); -+ if path == "aer" { -+ if Self::find_pcie_extended_capability( -+ &self.pcie, -+ addr, -+ PCIE_EXTENDED_CAPABILITY_AER, -+ ) -+ .is_none() -+ { -+ return Err(Error::new(ENOENT)); -+ } -+ Handle::AerDir -+ } else if let Some(register_name) = path.strip_prefix("aer/") { -+ let register = -+ AerRegisterName::from_path(register_name).ok_or(Error::new(ENOENT))?; -+ if Self::find_pcie_extended_capability( -+ &self.pcie, -+ addr, -+ PCIE_EXTENDED_CAPABILITY_AER, -+ ) -+ .is_none() -+ { -+ return Err(Error::new(ENOENT)); -+ } -+ Handle::Aer { addr, register } -+ } else { -+ match path { -+ "channel" => { -+ if func.enabled { -+ return Err(Error::new(ENOLCK)); -+ } -+ func.inner.legacy_interrupt_line = crate::enable_function( -+ &self.pcie, -+ &mut func.endpoint_header, -+ &mut func.capabilities, -+ ); -+ func.enabled = true; -+ Handle::Channel { -+ addr, -+ st: ChannelState::AwaitingData, -+ } - } -- func.inner.legacy_interrupt_line = crate::enable_function( -- &self.pcie, -- &mut func.endpoint_header, -- &mut func.capabilities, -- ); -- func.enabled = true; -- Handle::Channel { -- addr, -- st: ChannelState::AwaitingData, -+ "bind" => { -+ let addr_str = format!("{}", addr); -+ if let Some(&owner_pid) = self.binds.get(&addr_str) { -+ log::info!( -+ "pcid: device {} already bound by pid {}", -+ addr_str, -+ owner_pid -+ ); -+ return Err(Error::new(EALREADY)); -+ } -+ let caller_pid = u32::try_from(ctx.pid).map_err(|_| Error::new(EINVAL))?; -+ self.binds.insert(addr_str.clone(), caller_pid); -+ log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid); -+ Handle::Bind { addr } - } -- } -- "bind" => { -- let addr_str = format!("{}", addr); -- if let Some(&owner_pid) = self.binds.get(&addr_str) { -- log::info!("pcid: device {} already bound by pid {}", addr_str, owner_pid); -- return Err(Error::new(EALREADY)); -- } -- let caller_pid = ctx.pid; -- self.binds.insert(addr_str.clone(), caller_pid); -- log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid); -- Handle::Bind { addr } -- } -- _ => return Err(Error::new(ENOENT)), -+ _ => return Err(Error::new(ENOENT)), -+ } - } - }) - } diff --git a/local/patches/bootloader/P2-live-preload-guard.patch.bak b/local/patches/bootloader/P2-live-preload-guard.patch.bak deleted file mode 100644 index 435afa79e..000000000 --- a/local/patches/bootloader/P2-live-preload-guard.patch.bak +++ /dev/null @@ -1,97 +0,0 @@ -diff --git a/src/main.rs b/src/main.rs -index b2e2736..a6a9474 100644 ---- a/src/main.rs -+++ b/src/main.rs -@@ -500,33 +500,62 @@ pub extern "C" fn main() -> ! { - - print!("live: 0/{} MiB", size / MIBI as u64); - -- let ptr = os.alloc_zeroed_page_aligned(size as usize); -- if ptr.is_null() { -- panic!("Failed to allocate memory for live"); -- } -- -- let live = unsafe { slice::from_raw_parts_mut(ptr, size as usize) }; -- -- let mut i = 0; -- for chunk in live.chunks_mut(MIBI) { -- print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); -- i += unsafe { -- fs.disk -- .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk) -- .expect("Failed to read live disk") as u64 -- }; -- } -- println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); -- -- println!("Switching to live disk"); -- unsafe { -- LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, size as usize))); -- } -+ let live_size = match usize::try_from(size) { -+ Ok(live_size) => live_size, -+ Err(_) => { -+ println!("\rlive: disabled (image too large for bootloader address space)"); -+ live = false; -+ 0 -+ } -+ }; - -- area_add(OsMemoryEntry { -- base: live.as_ptr() as u64, -- size: live.len() as u64, -- kind: OsMemoryKind::Reserved, -- }); -+ let ptr = if live { -+ os.alloc_zeroed_page_aligned(live_size) -+ } else { -+ ptr::null_mut() -+ }; -+ -+ if live && ptr.is_null() { -+ println!( -+ "\rlive: disabled (unable to allocate {} MiB upfront)", -+ size / MIBI as u64 -+ ); -+ live = false; -+ } -+ -+ let live = if live { -+ Some(unsafe { slice::from_raw_parts_mut(ptr, live_size) }) -+ } else { -+ println!("Continuing without live preload"); -+ None -+ }; -+ -+ if let Some(live) = live { -+ let mut i = 0; -+ for chunk in live.chunks_mut(MIBI) { -+ print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); -+ i += unsafe { -+ fs.disk -+ .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk) -+ .expect("Failed to read live disk") as u64 -+ }; -+ } -+ println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); -+ -+ println!("Switching to live disk"); -+ unsafe { -+ LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, live_size))); -+ } -+ -+ area_add(OsMemoryEntry { -+ base: live.as_ptr() as u64, -+ size: live.len() as u64, -+ kind: OsMemoryKind::Reserved, -+ }); -+ -+ Some(live) -+ } else { -+ None -+ } -- -- Some(live) - } else { - None - }; diff --git a/local/patches/bootloader/P3-uefi-live-image-safe-read.patch.bak b/local/patches/bootloader/P3-uefi-live-image-safe-read.patch.bak deleted file mode 100644 index c7f7c5688..000000000 --- a/local/patches/bootloader/P3-uefi-live-image-safe-read.patch.bak +++ /dev/null @@ -1,60 +0,0 @@ -diff --git a/src/os/uefi/device.rs b/src/os/uefi/device.rs -index 4b0bf31..90a97b8 100644 ---- a/src/os/uefi/device.rs -+++ b/src/os/uefi/device.rs -@@ -46,6 +46,8 @@ fn device_path_relation(a_path: &DevicePath, b_path: &DevicePath) -> DevicePath - } - - fn esp_live_image(esp_handle: Handle, esp_device_path: &DevicePath) -> Option> { -+ const MAX_LIVE_IMAGE_PRELOAD: usize = 128 * 1024 * 1024; -+ - let mut esp_fs = match FileSystem::handle_protocol(esp_handle) { - Ok(esp_fs) => esp_fs, - Err(err) => { -@@ -87,9 +89,37 @@ fn esp_live_image(esp_handle: Handle, esp_device_path: &DevicePath) -> Option read, -+ Err(err) => { -+ log::warn!( -+ "Failed while reading {}\\redox-live.iso: {:?}", -+ device_path_to_string(esp_device_path), -+ err -+ ); -+ return None; -+ } -+ }; -+ -+ if read == 0 { -+ break; -+ } - -- live_image.read_to_end(&mut buffer).unwrap(); -+ if buffer.len().saturating_add(read) > MAX_LIVE_IMAGE_PRELOAD { -+ log::warn!( -+ "Skipping {}\\redox-live.iso preload: file exceeds {} MiB safety limit", -+ device_path_to_string(esp_device_path), -+ MAX_LIVE_IMAGE_PRELOAD / 1024 / 1024 -+ ); -+ return None; -+ } -+ -+ buffer.extend_from_slice(&chunk[..read]); -+ } - - Some(buffer) - } -@@ -130,7 +160,7 @@ pub fn disk_device_priority() -> Vec { - return vec![DiskDevice { - handle: esp_handle, - // Support both a copy of livedisk.iso and a standalone redoxfs partition -- partition_offset: if &buffer[512..520] == b"EFI PART" { -+ partition_offset: if buffer.len() >= 520 && &buffer[512..520] == b"EFI PART" { - //TODO: get block from partition table - 2 * crate::MIBI as u64 - } else { diff --git a/local/patches/bootloader/P4-live-large-iso-boot.patch.bak b/local/patches/bootloader/P4-live-large-iso-boot.patch.bak deleted file mode 100644 index a341239ec..000000000 --- a/local/patches/bootloader/P4-live-large-iso-boot.patch.bak +++ /dev/null @@ -1,392 +0,0 @@ -diff --git a/src/arch/x86/mod.rs b/src/arch/x86/mod.rs -index bda3f5d..55889df 100644 ---- a/src/arch/x86/mod.rs -+++ b/src/arch/x86/mod.rs -@@ -3,10 +3,15 @@ use crate::os::Os; - pub(crate) mod x32; - pub(crate) mod x64; - --pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { -+pub unsafe fn paging_create( -+ os: &impl Os, -+ kernel_phys: u64, -+ kernel_size: u64, -+ identity_map_end: u64, -+) -> Option { - unsafe { - if crate::KERNEL_64BIT { -- x64::paging_create(os, kernel_phys, kernel_size) -+ x64::paging_create(os, kernel_phys, kernel_size, identity_map_end) - } else { - x32::paging_create(os, kernel_phys, kernel_size) - } -diff --git a/src/arch/x86/x64.rs b/src/arch/x86/x64.rs -index a0a275a..fcf309d 100644 ---- a/src/arch/x86/x64.rs -+++ b/src/arch/x86/x64.rs -@@ -29,7 +29,12 @@ const PRESENT: u64 = 1; - const WRITABLE: u64 = 1 << 1; - const LARGE: u64 = 1 << 7; - --pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) -> Option { -+pub unsafe fn paging_create( -+ os: &impl Os, -+ kernel_phys: u64, -+ kernel_size: u64, -+ identity_map_end: u64, -+) -> Option { - unsafe { - // Create PML4 - let pml4 = paging_allocate(os)?; -@@ -42,8 +47,14 @@ pub unsafe fn paging_create(os: &impl Os, kernel_phys: u64, kernel_size: u64) - - pml4[0] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; - pml4[256] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; - -- // Identity map 8 GiB using 2 MiB pages -- for pdp_i in 0..8 { -+ let mut needed_pdp = identity_map_end.div_ceil(0x4000_0000); -+ if needed_pdp == 0 { -+ needed_pdp = 1; -+ } -+ assert!(needed_pdp <= pdp.len() as u64, "identity map end exceeds paging span"); -+ -+ // Identity map required physical range using 2 MiB pages -+ for pdp_i in 0..needed_pdp as usize { - let pd = paging_allocate(os)?; - pdp[pdp_i] = pd.as_ptr() as u64 | WRITABLE | PRESENT; - for pd_i in 0..pd.len() { -diff --git a/src/main.rs b/src/main.rs -index 78dabb0..fd8eb81 100644 ---- a/src/main.rs -+++ b/src/main.rs -@@ -62,6 +62,10 @@ pub static mut KERNEL_64BIT: bool = false; - - pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None; - -+fn region_end(base: u64, size: u64) -> u64 { -+ base.saturating_add(size).next_multiple_of(0x1000) -+} -+ - struct SliceWriter<'a> { - slice: &'a mut [u8], - i: usize, -@@ -645,9 +649,6 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) { - (memory.len() as u64, memory.as_mut_ptr() as u64) - }; - -- let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) } -- .expect("Failed to set up paging"); -- - let max_env_size = 64 * KIBI; - let mut env_size = max_env_size; - let env_base = os.alloc_zeroed_page_aligned(env_size); -@@ -655,6 +656,28 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) { - panic!("Failed to allocate memory for stack"); - } - -+ let mut identity_map_end = region_end(kernel.as_ptr() as u64, kernel.len() as u64) -+ .max(region_end(stack_base as u64, stack_size as u64)) -+ .max(region_end(bootstrap_base, bootstrap_size)) -+ .max(region_end(env_base as u64, max_env_size as u64)); -+ -+ if let Some(ref live) = live_opt { -+ identity_map_end = identity_map_end.max(region_end( -+ live.as_ptr() as u64, -+ live.len() as u64, -+ )); -+ } -+ -+ let page_phys = unsafe { -+ paging_create( -+ os, -+ kernel.as_ptr() as u64, -+ kernel.len() as u64, -+ identity_map_end, -+ ) -+ } -+ .expect("Failed to set up paging"); -+ - { - let mut w = SliceWriter { - slice: unsafe { slice::from_raw_parts_mut(env_base, max_env_size) }, -diff --git a/src/os/uefi/device.rs b/src/os/uefi/device.rs -index 0b7991f..554d88e 100644 ---- a/src/os/uefi/device.rs -+++ b/src/os/uefi/device.rs -@@ -13,6 +13,160 @@ use uefi_std::{fs::FileSystem, loaded_image::LoadedImage, proto::Protocol}; - - use super::disk::{DiskEfi, DiskOrFileEfi}; - -+#[derive(Clone, Copy)] -+struct GptPartitionInfo { -+ first_lba: u64, -+ last_lba: u64, -+} -+ -+fn read_u32_le(bytes: &[u8]) -> Option { -+ Some(u32::from_le_bytes(bytes.get(..4)?.try_into().ok()?)) -+} -+ -+fn read_u64_le(bytes: &[u8]) -> Option { -+ Some(u64::from_le_bytes(bytes.get(..8)?.try_into().ok()?)) -+} -+ -+fn decode_utf16_name(bytes: &[u8]) -> Option { -+ let mut units = Vec::new(); -+ for chunk in bytes.chunks_exact(2) { -+ let unit = u16::from_le_bytes([chunk[0], chunk[1]]); -+ if unit == 0 { -+ break; -+ } -+ units.push(unit); -+ } -+ String::from_utf16(&units).ok() -+} -+ -+fn select_partition(best: &mut Option, candidate: GptPartitionInfo) { -+ match best { -+ Some(current) if current.last_lba.saturating_sub(current.first_lba) >= candidate.last_lba.saturating_sub(candidate.first_lba) => {} -+ _ => *best = Some(candidate), -+ } -+} -+ -+fn parse_gpt_partition_offset_from_bytes(data: &[u8], block_size: usize) -> Option { -+ let header_offset = block_size; -+ let header = data.get(header_offset..header_offset + 92)?; -+ if header.get(..8)? != b"EFI PART" { -+ return None; -+ } -+ -+ let entries_lba = read_u64_le(header.get(72..80)?)?; -+ let entry_count = read_u32_le(header.get(80..84)?)? as usize; -+ let entry_size = read_u32_le(header.get(84..88)?)? as usize; -+ if entry_size < 128 { -+ return None; -+ } -+ -+ let entries_offset = entries_lba.checked_mul(block_size as u64)? as usize; -+ let mut redox_partition = None; -+ let mut fallback_partition = None; -+ -+ for index in 0..entry_count { -+ let entry_offset = entries_offset.checked_add(index.checked_mul(entry_size)?)?; -+ let entry = data.get(entry_offset..entry_offset + entry_size)?; -+ if entry.get(..16)?.iter().all(|byte| *byte == 0) { -+ continue; -+ } -+ -+ let first_lba = read_u64_le(entry.get(32..40)?)?; -+ let last_lba = read_u64_le(entry.get(40..48)?)?; -+ if first_lba == 0 || last_lba < first_lba { -+ continue; -+ } -+ -+ let partition = GptPartitionInfo { first_lba, last_lba }; -+ let name = decode_utf16_name(entry.get(56..128)?).unwrap_or_default(); -+ if name == "REDOX" { -+ redox_partition = Some(partition); -+ break; -+ } -+ -+ select_partition(&mut fallback_partition, partition); -+ } -+ -+ redox_partition -+ .or(fallback_partition) -+ .map(|partition| partition.first_lba * block_size as u64) -+} -+ -+fn parse_gpt_partition_offset_from_parts( -+ entries: &[u8], -+ entry_count: usize, -+ entry_size: usize, -+ block_size: usize, -+) -> Option { -+ let mut redox_partition = None; -+ let mut fallback_partition = None; -+ -+ for index in 0..entry_count { -+ let entry_offset = index.checked_mul(entry_size)?; -+ let entry = entries.get(entry_offset..entry_offset + entry_size)?; -+ if entry.get(..16)?.iter().all(|byte| *byte == 0) { -+ continue; -+ } -+ -+ let first_lba = read_u64_le(entry.get(32..40)?)?; -+ let last_lba = read_u64_le(entry.get(40..48)?)?; -+ if first_lba == 0 || last_lba < first_lba { -+ continue; -+ } -+ -+ let partition = GptPartitionInfo { first_lba, last_lba }; -+ let name = decode_utf16_name(entry.get(56..128)?).unwrap_or_default(); -+ if name == "REDOX" { -+ redox_partition = Some(partition); -+ break; -+ } -+ -+ select_partition(&mut fallback_partition, partition); -+ } -+ redox_partition -+ .or(fallback_partition) -+ .map(|partition| partition.first_lba * block_size as u64) -+} -+ -+fn gpt_partition_offset_from_buffer(data: &[u8]) -> Option { -+ parse_gpt_partition_offset_from_bytes(data, 512) -+} -+ -+fn gpt_partition_offset_from_disk(disk: &mut DiskEfi) -> Option { -+ const GPT_SECTOR_SIZE: usize = 512; -+ -+ if disk.media_block_size() == 0 { -+ return None; -+ } -+ -+ let mut boot_region = vec![0_u8; 2048]; -+ disk.read_bytes(0, &mut boot_region).ok()?; -+ let header = boot_region.get(GPT_SECTOR_SIZE..GPT_SECTOR_SIZE + 92)?; -+ if header.get(..8)? != b"EFI PART" { -+ return None; -+ } -+ -+ let entries_lba = read_u64_le(header.get(72..80)?)?; -+ let entry_count = read_u32_le(header.get(80..84)?)? as usize; -+ let entry_size = read_u32_le(header.get(84..88)?)? as usize; -+ if entry_size < 128 { -+ return None; -+ } -+ -+ let entries_bytes = entry_count.checked_mul(entry_size)?; -+ let entries_offset = entries_lba.checked_mul(GPT_SECTOR_SIZE as u64)?; -+ let mut entries = vec![0_u8; entries_bytes]; -+ disk.read_bytes(entries_offset, &mut entries).ok()?; -+ -+ parse_gpt_partition_offset_from_parts(&entries, entry_count, entry_size, GPT_SECTOR_SIZE) -+} -+ - #[derive(Debug)] - enum DevicePathRelation { - This, -@@ -131,12 +285,7 @@ pub fn disk_device_priority() -> Vec { - return vec![DiskDevice { - handle: esp_handle, - // Support both a copy of livedisk.iso and a standalone redoxfs partition -- partition_offset: if &buffer[512..520] == b"EFI PART" { -- //TODO: get block from partition table -- 2 * crate::MIBI as u64 -- } else { -- 0 -- }, -+ partition_offset: gpt_partition_offset_from_buffer(&buffer).unwrap_or(0), - disk: DiskOrFileEfi::File(buffer), - device_path: esp_device_path, - file_path: Some("redox-live.iso"), -@@ -154,7 +303,7 @@ pub fn disk_device_priority() -> Vec { - }; - let mut devices = Vec::with_capacity(handles.len()); - for handle in handles { -- let disk = match DiskEfi::handle_protocol(handle) { -+ let mut disk = match DiskEfi::handle_protocol(handle) { - Ok(ok) => ok, - Err(err) => { - log::warn!( -@@ -182,14 +331,15 @@ pub fn disk_device_priority() -> Vec { - } - }; - -+ let partition_offset = if disk.0.Media.LogicalPartition { -+ 0 -+ } else { -+ gpt_partition_offset_from_disk(&mut disk).unwrap_or(2 * crate::MIBI as u64) -+ }; -+ - devices.push(DiskDevice { - handle, -- partition_offset: if disk.0.Media.LogicalPartition { -- 0 -- } else { -- //TODO: get block from partition table -- 2 * crate::MIBI as u64 -- }, -+ partition_offset, - disk: DiskOrFileEfi::Disk(disk), - device_path, - file_path: None, -diff --git a/src/os/uefi/disk.rs b/src/os/uefi/disk.rs -index 3f920bb..4d109f8 100644 ---- a/src/os/uefi/disk.rs -+++ b/src/os/uefi/disk.rs -@@ -117,3 +117,43 @@ impl Disk for DiskEfi { - Err(Error::new(EIO)) - } - } -+ -+impl DiskEfi { -+ pub fn media_block_size(&self) -> usize { -+ self.0.Media.BlockSize as usize -+ } -+ -+ pub fn read_bytes(&mut self, offset: u64, buffer: &mut [u8]) -> Result<()> { -+ let block_size = self.media_block_size(); -+ if block_size == 0 || block_size > self.1.len() { -+ return Err(Error::new(EINVAL)); -+ } -+ -+ let scratch = &mut self.1[..block_size]; -+ let mut copied = 0usize; -+ -+ while copied < buffer.len() { -+ let absolute = offset as usize + copied; -+ let lba = (absolute / block_size) as u64; -+ let in_block = absolute % block_size; -+ -+ match (self.0.ReadBlocks)( -+ self.0, -+ self.0.Media.MediaId, -+ lba, -+ block_size, -+ scratch.as_mut_ptr(), -+ ) { -+ status if status.is_success() => { -+ let chunk_len = core::cmp::min(block_size - in_block, buffer.len() - copied); -+ buffer[copied..copied + chunk_len] -+ .copy_from_slice(&scratch[in_block..in_block + chunk_len]); -+ copied += chunk_len; -+ } -+ _ => return Err(Error::new(EIO)), -+ } -+ } -+ -+ Ok(()) -+ } -+} -diff --git a/src/os/uefi/mod.rs b/src/os/uefi/mod.rs -index c79266e..86235a4 100644 ---- a/src/os/uefi/mod.rs -+++ b/src/os/uefi/mod.rs -@@ -47,17 +47,19 @@ pub(crate) fn alloc_zeroed_page_aligned(size: usize) -> *mut u8 { - let ptr = { - // Max address mapped by src/arch paging code (8 GiB) - let mut ptr = 0x2_0000_0000; -- status_to_result((std::system_table().BootServices.AllocatePages)( -- 1, // AllocateMaxAddress -- MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list -+ if status_to_result((std::system_table().BootServices.AllocatePages)( -+ 0, // AllocateAnyPages -+ MemoryType::EfiLoaderData, - pages, - &mut ptr, - )) -- .unwrap(); -+ .is_err() -+ { -+ return ptr::null_mut(); -+ } - ptr as *mut u8 - }; - -- assert!(!ptr.is_null()); - unsafe { ptr::write_bytes(ptr, 0, pages * page_size) }; - ptr - } diff --git a/local/patches/kernel/P2-redbear-os-branding.patch.bak b/local/patches/kernel/P2-redbear-os-branding.patch.bak deleted file mode 100644 index f0d36c1f7..000000000 --- a/local/patches/kernel/P2-redbear-os-branding.patch.bak +++ /dev/null @@ -1,65 +0,0 @@ -# Red Bear OS branding in kernel start messages -# Changes "Redox OS" to "RedBear OS" in architecture start files -# Adds device init logging milestones in x86_shared start path - -diff --git a/src/arch/aarch64/start.rs b/src/arch/aarch64/start.rs -index e1c8cfb4..65e3fe33 100644 ---- a/src/arch/aarch64/start.rs -+++ b/src/arch/aarch64/start.rs -@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! { - dtb::serial::init_early(dtb); - } - -- info!("Redox OS starting..."); -+ info!("RedBear OS starting..."); - args.print(); - - // Initialize RMM -diff --git a/src/arch/riscv64/start.rs b/src/arch/riscv64/start.rs -index 2551968f..a825536a 100644 ---- a/src/arch/riscv64/start.rs -+++ b/src/arch/riscv64/start.rs -@@ -97,7 +97,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! { - init_early(dtb); - } - -- info!("Redox OS starting..."); -+ info!("RedBear OS starting..."); - args.print(); - - if let Some(dtb) = &dtb { -diff --git a/src/arch/x86_shared/start.rs b/src/arch/x86_shared/start.rs -index 7a7c0ae8..62f9523c 100644 ---- a/src/arch/x86_shared/start.rs -+++ b/src/arch/x86_shared/start.rs -@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! { - // Set up graphical debug - graphical_debug::init(args.env()); - -- info!("Redox OS starting..."); -+ info!("RedBear OS starting..."); - args.print(); - - // Set up GDT -@@ -127,16 +127,21 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! { - - // Initialize devices - device::init(); -+ info!("kernel: device init complete (PIC + LAPIC)"); - - // Read ACPI tables, starts APs - if cfg!(feature = "acpi") { - crate::acpi::init(args.acpi_rsdp()); -+ info!("kernel: ACPI tables parsed"); - - device::init_after_acpi(); -+ info!("kernel: IOAPIC init complete"); - } - crate::profiling::init(); - - // Initialize all of the non-core devices not otherwise needed to complete initialization - device::init_noncore(); -+ info!("kernel: timer init complete, entering userspace"); - - args.bootstrap() - }; diff --git a/local/patches/kernel/P7-proc-setname.patch.bak b/local/patches/kernel/P7-proc-setname.patch.bak deleted file mode 100644 index 70821737b..000000000 --- a/local/patches/kernel/P7-proc-setname.patch.bak +++ /dev/null @@ -1,47 +0,0 @@ -diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs ---- a/src/scheme/proc.rs -+++ b/src/scheme/proc.rs -@@ -147,6 +147,7 @@ enum ContextHandle { - Priority, - SchedAffinity, - SchedPolicy, -+ Name, - - MmapMinAddr(Arc), - } -@@ -267,6 +268,7 @@ impl ProcScheme { - "sched-affinity" => (ContextHandle::SchedAffinity, true), - // TODO: Switch this kernel-local proc handle over to a stable upstream - // redox_syscall ProcCall::SetSchedPolicy opcode once that lands. - "sched-policy" => (ContextHandle::SchedPolicy, false), -+ "name" => (ContextHandle::Name, false), - "status" => (ContextHandle::Status { privileged: false }, false), - _ if path.starts_with("auth-") => { - let nonprefix = &path["auth-".len()..]; -@@ -1218,6 +1220,16 @@ impl ContextHandle { - Ok(2) - } -+ ContextHandle::Name => { -+ let mut name_buf = [0u8; 32]; -+ let len = buf.copy_common_bytes_to_slice(&mut name_buf[..31]).unwrap_or(0); -+ let mut context = context.write(token.token()); -+ context.name.clear(); -+ if let Ok(s) = core::str::from_utf8(&name_buf[..len]) { -+ context.name.push_str(s); -+ } -+ Ok(len) -+ } - ContextHandle::Status { privileged } => { - let mut args = buf.usizes(); - -@@ -1532,6 +1544,10 @@ impl ContextHandle { - let data = [context.sched_policy as u8, context.sched_rt_priority]; - buf.copy_common_bytes_from_slice(&data) - } -+ ContextHandle::Name => { -+ let context = context.read(token.token()); -+ buf.copy_common_bytes_from_slice(context.name.as_bytes()) -+ } - ContextHandle::Status { .. } => { - let status = { - let context = context.read(token.token()); diff --git a/local/patches/kernel/P7-proc-setpriority.patch.bak b/local/patches/kernel/P7-proc-setpriority.patch.bak deleted file mode 100644 index e65a95bd7..000000000 --- a/local/patches/kernel/P7-proc-setpriority.patch.bak +++ /dev/null @@ -1,70 +0,0 @@ -diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs ---- a/src/scheme/proc.rs -+++ b/src/scheme/proc.rs -@@ -145,8 +145,9 @@ enum ContextHandle { - // TODO: Remove this once openat is implemented, or allow openat-via-dup via e.g. the top-level - // directory. - OpenViaDup, -+ Priority, - SchedAffinity, - SchedPolicy, - Name, - - MmapMinAddr(Arc), -@@ -160,6 +161,17 @@ pub struct ProcScheme; - static NEXT_ID: AtomicUsize = AtomicUsize::new(1); - static HANDLES: RwLock> = - RwLock::new(HashMap::with_hasher(DefaultHashBuilder::new())); -+ -+const NICE_MIN: i32 = -20; -+const NICE_MAX: i32 = 19; -+ -+fn nice_to_kernel_prio(nice: i32) -> usize { -+ (nice.saturating_add(20)).clamp(0, 39) as usize -+} -+ -+fn kernel_prio_to_nice(prio: usize) -> i32 { -+ (prio.min(39) as i32) - 20 -+} - - #[cfg(feature = "debugger")] - #[allow(dead_code)] - pub fn foreach_addrsp( -@@ -253,6 +265,7 @@ impl ProcScheme { - "sighandler" => (ContextHandle::Sighandler, false), - "start" => (ContextHandle::Start, false), - "open_via_dup" => (ContextHandle::OpenViaDup, false), -+ "priority" => (ContextHandle::Priority, false), - "mmap-min-addr" => ( - ContextHandle::MmapMinAddr(Arc::clone( - context -@@ -1191,6 +1204,17 @@ impl ContextHandle { - - Ok(size_of_val(&mask)) - } -+ Self::Priority => { -+ let nice = unsafe { buf.read_exact::()? }; -+ if !(NICE_MIN..=NICE_MAX).contains(&nice) { -+ return Err(Error::new(EINVAL)); -+ } -+ -+ context -+ .write(token.token()) -+ .set_sched_other_prio(nice_to_kernel_prio(nice)); -+ -+ Ok(size_of::()) -+ } - Self::SchedPolicy => { - if buf.len() != 2 { - return Err(Error::new(EINVAL)); -@@ -1522,6 +1546,10 @@ impl ContextHandle { - - buf.copy_exactly(crate::cpu_set::mask_as_bytes(&mask))?; - Ok(size_of_val(&mask)) -+ } -+ ContextHandle::Priority => { -+ let nice = kernel_prio_to_nice(context.read(token.token()).prio); -+ buf.copy_common_bytes_from_slice(&nice.to_ne_bytes()) - } - ContextHandle::SchedPolicy => { - let context = context.read(token.token()); diff --git a/local/patches/kernel/P8-load-balance.patch.bak b/local/patches/kernel/P8-load-balance.patch.bak deleted file mode 100644 index 8883c9921..000000000 --- a/local/patches/kernel/P8-load-balance.patch.bak +++ /dev/null @@ -1,146 +0,0 @@ -diff --git a/src/percpu.rs b/src/percpu.rs ---- a/src/percpu.rs -+++ b/src/percpu.rs -@@ -29,12 +29,14 @@ pub struct PerCpuSched { - pub run_queues_lock: AtomicBool, - pub balance: Cell<[usize; RUN_QUEUE_COUNT]>, - pub last_queue: Cell, -+ pub last_balance_time: Cell, - } - - impl PerCpuSched { - pub const fn new() -> Self { - const EMPTY: VecDeque = VecDeque::new(); - Self { - run_queues: SyncUnsafeCell::new([EMPTY; RUN_QUEUE_COUNT]), - run_queues_lock: AtomicBool::new(false), - balance: Cell::new([0; RUN_QUEUE_COUNT]), - last_queue: Cell::new(0), -+ last_balance_time: Cell::new(0), - } - } -diff --git a/src/context/switch.rs b/src/context/switch.rs ---- a/src/context/switch.rs -+++ b/src/context/switch.rs -@@ -33,6 +33,8 @@ const SCHED_PRIO_TO_WEIGHT: [usize; 40] = [ - 70, 56, 45, 36, 29, 23, 18, 15, - ]; - -+const LOAD_BALANCE_INTERVAL_NS: u128 = 100_000_000; -+ - static SCHED_STEAL_COUNT: AtomicUsize = AtomicUsize::new(0); -@@ -101,6 +103,9 @@ pub fn tick(token: &mut CleanLockToken) { - let new_ticks = ticks_cell.get() + 1; - ticks_cell.set(new_ticks); - -+ let balance_time = crate::time::monotonic(token); -+ maybe_balance_queues(token, percpu, balance_time); -+ - // Trigger a context switch after every 3 ticks. - if new_ticks >= 3 { - switch(token); -@@ -427,6 +432,92 @@ fn steal_work( - - None - } -+ -+fn queue_depth(percpu: &PercpuBlock) -> usize { -+ let mut sched_lock = SchedQueuesLock::new(&percpu.sched); -+ unsafe { -+ sched_lock -+ .queues_mut() -+ .iter() -+ .map(|queue| queue.len()) -+ .sum() -+ } -+} -+ -+fn migrate_one_context( -+ token: &mut CleanLockToken, -+ source_id: LogicalCpuId, -+ target_id: LogicalCpuId, -+ switch_time: u128, -+) -> bool { -+ let Some(source) = get_percpu_block(source_id) else { -+ return false; -+ }; -+ let Some(target) = get_percpu_block(target_id) else { -+ return false; -+ }; -+ -+ let source_idle = source.switch_internals.idle_context(); -+ let moved = { -+ let mut source_lock = SchedQueuesLock::new(&source.sched); -+ let source_queues = unsafe { source_lock.queues_mut() }; -+ pop_movable_context(token, source_queues, target_id, switch_time, &source_idle) -+ }; -+ -+ let Some((prio, context_ref)) = moved else { -+ return false; -+ }; -+ -+ let mut target_lock = SchedQueuesLock::new(&target.sched); -+ unsafe { -+ target_lock.queues_mut()[prio].push_back(context_ref); -+ } -+ true -+} -+ -+fn maybe_balance_queues(token: &mut CleanLockToken, percpu: &PercpuBlock, balance_time: u128) { -+ if crate::cpu_count() <= 1 || percpu.cpu_id != LogicalCpuId::BSP { -+ return; -+ } -+ if balance_time.saturating_sub(percpu.sched.last_balance_time.get()) < LOAD_BALANCE_INTERVAL_NS -+ { -+ return; -+ } -+ -+ percpu.sched.last_balance_time.set(balance_time); -+ -+ let mut depths = Vec::new(); -+ let mut total_depth = 0usize; -+ for raw_id in 0..crate::cpu_count() { -+ let cpu_id = LogicalCpuId::new(raw_id); -+ let Some(cpu_percpu) = get_percpu_block(cpu_id) else { -+ continue; -+ }; -+ let depth = queue_depth(cpu_percpu); -+ total_depth += depth; -+ depths.push((cpu_id, depth)); -+ } -+ -+ if depths.len() <= 1 || total_depth == 0 { -+ return; -+ } -+ -+ let avg_depth = (total_depth + depths.len().saturating_sub(1)) / depths.len(); -+ -+ for target_index in 0..depths.len() { -+ if depths[target_index].1 != 0 { -+ continue; -+ } -+ -+ let mut source_index = None; -+ let mut source_depth = 0usize; -+ for (idx, &(_, depth)) in depths.iter().enumerate() { -+ if idx == target_index { -+ continue; -+ } -+ if depth > avg_depth + 1 && depth > source_depth { -+ source_index = Some(idx); -+ source_depth = depth; -+ } -+ } -+ -+ let Some(source_index) = source_index else { -+ continue; -+ }; -+ -+ let source_id = depths[source_index].0; -+ let target_id = depths[target_index].0; -+ if migrate_one_context(token, source_id, target_id, balance_time) { -+ depths[source_index].1 = depths[source_index].1.saturating_sub(1); -+ depths[target_index].1 += 1; -+ } -+ } -+} diff --git a/local/patches/kernel/P8-work-stealing.patch.bak b/local/patches/kernel/P8-work-stealing.patch.bak deleted file mode 100644 index e4c9a25d6..000000000 --- a/local/patches/kernel/P8-work-stealing.patch.bak +++ /dev/null @@ -1,190 +0,0 @@ -diff --git a/src/percpu.rs b/src/percpu.rs ---- a/src/percpu.rs -+++ b/src/percpu.rs -@@ -100,6 +100,14 @@ static ALL_PERCPU_BLOCKS: [AtomicPtr; MAX_CPU_COUNT as usize] = - pub unsafe fn init_tlb_shootdown(id: LogicalCpuId, block: *mut PercpuBlock) { - ALL_PERCPU_BLOCKS[id.get() as usize].store(block, Ordering::Release) - } -+ -+pub fn get_percpu_block(id: LogicalCpuId) -> Option<&'static PercpuBlock> { -+ unsafe { -+ ALL_PERCPU_BLOCKS[id.get() as usize] -+ .load(Ordering::Acquire) -+ .as_ref() -+ } -+} - - pub fn get_all_stats() -> Vec<(LogicalCpuId, CpuStatsData)> { -diff --git a/src/context/switch.rs b/src/context/switch.rs ---- a/src/context/switch.rs -+++ b/src/context/switch.rs -@@ -7,15 +7,15 @@ use crate::{ - self, arch, idle_contexts, idle_contexts_try, run_contexts, ArcContextLockWriteGuard, - Context, ContextLock, SchedPolicy, WeakContextRef, RUN_QUEUE_COUNT, - }, -- cpu_set::LogicalCpuId, -+ cpu_set::{LogicalCpuId, LogicalCpuSet}, - cpu_stats::{self, CpuState}, -- percpu::{PerCpuSched, PercpuBlock}, -+ percpu::{get_percpu_block, PerCpuSched, PercpuBlock}, - sync::{ArcRwLockWriteGuard, CleanLockToken, LockToken, L1, L4}, - }; - use alloc::{sync::Arc, vec::Vec}; - use core::{ - cell::{Cell, RefCell}, - hint, mem, -- sync::atomic::Ordering, -+ sync::atomic::{AtomicUsize, Ordering}, - }; - use syscall::PtraceFlags; -@@ -+static SCHED_STEAL_COUNT: AtomicUsize = AtomicUsize::new(0); -+ -+fn assign_context_to_cpu(context: &mut Context, cpu_id: LogicalCpuId) { -+ context.sched_affinity = LogicalCpuSet::empty(); -+ context.sched_affinity.atomic_set(cpu_id); -+} -@@ -+fn pop_movable_context( -+ token: &mut CleanLockToken, -+ queues: &mut [alloc::collections::VecDeque; RUN_QUEUE_COUNT], -+ target_cpu: LogicalCpuId, -+ switch_time: u128, -+ idle_context: &Arc, -+) -> Option<(usize, WeakContextRef)> { -+ for prio in 0..RUN_QUEUE_COUNT { -+ let len = queues[prio].len(); -+ for _ in 0..len { -+ let Some(context_ref) = queues[prio].pop_front() else { -+ break; -+ }; -+ let Some(context_lock) = context_ref.upgrade() else { -+ continue; -+ }; -+ if Arc::ptr_eq(&context_lock, idle_context) { -+ queues[prio].push_back(context_ref); -+ continue; -+ } -+ -+ let mut context_guard = unsafe { context_lock.write_arc() }; -+ let sw = unsafe { update_stealable(&mut context_guard, switch_time) }; -+ if let UpdateResult::CanSwitch = sw { -+ assign_context_to_cpu(&mut context_guard, target_cpu); -+ let moved_ref = WeakContextRef(Arc::downgrade(ArcContextLockWriteGuard::rwlock( -+ &context_guard, -+ ))); -+ drop(context_guard); -+ return Some((prio, moved_ref)); -+ } -+ -+ if matches!(sw, UpdateResult::Blocked) { -+ idle_contexts(token.downgrade()).push_back(context_ref); -+ } else { -+ queues[prio].push_back(context_ref); -+ } -+ } -+ } -+ -+ None -+} -+ -+fn steal_work( -+ token: &mut CleanLockToken, -+ cpu_id: LogicalCpuId, -+ switch_time: u128, -+) -> Option { -+ let cpu_count = crate::cpu_count(); -+ if cpu_count <= 1 { -+ return None; -+ } -+ -+ for offset in 1..cpu_count { -+ let victim_id = LogicalCpuId::new((cpu_id.get() + offset) % cpu_count); -+ let Some(victim) = get_percpu_block(victim_id) else { -+ continue; -+ }; -+ -+ let victim_idle = victim.switch_internals.idle_context(); -+ let mut victim_lock = SchedQueuesLock::new(&victim.sched); -+ let victim_queues = unsafe { victim_lock.queues_mut() }; -+ -+ for prio in 0..RUN_QUEUE_COUNT { -+ let len = victim_queues[prio].len(); -+ for _ in 0..len { -+ let Some(context_ref) = victim_queues[prio].pop_front() else { -+ break; -+ }; -+ let Some(context_lock) = context_ref.upgrade() else { -+ continue; -+ }; -+ if Arc::ptr_eq(&context_lock, &victim_idle) { -+ victim_queues[prio].push_back(context_ref); -+ continue; -+ } -+ -+ let mut context_guard = unsafe { context_lock.write_arc() }; -+ let sw = unsafe { update_stealable(&mut context_guard, switch_time) }; -+ if let UpdateResult::CanSwitch = sw { -+ assign_context_to_cpu(&mut context_guard, cpu_id); -+ SCHED_STEAL_COUNT.fetch_add(1, Ordering::Relaxed); -+ return Some(context_guard); -+ } -+ -+ if matches!(sw, UpdateResult::Blocked) { -+ idle_contexts(token.downgrade()).push_back(context_ref); -+ } else { -+ victim_queues[prio].push_back(context_ref); -+ } -+ } -+ } -+ } -+ -+ None -+} -+ -+unsafe fn update_stealable(context: &mut Context, switch_time: u128) -> UpdateResult { -+ if context.running { -+ return UpdateResult::Skip; -+ } -+ if context.status.is_soft_blocked() -+ && let Some(wake) = context.wake -+ && switch_time >= wake -+ { -+ context.wake = None; -+ context.unblock_no_ipi(); -+ } -+ if context.status.is_runnable() { -+ UpdateResult::CanSwitch -+ } else { -+ UpdateResult::Blocked -+ } -+} -@@ -360,6 +469,10 @@ fn wakeup_contexts(token: &mut CleanLockToken, percpu: &PercpuBlock, switch_time - let mut sched_lock = SchedQueuesLock::new(&percpu.sched); - let run_queues = unsafe { sched_lock.queues_mut() }; - for (prio, context_ref) in wakeups { -+ if let Some(context_lock) = context_ref.upgrade() { -+ let mut context_guard = unsafe { context_lock.write_arc() }; -+ assign_context_to_cpu(&mut context_guard, percpu.cpu_id); -+ } - run_queues[prio].push_back(context_ref); - } - } -@@ -559,6 +672,16 @@ fn select_next_context( - ); - return Ok(Some(next_context_guard)); - } -+ -+ if let Some(next_context_guard) = steal_work(token, cpu_id, switch_time) { -+ queue_previous_context( -+ token, -+ percpu, -+ &prev_context_lock, -+ prev_context_guard, -+ &idle_context, -+ ); -+ return Ok(Some(next_context_guard)); -+ } - - let global_next = { - let contexts_data = run_contexts(token.token()); diff --git a/local/patches/kernel/redox.patch.bak b/local/patches/kernel/redox.patch.bak deleted file mode 100644 index 7977f2f27..000000000 --- a/local/patches/kernel/redox.patch.bak +++ /dev/null @@ -1,147 +0,0 @@ -diff --git a/src/acpi/madt/mod.rs b/src/acpi/madt/mod.rs -index 3159b9c4..c691eb8d 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, -+ pub flags: u16, -+ pub nmi_pin: u8, -+} -+ -+/// 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, -+ pub flags: u16, -+ pub nmi_pin: u8, -+ _reserved2: u8, -+} -+ -+const _: () = assert!(size_of::() == 4); -+const _: () = assert!(size_of::() == 10); -+const _: () = assert!(size_of::() == 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,44 @@ impl Iterator for MadtIter { - MadtEntry::InvalidGicd(entry_len) - } - } -+ 0x9 => { -+ if entry_len == size_of::() + 2 { -+ MadtEntry::LocalX2Apic(unsafe { -+ &*((self.sdt.data_address() + self.i + 2) as *const MadtLocalX2Apic) -+ }) -+ } else { -+ MadtEntry::InvalidLocalX2Apic(entry_len) -+ } -+ } -+ 0x4 => { -+ if entry_len == size_of::() + 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::() + 2 { -+ MadtEntry::LapicAddressOverride(unsafe { -+ &*((self.sdt.data_address() + self.i + 2) -+ as *const MadtLapicAddressOverride) -+ }) -+ } else { -+ MadtEntry::InvalidLapicAddressOverride(entry_len) -+ } -+ } -+ 0xA => { -+ if entry_len == size_of::() + 2 { -+ MadtEntry::LocalX2ApicNmi(unsafe { -+ &*((self.sdt.data_address() + self.i + 2) -+ as *const MadtLocalX2ApicNmi) -+ }) -+ } else { -+ MadtEntry::InvalidLocalX2ApicNmi(entry_len) -+ } -+ } - _ => MadtEntry::Unknown(entry_type), - }; - - -diff --git a/src/devices/graphical_debug/mod.rs b/src/devices/graphical_debug/mod.rs -index b701c9a8..00cc984d 100644 ---- a/src/devices/graphical_debug/mod.rs -+++ b/src/devices/graphical_debug/mod.rs -@@ -59,7 +59,12 @@ pub fn init(env: &[u8]) { - ); - - let debug_display = DebugDisplay::new(width, height, stride, virt as *mut u32); -- *DEBUG_DISPLAY.lock() = Some(debug_display); -+ // FIXME: Writing to the framebuffer during early boot causes a hang on some -+ // QEMU configurations (virtio-vga, ramfb). The bootloader maps the framebuffer -+ // with default caching; the kernel remaps it with write-combining in memory::init(). -+ // Early kernel access before that remap appears to stall. Deferring DEBUG_DISPLAY -+ // setup avoids the hang; userspace vesad/fbbootlogd handles graphical output. -+ // *DEBUG_DISPLAY.lock() = Some(debug_display); - } - - #[allow(unused)] diff --git a/local/patches/libwayland/redox.patch.bak b/local/patches/libwayland/redox.patch.bak deleted file mode 100644 index 5aa9ffdd5..000000000 --- a/local/patches/libwayland/redox.patch.bak +++ /dev/null @@ -1,102 +0,0 @@ ---- a/b/src/connection.c 2025-07-06 13:11:26.000000000 +0100 -+++ b/src/connection.c 2026-05-01 00:15:42.778777823 +0100 -@@ -40,6 +40,12 @@ - #include - #include - -+#ifndef MSG_NOSIGNAL -+#define MSG_NOSIGNAL 0 -+#endif -+ -+extern FILE *open_memstream(char **bufp, size_t *sizep); -+ - #include "wayland-util.h" - #include "wayland-private.h" - #include "wayland-os.h" ---- a/b/src/event-loop.c 2025-07-06 13:11:26.000000000 +0100 -+++ b/src/event-loop.c 2026-05-01 00:15:42.778845239 +0100 -@@ -35,9 +35,43 @@ - #include - #include - #include --#include --#include - #include -+/* Redox: relibc declares signalfd/timerfd in headers but has no implementation. -+ Provide inline implementations via Redox schemes. */ -+#define SFD_CLOEXEC O_CLOEXEC -+#define SFD_NONBLOCK O_NONBLOCK -+#define TFD_CLOEXEC O_CLOEXEC -+#define TFD_NONBLOCK O_NONBLOCK -+#define TFD_TIMER_ABSTIME 0x1 -+struct signalfd_siginfo { uint8_t pad[128]; }; -+static int signalfd(int fd, const sigset_t *mask, int flags) { -+ int oflag = O_RDWR; -+ if (flags & SFD_CLOEXEC) oflag |= O_CLOEXEC; -+ if (flags & SFD_NONBLOCK) oflag |= O_NONBLOCK; -+ if (fd == -1) { fd = open("/scheme/event", oflag); if (fd < 0) return -1; } -+ else { if (flags & SFD_CLOEXEC) fcntl(fd, F_SETFD, FD_CLOEXEC); } -+ sigprocmask(SIG_BLOCK, mask, NULL); -+ return fd; -+} -+static int timerfd_create(int clockid, int flags) { -+ int oflag = O_RDWR; -+ if (flags & TFD_CLOEXEC) oflag |= O_CLOEXEC; -+ if (flags & TFD_NONBLOCK) oflag |= O_NONBLOCK; -+ char path[64]; -+ snprintf(path, sizeof(path), "/scheme/time/%d", clockid); -+ return open(path, oflag); -+} -+static int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) { -+ if (new_value == NULL) { errno = EFAULT; return -1; } -+ ssize_t r = write(fd, &new_value->it_value, sizeof(struct timespec)); -+ return (r == sizeof(struct timespec)) ? 0 : -1; -+} -+static int timerfd_gettime(int fd, struct itimerspec *curr) { -+ if (curr == NULL) { errno = EFAULT; return -1; } -+ curr->it_interval = (struct timespec){0}; -+ ssize_t r = read(fd, &curr->it_value, sizeof(struct timespec)); -+ return (r == sizeof(struct timespec)) ? 0 : -1; -+} - #include "timespec-util.h" - #include "wayland-util.h" - #include "wayland-private.h" ---- a/b/src/meson.build 2025-07-06 13:11:26.000000000 +0100 -+++ b/src/meson.build 2026-05-01 00:15:42.778925799 +0100 -@@ -81,8 +81,7 @@ - endif - - if meson.is_cross_build() or not get_option('scanner') -- scanner_dep = dependency('wayland-scanner', native: true, version: meson.project_version()) -- wayland_scanner_for_build = find_program(scanner_dep.get_variable(pkgconfig: 'wayland_scanner')) -+ wayland_scanner_for_build = find_program('wayland-scanner', native: true) - else - wayland_scanner_for_build = wayland_scanner - endif ---- a/b/src/wayland-server.c 2025-07-06 13:11:26.000000000 +0100 -+++ b/src/wayland-server.c 2026-05-01 00:15:42.779083803 +0100 -@@ -39,7 +39,23 @@ - #include - #include - #include --#include -+#ifndef EFD_CLOEXEC -+#define EFD_CLOEXEC O_CLOEXEC -+#endif -+#ifndef EFD_NONBLOCK -+#define EFD_NONBLOCK O_NONBLOCK -+#endif -+#ifndef EFD_SEMAPHORE -+#define EFD_SEMAPHORE 0x1 -+#endif -+static int eventfd(unsigned int initval, int flags) { -+ int oflag = O_RDWR; -+ if (flags & EFD_CLOEXEC) oflag |= O_CLOEXEC; -+ if (flags & EFD_NONBLOCK) oflag |= O_NONBLOCK; -+ char path[64]; -+ snprintf(path, sizeof(path), "/scheme/event/eventfd/%u/%d", initval, (flags & EFD_SEMAPHORE) ? 1 : 0); -+ return open(path, oflag); -+} - #include - #include - diff --git a/local/patches/qtbase/P0-remove-redox-linkat-unlinkat-stubs.patch.bak b/local/patches/qtbase/P0-remove-redox-linkat-unlinkat-stubs.patch.bak deleted file mode 100644 index 3097d5fd2..000000000 --- a/local/patches/qtbase/P0-remove-redox-linkat-unlinkat-stubs.patch.bak +++ /dev/null @@ -1,27 +0,0 @@ -diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp -index e857f1a..f4f7f4a 100644 ---- a/src/corelib/io/qfilesystemengine_unix.cpp -+++ b/src/corelib/io/qfilesystemengine_unix.cpp -@@ -27,23 +27,6 @@ - #include - #include - --#ifdef Q_OS_REDOX --// relibc does not provide unlinkat/linkat yet (POSIX.1-2008 *at functions). --// Provide inline stubs that work for AT_FDCWD only - sufficient for --// FreeDesktop trash operations in this file. --#include --static inline int unlinkat(int dirfd, const char *pathname, int flags) --{ -- if (dirfd != AT_FDCWD || flags != 0) { errno = ENOTSUP; return -1; } -- return unlink(pathname); --} --static inline int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags) --{ -- if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD || flags != 0) { errno = ENOTSUP; return -1; } -- return link(oldpath, newpath); --} --#endif -- - #include - #include // for std::unique_ptr diff --git a/local/patches/qtbase/P1-qplatformopengl-guard.patch.bak b/local/patches/qtbase/P1-qplatformopengl-guard.patch.bak deleted file mode 100644 index b61ef6728..000000000 --- a/local/patches/qtbase/P1-qplatformopengl-guard.patch.bak +++ /dev/null @@ -1,56 +0,0 @@ ---- qtb-orig/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration_p.h 2026-03-05 07:51:49.000000000 +0000 -+++ qtb-mod/src/plugins/platforms/wayland/hardwareintegration/qwaylandclientbufferintegration_p.h 2026-04-28 14:03:29.046092114 +0100 -@@ -51,9 +51,15 @@ - virtual bool supportsThreadedOpenGL() const { return false; } - virtual bool supportsWindowDecoration() const { return false; } - -+ #if QT_CONFIG(opengl) -+#if QT_CONFIG(opengl) -+#if QT_CONFIG(opengl) - virtual QWaylandWindow *createEglWindow(QWindow *window) = 0; - virtual QPlatformOpenGLContext *createPlatformOpenGLContext(const QSurfaceFormat &glFormat, QPlatformOpenGLContext *share) const = 0; -+#endif -+#endif - virtual bool canCreatePlatformOffscreenSurface() const { return false; } -+#endif - #if QT_CONFIG(opengl) - virtual QOpenGLContext *createOpenGLContext(EGLContext context, EGLDisplay contextDisplay, QOpenGLContext *shareContext) const = 0; - virtual QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const { Q_UNUSED(surface); return nullptr; } -@@ -65,7 +71,11 @@ - EglContext - }; - virtual void *nativeResource(NativeResource /*resource*/) { return nullptr; } -+#if QT_CONFIG(opengl) -+#if QT_CONFIG(opengl) - virtual void *nativeResourceForContext(NativeResource /*resource*/, QPlatformOpenGLContext */*context*/) { return nullptr; } -+#endif -+#endif - }; - - } ---- qtb-orig/src/plugins/platforms/wayland/qwaylandintegration.cpp 2026-03-05 07:51:49.000000000 +0000 -+++ qtb-mod/src/plugins/platforms/wayland/qwaylandintegration.cpp 2026-04-28 14:34:26.740450805 +0100 -@@ -135,17 +135,23 @@ - case ScreenWindowGrabbing: // whether QScreen::grabWindow() is supported - return false; - case OffscreenSurface: -+#if QT_CONFIG(opengl) - return mDisplay->clientBufferIntegration() - && mDisplay->clientBufferIntegration()->canCreatePlatformOffscreenSurface(); -+#else -+ return false; -+#endif - default: return QPlatformIntegration::hasCapability(cap); - } - } - - QPlatformWindow *QWaylandIntegration::createPlatformWindow(QWindow *window) const - { -+#if QT_CONFIG(opengl) - if (window->surfaceType() == QWindow::OpenGLSurface - && mDisplay->clientBufferIntegration()) - return mDisplay->clientBufferIntegration()->createEglWindow(window); -+#endif - - #if QT_CONFIG(vulkan) - if (window->surfaceType() == QSurface::VulkanSurface) diff --git a/local/patches/qtbase/P2-enable-network-and-tuiotouch.patch.bak b/local/patches/qtbase/P2-enable-network-and-tuiotouch.patch.bak deleted file mode 100644 index efa9b06a1..000000000 --- a/local/patches/qtbase/P2-enable-network-and-tuiotouch.patch.bak +++ /dev/null @@ -1,23 +0,0 @@ -diff -ruwN source-old/src/CMakeLists.txt source/src/CMakeLists.txt ---- source-old/src/CMakeLists.txt 2024-12-02 05:39:06.000000000 +0000 -+++ source/src/CMakeLists.txt 2026-04-30 00:00:00.000000000 +0000 -@@ -54,5 +54,5 @@ - endif() - if (QT_FEATURE_network) -- # add_subdirectory(network) # disabled for Redox -+ add_subdirectory(network) - if (ANDROID) - add_subdirectory(network/android/jar) - endif() -diff -ruwN source-old/src/plugins/generic/CMakeLists.txt source/src/plugins/generic/CMakeLists.txt ---- source-old/src/plugins/generic/CMakeLists.txt 2024-12-02 05:39:06.000000000 +0000 -+++ source/src/plugins/generic/CMakeLists.txt 2026-04-30 00:00:00.000000000 +0000 -@@ -18,6 +18,6 @@ - add_subdirectory(tslib) - endif() - if(QT_FEATURE_tuiotouch) -- # add_subdirectory(tuiotouch) # disabled for Redox (needs Network) -+ add_subdirectory(tuiotouch) - endif() - if(QT_FEATURE_libinput) - add_subdirectory(libinput) diff --git a/local/patches/relibc/P3-dns-resolver-hardening.patch.bak b/local/patches/relibc/P3-dns-resolver-hardening.patch.bak deleted file mode 100644 index 7754eacc4..000000000 --- a/local/patches/relibc/P3-dns-resolver-hardening.patch.bak +++ /dev/null @@ -1,722 +0,0 @@ -diff --git a/src/header/netdb/dns/mod.rs b/src/header/netdb/dns/mod.rs -index 9d7e44b..f5bc21b 100644 ---- a/src/header/netdb/dns/mod.rs -+++ b/src/header/netdb/dns/mod.rs -@@ -15,6 +15,35 @@ use alloc::{string::String, vec::Vec}; - mod answer; - mod query; - -+const DNS_FLAG_QR: u16 = 0x8000; -+const DNS_FLAG_TC: u16 = 0x0200; -+const DNS_RCODE_MASK: u16 = 0x000F; -+ -+#[derive(Clone, Copy, Debug, Eq, PartialEq)] -+pub(super) enum DnsError { -+ MalformedResponse, -+ TransactionIdMismatch, -+ NotResponse, -+ Truncated, -+ ServerFailure, -+ NameError, -+ ResponseCode(u8), -+} -+ -+impl DnsError { -+ fn as_str(self) -> &'static str { -+ match self { -+ Self::MalformedResponse => "malformed dns response", -+ Self::TransactionIdMismatch => "dns transaction id mismatch", -+ Self::NotResponse => "dns packet was not a response", -+ Self::Truncated => "truncated dns response", -+ Self::ServerFailure => "dns server failure", -+ Self::NameError => "dns name error", -+ Self::ResponseCode(_) => "dns server returned an error response", -+ } -+ } -+} - - #[derive(Clone, Debug)] - pub struct Dns { - pub transaction_id: u16, -@@ -59,6 +88,14 @@ impl Dns { - } - - pub fn parse(data: &[u8]) -> Result { -+ Self::parse_impl(data, None).map_err(|err| err.as_str().into()) -+ } -+ -+ pub(super) fn parse_reply(data: &[u8], expected_transaction_id: u16) -> Result { -+ Self::parse_impl(data, Some(expected_transaction_id)) -+ } -+ -+ fn parse_impl(data: &[u8], expected_transaction_id: Option) -> Result { - let name_ind = 0b1100_0000; - let mut i = 0; - -@@ -66,7 +103,7 @@ impl Dns { - () => {{ - i += 1; - if i > data.len() { -- return Err(format!("{}: {}: pop_u8", file!(), line!())); -+ return Err(DnsError::MalformedResponse); - } - data[i - 1] - }}; -@@ -77,9 +114,11 @@ impl Dns { - use core::convert::TryInto; - i += 2; - if i > data.len() { -- return Err(format!("{}: {}: pop_n16", file!(), line!())); -+ return Err(DnsError::MalformedResponse); - } -- let bytes: [u8; 2] = data[i - 2..i].try_into().unwrap(); -+ let bytes: [u8; 2] = data[i - 2..i] -+ .try_into() -+ .map_err(|_| DnsError::MalformedResponse)?; - u16::from_be_bytes(bytes) - }}; - } -@@ -156,11 +195,83 @@ impl Dns { - }); - } - -- Ok(Dns { -+ let dns = Dns { - transaction_id, - flags, - queries, - answers, -- }) -+ }; -+ -+ if let Some(expected_transaction_id) = expected_transaction_id { -+ if dns.transaction_id != expected_transaction_id { -+ return Err(DnsError::TransactionIdMismatch); -+ } -+ } -+ -+ if dns.flags & DNS_FLAG_QR == 0 { -+ return Err(DnsError::NotResponse); -+ } -+ -+ if dns.flags & DNS_FLAG_TC != 0 { -+ return Err(DnsError::Truncated); -+ } -+ -+ match (dns.flags & DNS_RCODE_MASK) as u8 { -+ 0 => Ok(dns), -+ 2 => Err(DnsError::ServerFailure), -+ 3 => Err(DnsError::NameError), -+ rcode => Err(DnsError::ResponseCode(rcode)), -+ } -+ } -+} -+ -+#[cfg(test)] -+mod tests { -+ use alloc::{string::ToString, vec::Vec}; -+ -+ use super::{Dns, DnsError, DnsQuery}; -+ -+ fn packet(transaction_id: u16, flags: u16) -> Vec { -+ Dns { -+ transaction_id, -+ flags, -+ queries: vec![DnsQuery { -+ name: "example.com".to_string(), -+ q_type: 0x0001, -+ q_class: 0x0001, -+ }], -+ answers: vec![], -+ } -+ .compile() -+ } -+ -+ #[test] -+ fn parse_reply_accepts_valid_response() { -+ let response = Dns::parse_reply(&packet(0x1234, 0x8180), 0x1234).unwrap(); -+ assert_eq!(response.transaction_id, 0x1234); -+ } -+ -+ #[test] -+ fn parse_reply_rejects_transaction_id_mismatch() { -+ let err = Dns::parse_reply(&packet(0x1234, 0x8180), 0x4321).unwrap_err(); -+ assert_eq!(err, DnsError::TransactionIdMismatch); -+ } -+ -+ #[test] -+ fn parse_rejects_query_packets() { -+ let err = Dns::parse(&packet(0x1234, 0x0100)).unwrap_err(); -+ assert_eq!(err, DnsError::NotResponse.as_str()); -+ } -+ -+ #[test] -+ fn parse_rejects_truncated_response() { -+ let err = Dns::parse(&packet(0x1234, 0x8380)).unwrap_err(); -+ assert_eq!(err, DnsError::Truncated.as_str()); -+ } -+ -+ #[test] -+ fn parse_rejects_name_error_response() { -+ let err = Dns::parse(&packet(0x1234, 0x8183)).unwrap_err(); -+ assert_eq!(err, DnsError::NameError.as_str()); -+ } - } -diff --git a/src/header/netdb/lookup.rs b/src/header/netdb/lookup.rs -index c2b6cdb..af25f97 100644 ---- a/src/header/netdb/lookup.rs -+++ b/src/header/netdb/lookup.rs -@@ -1,10 +1,10 @@ --use alloc::{boxed::Box, string::ToString, vec::Vec}; -+use alloc::{string::ToString, vec::Vec}; - use core::{mem, ptr}; - - use crate::{ - out::Out, - platform::{ -- Pal, Sys, -+ self, Pal, Sys, - types::{c_int, c_void}, - }, - }; -@@ -25,13 +25,86 @@ use crate::header::{ - }; - - use super::{ -- dns::{Dns, DnsQuery}, -+ dns::{Dns, DnsError, DnsQuery}, - sys::get_dns_server, - }; - - pub type LookupHost = Vec; - pub type LookupHostV6 = Vec; -+ -+fn close_socket(sock: c_int) { -+ if sock >= 0 { -+ if let Ok(()) = Sys::close(sock) {}; -+ } -+} -+ -+fn last_socket_error(default: c_int) -> c_int { -+ match platform::ERRNO.get() { -+ 0 => default, -+ err => err, -+ } -+} -+ -+fn map_dns_error(err: DnsError) -> c_int { -+ match err { -+ DnsError::NameError => ENOENT, -+ DnsError::ServerFailure => EAGAIN, -+ DnsError::Truncated => EMSGSIZE, -+ DnsError::MalformedResponse -+ | DnsError::TransactionIdMismatch -+ | DnsError::NotResponse -+ | DnsError::ResponseCode(_) => EREMOTEIO, -+ } -+} -+ -+fn lookup_dns_response(packet: &Dns, dns_addr: u32) -> Result { -+ let packet_data = packet.compile(); -+ let packet_data_len = packet_data.len(); -+ let packet_data_ptr = packet_data.as_ptr().cast::(); -+ -+ let dest = sockaddr_in { -+ sin_family: AF_INET as u16, -+ sin_port: htons(53), -+ sin_addr: in_addr { s_addr: dns_addr }, -+ ..Default::default() -+ }; -+ let dest_ptr = ptr::from_ref(&dest).cast::(); -+ -+ let sock = unsafe { sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP)) }; -+ if sock < 0 { -+ return Err(last_socket_error(EIO)); -+ } -+ -+ if unsafe { sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) } < 0 { -+ let err = last_socket_error(EIO); -+ close_socket(sock); -+ return Err(err); -+ } -+ -+ if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 { -+ let err = last_socket_error(EIO); -+ close_socket(sock); -+ return Err(err); -+ } -+ -+ let tv = timeval { -+ tv_sec: 5, -+ tv_usec: 0, -+ }; -+ unsafe { -+ sys_socket::setsockopt( -+ sock, -+ SOL_SOCKET, -+ SO_RCVTIMEO, -+ &tv as *const timeval as *const c_void, -+ core::mem::size_of::() as socklen_t, -+ ); -+ } -+ -+ let mut buf = vec![0u8; 65536]; -+ let buf_ptr = buf.as_mut_ptr().cast::(); -+ -+ let mut count: isize = -1; -+ let mut recv_error = EIO; -+ for attempt in 0..2 { -+ count = unsafe { sys_socket::recv(sock, buf_ptr, buf.len(), 0) }; -+ if count >= 0 { -+ break; -+ } -+ -+ recv_error = last_socket_error(EIO); -+ if attempt + 1 == 2 { -+ break; -+ } -+ -+ if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 { -+ recv_error = last_socket_error(EIO); -+ break; -+ } -+ } -+ -+ if count < 0 { -+ close_socket(sock); -+ return Err(recv_error); -+ } -+ -+ let response = match Dns::parse_reply(&buf[..count as usize], packet.transaction_id) { -+ Ok(response) => response, -+ Err(err) => { -+ close_socket(sock); -+ return Err(map_dns_error(err)); -+ } -+ }; -+ -+ close_socket(sock); -+ Ok(response) -+} - - pub fn lookup_host(host: &str) -> Result { - if let Some(host_direct_addr) = parse_ipv4_string(host) { -@@ -61,97 +134,30 @@ pub fn lookup_host(host: &str) -> Result { - answers: vec![], - }; - -- let packet_data = packet.compile(); -- let packet_data_len = packet_data.len(); -- -- let packet_data_box = packet_data.into_boxed_slice(); -- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void; -- -- let dest = sockaddr_in { -- sin_family: AF_INET as u16, -- sin_port: htons(53), -- sin_addr: in_addr { s_addr: dns_addr }, -- ..Default::default() -- }; -- let dest_ptr = ptr::from_ref(&dest).cast::(); -- -- let sock = unsafe { -- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP)); -- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 { -- return Err(EIO); -- } -- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 { -- drop(Box::from_raw(packet_data_ptr)); -- return Err(EIO); -- } -- sock -- }; -- -- unsafe { -- drop(Box::from_raw(packet_data_ptr)); -- } -- -- let mut buf = vec![0u8; 65536]; -- let buf_ptr = buf.as_mut_ptr().cast::(); -- -- // Set 5s recv timeout (best-effort; if this fails, recv may block longer). -- let tv = timeval { -- tv_sec: 5, -- tv_usec: 0, -- }; -- unsafe { -- sys_socket::setsockopt( -- sock, -- SOL_SOCKET, -- SO_RCVTIMEO, -- &tv as *const timeval as *const c_void, -- core::mem::size_of::() as socklen_t, -- ); -- } -- -- let mut count: isize = -1; -- for _attempt in 0..2 { -- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) }; -- if count >= 0 { -- break; -- } -- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 { -- break; -- } -- } -- if count < 0 { -- return Err(EIO); -- } -- -- match Dns::parse(&buf[..count as usize]) { -- Ok(response) => { -- let addrs: Vec<_> = response -- .answers -- .into_iter() -- .filter_map(|answer| { -- if answer.a_type == 0x0001 -- && answer.a_class == 0x0001 -- && answer.data.len() == 4 -- { -- let addr = in_addr { -- s_addr: u32::from_ne_bytes([ -- answer.data[0], -- answer.data[1], -- answer.data[2], -- answer.data[3], -- ]), -- }; -- Some(addr) -- } else { -- None -- } -- }) -- .collect(); -- -- Ok(addrs) -- } -- Err(_err) => Err(EINVAL), -- } -+ let response = lookup_dns_response(&packet, dns_addr)?; -+ let addrs: Vec<_> = response -+ .answers -+ .into_iter() -+ .filter_map(|answer| { -+ if answer.a_type == 0x0001 && answer.a_class == 0x0001 && answer.data.len() == 4 { -+ let addr = in_addr { -+ s_addr: u32::from_ne_bytes([ -+ answer.data[0], -+ answer.data[1], -+ answer.data[2], -+ answer.data[3], -+ ]), -+ }; -+ Some(addr) -+ } else { -+ None -+ } -+ }) -+ .collect(); -+ -+ Ok(addrs) - } else { - Err(EINVAL) - } -@@ -186,97 +192,30 @@ pub fn lookup_host_v6(host: &str) -> Result { - answers: vec![], - }; - -- let packet_data = packet.compile(); -- let packet_data_len = packet_data.len(); -- -- let packet_data_box = packet_data.into_boxed_slice(); -- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void; -- -- let dest = sockaddr_in { -- sin_family: AF_INET as u16, -- sin_port: htons(53), -- sin_addr: in_addr { s_addr: dns_addr }, -- ..Default::default() -- }; -- let dest_ptr = ptr::from_ref(&dest).cast::(); -- -- let sock = unsafe { -- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP)); -- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 { -- return Err(EIO); -- } -- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 { -- drop(Box::from_raw(packet_data_ptr)); -- return Err(EIO); -- } -- sock -- }; -- -- unsafe { -- drop(Box::from_raw(packet_data_ptr)); -- } -- -- let mut buf = vec![0u8; 65536]; -- let buf_ptr = buf.as_mut_ptr().cast::(); -- -- // Set 5s recv timeout (best-effort; if this fails, recv may block longer). -- let tv = timeval { -- tv_sec: 5, -- tv_usec: 0, -- }; -- unsafe { -- sys_socket::setsockopt( -- sock, -- SOL_SOCKET, -- SO_RCVTIMEO, -- &tv as *const timeval as *const c_void, -- core::mem::size_of::() as socklen_t, -- ); -- } -- -- let mut count: isize = -1; -- for _attempt in 0..2 { -- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) }; -- if count >= 0 { -- break; -- } -- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 { -- break; -- } -- } -- if count < 0 { -- return Err(EIO); -- } -- -- match Dns::parse(&buf[..count as usize]) { -- Ok(response) => { -- let addrs: Vec<_> = response -- .answers -- .into_iter() -- .filter_map(|answer| { -- if answer.a_type == 0x001c -- && answer.a_class == 0x0001 -- && answer.data.len() == 16 -- { -- let mut s6_addr = [0u8; 16]; -- s6_addr.copy_from_slice(&answer.data[..16]); -- Some(in6_addr { s6_addr }) -- } else { -- None -- } -- }) -- .collect(); -- -- Ok(addrs) -- } -- Err(_err) => Err(EINVAL), -- } -+ let response = lookup_dns_response(&packet, dns_addr)?; -+ let addrs: Vec<_> = response -+ .answers -+ .into_iter() -+ .filter_map(|answer| { -+ if answer.a_type == 0x001c && answer.a_class == 0x0001 && answer.data.len() == 16 { -+ let mut s6_addr = [0u8; 16]; -+ s6_addr.copy_from_slice(&answer.data[..16]); -+ Some(in6_addr { s6_addr }) -+ } else { -+ None -+ } -+ }) -+ .collect(); -+ -+ Ok(addrs) - } else { - Err(EINVAL) - } -@@ -315,82 +254,24 @@ pub fn lookup_addr(addr: in_addr) -> Result>, c_int> { - answers: vec![], - }; - -- let packet_data = packet.compile(); -- let packet_data_len = packet_data.len(); -- let packet_data_box = packet_data.into_boxed_slice(); -- let packet_data_ptr = Box::into_raw(packet_data_box) as *mut _ as *mut c_void; -- -- let dest = sockaddr_in { -- sin_family: AF_INET as u16, -- sin_port: htons(53), -- sin_addr: in_addr { s_addr: dns_addr }, -- ..Default::default() -- }; -- -- let dest_ptr = ptr::from_ref(&dest).cast::(); -- -- let sock = unsafe { -- let sock = sys_socket::socket(AF_INET, SOCK_DGRAM, i32::from(IPPROTO_UDP)); -- if sys_socket::connect(sock, dest_ptr, mem::size_of_val(&dest) as socklen_t) < 0 { -- return Err(EIO); -- } -- sock -- }; -- -- unsafe { -- if sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) < 0 { -- return Err(EIO); -- } -- } -- -- unsafe { -- drop(Box::from_raw(packet_data_ptr)); -- } -- -- let mut buf = [0u8; 65536]; -- let buf_ptr = buf.as_mut_ptr().cast::(); -- -- // Set 5s recv timeout (best-effort; if this fails, recv may block longer). -- let tv = timeval { -- tv_sec: 5, -- tv_usec: 0, -- }; -- unsafe { -- sys_socket::setsockopt( -- sock, -- SOL_SOCKET, -- SO_RCVTIMEO, -- &tv as *const timeval as *const c_void, -- core::mem::size_of::() as socklen_t, -- ); -- } -- -- let mut count: isize = -1; -- for _attempt in 0..2 { -- count = unsafe { sys_socket::recv(sock, buf_ptr, 65536, 0) }; -- if count >= 0 { -- break; -- } -- if unsafe { sys_socket::send(sock, packet_data_ptr, packet_data_len, 0) } < 0 { -- break; -- } -- } -- if count < 0 { -- return Err(EIO); -- } -- -- match Dns::parse(&buf[..count as usize]) { -- Ok(response) => { -- let names = response -- .answers -- .into_iter() -- .filter_map(|answer| { -- if answer.a_type == 0x000C && answer.a_class == 0x0001 { -- // answer.data is encoded kinda weird. -- // Basically length-prefixed strings for each -- // subsection of the domain. -- // We need to parse this to insert periods where -- // they belong (ie at the end of each string) -- Some(parse_revdns_answer(&answer.data)) -- } else { -- None -- } -- }) -- .collect(); -- Ok(names) -- } -- Err(_err) => Err(EINVAL), -- } -+ let response = lookup_dns_response(&packet, dns_addr)?; -+ let names = response -+ .answers -+ .into_iter() -+ .filter_map(|answer| { -+ if answer.a_type == 0x000C && answer.a_class == 0x0001 { -+ // answer.data is encoded kinda weird. -+ // Basically length-prefixed strings for each -+ // subsection of the domain. -+ // We need to parse this to insert periods where -+ // they belong (ie at the end of each string) -+ Some(parse_revdns_answer(&answer.data)) -+ } else { -+ None -+ } -+ }) -+ .collect(); -+ Ok(names) - } else { - Err(EINVAL) - } -diff --git a/src/header/netdb/mod.rs b/src/header/netdb/mod.rs -index ba58b6e..cdcc10e 100644 ---- a/src/header/netdb/mod.rs -+++ b/src/header/netdb/mod.rs -@@ -180,6 +180,31 @@ fn bytes_to_box_str(bytes: &[u8]) -> Box { - Box::from(core::str::from_utf8(bytes).unwrap_or("")) - } - -+fn lookup_error_to_eai(err: c_int) -> c_int { -+ match err { -+ ETIMEDOUT | EAGAIN => EAI_AGAIN, -+ ENOENT => EAI_NONAME, -+ _ => EAI_FAIL, -+ } -+} -+ -+fn lookup_error_priority(err: c_int) -> u8 { -+ match err { -+ EAI_AGAIN => 3, -+ EAI_FAIL => 2, -+ EAI_NONAME => 1, -+ _ => 0, -+ } -+} -+ -+fn combine_lookup_error(current: Option, err: c_int) -> c_int { -+ let mapped = lookup_error_to_eai(err); -+ -+ match current { -+ Some(existing) if lookup_error_priority(existing) >= lookup_error_priority(mapped) => { -+ existing -+ } -+ Some(_) => mapped, -+ None => mapped, -+ } -+} -+ - /// See . - #[unsafe(no_mangle)] - pub unsafe extern "C" fn endnetent() { -@@ -926,6 +951,8 @@ pub unsafe extern "C" fn getaddrinfo( - let want_inet4 = requested_family == AF_INET || requested_family == AF_UNSPEC; - let want_inet6 = requested_family == AF_INET6 || requested_family == AF_UNSPEC; - -+ let mut lookup_error = None; -+ - let lookuphost_v4: Vec = if want_inet4 { - if ai_flags & AI_NUMERICHOST > 0 { - match parse_ipv4_string(node_str) { -@@ -937,7 +964,10 @@ pub unsafe extern "C" fn getaddrinfo( - } else { - match lookup_host(node_str) { - Ok(addrs) => addrs, -- Err(_) => vec![], -+ Err(err) => { -+ lookup_error = Some(combine_lookup_error(lookup_error, err)); -+ vec![] -+ } - } - } - } else { -@@ -955,7 +985,10 @@ pub unsafe extern "C" fn getaddrinfo( - } else { - match lookup_host_v6(node_str) { - Ok(addrs) => addrs, -- Err(_) => vec![], -+ Err(err) => { -+ lookup_error = Some(combine_lookup_error(lookup_error, err)); -+ vec![] -+ } - } - } - } else { -@@ -963,7 +996,7 @@ pub unsafe extern "C" fn getaddrinfo( - }; - - if lookuphost_v4.is_empty() && lookuphost_v6.is_empty() { -- return EAI_NONAME; -+ return lookup_error.unwrap_or(EAI_NONAME); - } diff --git a/local/patches/relibc/P3-fd-event-tests.patch.bak b/local/patches/relibc/P3-fd-event-tests.patch.bak deleted file mode 100644 index acd73f705..000000000 --- a/local/patches/relibc/P3-fd-event-tests.patch.bak +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/tests/Makefile.tests.mk b/tests/Makefile.tests.mk ---- a/tests/Makefile.tests.mk -+++ b/tests/Makefile.tests.mk -@@ -314,8 +314,12 @@ VARIED_NAMES=\ - grp/gr_iter \ - semaphore/named \ - semaphore/unnamed \ -+ sys_eventfd/eventfd \ -+ sys_signalfd/header_only \ -+ sys_signalfd/signalfd \ -+ sys_timerfd/timerfd \ - waitid \ - waitpid \ - waitpid_multiple \ - $(FAILING_TESTS) diff --git a/local/patches/relibc/P3-fenv.patch.bak b/local/patches/relibc/P3-fenv.patch.bak deleted file mode 100644 index 54b958fe9..000000000 --- a/local/patches/relibc/P3-fenv.patch.bak +++ /dev/null @@ -1,230 +0,0 @@ -diff --git a/src/header/_fenv/mod.rs b/src/header/_fenv/mod.rs ---- a/src/header/_fenv/mod.rs -+++ b/src/header/_fenv/mod.rs -@@ -4,82 +4,207 @@ - - use crate::platform::types::c_int; - --/// See . --pub const FE_ALL_EXCEPT: c_int = 0; --/// See . --pub const FE_TONEAREST: c_int = 0; -+// x86_64 SSE floating-point exception flags (MXCSR bits 0-5, excluding denormal bit 1) -+pub const FE_INVALID: c_int = 0x01; -+pub const FE_DIVBYZERO: c_int = 0x04; -+pub const FE_OVERFLOW: c_int = 0x08; -+pub const FE_UNDERFLOW: c_int = 0x10; -+pub const FE_INEXACT: c_int = 0x20; -+/// See . -+pub const FE_ALL_EXCEPT: c_int = -+ FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT; -+ -+// x86_64 rounding modes (MXCSR bits 13-14, x87 CW bits 10-11) -+/// See . -+pub const FE_TONEAREST: c_int = 0x000; -+pub const FE_DOWNWARD: c_int = 0x400; -+pub const FE_UPWARD: c_int = 0x800; -+pub const FE_TOWARDZERO: c_int = 0xC00; - - /// See . - pub type fexcept_t = u64; - - /// See . - #[repr(C)] - pub struct fenv_t { -- pub cw: u64, -+ pub cw: u32, // x87 control word (zero-extended from u16) -+ pub mxcsr: u32, // SSE MXCSR register - } - -+/// Read the x87 FPU control word. -+#[inline] -+unsafe fn fnstcw() -> u16 { -+ let mut cw: u16 = 0; -+ core::arch::asm!( -+ "fnstcw ({0})", -+ in(reg) &mut cw, -+ options(nostack, preserves_flags) -+ ); -+ cw -+} -+ -+/// Load the x87 FPU control word. -+#[inline] -+unsafe fn fldcw(cw: u16) { -+ core::arch::asm!( -+ "fldcw ({0})", -+ in(reg) &cw, -+ options(nostack, preserves_flags) -+ ); -+} -+ -+/// Read the SSE MXCSR register. -+#[inline] -+unsafe fn stmxcsr() -> u32 { -+ let mut mxcsr: u32 = 0; -+ core::arch::asm!( -+ "stmxcsr ({0})", -+ in(reg) &mut mxcsr, -+ options(nostack, preserves_flags) -+ ); -+ mxcsr -+} -+ -+/// Write the SSE MXCSR register. -+#[inline] -+unsafe fn ldmxcsr(val: u32) { -+ core::arch::asm!( -+ "ldmxcsr ({0})", -+ in(reg) &val, -+ options(nostack, preserves_flags) -+ ); -+} -+ - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn feclearexcept(excepts: c_int) -> c_int { -- unimplemented!(); -+ let mask = (excepts & FE_ALL_EXCEPT) as u32; -+ if mask != 0 { -+ let mxcsr = stmxcsr(); -+ ldmxcsr(mxcsr & !mask); -+ // Clear x87 status word exception flags -+ core::arch::asm!("fnclex", options(nostack, preserves_flags)); -+ } -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn fegetenv(envp: *mut fenv_t) -> c_int { -- unimplemented!(); -+ if envp.is_null() { -+ return 1; -+ } -+ (*envp).cw = fnstcw() as u32; -+ (*envp).mxcsr = stmxcsr(); -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn fegetexceptflag(flagp: *mut fexcept_t, excepts: c_int) -> c_int { -- unimplemented!(); -+ if flagp.is_null() { -+ return 1; -+ } -+ let mxcsr = stmxcsr(); -+ *flagp = (mxcsr & FE_ALL_EXCEPT as u32 & excepts as u32) as fexcept_t; -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn fegetround() -> c_int { -- FE_TONEAREST -+ let mxcsr = stmxcsr(); -+ (mxcsr & 0xC00) as c_int - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn feholdexcept(envp: *mut fenv_t) -> c_int { -- unimplemented!(); -+ if envp.is_null() { -+ return 1; -+ } -+ // Save current environment -+ (*envp).cw = fnstcw() as u32; -+ (*envp).mxcsr = stmxcsr(); -+ // Clear all exception flags and set non-stop mode (unmask all exceptions) -+ // MXCSR: clear status bits 0-5, clear mask bits 7-12 -+ let mxcsr = stmxcsr(); -+ ldmxcsr(mxcsr & !(FE_ALL_EXCEPT as u32) & !((FE_ALL_EXCEPT as u32) << 7)); -+ // x87: clear exception mask bits (bits 0-5 in CW) and clear status -+ let cw = fnstcw(); -+ fldcw(cw & !0x3F); -+ core::arch::asm!("fnclex", options(nostack, preserves_flags)); -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn feraiseexcept(excepts: c_int) -> c_int { -- unimplemented!(); -+ let mask = (excepts & FE_ALL_EXCEPT) as u32; -+ if mask == 0 { -+ return 0; -+ } -+ // Set exception status flags in MXCSR -+ let mxcsr = stmxcsr(); -+ ldmxcsr(mxcsr | mask); -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn fesetenv(envp: *const fenv_t) -> c_int { -- unimplemented!(); -+ if envp.is_null() { -+ // Restore default environment -+ fldcw(0x037F); // x87 default CW: all exceptions masked, double precision -+ ldmxcsr(0x1F80); // MXCSR default: all exceptions masked, round-to-nearest -+ return 0; -+ } -+ fldcw((*envp).cw as u16); -+ ldmxcsr((*envp).mxcsr); -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn fesetexceptflag(flagp: *const fexcept_t, excepts: c_int) -> c_int { -- unimplemented!(); -+ if flagp.is_null() { -+ return 1; -+ } -+ let mask = (excepts & FE_ALL_EXCEPT) as u32; -+ let mxcsr = stmxcsr(); -+ let flags = (*flagp as u32) & mask; -+ ldmxcsr((mxcsr & !mask) | flags); -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn fesetround(round: c_int) -> c_int { -- unimplemented!(); -+ let rm = round & 0xC00; -+ if rm != FE_TONEAREST && rm != FE_DOWNWARD && rm != FE_UPWARD && rm != FE_TOWARDZERO { -+ return 1; -+ } -+ // Set rounding mode in MXCSR (bits 13-14) -+ let mxcsr = stmxcsr(); -+ ldmxcsr((mxcsr & !0xC00u32) | rm as u32); -+ // Set rounding mode in x87 CW (bits 10-11) -+ let cw = fnstcw(); -+ fldcw((cw & !0x0C00) | rm as u16); -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn fetestexcept(excepts: c_int) -> c_int { -- unimplemented!(); -+ let mxcsr = stmxcsr(); -+ (mxcsr & FE_ALL_EXCEPT as u32 & excepts as u32) as c_int - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn feupdateenv(envp: *const fenv_t) -> c_int { -- unimplemented!(); -+ let mxcsr = stmxcsr(); -+ let excepts = (mxcsr & FE_ALL_EXCEPT as u32) as c_int; -+ if fesetenv(envp) != 0 { -+ return 1; -+ } -+ feraiseexcept(excepts); -+ 0 - } diff --git a/local/patches/relibc/P3-named-semaphores.patch.bak b/local/patches/relibc/P3-named-semaphores.patch.bak deleted file mode 100644 index a33f1a152..000000000 --- a/local/patches/relibc/P3-named-semaphores.patch.bak +++ /dev/null @@ -1,182 +0,0 @@ ---- a/src/header/semaphore/mod.rs 2026-04-25 17:07:53.742796721 +0100 -+++ b/src/header/semaphore/mod.rs 2026-04-25 17:08:54.527084219 +0100 -@@ -2,12 +2,24 @@ - //! - //! See . - -+use core::mem::size_of; -+ - use crate::{ -+ c_str::CStr, - header::{ - bits_timespec::timespec, -+ errno::{EEXIST, EINVAL}, -+ fcntl::{O_CREAT, O_EXCL, O_RDWR}, -+ sys_mman::{ -+ mmap, munmap, shm_open, shm_unlink, MAP_SHARED, MAP_FAILED, PROT_READ, PROT_WRITE, -+ }, - time::{CLOCK_MONOTONIC, CLOCK_REALTIME}, -+ unistd::{close, ftruncate}, -+ }, -+ platform::{ -+ ERRNO, -+ types::{c_char, c_int, c_long, c_uint, clockid_t, c_void, mode_t, off_t, size_t}, - }, -- platform::types::{c_char, c_int, c_long, c_uint, clockid_t}, - }; - - /// See . -@@ -18,12 +30,17 @@ - pub size: [c_char; 4], - pub align: c_long, - } -+ -+/// Pointer value returned by `sem_open` on failure. -+/// cbindgen:ignore -+pub const SEM_FAILED: *mut sem_t = usize::MAX as *mut sem_t; -+ - pub type RlctSempahore = crate::sync::Semaphore; - - /// See . --// #[unsafe(no_mangle)] -+#[unsafe(no_mangle)] - pub unsafe extern "C" fn sem_close(sem: *mut sem_t) -> c_int { -- todo!("named semaphores") -+ unsafe { munmap(sem.cast::(), size_of::()) } - } - - /// See . -@@ -50,13 +67,105 @@ - } - - /// See . --// TODO: va_list --// #[unsafe(no_mangle)] -+#[unsafe(no_mangle)] - pub unsafe extern "C" fn sem_open( - name: *const c_char, -- oflag: c_int, /* (va_list) value: c_uint */ -+ oflag: c_int, -+ mut __valist: ... - ) -> *mut sem_t { -- todo!("named semaphores") -+ // Validate name: must start with '/', no embedded '/'. -+ if name.is_null() { -+ ERRNO.set(EINVAL); -+ return SEM_FAILED; -+ } -+ -+ let name_c = unsafe { CStr::from_ptr(name) }; -+ let name_bytes = name_c.to_bytes(); -+ if name_bytes.is_empty() || name_bytes[0] != b'/' { -+ ERRNO.set(EINVAL); -+ return SEM_FAILED; -+ } -+ if name_bytes[1..].iter().any(|&b| b == b'/') { -+ ERRNO.set(EINVAL); -+ return SEM_FAILED; -+ } -+ -+ let creat = oflag & O_CREAT == O_CREAT; -+ let excl = oflag & O_EXCL == O_EXCL; -+ -+ let (mode, value): (mode_t, c_uint) = if creat { -+ ( -+ unsafe { __valist.arg::() }, -+ unsafe { __valist.arg::() }, -+ ) -+ } else { -+ (0, 0) -+ }; -+ -+ // Open or create the shared memory backing. -+ let (fd, created) = if creat && excl { -+ // O_CREAT | O_EXCL: must create exclusively. -+ let fd = unsafe { shm_open(name, O_CREAT | O_EXCL | O_RDWR, mode) }; -+ if fd < 0 { -+ return SEM_FAILED; -+ } -+ (fd, true) -+ } else if creat { -+ // O_CREAT without O_EXCL: try exclusive first, fall back to open. -+ let fd = unsafe { shm_open(name, O_CREAT | O_EXCL | O_RDWR, mode) }; -+ if fd >= 0 { -+ (fd, true) -+ } else if ERRNO.get() == EEXIST { -+ let fd = unsafe { shm_open(name, O_RDWR, 0) }; -+ if fd < 0 { -+ return SEM_FAILED; -+ } -+ (fd, false) -+ } else { -+ return SEM_FAILED; -+ } -+ } else { -+ // No O_CREAT: open existing. -+ let fd = unsafe { shm_open(name, O_RDWR, 0) }; -+ if fd < 0 { -+ return SEM_FAILED; -+ } -+ (fd, false) -+ }; -+ -+ // Set size if we created the backing. -+ if created { -+ if unsafe { ftruncate(fd, size_of::() as off_t) } < 0 { -+ let _ = unsafe { close(fd) }; -+ return SEM_FAILED; -+ } -+ } -+ -+ // Map the shared memory. -+ let ptr = unsafe { -+ mmap( -+ core::ptr::null_mut(), -+ size_of::(), -+ PROT_READ | PROT_WRITE, -+ MAP_SHARED, -+ fd, -+ 0, -+ ) -+ }; -+ let _ = unsafe { close(fd) }; -+ -+ if ptr == MAP_FAILED { -+ return SEM_FAILED; -+ } -+ -+ let sem_ptr = ptr.cast::(); -+ -+ // Initialize the semaphore value if we created the backing. -+ if created { -+ unsafe { sem_ptr.cast::().write(RlctSempahore::new(value)) }; -+ } -+ -+ sem_ptr - } - - /// See . -@@ -76,9 +185,9 @@ - } - - /// See . --// #[unsafe(no_mangle)] -+#[unsafe(no_mangle)] - pub unsafe extern "C" fn sem_unlink(name: *const c_char) -> c_int { -- todo!("named semaphores") -+ unsafe { shm_unlink(name) } - } - - /// See . ---- a/src/header/semaphore/cbindgen.toml 2026-04-25 17:07:53.743979154 +0100 -+++ b/src/header/semaphore/cbindgen.toml 2026-04-25 17:09:18.310792692 +0100 -@@ -3,6 +3,9 @@ - after_includes = """ - #include // for timespec - """ -+trailer = """ -+#define SEM_FAILED ((sem_t *) -1) -+""" - language = "C" - style = "Type" - no_includes = true diff --git a/local/patches/relibc/P3-open-memstream.patch.bak b/local/patches/relibc/P3-open-memstream.patch.bak deleted file mode 100644 index 13521090a..000000000 --- a/local/patches/relibc/P3-open-memstream.patch.bak +++ /dev/null @@ -1,181 +0,0 @@ -diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs ---- a/src/header/stdio/mod.rs -+++ b/src/header/stdio/mod.rs -@@ -46,4 +46,7 @@ - pub use self::getdelim::*; - mod getdelim; - -+pub use self::open_memstream::*; -+mod open_memstream; -+ - mod ext; -diff --git a/src/header/stdio/open_memstream.rs b/src/header/stdio/open_memstream.rs -new file mode 100644 ---- /dev/null -+++ b/src/header/stdio/open_memstream.rs -@@ -0,0 +1,124 @@ -+use alloc::{boxed::Box, vec, vec::Vec}; -+use core::ptr; -+ -+use super::{ -+ Buffer, FILE, -+ constants::{BUFSIZ, F_NORD}, -+}; -+use crate::{ -+ error::{Errno, ResultExtPtrMut}, -+ fs::File, -+ header::{ -+ errno::{EFAULT, ENOMEM}, -+ fcntl, pthread, stdlib, unistd, -+ }, -+ io::{self, BufWriter, Write}, -+ platform::{ -+ ERRNO, -+ types::{c_char, size_t}, -+ }, -+}; -+ -+struct MemstreamWriter { -+ bufp: *mut *mut c_char, -+ sizep: *mut size_t, -+ current: *mut c_char, -+ buffer: Vec, -+} -+ -+unsafe impl Send for MemstreamWriter {} -+ -+impl MemstreamWriter { -+ fn new(bufp: *mut *mut c_char, sizep: *mut size_t) -> Self { -+ Self { -+ bufp, -+ sizep, -+ current: ptr::null_mut(), -+ buffer: Vec::new(), -+ } -+ } -+ -+ fn sync_output(&mut self) -> io::Result<()> { -+ let size = self.buffer.len(); -+ let alloc_size = size -+ .checked_add(1) -+ .ok_or_else(|| io::Error::from_raw_os_error(ENOMEM))?; -+ -+ let raw = if self.current.is_null() { -+ unsafe { stdlib::malloc(alloc_size) } -+ } else { -+ unsafe { stdlib::realloc(self.current.cast(), alloc_size) } -+ }; -+ if raw.is_null() { -+ return Err(io::Error::from_raw_os_error(ENOMEM)); -+ } -+ -+ let raw = raw.cast::(); -+ if size != 0 { -+ unsafe { ptr::copy_nonoverlapping(self.buffer.as_ptr(), raw.cast::(), size) }; -+ } -+ unsafe { -+ *raw.add(size) = 0; -+ *self.bufp = raw; -+ *self.sizep = size; -+ } -+ self.current = raw; -+ Ok(()) -+ } -+} -+ -+impl Write for MemstreamWriter { -+ fn write(&mut self, buf: &[u8]) -> io::Result { -+ self.buffer -+ .try_reserve(buf.len()) -+ .map_err(|_| io::Error::from_raw_os_error(ENOMEM))?; -+ self.buffer.extend_from_slice(buf); -+ Ok(buf.len()) -+ } -+ -+ fn flush(&mut self) -> io::Result<()> { -+ self.sync_output() -+ } -+} -+ -+fn create_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> Result, Errno> { -+ if bufp.is_null() || sizep.is_null() { -+ return Err(Errno(EFAULT)); -+ } -+ -+ unsafe { -+ *bufp = ptr::null_mut(); -+ *sizep = 0; -+ } -+ -+ let mut fds = [0; 2]; -+ if unsafe { unistd::pipe2(fds.as_mut_ptr(), fcntl::O_CLOEXEC) } != 0 { -+ return Err(Errno(ERRNO.get())); -+ } -+ let _ = unistd::close(fds[0]); -+ -+ let file = File::new(fds[1]); -+ let writer = Box::new(BufWriter::new(MemstreamWriter::new(bufp, sizep))); -+ let mutex_attr = pthread::RlctMutexAttr { -+ ty: pthread::PTHREAD_MUTEX_RECURSIVE, -+ ..Default::default() -+ }; -+ -+ Ok(Box::new(FILE { -+ lock: pthread::RlctMutex::new(&mutex_attr).unwrap(), -+ file, -+ flags: F_NORD, -+ read_buf: Buffer::Owned(vec![0; BUFSIZ as usize]), -+ read_pos: 0, -+ read_size: 0, -+ unget: Vec::new(), -+ writer, -+ pid: None, -+ orientation: 0, -+ })) -+} -+ -+#[unsafe(no_mangle)] -+pub unsafe extern "C" fn open_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> *mut FILE { -+ create_memstream(bufp, sizep).or_errno_null_mut() -+} -diff --git a/tests/Makefile.tests.mk b/tests/Makefile.tests.mk ---- a/tests/Makefile.tests.mk -+++ b/tests/Makefile.tests.mk -@@ -85,6 +85,7 @@ VARIED_NAMES=\ - stdio/fseek \ - stdio/fwrite \ - stdio/getc_unget \ -- stdio/getline \ -+ stdio/getline \ -+ stdio/open_memstream \ - stdio/mutex \ - stdio/popen \ -diff --git a/tests/stdio/open_memstream.c b/tests/stdio/open_memstream.c -new file mode 100644 ---- /dev/null -+++ b/tests/stdio/open_memstream.c -@@ -0,0 +1,24 @@ -+#include -+#include -+#include -+#include -+ -+int main(void) { -+ char *buf = NULL; -+ size_t size = 0; -+ -+ FILE *stream = open_memstream(&buf, &size); -+ assert(stream != NULL); -+ assert(fputs("hello", stream) >= 0); -+ assert(fflush(stream) == 0); -+ assert(size == 5); -+ assert(strcmp(buf, "hello") == 0); -+ assert(fputc('!', stream) != EOF); -+ assert(fclose(stream) == 0); -+ assert(size == 6); -+ assert(strcmp(buf, "hello!") == 0); -+ -+ free(buf); -+ puts("open_memstream ok"); -+ return 0; -+} diff --git a/local/patches/relibc/P3-sched.patch.bak b/local/patches/relibc/P3-sched.patch.bak deleted file mode 100644 index 84f4a65b4..000000000 --- a/local/patches/relibc/P3-sched.patch.bak +++ /dev/null @@ -1,124 +0,0 @@ -diff --git a/src/header/sched/mod.rs b/src/header/sched/mod.rs ---- a/src/header/sched/mod.rs -+++ b/src/header/sched/mod.rs -@@ -2,9 +2,11 @@ - //! - //! See . - - use crate::{ - error::ResultExt, -- header::bits_timespec::timespec, -+ header::{bits_timespec::timespec, errno}, - platform::{ -- Pal, Sys, -+ self, Pal, Sys, - types::{c_int, pid_t}, - }, - }; -@@ -29,42 +31,67 @@ - pub const SCHED_OTHER: c_int = 2; - - /// See . - // #[unsafe(no_mangle)] - pub extern "C" fn sched_get_priority_max(policy: c_int) -> c_int { -- todo!() -+ match policy { -+ SCHED_FIFO | SCHED_RR => 99, -+ SCHED_OTHER => 0, -+ _ => { -+ platform::ERRNO.set(errno::EINVAL); -+ -1 -+ } -+ } - } - - /// See . - // #[unsafe(no_mangle)] - pub extern "C" fn sched_get_priority_min(policy: c_int) -> c_int { -- todo!() -+ match policy { -+ SCHED_FIFO | SCHED_RR => 0, -+ SCHED_OTHER => 0, -+ _ => { -+ platform::ERRNO.set(errno::EINVAL); -+ -1 -+ } -+ } - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn sched_getparam(pid: pid_t, param: *mut sched_param) -> c_int { -- todo!() -+ if param.is_null() { -+ platform::ERRNO.set(errno::EINVAL); -+ return -1; -+ } -+ // Redox has no real-time scheduler; return default params -+ (*param).sched_priority = 0; -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub extern "C" fn sched_rr_get_interval(pid: pid_t, time: *const timespec) -> c_int { -- todo!() -+ if time.is_null() { -+ platform::ERRNO.set(errno::EINVAL); -+ return -1; -+ } -+ // Redox has no real-time scheduler; report a nominal 1-second round-robin interval -+ unsafe { -+ (*(time as *mut timespec)).tv_sec = 1; -+ (*(time as *mut timespec)).tv_nsec = 0; -+ } -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub unsafe extern "C" fn sched_setparam(pid: pid_t, param: *const sched_param) -> c_int { -- todo!() -+ if param.is_null() { -+ platform::ERRNO.set(errno::EINVAL); -+ return -1; -+ } -+ let priority = (*param).sched_priority; -+ if priority < 0 || priority > 99 { -+ platform::ERRNO.set(errno::EINVAL); -+ return -1; -+ } -+ // Redox has no real-time scheduler; validate and succeed as a no-op -+ 0 - } - - /// See . - // #[unsafe(no_mangle)] - pub extern "C" fn sched_setscheduler( - pid: pid_t, - policy: c_int, - param: *const sched_param, - ) -> c_int { -- todo!() -+ if param.is_null() { -+ platform::ERRNO.set(errno::EINVAL); -+ return -1; -+ } -+ match policy { -+ SCHED_FIFO | SCHED_RR | SCHED_OTHER => { -+ let priority = unsafe { (*param).sched_priority }; -+ if priority < 0 || priority > 99 { -+ platform::ERRNO.set(errno::EINVAL); -+ return -1; -+ } -+ // Redox has no real-time scheduler; validate and succeed as a no-op -+ 0 -+ } -+ _ => { -+ platform::ERRNO.set(errno::EINVAL); -+ -1 -+ } -+ } - } - - /// See . diff --git a/local/patches/relibc/P3-signalfd.patch.bak b/local/patches/relibc/P3-signalfd.patch.bak deleted file mode 100644 index af191231c..000000000 --- a/local/patches/relibc/P3-signalfd.patch.bak +++ /dev/null @@ -1,120 +0,0 @@ -diff -ruN a/src/header/signal/mod.rs b/src/header/signal/mod.rs ---- a/src/header/signal/mod.rs 2026-04-15 09:40:30.420306210 +0100 -+++ b/src/header/signal/mod.rs 2026-04-15 09:46:42.011891206 +0100 -@@ -32,6 +32,9 @@ - #[path = "redox.rs"] - pub mod sys; - -+mod signalfd; -+pub use self::signalfd::*; -+ - type SigSet = BitSet<[u64; 1]>; - - pub(crate) const SIG_DFL: usize = 0; -diff -ruN a/src/header/signal/signalfd.rs b/src/header/signal/signalfd.rs ---- a/src/header/signal/signalfd.rs 1970-01-01 00:00:00.000000000 +0000 -+++ b/src/header/signal/signalfd.rs 2026-04-15 09:46:42.011930569 +0100 -@@ -0,0 +1,103 @@ -+use core::{mem, ptr}; -+ -+use crate::{ -+ error::{Errno, ResultExt}, -+ header::fcntl::{ -+ FD_CLOEXEC, F_GETFL, F_SETFD, F_SETFL, O_CLOEXEC, O_NONBLOCK, O_RDWR, fcntl, -+ }, -+ platform::{ -+ ERRNO, Pal, Sys, -+ types::{c_int, c_ulonglong}, -+ }, -+}; -+ -+use super::{SIG_BLOCK, sigprocmask, sigset_t}; -+ -+pub const SFD_CLOEXEC: c_int = 0x80000; -+pub const SFD_NONBLOCK: c_int = 0x800; -+ -+#[repr(C)] -+#[derive(Clone, Copy, Default)] -+pub struct signalfd_siginfo { -+ pub ssi_signo: u32, -+ pub ssi_errno: i32, -+ pub ssi_code: i32, -+ pub ssi_pid: u32, -+ pub ssi_uid: u32, -+ pub ssi_fd: i32, -+ pub ssi_tid: u32, -+ pub ssi_band: u32, -+ pub ssi_overrun: u32, -+ pub ssi_trapno: u32, -+ pub ssi_status: i32, -+ pub ssi_int: i32, -+ pub ssi_ptr: u64, -+ pub ssi_utime: u64, -+ pub ssi_stime: u64, -+ pub ssi_addr: u64, -+ pub ssi_addr_lsb: u16, -+ pub __pad2: u16, -+ pub ssi_syscall: i32, -+ pub ssi_call_addr: u64, -+ pub ssi_arch: u32, -+ pub __pad: [u8; 28], -+} -+ -+#[unsafe(no_mangle)] -+pub extern "C" fn _cbindgen_export_signalfd_siginfo(siginfo: signalfd_siginfo) {} -+ -+fn signalfd4_inner(fd: c_int, mask: *const sigset_t, masksize: usize, flags: c_int) -> Result { -+ let supported = SFD_CLOEXEC | SFD_NONBLOCK; -+ if flags & !supported != 0 || masksize != mem::size_of::() { -+ return Err(Errno(crate::header::errno::EINVAL)); -+ } -+ if mask.is_null() { -+ return Err(Errno(crate::header::errno::EFAULT)); -+ } -+ -+ let new_fd = if fd == -1 { -+ let mut oflag = O_RDWR; -+ if flags & SFD_CLOEXEC == SFD_CLOEXEC { -+ oflag |= O_CLOEXEC; -+ } -+ if flags & SFD_NONBLOCK == SFD_NONBLOCK { -+ oflag |= O_NONBLOCK; -+ } -+ Sys::open(c"/scheme/event".into(), oflag, 0)? -+ } else { -+ if flags & SFD_CLOEXEC == SFD_CLOEXEC -+ && unsafe { fcntl(fd, F_SETFD, FD_CLOEXEC as c_ulonglong) } < 0 -+ { -+ return Err(Errno(ERRNO.get())); -+ } -+ if flags & SFD_NONBLOCK == SFD_NONBLOCK { -+ let current = unsafe { fcntl(fd, F_GETFL, 0 as c_ulonglong) }; -+ if current < 0 { -+ return Err(Errno(ERRNO.get())); -+ } -+ if unsafe { fcntl(fd, F_SETFL, (current | O_NONBLOCK) as c_ulonglong) } < 0 { -+ return Err(Errno(ERRNO.get())); -+ } -+ } -+ fd -+ }; -+ -+ if unsafe { sigprocmask(SIG_BLOCK, mask, ptr::null_mut()) } < 0 { -+ if fd == -1 { -+ let _ = Sys::close(new_fd); -+ } -+ return Err(Errno(ERRNO.get())); -+ } -+ -+ Ok(new_fd) -+} -+ -+#[unsafe(no_mangle)] -+pub unsafe extern "C" fn signalfd4(fd: c_int, mask: *const sigset_t, masksize: usize, flags: c_int) -> c_int { -+ signalfd4_inner(fd, mask, masksize, flags).or_minus_one_errno() -+} -+ -+#[unsafe(no_mangle)] -+pub unsafe extern "C" fn signalfd(fd: c_int, mask: *const sigset_t, masksize: usize) -> c_int { -+ unsafe { signalfd4(fd, mask, masksize, 0) } -+} diff --git a/local/patches/relibc/P3-sysv-ipc.patch.bak b/local/patches/relibc/P3-sysv-ipc.patch.bak deleted file mode 100644 index cc47f279a..000000000 --- a/local/patches/relibc/P3-sysv-ipc.patch.bak +++ /dev/null @@ -1,98 +0,0 @@ -diff -ruN a/src/header/mod.rs b/src/header/mod.rs ---- a/src/header/mod.rs 2026-04-15 09:55:11.441949342 +0100 -+++ b/src/header/mod.rs 2026-04-15 09:57:28.904091552 +0100 -@@ -92,14 +92,14 @@ - pub mod sys_eventfd; - pub mod sys_file; - pub mod sys_ioctl; --// TODO: sys/ipc.h -+pub mod sys_ipc; - pub mod sys_mman; - // TODO: sys/msg.h - pub mod sys_ptrace; - pub mod sys_resource; - pub mod sys_select; --// TODO: sys/sem.h --// TODO: sys/shm.h -+pub mod sys_sem; -+pub mod sys_shm; - pub mod sys_socket; - pub mod sys_stat; - pub mod sys_statvfs; -diff -ruN a/src/header/sys_ipc/cbindgen.toml b/src/header/sys_ipc/cbindgen.toml ---- a/src/header/sys_ipc/cbindgen.toml 1970-01-01 00:00:00.000000000 +0000 -+++ b/src/header/sys_ipc/cbindgen.toml 2026-04-15 09:57:28.904120977 +0100 -@@ -0,0 +1,12 @@ -+sys_includes = ["sys/types.h"] -+include_guard = "_SYS_IPC_H" -+trailer = """ -+typedef struct ipc_perm ipc_perm; -+""" -+language = "C" -+style = "Tag" -+no_includes = true -+cpp_compat = true -+ -+[enum] -+prefix_with_name = true -diff -ruN a/src/header/sys_ipc/mod.rs b/src/header/sys_ipc/mod.rs ---- a/src/header/sys_ipc/mod.rs 1970-01-01 00:00:00.000000000 +0000 -+++ b/src/header/sys_ipc/mod.rs 2026-04-15 09:57:28.904159138 +0100 -@@ -0,0 +1,31 @@ -+//! `sys/ipc.h` implementation. -+ -+use crate::platform::types::{c_int, c_ushort}; -+ -+pub type key_t = c_int; -+ -+pub const IPC_PRIVATE: key_t = 0; -+pub const IPC_CREAT: c_int = 0o1000; -+pub const IPC_EXCL: c_int = 0o2000; -+pub const IPC_NOWAIT: c_int = 0o4000; -+ -+pub const IPC_RMID: c_int = 0; -+pub const IPC_SET: c_int = 1; -+pub const IPC_STAT: c_int = 2; -+ -+#[repr(C)] -+#[derive(Clone, Copy, Default)] -+pub struct ipc_perm { -+ pub __key: key_t, -+ pub uid: c_ushort, -+ pub gid: c_ushort, -+ pub cuid: c_ushort, -+ pub cgid: c_ushort, -+ pub mode: c_ushort, -+ pub __seq: c_ushort, -+} -+ -+#[unsafe(no_mangle)] -+pub extern "C" fn _cbindgen_export_ipc_perm(value: ipc_perm) { -+ let _ = value; -+} -diff -ruN a/src/header/sys_sem/cbindgen.toml b/src/header/sys_sem/cbindgen.toml ---- a/src/header/sys_sem/cbindgen.toml 1970-01-01 00:00:00.000000000 +0000 -+++ b/src/header/sys_sem/cbindgen.toml 2026-04-15 09:57:28.904183804 +0100 -@@ -0,0 +1,9 @@ -+sys_includes = ["sys/types.h", "sys/ipc.h", "stdint.h"] -+include_guard = "_SYS_SEM_H" -+language = "C" -+style = "Tag" -+no_includes = true -+cpp_compat = true -+ -+[enum] -+prefix_with_name = true -diff -ruN a/src/header/sys_shm/cbindgen.toml b/src/header/sys_shm/cbindgen.toml ---- a/src/header/sys_shm/cbindgen.toml 1970-01-01 00:00:00.000000000 +0000 -+++ b/src/header/sys_shm/cbindgen.toml 2026-04-15 09:57:28.904207067 +0100 -@@ -0,0 +1,9 @@ -+sys_includes = ["sys/types.h", "sys/ipc.h", "sys/mman.h", "stdint.h"] -+include_guard = "_SYS_SHM_H" -+language = "C" -+style = "Tag" -+no_includes = true -+cpp_compat = true -+ -+[enum] -+prefix_with_name = true diff --git a/local/patches/relibc/P3-tcp-sockopt-forward.patch.bak b/local/patches/relibc/P3-tcp-sockopt-forward.patch.bak deleted file mode 100644 index 793f6ebcd..000000000 --- a/local/patches/relibc/P3-tcp-sockopt-forward.patch.bak +++ /dev/null @@ -1,59 +0,0 @@ -diff --git a/src/platform/redox/socket.rs b/src/platform/redox/socket.rs -index d223c36f..f8a1c2e0 100644 ---- a/src/platform/redox/socket.rs -+++ b/src/platform/redox/socket.rs -@@ -774,6 +774,21 @@ impl PalSocket for Sys { - return Ok(()); - } - }, -+ crate::header::sys_socket::constants::IPPROTO_TCP => { -+ let metadata = [SocketCall::GetSockOpt as u64, option_name as u64]; -+ let payload = -+ unsafe { slice::from_raw_parts_mut(option_value as *mut u8, option_len) }; -+ let call_flags = CallFlags::empty(); -+ unsafe { -+ *option_len_ptr = redox_rt::sys::sys_call_ro( -+ socket as usize, -+ payload, -+ CallFlags::empty(), -+ &metadata, -+ )? as socklen_t; -+ } -+ return Ok(()); -+ } - _ => (), - } - -@@ -1069,18 +1069,13 @@ impl PalSocket for Sys { - crate::header::sys_socket::constants::IPPROTO_TCP => { -- match option_name { -- crate::header::sys_socket::constants::TCP_NODELAY => { -- let metadata = [SocketCall::SetSockOpt as u64, option_name as u64]; -- let payload = unsafe { -- slice::from_raw_parts(option_value as *const u8, option_len as usize) -- }; -- redox_rt::sys::sys_call_wo( -- socket as usize, -- payload, -- CallFlags::empty(), -- &metadata, -- )?; -- return Ok(()); -- } -- _ => (), -- } -+ let metadata = [SocketCall::SetSockOpt as u64, option_name as u64]; -+ let payload = unsafe { -+ slice::from_raw_parts(option_value as *const u8, option_len as usize) -+ }; -+ redox_rt::sys::sys_call_wo( -+ socket as usize, -+ payload, -+ CallFlags::empty(), -+ &metadata, -+ )?; -+ return Ok(()); - } - _ => (), - } - diff --git a/local/patches/relibc/P3-vfork.patch.bak b/local/patches/relibc/P3-vfork.patch.bak deleted file mode 100644 index 8fef6518b..000000000 --- a/local/patches/relibc/P3-vfork.patch.bak +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs ---- a/src/header/unistd/mod.rs -+++ b/src/header/unistd/mod.rs -@@ -1262,9 +1262,9 @@ - /// Specifications Issue 6, and removed in Issue 7. - #[deprecated] - // #[unsafe(no_mangle)] - pub extern "C" fn vfork() -> pid_t { -- unimplemented!(); -+ unsafe { fork() } - } - - unsafe fn with_argv( diff --git a/local/patches/relibc/P4-setgroups-getgroups.patch.bak b/local/patches/relibc/P4-setgroups-getgroups.patch.bak deleted file mode 100644 index 038901300..000000000 --- a/local/patches/relibc/P4-setgroups-getgroups.patch.bak +++ /dev/null @@ -1,319 +0,0 @@ -diff --git a/redox-rt/src/lib.rs b/redox-rt/src/lib.rs -index 12835a6..3e99860 100644 ---- a/redox-rt/src/lib.rs -+++ b/redox-rt/src/lib.rs -@@ -18,6 +18,8 @@ use self::{ - - extern crate alloc; -+ -+use alloc::vec::Vec; - - #[macro_export] - macro_rules! asmfunction( -@@ -224,6 +226,7 @@ pub unsafe fn initialize( - rgid: metadata.rgid, - sgid: metadata.sgid, - ns_fd, -+ groups: Vec::new(), - }; - } - } -@@ -241,6 +244,7 @@ pub struct DynamicProcInfo { - pub rgid: u32, - pub sgid: u32, - pub ns_fd: Option, -+ pub groups: Vec, - } - - static DYNAMIC_PROC_INFO: Mutex = Mutex::new(DynamicProcInfo { -@@ -252,6 +256,7 @@ static DYNAMIC_PROC_INFO: Mutex = Mutex::new(DynamicProcInfo { - egid: u32::MAX, - sgid: u32::MAX, - ns_fd: None, -+ groups: Vec::new(), - }); - - #[inline] -diff --git a/redox-rt/src/proc.rs b/redox-rt/src/proc.rs -index 48cce34..7c0cdb7 100644 ---- a/redox-rt/src/proc.rs -+++ b/redox-rt/src/proc.rs -@@ -9,7 +9,7 @@ use crate::{ - }; - use redox_protocols::protocol::{ProcCall, ThreadCall}; - --use alloc::{boxed::Box, vec}; -+use alloc::{boxed::Box, vec, vec::Vec}; - - use goblin::elf::header::ET_DYN; - //TODO: allow use of either 32-bit or 64-bit programs -@@ -1177,6 +1177,7 @@ pub unsafe fn make_init(proc_cap: usize) -> (&'static FdGuardUpper, &'static FdG - egid: 0, - sgid: 0, - ns_fd: None, -+ groups: Vec::new(), - }; - ( - unsafe { (*STATIC_PROC_INFO.get()).proc_fd.as_ref().unwrap() }, -diff --git a/redox-rt/src/sys.rs b/redox-rt/src/sys.rs -index f0363a3..fb9fc52 100644 ---- a/redox-rt/src/sys.rs -+++ b/redox-rt/src/sys.rs -@@ -18,6 +18,7 @@ use crate::{ - signal::tmp_disable_signals, - }; -+use alloc::vec; - use alloc::vec::Vec; - use redox_protocols::protocol::{ - NsDup, ProcCall, ProcKillTarget, RtSigInfo, ThreadCall, WaitFlags, -@@ -415,6 +416,54 @@ pub fn posix_getresugid() -> Resugid { - sgid, - } - } -+pub fn posix_setgroups(groups: &[u32]) -> Result<()> { -+ let _sig_guard = tmp_disable_signals(); -+ -+ let mut buf = Vec::with_capacity(groups.len() * size_of::()); -+ for gid in groups { -+ buf.extend_from_slice(&gid.to_ne_bytes()); -+ } -+ -+ let auth_fd = crate::current_proc_fd().as_raw_fd(); -+ let groups_path = alloc::format!("auth-{}-groups", auth_fd); -+ -+ let thr_fd = crate::RtTcb::current().thread_fd(); -+ let groups_fd = thr_fd.dup(groups_path.as_bytes())?; -+ -+ syscall::write(groups_fd.as_raw_fd(), &buf)?; -+ -+ let mut guard = DYNAMIC_PROC_INFO.lock(); -+ guard.groups = groups.to_vec(); -+ Ok(()) -+} -+ -+pub fn posix_getgroups() -> Vec { -+ let _sig_guard = tmp_disable_signals(); -+ let groups = DYNAMIC_PROC_INFO.lock().groups.clone(); -+ if !groups.is_empty() { -+ return groups; -+ } -+ drop(_sig_guard); -+ posix_readback_groups().unwrap_or_default() -+} -+ -+fn posix_readback_groups() -> Result> { -+ let auth_fd = crate::current_proc_fd().as_raw_fd(); -+ let groups_path = alloc::format!("auth-{}-groups", auth_fd); -+ let thr_fd = crate::RtTcb::current().thread_fd(); -+ let groups_fd = thr_fd.dup(groups_path.as_bytes())?; -+ -+ let mut buf = vec![0u8; 65536 * size_of::()]; -+ let n = syscall::read(groups_fd.as_raw_fd(), &mut buf)?; -+ let count = n / size_of::(); -+ let mut groups = Vec::with_capacity(count); -+ for chunk in buf[..n].chunks_exact(size_of::()) { -+ groups.push(u32::from_ne_bytes(<[u8; size_of::()]>::try_from(chunk).unwrap())); -+ } -+ let mut guard = DYNAMIC_PROC_INFO.lock(); -+ guard.groups = groups.clone(); -+ Ok(groups) -+} - pub fn getens() -> Result { - read_proc_meta(crate::current_proc_fd()).map(|meta| meta.ens as usize) - } -diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs -index 752339a..a0b4304 100644 ---- a/src/platform/redox/mod.rs -+++ b/src/platform/redox/mod.rs -@@ -43,7 +43,7 @@ use crate::{ - sys_file, - sys_mman::{MAP_ANONYMOUS, PROT_READ, PROT_WRITE}, - sys_random, -- sys_resource::{RLIM_INFINITY, rlimit, rusage}, -+ sys_resource::{RLIMIT_AS, RLIMIT_CORE, RLIMIT_DATA, RLIMIT_FSIZE, RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK, RLIM_INFINITY, rlimit, rusage}, - sys_select::timeval, - sys_stat::{S_ISVTX, stat}, - sys_statvfs::statvfs, -@@ -605,51 +605,17 @@ impl Pal for Sys { - } - - fn getgroups(mut list: Out<[gid_t]>) -> Result { -- // FIXME: this operation doesn't scale when group/passwd file grows -- -- let uid = Self::geteuid(); -- let pwd = crate::header::pwd::getpwuid(uid); -- -- if pwd.is_null() { -- return Err(Errno(ENOENT)); -- } -- -- let username = unsafe { CStr::from_ptr((*pwd).pw_name) }; -- let username = username.to_bytes_with_nul(); -- let mut count = 0; -- -- unsafe { -- use crate::header::grp; -- grp::setgrent(); -- -- while let Some(grp) = grp::getgrent().as_ref() { -- let mut i = 0; -- let mut found = false; -- -- while !(*grp.gr_mem.offset(i)).is_null() { -- let member = CStr::from_ptr(*grp.gr_mem.offset(i)); -- if member.to_bytes_with_nul() == username { -- found = true; -- break; -- } -- i += 1; -- } -- -- if found { -- if !list.is_empty() && (count as usize) < list.len() { -- list.index(count).write(grp.gr_gid); -- } -- count += 1; -- } -+ let groups = redox_rt::sys::posix_getgroups(); -+ let count = groups.len(); -+ if !list.is_empty() { -+ if count > list.len() { -+ return Err(Errno(EINVAL)); -+ } -+ for (i, gid) in groups.iter().enumerate() { -+ list.index(i as _).write(*gid as gid_t); - } -- grp::endgrent(); -- } -- -- if !list.is_empty() && (count as usize) > list.len() { -- return Err(Errno(EINVAL)); - } -- -- Ok(count as i32) -+ Ok(count as c_int) - } - - fn getpagesize() -> usize { -@@ -736,21 +702,45 @@ impl Pal for Sys { - } - - fn getrlimit(resource: c_int, mut rlim: Out) -> Result<()> { -- todo_skip!(0, "getrlimit({}, {:p}): not implemented", resource, rlim); -- rlim.write(rlimit { -- rlim_cur: RLIM_INFINITY, -- rlim_max: RLIM_INFINITY, -- }); -+ let (cur, max) = match resource as u32 { -+ r if r == RLIMIT_NOFILE as u32 => (1024, 4096), -+ r if r == RLIMIT_NPROC as u32 => (256, 1024), -+ r if r == RLIMIT_CORE as u32 => (0, RLIM_INFINITY), -+ r if r == RLIMIT_STACK as u32 => (8 * 1024 * 1024, RLIM_INFINITY), -+ r if r == RLIMIT_DATA as u32 => (RLIM_INFINITY, RLIM_INFINITY), -+ r if r == RLIMIT_AS as u32 => (RLIM_INFINITY, RLIM_INFINITY), -+ r if r == RLIMIT_FSIZE as u32 => (RLIM_INFINITY, RLIM_INFINITY), -+ _ => return Err(Errno(EINVAL)), -+ }; -+ rlim.write(rlimit { rlim_cur: cur, rlim_max: max }); - Ok(()) - } - -- unsafe fn setrlimit(resource: c_int, rlim: *const rlimit) -> Result<()> { -- todo_skip!(0, "setrlimit({}, {:p}): not implemented", resource, rlim); -- Err(Errno(EPERM)) -+ unsafe fn setrlimit(resource: c_int, _rlim: *const rlimit) -> Result<()> { -+ match resource as u32 { -+ r if r == RLIMIT_NOFILE as u32 || r == RLIMIT_NPROC as u32 => Err(Errno(EPERM)), -+ r if r == RLIMIT_CORE as u32 -+ || r == RLIMIT_STACK as u32 -+ || r == RLIMIT_DATA as u32 -+ || r == RLIMIT_AS as u32 -+ || r == RLIMIT_FSIZE as u32 => -+ { -+ Ok(()) -+ } -+ _ => Err(Errno(EINVAL)), -+ } - } - -- fn getrusage(who: c_int, r_usage: Out) -> Result<()> { -- todo_skip!(0, "getrusage({}, {:p}): not implemented", who, r_usage); -+ fn getrusage(_who: c_int, mut r_usage: Out) -> Result<()> { -+ r_usage.write(rusage { -+ ru_utime: timeval { tv_sec: 0, tv_usec: 0 }, -+ ru_stime: timeval { tv_sec: 0, tv_usec: 0 }, -+ ru_maxrss: 0, ru_ixrss: 0, ru_idrss: 0, ru_isrss: 0, -+ ru_minflt: 0, ru_majflt: 0, ru_nswap: 0, -+ ru_inblock: 0, ru_oublock: 0, -+ ru_msgsnd: 0, ru_msgrcv: 0, ru_nsignals: 0, -+ ru_nvcsw: 0, ru_nivcsw: 0, -+ }); - Ok(()) - } - -@@ -913,23 +903,7 @@ impl Pal for Sys { - Ok(()) - } - -- unsafe fn msync(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> { -- todo_skip!( -- 0, -- "msync({:p}, 0x{:x}, 0x{:x}): not implemented", -- addr, -- len, -- flags -- ); -- Err(Errno(ENOSYS)) -- /* TODO -- syscall::msync( -- addr as usize, -- round_up_to_page_size(len), -- flags -- )?; -- */ -- } -+ unsafe fn msync(_addr: *mut c_void, _len: usize, _flags: c_int) -> Result<()> { Ok(()) } - - unsafe fn munlock(addr: *const c_void, len: usize) -> Result<()> { - // Redox never swaps -@@ -953,16 +927,7 @@ impl Pal for Sys { - Ok(()) - } - -- unsafe fn madvise(addr: *mut c_void, len: usize, flags: c_int) -> Result<()> { -- todo_skip!( -- 0, -- "madvise({:p}, 0x{:x}, 0x{:x}): not implemented", -- addr, -- len, -- flags -- ); -- Err(Errno(ENOSYS)) -- } -+ unsafe fn madvise(_addr: *mut c_void, _len: usize, _flags: c_int) -> Result<()> { Ok(()) } - - unsafe fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> Result<()> { - let redox_rqtp = unsafe { redox_timespec::from(&*rqtp) }; -@@ -1220,9 +1185,19 @@ impl Pal for Sys { - } - - unsafe fn setgroups(size: size_t, list: *const gid_t) -> Result<()> { -- // TODO -- todo_skip!(0, "setgroups({}, {:p}): not implemented", size, list); -- Err(Errno(ENOSYS)) -+ if size as usize > crate::header::limits::NGROUPS_MAX { -+ return Err(Errno(EINVAL)); -+ } -+ if size > 0 && list.is_null() { -+ return Err(Errno(EFAULT)); -+ } -+ let groups: &[u32] = if size == 0 { -+ &[] -+ } else { -+ core::slice::from_raw_parts(list as *const u32, size as usize) -+ }; -+ redox_rt::sys::posix_setgroups(groups)?; -+ Ok(()) - } - - fn setpgid(pid: pid_t, pgid: pid_t) -> Result<()> { diff --git a/local/patches/relibc/P5-fatal-handler-diagnostics.patch.bak b/local/patches/relibc/P5-fatal-handler-diagnostics.patch.bak deleted file mode 100644 index b4a88edfa..000000000 --- a/local/patches/relibc/P5-fatal-handler-diagnostics.patch.bak +++ /dev/null @@ -1,188 +0,0 @@ -diff --git a/src/lib.rs b/src/lib.rs ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -57,61 +57,201 @@ pub mod start; - pub mod sync; - --use crate::platform::{Allocator, NEWALLOCATOR}; -+use crate::platform::{Allocator, NEWALLOCATOR, Pal, Sys}; - - #[global_allocator] - static ALLOCATOR: Allocator = NEWALLOCATOR; -+ -+const MAX_FATAL_BACKTRACE_FRAMES: usize = 16; -+const MAX_FATAL_FRAME_STRIDE: usize = 1024 * 1024; -+ -+#[inline(never)] -+fn write_process_thread_identity(w: &mut platform::FileWriter) { -+ use core::fmt::Write; -+ -+ let pid = Sys::getpid(); -+ let tid = Sys::gettid(); -+ -+ match crate::pthread::current_thread() { -+ Some(thread) => { -+ let _ = w.write_fmt(format_args!( -+ "RELIBC CONTEXT: pid={} tid={} pthread={:#x}\n", -+ pid, -+ tid, -+ thread as *const _ as usize, -+ )); -+ } -+ None => { -+ let _ = w.write_fmt(format_args!( -+ "RELIBC CONTEXT: pid={} tid={} pthread=\n", -+ pid, tid, -+ )); -+ } -+ } -+} -+ -+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -+#[inline(never)] -+fn current_frame_pointer() -> *const usize { -+ let frame: *const usize; -+ -+ #[cfg(target_arch = "x86_64")] -+ unsafe { -+ core::arch::asm!("mov {}, rbp", out(reg) frame, options(nomem, nostack, preserves_flags)); -+ } -+ -+ #[cfg(target_arch = "x86")] -+ unsafe { -+ core::arch::asm!("mov {}, ebp", out(reg) frame, options(nomem, nostack, preserves_flags)); -+ } -+ -+ #[cfg(target_arch = "aarch64")] -+ unsafe { -+ core::arch::asm!("mov {}, x29", out(reg) frame, options(nomem, nostack, preserves_flags)); -+ } -+ -+ frame -+} -+ -+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -+fn read_backtrace_frame(frame: *const usize) -> Option<(*const usize, usize)> { -+ let align = core::mem::align_of::(); -+ let frame_addr = frame as usize; -+ -+ if frame.is_null() || frame_addr % align != 0 { -+ return None; -+ } -+ -+ let next_frame = unsafe { frame.read() } as *const usize; -+ let return_address = unsafe { frame.add(1).read() }; -+ -+ if return_address == 0 { -+ return None; -+ } -+ -+ Some((next_frame, return_address)) -+} -+ -+#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -+fn is_sane_next_backtrace_frame(current: *const usize, next: *const usize) -> bool { -+ let align = core::mem::align_of::(); -+ let current_addr = current as usize; -+ let next_addr = next as usize; -+ -+ !next.is_null() -+ && next_addr % align == 0 -+ && next_addr > current_addr -+ && next_addr - current_addr <= MAX_FATAL_FRAME_STRIDE -+} -+ -+#[inline(never)] -+fn write_best_effort_backtrace(w: &mut platform::FileWriter) { -+ use core::fmt::Write; -+ -+ let _ = w.write_str("RELIBC: attempting best-effort backtrace\n"); -+ -+ #[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] -+ { -+ let mut frame = current_frame_pointer(); -+ let mut wrote_frame = false; -+ -+ for frame_index in 0..MAX_FATAL_BACKTRACE_FRAMES { -+ let Some((next_frame, return_address)) = read_backtrace_frame(frame) else { -+ break; -+ }; -+ -+ wrote_frame = true; -+ let _ = w.write_fmt(format_args!( -+ "RELIBC BACKTRACE[{frame_index:02}]: {:#x}\n", -+ return_address, -+ )); -+ -+ if !is_sane_next_backtrace_frame(frame, next_frame) { -+ break; -+ } -+ -+ frame = next_frame; -+ } -+ -+ if !wrote_frame { -+ let _ = w.write_str("RELIBC: backtrace attempt produced no frames\n"); -+ } -+ } -+ -+ #[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] -+ { -+ let _ = w.write_str("RELIBC: backtrace unavailable on this architecture\n"); -+ } -+} - - #[unsafe(no_mangle)] - pub extern "C" fn relibc_panic(pi: &::core::panic::PanicInfo) -> ! { - use core::fmt::Write; - - let mut w = platform::FileWriter::new(2); -- let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi)); -+ -+ if let Some(location) = pi.location() { -+ let _ = w.write_fmt(format_args!( -+ "RELIBC PANIC LOCATION: {}:{}:{}\n", -+ location.file(), -+ location.line(), -+ location.column(), -+ )); -+ } else { -+ let _ = w.write_str("RELIBC PANIC LOCATION: \n"); -+ } -+ -+ write_process_thread_identity(&mut w); -+ let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi)); - - core::intrinsics::abort(); - } -@@ -95,23 +235,27 @@ pub extern "C" fn rust_oom(layout: ::core::alloc::Layout) -> ! { - - let mut w = platform::FileWriter::new(2); - let _ = w.write_fmt(format_args!( -- "RELIBC OOM: {} bytes aligned to {} bytes\n", -+ "RELIBC OOM: {} bytes aligned to {} bytes - process will abort\n", - layout.size(), - layout.align() - )); -+ write_process_thread_identity(&mut w); -+ write_best_effort_backtrace(&mut w); - - core::intrinsics::abort(); - } - - #[cfg(not(test))] - #[allow(non_snake_case)] - #[linkage = "weak"] - #[unsafe(no_mangle)] - pub extern "C" fn _Unwind_Resume() -> ! { - use core::fmt::Write; - - let mut w = platform::FileWriter::new(2); -- let _ = w.write_str("_Unwind_Resume\n"); -+ let _ = w.write_str( -+ "RELIBC: _Unwind_Resume called - exception propagation failed, aborting\n", -+ ); -+ write_process_thread_identity(&mut w); - - core::intrinsics::abort(); - } diff --git a/local/patches/relibc/P5-robust-mutex-enotrec-fix.patch.bak b/local/patches/relibc/P5-robust-mutex-enotrec-fix.patch.bak deleted file mode 100644 index b6cbc202b..000000000 --- a/local/patches/relibc/P5-robust-mutex-enotrec-fix.patch.bak +++ /dev/null @@ -1,87 +0,0 @@ -Fix ENOTRECOVERABLE returned for non-robust mutexes and register main -thread in OS_TID_TO_PTHREAD. - -The robust mutex liveness check (mutex_owner_id_is_live) was returning -ENOTRECOVERABLE for non-robust mutexes when the owner appeared dead. -Per POSIX, the behaviour of a non-robust mutex whose owner has died is -undefined; returning an error crashes every Rust std::sync::Mutex user. -For lock_inner, fall through to spin/futex-wait instead. For try_lock, -return EBUSY instead. - -Additionally, pthread::init() never registered the main thread in -OS_TID_TO_PTHREAD, so any mutex owned by the main thread would always -appear to have a dead owner, making the liveness check unreliable. - -diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs -index 8243a48..c455a67 100644 ---- a/src/pthread/mod.rs -+++ b/src/pthread/mod.rs -@@ -43,9 +43,13 @@ pub unsafe fn init() { - thread.stack_size = STACK_SIZE; - } - -- unsafe { Tcb::current() } -- .expect_notls("no TCB present for main thread") -- .pthread = thread; -+ let tcb = unsafe { Tcb::current() } -+ .expect_notls("no TCB present for main thread"); -+ tcb.pthread = thread; -+ -+ OS_TID_TO_PTHREAD -+ .lock() -+ .insert(Sys::current_os_tid(), ForceSendSync(tcb as *const Tcb as *mut Tcb)); - } - - //static NEXT_INDEX: AtomicU32 = AtomicU32::new(FIRST_THREAD_IDX + 1); -diff --git a/src/sync/pthread_mutex.rs b/src/sync/pthread_mutex.rs -index af0c429..1b2b3ca 100644 ---- a/src/sync/pthread_mutex.rs -+++ b/src/sync/pthread_mutex.rs -@@ -136,14 +136,17 @@ impl RlctMutex { - Err(thread) => { - let owner = thread & INDEX_MASK; - -- if !crate::pthread::mutex_owner_id_is_live(owner) { -- if !self.robust { -- return Err(Errno(ENOTRECOVERABLE)); -- } -- -+ if !crate::pthread::mutex_owner_id_is_live(owner) && self.robust { - let new_value = (thread & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread; - match self.inner.compare_exchange( - thread, -@@ -152,6 +155,12 @@ impl RlctMutex { - Ok(_) => return self.finish_lock_acquire(true), - Err(_) => continue, - } -+ } else if !crate::pthread::mutex_owner_id_is_live(owner) { -+ // Non-robust mutex with apparently-dead owner: per POSIX the -+ // behaviour is undefined. We conservatively keep spinning / -+ // futex-waiting rather than returning ENOTRECOVERABLE, which -+ // would crash any Rust std::sync::Mutex user. - } - - if spins_left > 0 { -@@ -241,14 +250,17 @@ impl RlctMutex { - - if current & FUTEX_OWNER_DIED != 0 || (owner != 0 && !crate::pthread::mutex_owner_id_is_live(owner)) { -- if !self.robust { -- return Err(Errno(ENOTRECOVERABLE)); -- } -- -+ if self.robust { - let new_value = (current & WAITING_BIT) | FUTEX_OWNER_DIED | this_thread; - match self.inner.compare_exchange( - current, -@@ -257,6 +269,11 @@ impl RlctMutex { - Ok(_) => return self.finish_lock_acquire(true), - Err(_) => continue, - } -+ } else { -+ // Non-robust mutex: owner appears dead but POSIX behaviour is -+ // undefined; report busy rather than ENOTRECOVERABLE. -+ return Err(Errno(EBUSY)); -+ } - } - - return Err(Errno(EBUSY)); diff --git a/local/patches/relibc/P5-startup-init-panic-hardening.patch.bak b/local/patches/relibc/P5-startup-init-panic-hardening.patch.bak deleted file mode 100644 index f9df2d50f..000000000 --- a/local/patches/relibc/P5-startup-init-panic-hardening.patch.bak +++ /dev/null @@ -1,101 +0,0 @@ -diff --git a/src/start.rs b/src/start.rs ---- a/src/start.rs -+++ b/src/start.rs -@@ -1,8 +1,6 @@ - //! Startup code. - - use alloc::{boxed::Box, vec::Vec}; --use core::{intrinsics, ptr}; -- --#[cfg(target_os = "redox")] --use generic_rt::ExpectTlsFree; -+use core::{fmt::Write, intrinsics, panic::AssertUnwindSafe, ptr}; - - use crate::{ - ALLOCATOR, -@@ -143,6 +141,28 @@ fn io_init() { - stdio::stderr = stdio::default_stderr().get(); - } - } -+ -+fn catch_unwind(f: AssertUnwindSafe) -> Result<(), ()> { -+ fn do_call(data: *mut u8) { -+ let callback = unsafe { &mut *data.cast::>>() }; -+ if let Some(callback) = callback.take() { -+ callback.0(); -+ } -+ } -+ -+ fn do_catch(_data: *mut u8, _payload: *mut u8) {} -+ -+ let mut callback = Some(f); -+ let panicked = unsafe { -+ intrinsics::catch_unwind( -+ do_call::, -+ (&mut callback as *mut Option>).cast(), -+ do_catch::, -+ ) != 0 -+ }; -+ -+ if panicked { Err(()) } else { Ok(()) } -+} -+ - #[cold] - fn abort_startup(args: core::fmt::Arguments<'_>) -> ! { - let mut w = platform::FileWriter::new(2); -@@ -164,15 +184,24 @@ pub unsafe extern "C" fn relibc_start_v1( - unsafe { relibc_verify_host() }; - - #[cfg(target_os = "redox")] -- let thr_fd = redox_rt::proc::FdGuard::new( -- unsafe { -- crate::platform::get_auxv_raw(sp.auxv().cast(), redox_rt::auxv_defs::AT_REDOX_THR_FD) -- } -- .expect_notls("no thread fd present"), -- ) -- .to_upper() -- .expect_notls("failed to move thread fd to upper table"); -+ let thr_fd = { -+ let thr_fd = match unsafe { -+ crate::platform::get_auxv_raw(sp.auxv().cast(), redox_rt::auxv_defs::AT_REDOX_THR_FD) -+ } { -+ Some(thr_fd) => thr_fd, -+ None => abort_startup(format_args!( -+ "relibc_start_v1: missing AT_REDOX_THR_FD auxv entry; no thread fd present\n" -+ )), -+ }; -+ -+ match redox_rt::proc::FdGuard::new(thr_fd).to_upper() { -+ Ok(thr_fd) => thr_fd, -+ Err(err) => abort_startup(format_args!( -+ "relibc_start_v1: failed to move thread fd to upper table: {err:?}\n" -+ )), -+ } -+ }; - - // Initialize TLS, if necessary - unsafe { -@@ -237,7 +266,10 @@ pub unsafe extern "C" fn relibc_start_v1( - let mut f = unsafe { &__preinit_array_start } as *const _; - #[allow(clippy::op_ref)] - while f < &raw const __preinit_array_end { -- (unsafe { *f })(); -+ let func = unsafe { *f }; -+ if catch_unwind(AssertUnwindSafe(|| unsafe { (*f)() })).is_err() { -+ log_initializer_panic(".preinit_array", func); -+ } - f = unsafe { f.offset(1) }; - } - } -@@ -247,7 +279,10 @@ pub unsafe extern "C" fn relibc_start_v1( - let mut f = unsafe { &__init_array_start } as *const _; - #[allow(clippy::op_ref)] - while f < &raw const __init_array_end { -- (unsafe { *f })(); -+ let func = unsafe { *f }; -+ if catch_unwind(AssertUnwindSafe(|| unsafe { (*f)() })).is_err() { -+ log_initializer_panic(".init_array", func); -+ } - f = unsafe { f.offset(1) }; - } - } diff --git a/local/patches/relibc/P7-setpriority.patch.bak b/local/patches/relibc/P7-setpriority.patch.bak deleted file mode 100644 index dcc499f26..000000000 --- a/local/patches/relibc/P7-setpriority.patch.bak +++ /dev/null @@ -1,104 +0,0 @@ -diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs ---- a/src/platform/redox/mod.rs -+++ b/src/platform/redox/mod.rs -@@ -77,11 +77,74 @@ static mut BRK_CUR: *mut c_void = ptr::null_mut(); - static mut BRK_END: *mut c_void = ptr::null_mut(); - - const PAGE_SIZE: usize = 4096; -+const NICE_MIN: c_int = -20; -+const NICE_MAX: c_int = 19; - - fn round_up_to_page_size(val: usize) -> Option { - val.checked_add(PAGE_SIZE) - .map(|val| (val - 1) / PAGE_SIZE * PAGE_SIZE) - } -+ -+fn is_current_process_priority_target(which: c_int, who: id_t) -> bool { -+ which == crate::header::sys_resource::PRIO_PROCESS -+ && (who == 0 || who == redox_rt::sys::posix_getpid() as id_t) -+} -+ -+fn current_process_thread_handle(index: usize) -> Result> { -+ let thread_name = format!("thread-{index}"); -+ match redox_rt::current_proc_fd().dup(thread_name.as_bytes()) { -+ Ok(thread_fd) => Ok(Some(thread_fd)), -+ Err(error) if error.errno == ENOENT => Ok(None), -+ Err(error) => Err(Errno(error.errno)), -+ } -+} -+ -+fn current_process_priority_handle(index: usize) -> Result> { -+ let Some(thread_fd) = current_process_thread_handle(index)? else { -+ return Ok(None); -+ }; -+ -+ thread_fd -+ .dup(b"priority") -+ .map(Some) -+ .map_err(|error| Errno(error.errno)) -+} -+ -+fn read_current_process_nice() -> Result { -+ let Some(priority_fd) = current_process_priority_handle(0)? else { -+ return Err(Errno(ESRCH)); -+ }; -+ -+ let mut nice_bytes = [0_u8; size_of::()]; -+ if priority_fd.read(&mut nice_bytes)? != size_of::() { -+ return Err(Errno(EIO)); -+ } -+ -+ Ok(c_int::from_ne_bytes(nice_bytes)) -+} -+ -+fn write_current_process_nice(nice: c_int) -> Result<()> { -+ let mut updated_threads = 0; -+ let nice_bytes = nice.to_ne_bytes(); -+ -+ for index in 0.. { -+ let Some(priority_fd) = current_process_priority_handle(index)? else { -+ break; -+ }; -+ -+ if priority_fd.write(&nice_bytes)? != nice_bytes.len() { -+ return Err(Errno(EIO)); -+ } -+ updated_threads += 1; -+ } -+ -+ if updated_threads == 0 { -+ return Err(Errno(ESRCH)); -+ } -+ -+ Ok(()) -+} - - fn cvt_uid(id: c_int) -> Result> { - if id == -1 { - return Ok(None); -@@ -698,6 +761,11 @@ impl Pal for Sys { - } - - fn getpriority(which: c_int, who: id_t) -> Result { -+ if is_current_process_priority_target(which, who) { -+ let nice = read_current_process_nice()?; -+ return Ok(20 - nice); -+ } -+ - match redox_rt::sys::posix_getpriority(which, who as u32) { - Ok(kernel_prio) => { - let posix_prio = (kernel_prio as i32 * -1) + 40 as i32; -@@ -1274,7 +1342,12 @@ impl Pal for Sys { - } - - fn setpriority(which: c_int, who: id_t, prio: c_int) -> Result<()> { -- let clamped_prio = prio.clamp(-20, 19); -+ let clamped_prio = prio.clamp(NICE_MIN, NICE_MAX); -+ -+ if is_current_process_priority_target(which, who) { -+ return write_current_process_nice(clamped_prio); -+ } -+ - let kernel_prio = (20 + clamped_prio) as u32; - - match redox_rt::sys::posix_setpriority(which, who as u32, kernel_prio) { diff --git a/local/patches/relibc/P9-spin-and-barrier.patch.bak b/local/patches/relibc/P9-spin-and-barrier.patch.bak deleted file mode 100644 index 51ecc3e98..000000000 --- a/local/patches/relibc/P9-spin-and-barrier.patch.bak +++ /dev/null @@ -1,43 +0,0 @@ -diff --git a/src/sync/pthread_mutex.rs b/src/sync/pthread_mutex.rs -index 2871a6149..3c8e73f15 100644 ---- a/src/sync/pthread_mutex.rs -+++ b/src/sync/pthread_mutex.rs -@@ -35,7 +35,7 @@ const FUTEX_OWNER_DIED: u32 = 1 << 30; - const INDEX_MASK: u32 = !(WAITING_BIT | FUTEX_OWNER_DIED); - // TODO: Lower limit is probably better. - const RECURSIVE_COUNT_MAX_INCLUSIVE: u32 = u32::MAX; --const SPIN_COUNT: usize = 0; -+const SPIN_COUNT: usize = 100; - - impl RlctMutex { - pub(crate) fn new(attr: &RlctMutexAttr) -> Result { -diff --git a/src/sync/barrier.rs b/src/sync/barrier.rs -index b5847b5..a8e3c2f0 100644 ---- a/src/sync/barrier.rs -+++ b/src/sync/barrier.rs -@@ -47,6 +47,9 @@ impl Barrier { - cvar: FutexState::new(count.get()), - } - } -+ pub fn destroy(&self) {} -+ - pub fn wait(&self) -> WaitResult { - let _ = &self.lock; - let sense = self.cvar.sense.load(Ordering::Acquire); -diff --git a/src/header/pthread/barrier.rs b/src/header/pthread/barrier.rs -index 1a5df3a..e69e2b9 100644 ---- a/src/header/pthread/barrier.rs -+++ b/src/header/pthread/barrier.rs -@@ -24,10 +24,10 @@ pub(crate) struct RlctBarrierAttr { - // Not async-signal-safe. - #[unsafe(no_mangle)] - pub unsafe extern "C" fn pthread_barrier_destroy(barrier: *mut pthread_barrier_t) -> c_int { -- // Behavior is undefined if any thread is currently waiting when this is called. -- -- // No-op, currently. -- unsafe { core::ptr::drop_in_place(barrier.cast::()) }; -+ let barrier = unsafe { &*barrier.cast::() }; -+ barrier.destroy(); - - 0 - } \ No newline at end of file diff --git a/local/patches/relibc/absorbed/P5-robust-mutex-enotrec-fix.patch.bak b/local/patches/relibc/absorbed/P5-robust-mutex-enotrec-fix.patch.bak deleted file mode 100644 index fa0043b94..000000000 --- a/local/patches/relibc/absorbed/P5-robust-mutex-enotrec-fix.patch.bak +++ /dev/null @@ -1,35 +0,0 @@ -Fix ENOTRECOVERABLE returned for non-robust mutexes and register main -thread in OS_TID_TO_PTHREAD. - -The robust mutex liveness check (mutex_owner_id_is_live) was returning -ENOTRECOVERABLE for non-robust mutexes when the owner appeared dead. -Per POSIX, the behaviour of a non-robust mutex whose owner has died is -undefined; returning an error crashes every Rust std::sync::Mutex user. -For lock_inner, fall through to spin/futex-wait instead. For try_lock, -return EBUSY instead. - -Additionally, pthread::init() never registered the main thread in -OS_TID_TO_PTHREAD, so any mutex owned by the main thread would always -appear to have a dead owner, making the liveness check unreliable. - -diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs -index 8243a48..c455a67 100644 ---- a/src/pthread/mod.rs -+++ b/src/pthread/mod.rs -@@ -43,9 +43,13 @@ pub unsafe fn init() { - thread.stack_size = STACK_SIZE; - } - -- unsafe { Tcb::current() } -- .expect_notls("no TCB present for main thread") -- .pthread = thread; -+ let tcb = unsafe { Tcb::current() } -+ .expect_notls("no TCB present for main thread"); -+ tcb.pthread = thread; -+ -+ OS_TID_TO_PTHREAD -+ .lock() -+ .insert(Sys::current_os_tid(), ForceSendSync(tcb as *const Tcb as *mut Tcb)); - } - - //static NEXT_INDEX: AtomicU32 = AtomicU32::new(FIRST_THREAD_IDX + 1); diff --git a/local/patches/relibc/redox.patch.bak b/local/patches/relibc/redox.patch.bak deleted file mode 100644 index 294cf154c..000000000 --- a/local/patches/relibc/redox.patch.bak +++ /dev/null @@ -1,127 +0,0 @@ -diff --git a/src/header/fcntl/mod.rs b/src/header/fcntl/mod.rs ---- a/src/header/fcntl/mod.rs -+++ b/src/header/fcntl/mod.rs -@@ -9,0 +10 @@ -+ header::unistd::close, -@@ -75,0 +77,17 @@ -+ -+ if cmd == F_DUPFD_CLOEXEC { -+ let new_fd = Sys::fcntl(fildes, F_DUPFD_CLOEXEC, arg).or_minus_one_errno(); -+ if new_fd >= 0 { -+ return new_fd; -+ } -+ -+ let new_fd = Sys::fcntl(fildes, F_DUPFD, arg).or_minus_one_errno(); -+ if new_fd < 0 { -+ return -1; -+ } -+ if Sys::fcntl(new_fd, F_SETFD, FD_CLOEXEC as c_ulonglong).or_minus_one_errno() < 0 { -+ let _ = close(new_fd); -+ return -1; -+ } -+ return new_fd; -+ } - -diff --git a/src/pthread/mod.rs b/src/pthread/mod.rs ---- a/src/pthread/mod.rs -+++ b/src/pthread/mod.rs -@@ -2,6 +2,7 @@ - - use core::{ - cell::UnsafeCell, -+ panic::AssertUnwindSafe, - ptr, - sync::atomic::{AtomicBool, AtomicUsize, Ordering}, - }; -@@ -208,11 +209,39 @@ pub(crate) unsafe fn create( - } - - /// A shim to wrap thread entry points in logic to set up TLS, for example -+fn catch_unwind(f: AssertUnwindSafe) -> Result<(), ()> { -+ fn do_call(data: *mut u8) { -+ let callback = unsafe { &mut *data.cast::>>() }; -+ if let Some(callback) = callback.take() { -+ callback.0(); -+ } -+ } -+ -+ fn do_catch(_data: *mut u8, _payload: *mut u8) {} -+ -+ let mut callback = Some(f); -+ let panicked = unsafe { -+ core::intrinsics::catch_unwind( -+ do_call::, -+ (&mut callback as *mut Option>).cast(), -+ do_catch::, -+ ) != 0 -+ }; -+ -+ if panicked { Err(()) } else { Ok(()) } -+} -+ - unsafe extern "C" fn new_thread_shim( - tcb: *mut Tcb, - synchronization_mutex: *const Mutex, - ) -> ! { -- let tcb = unsafe { tcb.as_mut() }.expect_notls("non-null TLS is required"); -+ let tcb = match unsafe { tcb.as_mut() } { -+ Some(tcb) => tcb, -+ None => { -+ log::error!("pthread: child thread started without a TCB"); -+ unsafe { exit_current_thread(Retval(ptr::null_mut())) } -+ } -+ }; - - #[cfg(not(target_os = "redox"))] - { -@@ -227,12 +256,23 @@ unsafe extern "C" fn new_thread_shim( - unsafe { - tcb.activate(None); - } -- redox_rt::signal::setup_sighandler(&tcb.os_specific, false); -+ match catch_unwind(AssertUnwindSafe(|| { -+ redox_rt::signal::setup_sighandler(&tcb.os_specific, false) -+ })) { -+ Ok(()) => {} -+ Err(()) => { -+ log::error!("pthread: failed to set up child thread signal handler"); -+ unsafe { exit_current_thread(Retval(ptr::null_mut())) } -+ } -+ } - } - - let procmask = unsafe { (&*synchronization_mutex).as_ptr().read() }; - -- unsafe { tcb.copy_masters() }.unwrap(); -+ if let Err(err) = unsafe { tcb.copy_masters() } { -+ log::error!("pthread: failed to copy TLS masters for child thread: {err:?}"); -+ unsafe { exit_current_thread(Retval(ptr::null_mut())) } -+ } - - unsafe { (*tcb).pthread.os_tid.get().write(Sys::current_os_tid()) }; - -@@ -240,11 +280,21 @@ unsafe extern "C" fn new_thread_shim( - - #[cfg(target_os = "redox")] - { -- redox_rt::signal::set_sigmask(Some(procmask), None) -- .expect("failed to set procmask in child thread"); -+ if let Err(err) = redox_rt::signal::set_sigmask(Some(procmask), None) { -+ log::error!("pthread: failed to set child thread signal mask: {err:?}"); -+ } - } - -- let retval = unsafe { entry_point(arg) }; -+ let mut retval = ptr::null_mut(); -+ match catch_unwind(AssertUnwindSafe(|| { -+ retval = unsafe { entry_point(arg) }; -+ })) { -+ Ok(()) => {} -+ Err(()) => { -+ log::error!("pthread: child thread entry point panicked"); -+ unsafe { exit_current_thread(Retval(ptr::null_mut())) } -+ } -+ } - - unsafe { exit_current_thread(Retval(retval)) } - } diff --git a/local/recipes/drivers/ehcid/source/src/main.rs.bak b/local/recipes/drivers/ehcid/source/src/main.rs.bak deleted file mode 100644 index fcd01d234..000000000 --- a/local/recipes/drivers/ehcid/source/src/main.rs.bak +++ /dev/null @@ -1,33 +0,0 @@ -mod registers; - -use std::env; -use std::process; -use log::{info, error, LevelFilter}; - -struct StderrLogger; -impl log::Log for StderrLogger { - fn enabled(&self, md: &log::Metadata) -> bool { md.level() <= LevelFilter::Info } - fn log(&self, r: &log::Record) { eprintln!("[{}] ehcid: {}", r.level(), r.args()); } - fn flush(&self) {} -} - -fn main() { - log::set_logger(&StderrLogger).ok(); - log::set_max_level(LevelFilter::Info); - - let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") { - Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, - Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); } - }; - - let device_path = env::var("PCID_DEVICE_PATH").unwrap_or_default(); - info!("EHCI USB 2.0 controller at {} (PCI fd: {})", device_path, channel_fd); - - // Enable bus mastering and MMIO via the channel - let enable_cmd: [u8; 4] = [0x07, 0x00, 0x00, 0x00]; // IO + MEM + BUS_MASTER - if let Err(e) = syscall::write(channel_fd, &enable_cmd) { - error!("failed to enable device: {}", e); - } - - info!("ehcid: initialized — ready for enumeration"); -} diff --git a/local/recipes/drivers/ohcid/source/src/main.rs.bak b/local/recipes/drivers/ohcid/source/src/main.rs.bak deleted file mode 100644 index 9a8bf5157..000000000 --- a/local/recipes/drivers/ohcid/source/src/main.rs.bak +++ /dev/null @@ -1,23 +0,0 @@ -mod registers; - -use std::env; -use std::process; -use log::{info, error, LevelFilter}; - -struct StderrLogger; -impl log::Log for StderrLogger { - fn enabled(&self, md: &log::Metadata) -> bool { md.level() <= LevelFilter::Info } - fn log(&self, r: &log::Record) { eprintln!("[{}] ohcid: {}", r.level(), r.args()); } - fn flush(&self) {} -} - -fn main() { - log::set_logger(&StderrLogger).ok(); - log::set_max_level(LevelFilter::Info); - let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") { - Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, - Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); } - }; - info!("OHCI USB 1.1 controller (PCI fd: {})", channel_fd); - info!("ohcid: ready"); -} diff --git a/local/recipes/drivers/uhcid/source/src/main.rs.bak b/local/recipes/drivers/uhcid/source/src/main.rs.bak deleted file mode 100644 index 49bf63579..000000000 --- a/local/recipes/drivers/uhcid/source/src/main.rs.bak +++ /dev/null @@ -1,23 +0,0 @@ -mod registers; - -use std::env; -use std::process; -use log::{info, error, LevelFilter}; - -struct StderrLogger; -impl log::Log for StderrLogger { - fn enabled(&self, md: &log::Metadata) -> bool { md.level() <= LevelFilter::Info } - fn log(&self, r: &log::Record) { eprintln!("[{}] uhcid: {}", r.level(), r.args()); } - fn flush(&self) {} -} - -fn main() { - log::set_logger(&StderrLogger).ok(); - log::set_max_level(LevelFilter::Info); - let channel_fd: usize = match env::var("PCID_CLIENT_CHANNEL") { - Ok(s) => match s.parse() { Ok(fd) => fd, Err(_) => { error!("invalid PCID_CLIENT_CHANNEL"); process::exit(1); } }, - Err(_) => { error!("PCID_CLIENT_CHANNEL not set"); process::exit(1); } - }; - info!("UHCI USB 1.1 controller (PCI fd: {})", channel_fd); - info!("uhcid: ready"); -} diff --git a/local/recipes/system/redbear-wifictl/source/src/dbus_nm.rs.bak b/local/recipes/system/redbear-wifictl/source/src/dbus_nm.rs.bak deleted file mode 100644 index c44bca9cd..000000000 --- a/local/recipes/system/redbear-wifictl/source/src/dbus_nm.rs.bak +++ /dev/null @@ -1,47 +0,0 @@ -// D-Bus org.freedesktop.NetworkManager interface -// Exposes Wi-Fi device list, access points, connection state -// Uses zbus for D-Bus communication - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct NmWifiDevice { - pub interface: String, - pub hw_address: String, - pub state: NmDeviceState, - pub access_points: Vec, -} - -#[repr(u32)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum NmDeviceState { - Unknown = 0, - Unmanaged = 10, - Unavailable = 20, - Disconnected = 30, - Prepare = 40, - Config = 50, - NeedAuth = 60, - IpConfig = 70, - IpCheck = 80, - Activated = 100, - Failed = 120, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct NmAccessPoint { - pub ssid: String, - pub strength: u8, - pub security: String, - pub frequency: u32, -} - -// Register D-Bus object path: /org/freedesktop/NetworkManager -// Properties: Devices, WirelessEnabled -// Methods: GetDevices, ActivateConnection, DeactivateConnection -pub fn register_nm_interface() { - #[cfg(feature = "dbus-nm")] - { - let _ = std::any::type_name::(); - } - - log::info!("wifictl: D-Bus NetworkManager interface registered"); -}