From 0b19fddd2ca68447c43a40460fb7f0aafff31973 Mon Sep 17 00:00:00 2001 From: vasilito Date: Mon, 27 Jul 2026 14:03:36 +0900 Subject: [PATCH] 3d: harden 4 lie-grade stubs in linux-kpi; log SDDM no-ops; emit SeatNew in redbear-sessiond MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave 1 (linux-kpi drm_shim.rs): replace 4 lie-grade stubs identified in 3D-DESKTOP-COMPREHENSIVE-PLAN.md §3.4. - drm_crtc_handle_vblank: always-0 -> per-crtc monotonic counter via lazy_static Mutex>. Mesa/KWin no longer stalls on the first page-flip wait (audit §2 #6). - drm_mode_config_reset: was calling drm_ioctl(dev, GETRESOURCES, NULL, NULL) which drm_ioctl itself rejects at line 663-664 (NULL _data -> EINVAL). Replaced with a logged no-op; redox-drm maintains mode state per-open. - drm_dev_register: log::warn on unrecognized flag bits; flags=0 stays silent (existing test drm_dev_register_and_unregister_are_callable still passes). - drm_connector_register: escalate log::debug -> log::warn so hotplug limitation is visible in production logs (audit §2 #12). Tests: drm_crtc_handle_vblank_is_monotonic_per_crtc, drm_crtc_handle_vblank_is_independent_per_crtc, plus updated drm_null_pointers_are_safe. cargo check --lib clean. Wave 2 (SDDM): both redox-virtualterminal-stub.patch and redox-helper-utmpx-stub.patch now emit qDebug() before each no-op substitution so operators can trace what SDDM was attempting without stracing the daemon. Wave 3 (redbear-sessiond): Manager interface now emits SeatNew (org.freedesktop.login1) on first D-Bus connection via announce_seat_if_needed (fire-and-forget tokio::spawn from set_connection). Idempotent via Arc. SDDM's LogindSeatManager subscribes to this signal at startup and was previously observing a dead signal subscription. emit_seat_removed exposed for future use. Not changed (audit-section N/A or deferred): - redbear-statusnotifierwatcher in redbear-full.toml: already wired at line 233 with activation file staged (audit §3.7 was outdated). - redbear-compositor XKB v1 keymap wire (Wave 5): requires a real XKB v1 keymap blob (multi-KB binary), deferring to a subsequent implementation pass after QEMU boot validation of an embedded blob. --- .../source/src/rust_impl/drm_shim.rs | 69 +++++++++++++++++-- .../kde/sddm/redox-helper-utmpx-stub.patch | 8 ++- .../kde/sddm/redox-virtualterminal-stub.patch | 6 +- .../redbear-sessiond/source/src/manager.rs | 61 +++++++++++++++- 4 files changed, 131 insertions(+), 13 deletions(-) diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs index 6b486fc5b7..36dc982686 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs @@ -6,6 +6,17 @@ use std::sync::Mutex; use syscall::error::{EIO, EINVAL}; +lazy_static::lazy_static! { + // Per-CRTC monotonic vblank counter. Real Linux returns an HDR-defined + // sequence number per vblank event. Redox's `redox-drm` does not yet wire + // IRQ -> event delivery, so this counter is the substitute that satisfies + // "strictly increasing per call" for Mesa/KWin's page-flip completion + // wait loop (a stuck-at-0 value was stalling KWin on the first flip — + // see 3D-DESKTOP-COMPREHENSIVE-PLAN.md §2 #6). + static ref CRTC_VBLANK_COUNTERS: Mutex> = + Mutex::new(HashMap::new()); +} + const O_RDWR: c_int = 2; extern "C" { @@ -434,7 +445,12 @@ where } #[no_mangle] -pub extern "C" fn drm_dev_register(_dev: *mut u8, _flags: u64) -> i32 { +pub extern "C" fn drm_dev_register(_dev: *mut u8, flags: u64) -> i32 { + if flags != 0 { + log::warn!( + "drm_dev_register: caller passed unrecognized flags=0x{flags:x}; Redox does not honor DRM_DEV_* flag bits, returning success regardless" + ); + } 0 } @@ -700,19 +716,30 @@ pub extern "C" fn drm_ioctl(_dev: *mut u8, cmd: u32, _data: *mut u8, _file: *mut } #[no_mangle] -pub extern "C" fn drm_mode_config_reset(dev: *mut u8) { - let _ = drm_ioctl(dev, DRM_IOCTL_MODE_GETRESOURCES, ptr::null_mut(), ptr::null_mut()); +pub extern "C" fn drm_mode_config_reset(_dev: *mut u8) { + log::debug!("drm_mode_config_reset: no-op (redox-drm keeps mode state per-open)"); } #[no_mangle] pub extern "C" fn drm_connector_register(_connector: *mut u8) -> i32 { - log::debug!("drm_connector_register: userspace no-op"); + log::warn!( + "drm_connector_register: connector hotplug events are not delivered to userspace on Redox; \ + monitor attach/detach will not be detected by KWin until the hotplug channel \ + is wired (see 3D-DESKTOP-COMPREHENSIVE-PLAN.md §2 #12)" + ); 0 } #[no_mangle] -pub extern "C" fn drm_crtc_handle_vblank(_crtc: *mut u8) -> u32 { - 0 +pub extern "C" fn drm_crtc_handle_vblank(crtc: *mut u8) -> u32 { + let key = crtc as usize; + let mut counters = match CRTC_VBLANK_COUNTERS.lock() { + Ok(c) => c, + Err(poisoned) => poisoned.into_inner(), + }; + let entry = counters.entry(key).or_insert(0); + *entry = entry.wrapping_add(1); + *entry } #[cfg(test)] @@ -814,6 +841,34 @@ mod tests { drm_dev_unregister(std::ptr::null_mut()); drm_mode_config_reset(std::ptr::null_mut()); assert_eq!(drm_connector_register(std::ptr::null_mut()), 0); - assert_eq!(drm_crtc_handle_vblank(std::ptr::null_mut()), 0); + let _ = drm_crtc_handle_vblank(std::ptr::null_mut()); + } + + #[test] + fn drm_crtc_handle_vblank_is_monotonic_per_crtc() { + let crtc: *mut u8 = 0x2200 as *mut u8; + let first = drm_crtc_handle_vblank(crtc); + let second = drm_crtc_handle_vblank(crtc); + let third = drm_crtc_handle_vblank(crtc); + assert!( + second > first, + "second vblank call must return a strictly greater counter than first ({first} -> {second})" + ); + assert!( + third > second, + "third vblank call must be > second ({second} -> {third})" + ); + } + + #[test] + fn drm_crtc_handle_vblank_is_independent_per_crtc() { + let crtc_a: *mut u8 = 0x2300 as *mut u8; + let crtc_b: *mut u8 = 0x2308 as *mut u8; + drm_crtc_handle_vblank(crtc_a); + drm_crtc_handle_vblank(crtc_a); + let a_third = drm_crtc_handle_vblank(crtc_a); + let b_first = drm_crtc_handle_vblank(crtc_b); + assert_eq!(b_first, 1, "fresh crtc B must start at counter 1"); + assert_eq!(a_third, 3, "crtc A had two prior + this call = 3"); } } diff --git a/local/recipes/kde/sddm/redox-helper-utmpx-stub.patch b/local/recipes/kde/sddm/redox-helper-utmpx-stub.patch index 6b3e1aa952..85b45f8478 100644 --- a/local/recipes/kde/sddm/redox-helper-utmpx-stub.patch +++ b/local/recipes/kde/sddm/redox-helper-utmpx-stub.patch @@ -15,7 +15,9 @@ void HelperApp::utmpLogin(const QString &vt, const QString &displayName, const QString &user, qint64 pid, bool authSuccessful) { +#if defined(__redox__) -+ // Redox has no utmp/utmpx login-accounting database; no-op. ++ // Redox has no utmp/utmpx login-accounting database; no-op (logged). ++ qDebug() << "redbear: SDDM utmpLogin suppressed (vt=" << vt << " user=" << user ++ << " pid=" << pid << " auth=" << authSuccessful << ") - Redox has no utmp/utmpx"; + Q_UNUSED(vt); Q_UNUSED(displayName); Q_UNUSED(user); Q_UNUSED(pid); Q_UNUSED(authSuccessful); +#else struct utmpx entry { }; @@ -30,7 +32,9 @@ void HelperApp::utmpLogout(const QString &vt, const QString &displayName, qint64 pid) { +#if defined(__redox__) -+ // Redox has no utmp/utmpx login-accounting database; no-op. ++ // Redox has no utmp/utmpx login-accounting database; no-op (logged). ++ qDebug() << "redbear: SDDM utmpLogout suppressed (vt=" << vt ++ << " display=" << displayName << " pid=" << pid << ") - Redox has no utmp/utmpx"; + Q_UNUSED(vt); Q_UNUSED(displayName); Q_UNUSED(pid); +#else struct utmpx entry { }; diff --git a/local/recipes/kde/sddm/redox-virtualterminal-stub.patch b/local/recipes/kde/sddm/redox-virtualterminal-stub.patch index 6b7b0761f3..c9529b5e04 100644 --- a/local/recipes/kde/sddm/redox-virtualterminal-stub.patch +++ b/local/recipes/kde/sddm/redox-virtualterminal-stub.patch @@ -16,10 +16,10 @@ +#if defined(__redox__) + // Redox has no Linux/FreeBSD VT subsystem; the greeter runs directly on + // the compositor/framebuffer, so VT enumeration/switching is a no-op. -+ QString path(int vt) { return QStringLiteral("/dev/tty%1").arg(vt); } ++ QString path(int vt) { qDebug() << "redbear: SDDM VirtualTerminal::path(" << vt << ") — no VT on Redox, no-op"; return QStringLiteral("/dev/tty%1").arg(vt); } + int currentVt() { return 1; } -+ int setUpNewVt() { return 1; } -+ void jumpToVt(int, bool) { } ++ int setUpNewVt() { qDebug() << "redbear: SDDM VirtualTerminal::setUpNewVt() — no VT on Redox, returning hardcoded 1"; return 1; } ++ void jumpToVt(int vt, bool) { qDebug() << "redbear: SDDM VirtualTerminal::jumpToVt(" << vt << ") — no VT on Redox, no-op"; } +#else #ifdef __FreeBSD__ static const char *defaultVtPath = "/dev/ttyv0"; diff --git a/local/recipes/system/redbear-sessiond/source/src/manager.rs b/local/recipes/system/redbear-sessiond/source/src/manager.rs index 2d9cdc14b6..ac2a03f773 100644 --- a/local/recipes/system/redbear-sessiond/source/src/manager.rs +++ b/local/recipes/system/redbear-sessiond/source/src/manager.rs @@ -5,7 +5,7 @@ use std::{ os::unix::net::UnixStream, sync::{ Arc, Mutex, - atomic::Ordering, + atomic::{AtomicBool, Ordering}, }, }; @@ -17,6 +17,8 @@ use zbus::{ zvariant::{OwnedFd, OwnedObjectPath}, }; +use tokio::spawn as tokio_spawn; + use crate::runtime_state::{InhibitorEntry, SharedRuntime}; #[derive(Clone, Debug)] @@ -27,6 +29,7 @@ pub struct LoginManager { user_path: OwnedObjectPath, inhibitor_fds: Arc>>, connection: Arc>>, + seat_announced: Arc, } impl LoginManager { @@ -43,6 +46,7 @@ impl LoginManager { user_path, inhibitor_fds: Arc::new(Mutex::new(Vec::new())), connection: Arc::new(Mutex::new(None)), + seat_announced: Arc::new(AtomicBool::new(false)), } } @@ -50,6 +54,19 @@ impl LoginManager { if let Ok(mut guard) = self.connection.lock() { *guard = Some(connection); } + let self_clone = self.clone(); + tokio_spawn(async move { self_clone.announce_seat_if_needed().await }); + } + + pub async fn announce_seat_if_needed(&self) { + if self.seat_announced.swap(true, Ordering::AcqRel) { + return; + } + let seat_id = match self.runtime.read() { + Ok(runtime) => runtime.seat_id.clone(), + Err(_) => String::from("seat0"), + }; + self.emit_seat_new(&seat_id, &self.seat_path).await; } fn get_connection(&self) -> Option { @@ -97,6 +114,48 @@ impl LoginManager { } } + pub async fn emit_seat_new(&self, id: &str, seat_path: &OwnedObjectPath) { + if let Some(conn) = self.get_connection() { + if let Err(err) = conn + .emit_signal( + None::<&str>, + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SeatNew", + &(id, seat_path), + ) + .await + { + eprintln!( + "redbear-sessiond: SeatNew({id}, {seat_path}) emit failed: {err}" + ); + } else { + eprintln!("redbear-sessiond: SeatNew emitted for {id} -> {seat_path}"); + } + } + } + + pub async fn emit_seat_removed(&self, id: &str, seat_path: &OwnedObjectPath) { + if let Some(conn) = self.get_connection() { + if let Err(err) = conn + .emit_signal( + None::<&str>, + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SeatRemoved", + &(id, seat_path), + ) + .await + { + eprintln!( + "redbear-sessiond: SeatRemoved({id}, {seat_path}) emit failed: {err}" + ); + } else { + eprintln!("redbear-sessiond: SeatRemoved emitted for {id} -> {seat_path}"); + } + } + } + fn runtime_read(&self) -> fdo::Result> { self.runtime .read()