diff --git a/ffi/src/include/rutabaga_gfx_ffi.h b/ffi/src/include/rutabaga_gfx_ffi.h index 91f9b5a..01d5679 100644 --- a/ffi/src/include/rutabaga_gfx_ffi.h +++ b/ffi/src/include/rutabaga_gfx_ffi.h @@ -92,6 +92,8 @@ extern "C" { * Rutabaga channel types */ #define RUTABAGA_CHANNEL_TYPE_WAYLAND 1 +#define RUTABAGA_CHANNEL_TYPE_PIPEWIRE 0x10 +#define RUTABAGA_CHANNEL_TYPE_X11 0x11 /** * Rutabaga WSI diff --git a/ffi/src/tests/virtgpu_cross_domain_protocol.h b/ffi/src/tests/virtgpu_cross_domain_protocol.h index ebcd009..1b4feb1 100644 --- a/ffi/src/tests/virtgpu_cross_domain_protocol.h +++ b/ffi/src/tests/virtgpu_cross_domain_protocol.h @@ -19,6 +19,8 @@ // Channel types (must match rutabaga channel types) #define CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND 0x0001 #define CROSS_DOMAIN_CHANNEL_TYPE_CAMERA 0x0002 +#define CROSS_DOMAIN_CHANNEL_TYPE_PIPEWIRE 0x0010 +#define CROSS_DOMAIN_CHANNEL_TYPE_X11 0x0011 // The maximum number of identifiers (value based on wp_linux_dmabuf) #define CROSS_DOMAIN_MAX_IDENTIFIERS 4 diff --git a/src/cross_domain/atomic_memory_sentinel_manager.rs b/src/cross_domain/atomic_memory_sentinel_manager.rs new file mode 100644 index 0000000..389fc44 --- /dev/null +++ b/src/cross_domain/atomic_memory_sentinel_manager.rs @@ -0,0 +1,270 @@ +// Copyright 2026 Red Hat, Inc. +// Copyright 2021 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! Manager for atomic memory sentinel instances. +//! +//! This module encapsulates the management of AtomicMemorySentinelThread instances, +//! providing a clean interface for creating, signaling, and destroying memory watchers. + +use std::collections::BTreeMap as Map; +use std::mem::size_of; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; + +use log::info; +use mesa3d_util::AtomicMemorySentinel; +use mesa3d_util::Event; +use mesa3d_util::MemoryMapping; +use mesa3d_util::MesaError; +use mesa3d_util::OwnedDescriptor; + +use crate::rutabaga_core::VirtioFsKey; +use crate::rutabaga_core::VirtioFsTable; +use crate::rutabaga_utils::RutabagaError; +use crate::rutabaga_utils::RutabagaResult; +use crate::rutabaga_utils::RUTABAGA_MAP_ACCESS_RW; +use crate::rutabaga_utils::RUTABAGA_MAP_CACHE_CACHED; + +use super::cross_domain_protocol::CrossDomainSignalAtomicMemorySentinel; + +/// Thread worker for monitoring atomic memory changes. +struct AtomicMemorySentinelThread { + sentinel: Arc, + shutdown: Arc, + sender: Event, + initial_value: u32, +} + +impl AtomicMemorySentinelThread { + fn new( + sentinel: Arc, + shutdown: Arc, + sender: Event, + initial_value: u32, + ) -> Self { + Self { + sentinel, + shutdown, + sender, + initial_value, + } + } + + fn run(mut self) { + // The goal of this code is to ensure that the other side observes at least + // the latest wake event along with the value that the futex had when that + // wake event was signaled. + + // The initial value is passed in from the futex creation, and therefore + // was loaded synchronously with the cross domain operation that created + // the futex, so it cannot have an associated wake event yet. + let mut val = self.initial_value; + let _ = self.sender.signal(); + + loop { + // This returns when the futex is woken up OR if the value has changed. + self.sentinel.wait(val); + // Load the new value, which the other side is guaranteed to observe. + val = self.sentinel.load(); + + // If this wake was triggered by the shutdown code below, just bail. + // If the shutdown command is issued after this point, then it will + // change the futex value, which will disagree with the one we read + // above, so we will still not block in futex wait. + if self.shutdown.load(Ordering::SeqCst) { + // Signal the futex to process the shutdown and remove it from + // the waiter table + let _ = self.sender.signal(); + break; + } + + // Signal the other side after the load. If another change occurs and + // another wake is signaled here, we will miss the wake, but the + // disagreeing value will cause futex wait to return early. + if self.sender.signal().is_err() { + break; + } + } + } +} + +/// Private struct containing a single memory watcher instance +struct SentinelInstance { + sentinel: Arc, + watcher_thread: Option>, + shutdown: Arc, + evt: Event, +} + +impl SentinelInstance { + fn shutdown(&mut self) { + self.shutdown.store(true, Ordering::SeqCst); + self.sentinel.wake_all(); + if let Some(thread) = self.watcher_thread.take() { + let _ = thread.join(); + } + } + + fn is_shutdown(&self) -> bool { + self.shutdown.load(Ordering::Relaxed) + } +} + +impl Drop for SentinelInstance { + fn drop(&mut self) { + if !self.is_shutdown() { + info!("Atomic memory sentinel worker dropped without shutdown"); + self.shutdown(); + } + } +} + +/// Manager for all atomic memory sentinel watchers +pub struct AtomicMemorySentinelManager { + watchers: Map, + virtiofs_table: Option, +} + +impl AtomicMemorySentinelManager { + /// Creates a new AtomicMemorySentinelManager + pub fn new(virtiofs_table: Option) -> Self { + Self { + watchers: Map::new(), + virtiofs_table, + } + } + + /// Creates a new memory watcher and returns its ID and event descriptor + pub fn create_watcher(&mut self, id: u32, fs_id: u64, handle: u64) -> RutabagaResult { + if self.watchers.contains_key(&id) { + return Err(RutabagaError::AlreadyInUse); + } + + let virtiofs_table = self + .virtiofs_table + .as_ref() + .ok_or(RutabagaError::InvalidCrossDomainItemId)?; + let virtiofs = virtiofs_table.lock().unwrap(); + + let key = VirtioFsKey { fs_id, handle }; + let file = virtiofs + .get(&key) + .ok_or(RutabagaError::InvalidCrossDomainItemId)? + .try_clone() + .map_err(MesaError::IoError)?; + + let handle = OwnedDescriptor::from(file); + let mapping = MemoryMapping::from_safe_descriptor( + handle, + size_of::(), + RUTABAGA_MAP_ACCESS_RW | RUTABAGA_MAP_CACHE_CACHED, + )?; + + let sentinel = Arc::new(AtomicMemorySentinel::new(mapping)?); + let initial_value = sentinel.load(); + + let memory_watcher_evt = Event::new()?; + let evt_for_waitctx = memory_watcher_evt.try_clone()?; + + let shutdown = Arc::new(AtomicBool::new(false)); + let thread_shutdown = shutdown.clone(); + let thread_sentinel = sentinel.clone(); + + // Spawn the watcher thread + let watcher_thread = thread::Builder::new() + .name(format!("atomic_memory_sentinel_{}", id)) + .spawn(move || { + AtomicMemorySentinelThread::new( + thread_sentinel, + thread_shutdown, + memory_watcher_evt, + initial_value, + ) + .run(); + }) + .map_err(MesaError::IoError)?; + + self.watchers.insert( + id, + SentinelInstance { + sentinel, + watcher_thread: Some(watcher_thread), + shutdown, + evt: evt_for_waitctx.try_clone()?, + }, + ); + + Ok(evt_for_waitctx) + } + + /// Handles an event for a memory watcher, returns the command to write to the ring + pub fn handle_event( + &self, + id: u32, + ) -> RutabagaResult> { + if let Some(watcher) = self.watchers.get(&id) { + if watcher.is_shutdown() { + Ok(None) + } else { + watcher.evt.wait()?; + + let mut cmd_memory_watcher: CrossDomainSignalAtomicMemorySentinel = + Default::default(); + cmd_memory_watcher.hdr.cmd = + super::cross_domain_protocol::CROSS_DOMAIN_CMD_SIGNAL_ATOMIC_MEMORY_SENTINEL; + cmd_memory_watcher.id = id; + Ok(Some(cmd_memory_watcher)) + } + } else { + Err(RutabagaError::InvalidCrossDomainItemId) + } + } + + /// Signals a memory watcher + pub fn signal_watcher(&self, id: u32) -> RutabagaResult<()> { + if let Some(worker) = self.watchers.get(&id) { + worker.sentinel.signal()?; + Ok(()) + } else { + Err(RutabagaError::InvalidCrossDomainItemId) + } + } + + /// Destroys a memory watcher + pub fn destroy_watcher(&mut self, id: u32) -> RutabagaResult<()> { + self.watchers + .get_mut(&id) + .ok_or(RutabagaError::InvalidCrossDomainItemId)? + .shutdown(); + Ok(()) + } + + /// Checks if a watcher is shutdown (for cleanup in handle_fence) + pub fn is_shutdown(&self, id: u32) -> bool { + self.watchers + .get(&id) + .map(|w| w.is_shutdown()) + .unwrap_or(true) + } + + /// Removes a watcher from the map (after shutdown) + pub fn remove_watcher(&mut self, id: u32) { + self.watchers.remove(&id); + } + + /// Gets the event descriptor for a watcher + pub fn get_event(&self, id: u32) -> Option<&Event> { + self.watchers.get(&id).map(|w| &w.evt) + } +} + +impl Drop for AtomicMemorySentinelManager { + fn drop(&mut self) { + for (_, mut watcher) in std::mem::take(&mut self.watchers) { + watcher.shutdown(); + } + } +} diff --git a/src/cross_domain/cross_domain_protocol.rs b/src/cross_domain/cross_domain_protocol.rs index 135c6ec..f173261 100644 --- a/src/cross_domain/cross_domain_protocol.rs +++ b/src/cross_domain/cross_domain_protocol.rs @@ -19,10 +19,16 @@ pub const CROSS_DOMAIN_CMD_SEND: u8 = 4; pub const CROSS_DOMAIN_CMD_RECEIVE: u8 = 5; pub const CROSS_DOMAIN_CMD_READ: u8 = 6; pub const CROSS_DOMAIN_CMD_WRITE: u8 = 7; +pub const CROSS_DOMAIN_CMD_CREATE_ATOMIC_MEMORY_SENTINEL: u8 = 8; +pub const CROSS_DOMAIN_CMD_SIGNAL_ATOMIC_MEMORY_SENTINEL: u8 = 9; +pub const CROSS_DOMAIN_CMD_DESTROY_ATOMIC_MEMORY_SENTINEL: u8 = 10; +pub const CROSS_DOMAIN_CMD_READ_CREATE_EVENT: u8 = 11; /// Channel types (must match rutabaga channel types) pub const CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND: u32 = 0x0001; pub const CROSS_DOMAIN_CHANNEL_TYPE_CAMERA: u32 = 0x0002; +pub const CROSS_DOMAIN_CHANNEL_TYPE_PIPEWIRE: u32 = 0x0010; +pub const CROSS_DOMAIN_CHANNEL_TYPE_X11: u32 = 0x0011; /// The maximum number of identifiers pub const CROSS_DOMAIN_MAX_IDENTIFIERS: usize = 28; @@ -38,6 +44,8 @@ pub const CROSS_DOMAIN_ID_TYPE_READ_PIPE: u32 = 3; /// The host receives the write end of the pipe over the host Wayland socket. pub const CROSS_DOMAIN_ID_TYPE_WRITE_PIPE: u32 = 4; +pub const CROSS_DOMAIN_ID_TYPE_EVENT: u32 = 5; + /// No ring pub const CROSS_DOMAIN_RING_NONE: u32 = 0xffffffff; /// A ring for metadata queries. @@ -47,6 +55,8 @@ pub const CROSS_DOMAIN_CHANNEL_RING: u32 = 1; /// Read pipe IDs start at this value. pub const CROSS_DOMAIN_PIPE_READ_START: u32 = 0x80000000; +/// Memory watcher IDs start at this value. +pub const CROSS_DOMAIN_ATOMIC_MEMORY_SENTINEL_START: u32 = 0x40000000; #[repr(C)] #[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)] @@ -80,7 +90,7 @@ pub struct CrossDomainHeader { } #[repr(C)] -#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)] +#[derive(Copy, Clone, Default, FromBytes, IntoBytes, Immutable)] pub struct CrossDomainInit { pub hdr: CrossDomainHeader, pub query_ring_id: u32, @@ -120,3 +130,40 @@ pub struct CrossDomainReadWrite { pub pad: u32, // Data of size "opaque data size follows" } + +#[repr(C)] +#[derive(Copy, Clone, Default, FromBytes, IntoBytes, Immutable)] +pub struct CrossDomainCreateAtomicMemorySentinel { + pub hdr: CrossDomainHeader, + /// VirtioFS filesystem ID - identifies which virtio-fs instance + pub fs_id: u64, + /// VirtioFS file handle - identifies the file within the filesystem to map and watch + pub handle: u64, + /// Memory watcher ID - unique ID for this watcher, used in signal/destroy commands + pub id: u32, + pub pad: u32, +} + +#[repr(C)] +#[derive(Copy, Clone, Default, FromBytes, IntoBytes, Immutable)] +pub struct CrossDomainSignalAtomicMemorySentinel { + pub hdr: CrossDomainHeader, + pub id: u32, + pub pad: u32, +} + +#[repr(C)] +#[derive(Copy, Clone, Default, FromBytes, IntoBytes, Immutable)] +pub struct CrossDomainDestroyAtomicMemorySentinel { + pub hdr: CrossDomainHeader, + pub id: u32, + pub pad: u32, +} + +#[repr(C)] +#[derive(Copy, Clone, Default, FromBytes, IntoBytes, Immutable)] +pub struct CrossDomainCreateEvent { + pub hdr: CrossDomainHeader, + pub id: u32, + pub pad: u32, +} diff --git a/src/cross_domain/mod.rs b/src/cross_domain/mod.rs index fa3c469..073af15 100644 --- a/src/cross_domain/mod.rs +++ b/src/cross_domain/mod.rs @@ -5,6 +5,7 @@ //! The cross-domain component type, specialized for allocating and sharing resources across domain //! boundaries. +use log::error; use std::cmp::max; use std::collections::BTreeMap as Map; use std::collections::VecDeque; @@ -15,7 +16,6 @@ use std::sync::Condvar; use std::sync::Mutex; use std::thread; -use log::error; use mesa3d_util::create_pipe; use mesa3d_util::AsBorrowedDescriptor; use mesa3d_util::AsRawDescriptor; @@ -32,6 +32,7 @@ use mesa3d_util::WaitTimeout; use mesa3d_util::WritePipe; use mesa3d_util::MESA_HANDLE_TYPE_MEM_DMABUF; use mesa3d_util::MESA_HANDLE_TYPE_MEM_SHM; +use mesa3d_util::MESA_HANDLE_TYPE_SIGNAL_EVENT_FD; use zerocopy::FromBytes; use zerocopy::Immutable; use zerocopy::IntoBytes; @@ -43,6 +44,7 @@ use crate::handle::RutabagaHandle; use crate::rutabaga_core::RutabagaComponent; use crate::rutabaga_core::RutabagaContext; use crate::rutabaga_core::RutabagaResource; +use crate::rutabaga_core::VirtioFsTable; use crate::rutabaga_utils::Resource3DInfo; use crate::rutabaga_utils::ResourceCreateBlob; use crate::rutabaga_utils::RutabagaComponentType; @@ -64,8 +66,11 @@ use crate::RutabagaGralloc; use crate::RutabagaGrallocBackendFlags; use crate::RutabagaGrallocFlags; +mod atomic_memory_sentinel_manager; mod cross_domain_protocol; +use atomic_memory_sentinel_manager::AtomicMemorySentinelManager; + const CROSS_DOMAIN_CONTEXT_CHANNEL_ID: u64 = 1; const CROSS_DOMAIN_RESAMPLE_ID: u64 = 2; const CROSS_DOMAIN_KILL_ID: u64 = 3; @@ -79,12 +84,15 @@ enum CrossDomainItem { Blob(MesaHandle), WaylandReadPipe(ReadPipe), WaylandWritePipe(WritePipe), + Event(Event), } enum CrossDomainJob { HandleFence(RutabagaFence), AddReadPipe(u32), Finish, + AddAtomicMemorySentinel(u32, Event), + AddReadEvent(u32), } enum RingWrite<'a, T> { @@ -94,6 +102,7 @@ enum RingWrite<'a, T> { type CrossDomainJobs = Mutex>>; type CrossDomainItemState = Arc>; +type SentinelManager = Arc>; struct CrossDomainItems { descriptor_id: u32, @@ -103,6 +112,7 @@ struct CrossDomainItems { struct CrossDomainState { context_resources: ContextResources, + sentinel_manager: SentinelManager, query_ring_id: u32, channel_ring_id: u32, connection: Option, @@ -123,6 +133,7 @@ struct CrossDomainContext { state: Option>, context_resources: ContextResources, item_state: CrossDomainItemState, + sentinel_manager: SentinelManager, fence_handler: RutabagaFenceHandler, worker_thread: Option>>, resample_evt: Option, @@ -135,6 +146,7 @@ pub struct CrossDomain { paths: Option>, gralloc: Arc>, fence_handler: RutabagaFenceHandler, + virtiofs_table: Option, } // TODO(gurchetansingh): optimize the item tracker. Each requirements blob is long-lived and can @@ -175,12 +187,14 @@ impl CrossDomainState { query_ring_id: u32, channel_ring_id: u32, context_resources: ContextResources, + sentinel_manager: SentinelManager, connection: Option, ) -> CrossDomainState { CrossDomainState { query_ring_id, channel_ring_id, context_resources, + sentinel_manager, connection, jobs: Mutex::new(Some(VecDeque::new())), jobs_cvar: Condvar::new(), @@ -328,63 +342,7 @@ impl CrossDomainWorker { if let Some(event) = events.first() { match event.connection_id { CROSS_DOMAIN_CONTEXT_CHANNEL_ID => { - let (len, files) = self.state.receive_msg(receive_buf)?; - let mut cmd_receive: CrossDomainSendReceive = Default::default(); - - let num_files = files.len(); - cmd_receive.hdr.cmd = CROSS_DOMAIN_CMD_RECEIVE; - cmd_receive.num_identifiers = files - .len() - .try_into() - .map_err(|_| RutabagaError::InvalidCommandSize(files.len()))?; - cmd_receive.opaque_data_size = len - .try_into() - .map_err(|_| RutabagaError::InvalidCommandSize(len))?; - - let iter = cmd_receive - .identifiers - .iter_mut() - .zip(cmd_receive.identifier_types.iter_mut()) - .zip(cmd_receive.identifier_sizes.iter_mut()) - .zip(files) - .take(num_files); - - for (((identifier, identifier_type), identifier_size), file) in iter { - // Determine the descriptor type and size - let desc_type = file - .determine_type() - .map_err(|e| RutabagaError::MesaError(e.into()))?; - match desc_type { - DescriptorType::Memory(size, handle_type) => { - *identifier_type = CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB; - *identifier_size = size; - - let mesa_handle = MesaHandle { - os_handle: file, - handle_type, - }; - *identifier = - add_item(&self.item_state, CrossDomainItem::Blob(mesa_handle)); - } - DescriptorType::WritePipe => { - *identifier_type = CROSS_DOMAIN_ID_TYPE_WRITE_PIPE; - *identifier_size = 0; - let write_pipe = WritePipe::new(file.as_raw_descriptor()); - std::mem::forget(file); // Prevent double-free since WritePipe now owns the descriptor - *identifier = add_item( - &self.item_state, - CrossDomainItem::WaylandWritePipe(write_pipe), - ); - } - _ => return Err(RutabagaError::InvalidCrossDomainItemType), - } - } - - self.state.write_to_ring( - RingWrite::Write(cmd_receive, Some(&receive_buf[0..len])), - self.state.channel_ring_id, - )?; - self.fence_handler.call(fence); + self.process_receive(fence, receive_buf)?; } CROSS_DOMAIN_RESAMPLE_ID => { // The resample event is triggered when the job queue is in the following state: @@ -403,21 +361,53 @@ impl CrossDomainWorker { CROSS_DOMAIN_KILL_ID => { self.fence_handler.call(fence); } + id if id >= CROSS_DOMAIN_ATOMIC_MEMORY_SENTINEL_START as u64 + && id < CROSS_DOMAIN_PIPE_READ_START as u64 => + { + let memory_watcher_id: u32 = + id.try_into().map_err(MesaError::TryFromIntError)?; + let mut manager = self.state.sentinel_manager.lock().unwrap(); + let mut remove = false; + let mut fence_opt = Some(fence); + + if manager.is_shutdown(memory_watcher_id) { + if let Some(evt) = manager.get_event(memory_watcher_id) { + self.wait_ctx.delete(evt.as_borrowed_descriptor())?; + } + remove = true; + } else if let Some(cmd_memory_watcher) = + manager.handle_event(memory_watcher_id)? + { + self.state.write_to_ring( + RingWrite::Write(cmd_memory_watcher, None), + self.state.channel_ring_id, + )?; + self.fence_handler.call(fence_opt.take().unwrap()); + } + + if let Some(fence) = fence_opt { + self.state.add_job(CrossDomainJob::HandleFence(fence)); + } + + if remove { + manager.remove_watcher(memory_watcher_id); + } + } _ => { let mut items = self.item_state.lock().unwrap(); let mut cmd_read: CrossDomainReadWrite = Default::default(); - let pipe_id: u32 = event + let item_id: u32 = event .connection_id .try_into() .map_err(MesaError::TryFromIntError)?; let bytes_read; cmd_read.hdr.cmd = CROSS_DOMAIN_CMD_READ; - cmd_read.identifier = pipe_id; + cmd_read.identifier = item_id; let item = items .table - .get_mut(&pipe_id) + .get_mut(&item_id) .ok_or(RutabagaError::InvalidCrossDomainItemId)?; match item { @@ -434,11 +424,18 @@ impl CrossDomainWorker { self.wait_ctx.delete(readpipe.as_borrowed_descriptor())?; } } + CrossDomainItem::Event(ref evt) => { + // For eventfd, we can use wait() to consume the event + if event.readable { + let _ = evt.wait(); + } + bytes_read = 0; // eventfd doesn't have data to read like pipes + } _ => return Err(RutabagaError::InvalidCrossDomainItemType), } if event.hung_up && bytes_read == 0 { - items.table.remove(&pipe_id); + items.table.remove(&item_id); } self.fence_handler.call(fence); @@ -449,6 +446,79 @@ impl CrossDomainWorker { Ok(()) } + fn process_receive( + &mut self, + fence: RutabagaFence, + receive_buf: &mut [u8], + ) -> RutabagaResult<()> { + let (len, files) = self.state.receive_msg(receive_buf)?; + let mut cmd_receive: CrossDomainSendReceive = Default::default(); + + let num_files = files.len(); + cmd_receive.hdr.cmd = CROSS_DOMAIN_CMD_RECEIVE; + cmd_receive.num_identifiers = files.len().try_into().map_err(MesaError::TryFromIntError)?; + cmd_receive.opaque_data_size = len.try_into().map_err(MesaError::TryFromIntError)?; + + let iter = cmd_receive + .identifiers + .iter_mut() + .zip(cmd_receive.identifier_types.iter_mut()) + .zip(cmd_receive.identifier_sizes.iter_mut()) + .map(|((i, it), is)| (i, it, is)) + .zip(files) + .take(num_files); + + for ((identifier, identifier_type, identifier_size), file) in iter { + { + // Determine the descriptor type using the platform abstraction + let desc_type = file + .determine_type() + .map_err(|e| RutabagaError::MesaError(e.into()))?; + + match desc_type { + DescriptorType::Event => { + *identifier_type = CROSS_DOMAIN_ID_TYPE_EVENT; + *identifier_size = 0; + let event = Event::try_from(MesaHandle { + os_handle: file, + handle_type: MESA_HANDLE_TYPE_SIGNAL_EVENT_FD, + })?; + *identifier = add_item(&self.item_state, CrossDomainItem::Event(event)); + } + DescriptorType::Memory(size, handle_type) => { + *identifier_type = CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB; + *identifier_size = size; + + let mesa_handle = MesaHandle { + os_handle: file, + handle_type, + }; + *identifier = + add_item(&self.item_state, CrossDomainItem::Blob(mesa_handle)); + } + DescriptorType::WritePipe => { + *identifier_type = CROSS_DOMAIN_ID_TYPE_WRITE_PIPE; + *identifier_size = 0; + let write_pipe = WritePipe::new(file.as_raw_descriptor()); + std::mem::forget(file); // WritePipe now owns the descriptor + *identifier = add_item( + &self.item_state, + CrossDomainItem::WaylandWritePipe(write_pipe), + ); + } + _ => return Err(RutabagaError::InvalidCrossDomainItemType), + } + } + } + + self.state.write_to_ring( + RingWrite::Write(cmd_receive, Some(&receive_buf[0..len])), + self.state.channel_ring_id, + )?; + self.fence_handler.call(fence); + Ok(()) + } + fn run(&mut self, thread_kill_evt: Event, thread_resample_evt: Event) -> RutabagaResult<()> { self.wait_ctx.add( CROSS_DOMAIN_RESAMPLE_ID, @@ -485,6 +555,24 @@ impl CrossDomainWorker { _ => return Err(RutabagaError::InvalidCrossDomainItemType), } } + CrossDomainJob::AddReadEvent(efd_id) => { + let items = self.item_state.lock().unwrap(); + let item = items + .table + .get(&efd_id) + .ok_or(RutabagaError::InvalidCrossDomainItemId)?; + + match item { + CrossDomainItem::Event(event) => self + .wait_ctx + .add(efd_id as u64, event.as_borrowed_descriptor())?, + _ => return Err(RutabagaError::InvalidCrossDomainItemType), + } + } + CrossDomainJob::AddAtomicMemorySentinel(id, recv) => { + self.wait_ctx + .add(id as u64, recv.as_borrowed_descriptor())?; + } CrossDomainJob::Finish => return Ok(()), } } @@ -499,12 +587,14 @@ impl CrossDomain { pub fn init( paths: Option>, fence_handler: RutabagaFenceHandler, + virtiofs_table: Option, ) -> RutabagaResult> { let gralloc = RutabagaGralloc::new(RutabagaGrallocBackendFlags::new())?; Ok(Box::new(CrossDomain { paths, gralloc: Arc::new(Mutex::new(gralloc)), fence_handler, + virtiofs_table, })) } } @@ -538,6 +628,7 @@ impl CrossDomainContext { let query_ring_id = cmd_init.query_ring_id; let channel_ring_id = cmd_init.channel_ring_id; let context_resources = self.context_resources.clone(); + let sentinel_manager = self.sentinel_manager.clone(); // Zero means no requested channel. if cmd_init.channel_type != 0 { @@ -568,6 +659,7 @@ impl CrossDomainContext { query_ring_id, channel_ring_id, context_resources, + sentinel_manager, Some(connection), )); @@ -596,6 +688,7 @@ impl CrossDomainContext { query_ring_id, channel_ring_id, context_resources, + sentinel_manager, None, ))); } @@ -649,17 +742,14 @@ impl CrossDomainContext { fn send( &mut self, - cmd_send: &CrossDomainSendReceive, + cmd_send: &mut CrossDomainSendReceive, opaque_data: &[u8], ) -> RutabagaResult<()> { let mut descriptors: Vec = vec![]; let mut write_pipe_opt: Option = None; let mut read_pipe_id_opt: Option = None; - let num_identifiers = cmd_send - .num_identifiers - .try_into() - .map_err(MesaError::TryFromIntError)?; + let num_identifiers = cmd_send.num_identifiers as usize; if num_identifiers > CROSS_DOMAIN_MAX_IDENTIFIERS { return Err(MesaError::WithContext("max cross domain identifiers exceeded").into()); @@ -667,11 +757,13 @@ impl CrossDomainContext { let iter = cmd_send .identifiers - .iter() - .zip(cmd_send.identifier_types.iter()) + .iter_mut() + .zip(cmd_send.identifier_types.iter_mut()) + .zip(cmd_send.identifier_sizes.iter_mut()) + .map(|((i, it), is)| (i, it, is)) .take(num_identifiers); - for (identifier, identifier_type) in iter { + for (identifier, identifier_type, _identifier_size) in iter { if *identifier_type == CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB { let context_resources = self.context_resources.lock().unwrap(); @@ -746,6 +838,61 @@ impl CrossDomainContext { Ok(()) } + fn atomic_memory_sentinel_signal( + &mut self, + cmd_atomic_memory_sentinel_signal: &CrossDomainSignalAtomicMemorySentinel, + ) -> RutabagaResult<()> { + let manager = self.sentinel_manager.lock().unwrap(); + manager.signal_watcher(cmd_atomic_memory_sentinel_signal.id) + } + + fn atomic_memory_sentinel_destroy( + &mut self, + cmd_atomic_memory_sentinel_destroy: &CrossDomainDestroyAtomicMemorySentinel, + ) -> RutabagaResult<()> { + let mut manager = self.sentinel_manager.lock().unwrap(); + manager.destroy_watcher(cmd_atomic_memory_sentinel_destroy.id) + } + + fn atomic_memory_sentinel_new( + &mut self, + cmd_atomic_memory_sentinel_new: &CrossDomainCreateAtomicMemorySentinel, + ) -> RutabagaResult<()> { + let id = cmd_atomic_memory_sentinel_new.id; + let fs_id = cmd_atomic_memory_sentinel_new.fs_id; + let handle = cmd_atomic_memory_sentinel_new.handle; + + let mut manager = self.sentinel_manager.lock().unwrap(); + let evt = manager.create_watcher(id, fs_id, handle)?; + + let state = self + .state + .as_ref() + .ok_or(RutabagaError::InvalidCrossDomainState)?; + state.add_job(CrossDomainJob::AddAtomicMemorySentinel(id, evt)); + + Ok(()) + } + + fn read_event_new(&mut self, cmd_event_new: &CrossDomainCreateEvent) -> RutabagaResult<()> { + let items = self.item_state.lock().unwrap(); + + if let Some(item) = items.table.get(&cmd_event_new.id) { + if let CrossDomainItem::Event(_) = item { + self.state + .as_ref() + .unwrap() + .add_job(CrossDomainJob::AddReadEvent(cmd_event_new.id)); + self.resample_evt.as_mut().unwrap().signal()?; + Ok(()) + } else { + Err(RutabagaError::InvalidCrossDomainItemType) + } + } else { + Err(RutabagaError::InvalidCrossDomainItemId) + } + } + fn write(&self, cmd_write: &CrossDomainReadWrite, opaque_data: &[u8]) -> RutabagaResult<()> { let mut items = self.item_state.lock().unwrap(); @@ -779,6 +926,21 @@ impl CrossDomainContext { _ => Err(RutabagaError::InvalidCrossDomainItemType), } } + + fn process_cmd_send(&mut self, commands: &mut [u8]) -> RutabagaResult<()> { + let opaque_data_offset = size_of::(); + let (mut cmd_send, _) = CrossDomainSendReceive::read_from_prefix(commands.as_bytes()) + .map_err(|_| RutabagaError::InvalidCommandBuffer)?; + + let opaque_data = commands + .get_mut(opaque_data_offset..opaque_data_offset + cmd_send.opaque_data_size as usize) + .ok_or(RutabagaError::InvalidCommandSize( + cmd_send.opaque_data_size as usize, + ))?; + + self.send(&mut cmd_send, opaque_data)?; + Ok(()) + } } impl Drop for CrossDomainContext { @@ -804,13 +966,24 @@ impl Drop for CrossDomainContext { } #[repr(C)] -#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)] +#[derive(Copy, Clone, Default, FromBytes, IntoBytes, Immutable)] struct CrossDomainInitLegacy { hdr: CrossDomainHeader, query_ring_id: u32, channel_type: u32, } +impl CrossDomainInitLegacy { + pub(crate) fn upgrade(&self) -> CrossDomainInit { + CrossDomainInit { + hdr: self.hdr, + query_ring_id: self.query_ring_id, + channel_ring_id: self.query_ring_id, + channel_type: self.channel_type, + } + } +} + impl RutabagaContext for CrossDomainContext { fn context_create_blob( &mut self, @@ -918,24 +1091,13 @@ impl RutabagaContext for CrossDomainContext { match hdr.cmd { CROSS_DOMAIN_CMD_INIT => { - let cmd_init = match CrossDomainInit::read_from_prefix(commands) { - Ok((cmd_init, _)) => cmd_init, - _ => { - if let Ok((cmd_init, _)) = - CrossDomainInitLegacy::read_from_prefix(commands) - { - CrossDomainInit { - hdr: cmd_init.hdr, - query_ring_id: cmd_init.query_ring_id, - channel_ring_id: cmd_init.query_ring_id, - channel_type: cmd_init.channel_type, - } - } else { - return Err(RutabagaError::InvalidCommandBuffer); - } - } - }; - + let cmd_init = CrossDomainInit::read_from_prefix(commands) + .map(|(v, _)| v) + .or_else(|_| { + CrossDomainInitLegacy::read_from_prefix(commands) + .map(|(v, _)| v.upgrade()) + }) + .map_err(|_| RutabagaError::InvalidCommandBuffer)?; self.initialize(&cmd_init)?; } CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS => { @@ -946,20 +1108,7 @@ impl RutabagaContext for CrossDomainContext { self.get_image_requirements(&cmd_get_reqs)?; } CROSS_DOMAIN_CMD_SEND => { - let opaque_data_offset = size_of::(); - let (cmd_send, _) = CrossDomainSendReceive::read_from_prefix(commands) - .map_err(|_e| RutabagaError::InvalidCommandBuffer)?; - - let opaque_data = commands - .get_mut( - opaque_data_offset - ..opaque_data_offset + cmd_send.opaque_data_size as usize, - ) - .ok_or(RutabagaError::InvalidCommandSize( - cmd_send.opaque_data_size as usize, - ))?; - - self.send(&cmd_send, opaque_data)?; + self.process_cmd_send(commands)?; } CROSS_DOMAIN_CMD_POLL => { // Actual polling is done in the subsequent when creating a fence. @@ -980,6 +1129,29 @@ impl RutabagaContext for CrossDomainContext { self.write(&cmd_write, opaque_data)?; } + CROSS_DOMAIN_CMD_CREATE_ATOMIC_MEMORY_SENTINEL => { + let (cmd_atomic_memory_sentinel_new, _) = + CrossDomainCreateAtomicMemorySentinel::read_from_prefix(commands) + .map_err(|_e| RutabagaError::InvalidCommandBuffer)?; + self.atomic_memory_sentinel_new(&cmd_atomic_memory_sentinel_new)?; + } + CROSS_DOMAIN_CMD_SIGNAL_ATOMIC_MEMORY_SENTINEL => { + let (cmd_atomic_memory_sentinel_signal, _) = + CrossDomainSignalAtomicMemorySentinel::read_from_prefix(commands) + .map_err(|_e| RutabagaError::InvalidCommandBuffer)?; + self.atomic_memory_sentinel_signal(&cmd_atomic_memory_sentinel_signal)?; + } + CROSS_DOMAIN_CMD_DESTROY_ATOMIC_MEMORY_SENTINEL => { + let (cmd_atomic_memory_sentinel_destroy, _) = + CrossDomainDestroyAtomicMemorySentinel::read_from_prefix(commands) + .map_err(|_e| RutabagaError::InvalidCommandBuffer)?; + self.atomic_memory_sentinel_destroy(&cmd_atomic_memory_sentinel_destroy)?; + } + CROSS_DOMAIN_CMD_READ_CREATE_EVENT => { + let (cmd_new_evt, _) = CrossDomainCreateEvent::read_from_prefix(commands) + .map_err(|_| RutabagaError::InvalidCommandBuffer)?; + self.read_event_new(&cmd_new_evt)?; + } _ => return Err(MesaError::WithContext("invalid cross domain command").into()), } @@ -1107,6 +1279,9 @@ impl RutabagaComponent for CrossDomain { state: None, context_resources: Arc::new(Mutex::new(Default::default())), item_state: Arc::new(Mutex::new(Default::default())), + sentinel_manager: Arc::new(Mutex::new(AtomicMemorySentinelManager::new( + self.virtiofs_table.clone(), + ))), fence_handler, worker_thread: None, resample_evt: None, diff --git a/src/rutabaga_core.rs b/src/rutabaga_core.rs index 6a1b979..259926e 100644 --- a/src/rutabaga_core.rs +++ b/src/rutabaga_core.rs @@ -5,10 +5,11 @@ //! rutabaga_core: Cross-platform, Rust-based, Wayland and Vulkan centric GPU virtualization. use std::collections::BTreeMap as Map; use std::convert::TryInto; +use std::fs::File; use std::io::IoSlice; use std::io::IoSliceMut; use std::path::Path; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use mesa3d_util::MemoryMapping; use mesa3d_util::MesaError; @@ -63,6 +64,17 @@ use crate::snapshot::RutabagaSnapshotWriter; use crate::virgl_renderer::VirglRenderer; use crate::RutabagaPaths; +/// Key for identifying a file in the VirtioFS table. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct VirtioFsKey { + /// VirtioFS filesystem ID (identifies which virtio-fs instance) + pub fs_id: u64, + /// File handle within the filesystem (identifies a specific file) + pub handle: u64, +} + +pub type VirtioFsTable = Arc>>; + const RUTABAGA_DEFAULT_WIDTH: u32 = 1280; const RUTABAGA_DEFAULT_HEIGHT: u32 = 1024; @@ -1289,6 +1301,7 @@ pub struct RutabagaBuilder { debug_handler: Option, renderer_features: Option, server_descriptor: Option, + virtiofs_table: Option, } impl RutabagaBuilder { @@ -1310,6 +1323,7 @@ impl RutabagaBuilder { debug_handler: None, renderer_features: None, server_descriptor: None, + virtiofs_table: None, } } @@ -1414,6 +1428,12 @@ impl RutabagaBuilder { self } + /// Set export table for the RutabagaBuilder + pub fn set_export_table(mut self, virtiofs_table: VirtioFsTable) -> RutabagaBuilder { + self.virtiofs_table = Some(virtiofs_table); + self + } + /// Builds Rutabaga and returns a handle to it. /// /// This should be only called once per every virtual machine instance. Rutabaga tries to @@ -1528,7 +1548,11 @@ impl RutabagaBuilder { rutabaga_components.insert(RutabagaComponentType::Magma, magma); } - let cross_domain = CrossDomain::init(self.paths.clone(), self.fence_handler.clone())?; + let cross_domain = CrossDomain::init( + self.paths.clone(), + self.fence_handler.clone(), + self.virtiofs_table.clone(), + )?; rutabaga_components.insert(RutabagaComponentType::CrossDomain, cross_domain); push_capset(RUTABAGA_CAPSET_CROSS_DOMAIN); } diff --git a/src/rutabaga_utils.rs b/src/rutabaga_utils.rs index 56539a9..1621151 100644 --- a/src/rutabaga_utils.rs +++ b/src/rutabaga_utils.rs @@ -608,6 +608,8 @@ impl Transfer3D { /// Rutabaga path types pub const RUTABAGA_PATH_TYPE_WAYLAND: u32 = 0x0001; pub const RUTABAGA_PATH_TYPE_GPU: u32 = 0x0002; +pub const RUTABAGA_PATH_TYPE_PIPEWIRE: u32 = 0x0010; +pub const RUTABAGA_PATH_TYPE_X11: u32 = 0x0011; pub type RutabagaPaths = Vec; diff --git a/subprojects/packagefiles/rustix-1-rs/meson.build b/subprojects/packagefiles/rustix-1-rs/meson.build index b46fdb4..c73ffa1 100644 --- a/subprojects/packagefiles/rustix-1-rs/meson.build +++ b/subprojects/packagefiles/rustix-1-rs/meson.build @@ -29,6 +29,7 @@ if host_machine.system() == 'linux' or host_machine.system() == 'android' '--cfg', 'feature="net"', '--cfg', 'feature="param"', '--cfg', 'feature="pipe"', + '--cfg', 'feature="thread"', ] elif host_machine.system() == 'darwin' or host_machine.system() == 'macos' rustix_args += [ diff --git a/third_party/mesa3d/src/util/rust/Cargo.toml b/third_party/mesa3d/src/util/rust/Cargo.toml index 177d278..520509a 100644 --- a/third_party/mesa3d/src/util/rust/Cargo.toml +++ b/third_party/mesa3d/src/util/rust/Cargo.toml @@ -20,4 +20,4 @@ log = "0.4" rustix = "1.0.7" [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] -rustix = { version = "1.0.7", features = ["event", "fs", "mm", "net", "param", "pipe", "use-libc", "use-libc-auxv", "libc_errno"] } +rustix = { version = "1.0.7", features = ["event", "fs", "mm", "net", "param", "pipe", "thread", "use-libc", "use-libc-auxv", "libc_errno"] } diff --git a/third_party/mesa3d/src/util/rust/atomic_memory_sentinel.rs b/third_party/mesa3d/src/util/rust/atomic_memory_sentinel.rs new file mode 100644 index 0000000..ad7de31 --- /dev/null +++ b/third_party/mesa3d/src/util/rust/atomic_memory_sentinel.rs @@ -0,0 +1,88 @@ +// Copyright 2026 Red Hat, Inc. +// SPDX-License-Identifier: MIT + +//! AtomicMemorySentinel - Cross-platform atomic memory synchronization primitive. +//! +//! Provides access to futex and WaitOnAddress-like APIs for atomic memory operations. + +use std::mem::size_of; +use std::sync::atomic::{AtomicU32, Ordering}; + +use crate::sys::platform::atomic_memory_sentinel; +use crate::MappedRegion; +use crate::MemoryMapping; +use crate::MesaError; +use crate::MesaResult; + +/// A sentinel that provides access to atomic memory. +/// Safe to share across threads. +pub struct AtomicMemorySentinel { + _memory_mapping: MemoryMapping, + atomic_ptr: *mut u32, +} + +// SAFETY: AtomicMemorySentinel can be sent across threads. +unsafe impl Send for AtomicMemorySentinel {} +// SAFETY: AtomicMemorySentinel can be shared across threads. +unsafe impl Sync for AtomicMemorySentinel {} + +impl AtomicMemorySentinel { + /// Create a new AtomicMemorySentinel from a memory mapping. + pub fn new(memory_mapping: MemoryMapping) -> MesaResult { + if memory_mapping.size() < size_of::() { + return Err(MesaError::WithContext( + "memory mapping too small for AtomicU32", + )); + } + + let atomic_ptr = memory_mapping.as_ptr() as *mut u32; + + Ok(Self { + _memory_mapping: memory_mapping, + atomic_ptr, + }) + } + + /// Signal that the atomic memory has changed. + /// + /// This will wake up waiters on this sentinel. + pub fn signal(&self) -> MesaResult<()> { + // SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping, + // properly aligned, and we have exclusive ownership via the MemoryMapping. + let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) }; + atomic_memory_sentinel::wake_bitset(atomic_val, i32::MAX, 1); + Ok(()) + } + + /// Load the current value from atomic memory. + pub fn load(&self) -> u32 { + // SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping, + // properly aligned, and we have exclusive ownership via the MemoryMapping. + let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) }; + atomic_val.load(Ordering::SeqCst) + } + + /// Store a value to atomic memory. + pub fn store(&self, val: u32) { + // SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping, + // properly aligned, and we have exclusive ownership via the MemoryMapping. + let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) }; + atomic_val.store(val, Ordering::SeqCst); + } + + /// Wake all threads waiting on this sentinel. + pub fn wake_all(&self) { + // SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping, + // properly aligned, and we have exclusive ownership via the MemoryMapping. + let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) }; + atomic_memory_sentinel::wake_all(atomic_val); + } + + /// Blocks until the value changes from `val` or is woken up. + pub fn wait(&self, val: u32) { + // SAFETY: self.atomic_ptr is valid for the lifetime of the MemoryMapping, + // properly aligned, and we have exclusive ownership via the MemoryMapping. + let atomic_val = unsafe { AtomicU32::from_ptr(self.atomic_ptr) }; + atomic_memory_sentinel::wait_bitset(atomic_val, val, 1); + } +} diff --git a/third_party/mesa3d/src/util/rust/defines.rs b/third_party/mesa3d/src/util/rust/defines.rs index ace3202..e56e3b4 100644 --- a/third_party/mesa3d/src/util/rust/defines.rs +++ b/third_party/mesa3d/src/util/rust/defines.rs @@ -88,6 +88,7 @@ pub enum DescriptorType { Unknown, Memory(u32, u32), // (size, handle_type) WritePipe, + Event, } /// # Safety diff --git a/third_party/mesa3d/src/util/rust/lib.rs b/third_party/mesa3d/src/util/rust/lib.rs index db03226..bb9804c 100644 --- a/third_party/mesa3d/src/util/rust/lib.rs +++ b/third_party/mesa3d/src/util/rust/lib.rs @@ -1,6 +1,7 @@ // Copyright 2025 Google // SPDX-License-Identifier: MIT +mod atomic_memory_sentinel; mod bytestream; mod defines; mod descriptor; @@ -9,6 +10,7 @@ mod memory_mapping; mod shm; mod sys; +pub use atomic_memory_sentinel::AtomicMemorySentinel; pub use bytestream::Reader; pub use bytestream::Writer; pub use defines::*; diff --git a/third_party/mesa3d/src/util/rust/sys/linux/atomic_memory_sentinel.rs b/third_party/mesa3d/src/util/rust/sys/linux/atomic_memory_sentinel.rs new file mode 100644 index 0000000..f388f43 --- /dev/null +++ b/third_party/mesa3d/src/util/rust/sys/linux/atomic_memory_sentinel.rs @@ -0,0 +1,40 @@ +// Copyright 2026 Red Hat, Inc. +// SPDX-License-Identifier: MIT + +//! Linux futex wrappers for cross-domain synchronization. + +use std::num::NonZeroU32; +use std::sync::atomic::AtomicU32; + +use rustix::thread::futex; + +/// Wait on a futex with a bitset mask. +/// +/// Blocks until the futex is woken up or the value changes from `val`. +pub fn wait_bitset(atomic_val: &AtomicU32, val: u32, bitset: u32) { + let flags = futex::Flags::PRIVATE; + let timeout = None; + let val3 = NonZeroU32::new(bitset).unwrap_or(NonZeroU32::new(1).unwrap()); + + // Ignore errors - futex::wait_bitset returns an error if the value + // has already changed or if interrupted, both are expected + let _ = futex::wait_bitset(atomic_val, flags, val, timeout, val3); +} + +/// Wake threads waiting on a futex with a bitset mask. +/// +/// Only wakes threads whose bitset matches the provided mask. +pub fn wake_bitset(atomic_val: &AtomicU32, val: i32, bitset: u32) { + let flags = futex::Flags::PRIVATE; + let val_u32 = if val < 0 { i32::MAX as u32 } else { val as u32 }; + let val3 = NonZeroU32::new(bitset).unwrap_or(NonZeroU32::new(1).unwrap()); + + let _ = futex::wake_bitset(atomic_val, flags, val_u32, val3); +} + +/// Wake all threads waiting on a futex. +pub fn wake_all(atomic_val: &AtomicU32) { + let flags = futex::Flags::PRIVATE; + // u32::MAX means wake all waiters + let _ = futex::wake(atomic_val, flags, u32::MAX); +} diff --git a/third_party/mesa3d/src/util/rust/sys/linux/descriptor.rs b/third_party/mesa3d/src/util/rust/sys/linux/descriptor.rs index 801b3b4..a5897bd 100644 --- a/third_party/mesa3d/src/util/rust/sys/linux/descriptor.rs +++ b/third_party/mesa3d/src/util/rust/sys/linux/descriptor.rs @@ -1,8 +1,8 @@ // Copyright 2025 Google // SPDX-License-Identifier: MIT -use std::fs::File; use std::fs::read_link; +use std::fs::File; use std::io::Error; use std::io::ErrorKind; use std::io::Result; @@ -41,6 +41,14 @@ impl OwnedDescriptor { } pub fn determine_type(&self) -> Result { + // Check for eventfd first (not seekable, special symlink) + if let Ok(fd_path) = read_link(format!("/proc/self/fd/{}", self.as_raw_descriptor())) { + let path_str = fd_path.to_string_lossy(); + if path_str.starts_with("anon_inode:[eventfd]") { + return Ok(DescriptorType::Event); + } + } + match seek(&self.owned, SeekFrom::End(0)) { Ok(seek_size) => { let size: u32 = seek_size diff --git a/third_party/mesa3d/src/util/rust/sys/linux/mod.rs b/third_party/mesa3d/src/util/rust/sys/linux/mod.rs index 185538e..77e7692 100644 --- a/third_party/mesa3d/src/util/rust/sys/linux/mod.rs +++ b/third_party/mesa3d/src/util/rust/sys/linux/mod.rs @@ -1,6 +1,7 @@ // Copyright 2025 Google // SPDX-License-Identifier: MIT +pub mod atomic_memory_sentinel; pub mod descriptor; pub mod event; pub mod memory_mapping; diff --git a/third_party/mesa3d/src/util/rust/sys/stub/atomic_memory_sentinel.rs b/third_party/mesa3d/src/util/rust/sys/stub/atomic_memory_sentinel.rs new file mode 100644 index 0000000..d66fc25 --- /dev/null +++ b/third_party/mesa3d/src/util/rust/sys/stub/atomic_memory_sentinel.rs @@ -0,0 +1,24 @@ +// Copyright 2026 Red Hat, Inc. +// SPDX-License-Identifier: MIT + +//! Stub atomic memory synchronization implementation. +//! +//! The Linux backend uses futexes for atomic memory synchronization. +//! This stub implementation allows compilation on platforms without native support. + +use std::sync::atomic::AtomicU32; + +/// Stub implementation - no-op on non-Linux platforms. +pub fn wait_bitset(_atomic_val: &AtomicU32, _val: u32, _bitset: u32) { + // No-op: atomic memory synchronization not implemented on this platform +} + +/// Stub implementation - no-op on non-Linux platforms. +pub fn wake_bitset(_atomic_val: &AtomicU32, _val: i32, _bitset: u32) { + // No-op: atomic memory synchronization not implemented on this platform +} + +/// Stub implementation - no-op on non-Linux platforms. +pub fn wake_all(_atomic_val: &AtomicU32) { + // No-op: atomic memory synchronization not implemented on this platform +} diff --git a/third_party/mesa3d/src/util/rust/sys/stub/mod.rs b/third_party/mesa3d/src/util/rust/sys/stub/mod.rs index 185538e..77e7692 100644 --- a/third_party/mesa3d/src/util/rust/sys/stub/mod.rs +++ b/third_party/mesa3d/src/util/rust/sys/stub/mod.rs @@ -1,6 +1,7 @@ // Copyright 2025 Google // SPDX-License-Identifier: MIT +pub mod atomic_memory_sentinel; pub mod descriptor; pub mod event; pub mod memory_mapping; diff --git a/third_party/mesa3d/src/util/rust/sys/windows/atomic_memory_sentinel.rs b/third_party/mesa3d/src/util/rust/sys/windows/atomic_memory_sentinel.rs new file mode 100644 index 0000000..1daa688 --- /dev/null +++ b/third_party/mesa3d/src/util/rust/sys/windows/atomic_memory_sentinel.rs @@ -0,0 +1,24 @@ +// Copyright 2026 Red Hat, Inc. +// SPDX-License-Identifier: MIT + +//! Stub atomic memory synchronization implementation for Windows. +//! +//! The Linux backend uses futexes for atomic memory synchronization. +//! This stub implementation allows compilation on Windows without native support. + +use std::sync::atomic::AtomicU32; + +/// Stub implementation - no-op on Windows. +pub fn wait_bitset(_atomic_val: &AtomicU32, _val: u32, _bitset: u32) { + // No-op: atomic memory synchronization not implemented on this platform +} + +/// Stub implementation - no-op on Windows. +pub fn wake_bitset(_atomic_val: &AtomicU32, _val: i32, _bitset: u32) { + // No-op: atomic memory synchronization not implemented on this platform +} + +/// Stub implementation - no-op on Windows. +pub fn wake_all(_atomic_val: &AtomicU32) { + // No-op: atomic memory synchronization not implemented on this platform +} diff --git a/third_party/mesa3d/src/util/rust/sys/windows/mod.rs b/third_party/mesa3d/src/util/rust/sys/windows/mod.rs index 185538e..77e7692 100644 --- a/third_party/mesa3d/src/util/rust/sys/windows/mod.rs +++ b/third_party/mesa3d/src/util/rust/sys/windows/mod.rs @@ -1,6 +1,7 @@ // Copyright 2025 Google // SPDX-License-Identifier: MIT +pub mod atomic_memory_sentinel; pub mod descriptor; pub mod event; pub mod memory_mapping;