Skip to content

Commit 44f7482

Browse files
WhatAmISupposedToPutHereslp
authored andcommitted
Add pipewire passthrough to x11bridge
Signed-off-by: Sasha Finkelstein <[email protected]>
1 parent a09d1f5 commit 44f7482

5 files changed

Lines changed: 505 additions & 0 deletions

File tree

crates/muvm/src/guest/bin/muvm-guest.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::{cmp, env, fs, thread};
77

88
use anyhow::{Context, Result};
99
use muvm::guest::box64::setup_box;
10+
use muvm::guest::bridge::pipewire::start_pwbridge;
1011
use muvm::guest::bridge::x11::start_x11bridge;
1112
use muvm::guest::fex::setup_fex;
1213
use muvm::guest::hidpipe::start_hidpipe;
@@ -121,6 +122,12 @@ fn main() -> Result<()> {
121122
}
122123
});
123124

125+
thread::spawn(|| {
126+
if catch_unwind(start_pwbridge).is_err() {
127+
eprintln!("pwbridge thread crashed, pipewire passthrough will no longer function");
128+
}
129+
});
130+
124131
let rt = tokio::runtime::Runtime::new().unwrap();
125132
rt.block_on(async { server_main(options.command.command, options.command.command_args).await })
126133
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod common;
2+
pub mod pipewire;
23
pub mod x11;
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
use std::collections::{HashMap, VecDeque};
2+
use std::ffi::CStr;
3+
use std::os::fd::{AsFd, AsRawFd, OwnedFd};
4+
use std::{env, mem};
5+
6+
use anyhow::Result;
7+
use nix::errno::Errno;
8+
use nix::sys::epoll::EpollFlags;
9+
use nix::sys::eventfd::{EfdFlags, EventFd};
10+
11+
use crate::guest::bridge::common;
12+
use crate::guest::bridge::common::{
13+
Client, CrossDomainHeader, CrossDomainResource, MessageResourceFinalizer, ProtocolHandler,
14+
StreamRecvResult, StreamSendResult,
15+
};
16+
17+
const CROSS_DOMAIN_CHANNEL_TYPE_PW: u32 = 0x10;
18+
const CROSS_DOMAIN_CMD_READ_EVENTFD_NEW: u8 = 11;
19+
const CROSS_DOMAIN_CMD_READ: u8 = 6;
20+
21+
const SPA_TYPE_STRUCT: u32 = 14;
22+
23+
const PW_OPC_CORE_CREATE_OBJECT: u8 = 6;
24+
const PW_OPC_CORE_ADD_MEM: u8 = 6;
25+
const PW_OPC_CLIENT_UPDATE_PROPERTIES: u8 = 2;
26+
const PW_OPC_CLIENT_NODE_TRANSPORT: u8 = 0;
27+
const PW_OPC_CLIENT_NODE_SET_ACTIVATION: u8 = 10;
28+
29+
#[repr(C)]
30+
struct CrossDomainReadWrite<T: ?Sized> {
31+
hdr: CrossDomainHeader,
32+
identifier: u32,
33+
hang_up: u32,
34+
opaque_data_size: u32,
35+
pad: u32,
36+
data: T,
37+
}
38+
39+
#[repr(C)]
40+
struct CrossDomainReadEventfdNew {
41+
pub hdr: CrossDomainHeader,
42+
pub id: u32,
43+
pub pad: u32,
44+
}
45+
46+
fn align_up(v: u32, a: u32) -> u32 {
47+
(v + a - 1) & !(a - 1)
48+
}
49+
50+
fn read_u32(data: &[u8], at: usize) -> u32 {
51+
u32::from_ne_bytes(data[at..(at + 4)].try_into().unwrap())
52+
}
53+
54+
#[derive(Debug)]
55+
struct CoreCreateObject<'a> {
56+
obj_type: &'a CStr,
57+
new_id: u32,
58+
}
59+
60+
impl<'a> CoreCreateObject<'a> {
61+
fn new(data: &'a [u8]) -> Self {
62+
let ty = read_u32(data, 4);
63+
assert_eq!(ty, SPA_TYPE_STRUCT);
64+
let factory_name_ptr = 8;
65+
let factory_name_size = read_u32(data, factory_name_ptr);
66+
let type_ptr = factory_name_ptr + align_up(factory_name_size + 8, 8) as usize;
67+
let type_size = read_u32(data, type_ptr);
68+
let obj_type =
69+
CStr::from_bytes_with_nul(&data[(type_ptr + 8)..(type_ptr + 8 + type_size as usize)])
70+
.unwrap();
71+
let version_ptr = type_ptr + align_up(type_size + 8, 8) as usize;
72+
let version_size = read_u32(data, version_ptr);
73+
let props_ptr = version_ptr + align_up(version_size + 8, 8) as usize;
74+
let props_size = read_u32(data, props_ptr);
75+
let new_id_ptr = props_ptr + align_up(props_size + 8, 8) as usize;
76+
let new_id = read_u32(data, new_id_ptr + 8);
77+
CoreCreateObject { obj_type, new_id }
78+
}
79+
}
80+
81+
#[derive(Debug)]
82+
struct ClientUpdateProperties<'a> {
83+
props: Vec<(&'a mut [u8], &'a mut [u8])>,
84+
}
85+
86+
impl<'a> ClientUpdateProperties<'a> {
87+
fn new(mut data: &'a mut [u8]) -> Self {
88+
let ty = read_u32(data, 4);
89+
assert_eq!(ty, SPA_TYPE_STRUCT);
90+
let props_ptr = 8;
91+
let n_items_ptr = props_ptr + 8;
92+
let n_items_size = read_u32(data, n_items_ptr);
93+
let n_items = read_u32(data, n_items_ptr + 8) as usize;
94+
let key_ptr = n_items_ptr + align_up(n_items_size + 8, 8) as usize;
95+
let mut props = Vec::with_capacity(n_items);
96+
data = data.split_at_mut(key_ptr).1;
97+
for _ in 0..n_items {
98+
let key_size = read_u32(data, 0);
99+
data = data.split_at_mut(8).1;
100+
let (key, data2) = data.split_at_mut(key_size as usize);
101+
data = data2;
102+
let pad_size = (align_up(key_size, 8) - key_size) as usize;
103+
data = data.split_at_mut(pad_size).1;
104+
let value_size = read_u32(data, 0);
105+
data = data.split_at_mut(8).1;
106+
let (value, data2) = data.split_at_mut(value_size as usize);
107+
data = data2;
108+
let pad_size = (align_up(value_size, 8) - value_size) as usize;
109+
data = data.split_at_mut(pad_size).1;
110+
props.push((key, value));
111+
}
112+
ClientUpdateProperties { props }
113+
}
114+
}
115+
116+
struct PipeWireHeader {
117+
id: u32,
118+
opcode: u8,
119+
size: usize,
120+
num_fd: usize,
121+
}
122+
123+
impl PipeWireHeader {
124+
const SIZE: usize = 16;
125+
fn from_stream(data: &[u8]) -> PipeWireHeader {
126+
let id = read_u32(data, 0);
127+
let opc_len_word = read_u32(data, 4) as usize;
128+
let opcode = (opc_len_word >> 24) as u8;
129+
let size = (opc_len_word & 0xFFFFFF) + 16;
130+
let num_fd = read_u32(data, 12) as usize;
131+
PipeWireHeader {
132+
id,
133+
opcode,
134+
size,
135+
num_fd,
136+
}
137+
}
138+
}
139+
140+
struct PipeWireResourceFinalizer;
141+
142+
impl MessageResourceFinalizer for PipeWireResourceFinalizer {
143+
type Handler = PipeWireProtocolHandler;
144+
145+
fn finalize(self, _: &mut Client<Self::Handler>) -> Result<()> {
146+
unreachable!()
147+
}
148+
}
149+
150+
struct CrossDomainEventFd {
151+
event_fd: EventFd,
152+
}
153+
154+
struct ClientNodeData {
155+
host_to_guest: Vec<u32>,
156+
guest_to_host: Vec<u64>,
157+
}
158+
159+
impl ClientNodeData {
160+
fn new() -> Self {
161+
ClientNodeData {
162+
host_to_guest: Vec::new(),
163+
guest_to_host: Vec::new(),
164+
}
165+
}
166+
}
167+
168+
struct PipeWireProtocolHandler {
169+
client_nodes: HashMap<u32, ClientNodeData>,
170+
guest_to_host_eventfds: HashMap<u64, CrossDomainEventFd>,
171+
host_to_guest_eventfds: HashMap<u32, CrossDomainEventFd>,
172+
}
173+
174+
impl PipeWireProtocolHandler {
175+
fn create_guest_to_host_eventfd(this: &mut Client<Self>, node_id: u32) -> Result<OwnedFd> {
176+
let efd = EventFd::from_flags(EfdFlags::EFD_NONBLOCK)?;
177+
let ofd = efd.as_fd().try_clone_to_owned()?;
178+
this.sub_poll.add(efd.as_fd(), EpollFlags::EPOLLIN);
179+
let raw = efd.as_raw_fd() as u64;
180+
this.protocol_handler
181+
.guest_to_host_eventfds
182+
.insert(raw, CrossDomainEventFd { event_fd: efd });
183+
this.protocol_handler
184+
.client_nodes
185+
.get_mut(&node_id)
186+
.unwrap()
187+
.guest_to_host
188+
.push(raw);
189+
Ok(ofd)
190+
}
191+
192+
fn create_host_to_guest_eventfd(
193+
this: &mut Client<Self>,
194+
node_id: u32,
195+
resource: CrossDomainResource,
196+
) -> Result<OwnedFd> {
197+
let efd = EventFd::from_flags(EfdFlags::EFD_NONBLOCK)?;
198+
let ofd = efd.as_fd().try_clone_to_owned()?;
199+
let msg_size = mem::size_of::<CrossDomainReadEventfdNew>();
200+
let msg = CrossDomainReadEventfdNew {
201+
hdr: CrossDomainHeader::new(CROSS_DOMAIN_CMD_READ_EVENTFD_NEW, msg_size as u16),
202+
id: resource.identifier,
203+
pad: 0,
204+
};
205+
this.protocol_handler
206+
.client_nodes
207+
.get_mut(&node_id)
208+
.unwrap()
209+
.host_to_guest
210+
.push(resource.identifier);
211+
this.gpu_ctx.submit_cmd(&msg, msg_size, None, None)?;
212+
this.protocol_handler
213+
.host_to_guest_eventfds
214+
.insert(resource.identifier, CrossDomainEventFd { event_fd: efd });
215+
Ok(ofd)
216+
}
217+
}
218+
219+
impl ProtocolHandler for PipeWireProtocolHandler {
220+
type ResourceFinalizer = PipeWireResourceFinalizer;
221+
222+
const CHANNEL_TYPE: u32 = CROSS_DOMAIN_CHANNEL_TYPE_PW;
223+
224+
fn new() -> Self {
225+
PipeWireProtocolHandler {
226+
client_nodes: HashMap::new(),
227+
guest_to_host_eventfds: HashMap::new(),
228+
host_to_guest_eventfds: HashMap::new(),
229+
}
230+
}
231+
232+
fn process_recv_stream(
233+
this: &mut Client<Self>,
234+
data: &[u8],
235+
resources: &mut VecDeque<CrossDomainResource>,
236+
) -> Result<StreamRecvResult> {
237+
if data.len() < PipeWireHeader::SIZE {
238+
eprintln!(
239+
"Pipewire message truncated (expected at least 16 bytes, got {})",
240+
data.len(),
241+
);
242+
return Ok(StreamRecvResult::WantMore);
243+
}
244+
let hdr = PipeWireHeader::from_stream(data);
245+
let mut fds = Vec::with_capacity(hdr.num_fd);
246+
if hdr.num_fd != 0 {
247+
if hdr.id == 0 && hdr.opcode == PW_OPC_CORE_ADD_MEM {
248+
let rsc = resources.pop_front().ok_or(Errno::EIO)?;
249+
fds.push(this.virtgpu_id_to_prime(rsc)?);
250+
} else if this.protocol_handler.client_nodes.contains_key(&hdr.id) {
251+
if hdr.opcode == PW_OPC_CLIENT_NODE_SET_ACTIVATION {
252+
resources.pop_front().ok_or(Errno::EIO)?;
253+
fds.push(Self::create_guest_to_host_eventfd(this, hdr.id)?);
254+
} else if hdr.opcode == PW_OPC_CLIENT_NODE_TRANSPORT {
255+
let rsc1 = resources.pop_front().ok_or(Errno::EIO)?;
256+
fds.push(Self::create_host_to_guest_eventfd(this, hdr.id, rsc1)?);
257+
resources.pop_front().ok_or(Errno::EIO)?;
258+
fds.push(Self::create_guest_to_host_eventfd(this, hdr.id)?);
259+
} else {
260+
unimplemented!()
261+
}
262+
} else {
263+
unimplemented!();
264+
}
265+
};
266+
Ok(StreamRecvResult::Processed {
267+
consumed_bytes: hdr.size,
268+
fds,
269+
})
270+
}
271+
272+
fn process_send_stream(
273+
this: &mut Client<Self>,
274+
data: &mut [u8],
275+
) -> Result<StreamSendResult<Self::ResourceFinalizer>> {
276+
if data.len() < PipeWireHeader::SIZE {
277+
eprintln!(
278+
"Pipewire message truncated (expected at least 16 bytes, got {})",
279+
data.len(),
280+
);
281+
return Ok(StreamSendResult::WantMore);
282+
}
283+
let hdr = PipeWireHeader::from_stream(data);
284+
if hdr.id == 1 && hdr.opcode == PW_OPC_CLIENT_UPDATE_PROPERTIES {
285+
let msg = ClientUpdateProperties::new(&mut data[PipeWireHeader::SIZE..]);
286+
for (k, _) in msg.props {
287+
if CStr::from_bytes_with_nul(k).unwrap() == c"pipewire.access.portal.app_id" {
288+
k.copy_from_slice(c"pipewire.access.muvm00.app_id".to_bytes_with_nul());
289+
}
290+
}
291+
}
292+
if hdr.id == 0 && hdr.opcode == PW_OPC_CORE_CREATE_OBJECT {
293+
let msg = CoreCreateObject::new(&data[PipeWireHeader::SIZE..]);
294+
if msg.obj_type == c"PipeWire:Interface:ClientNode" {
295+
this.protocol_handler
296+
.client_nodes
297+
.insert(msg.new_id, ClientNodeData::new());
298+
}
299+
}
300+
if hdr.num_fd != 0 {
301+
unimplemented!();
302+
};
303+
Ok(StreamSendResult::Processed {
304+
consumed_bytes: hdr.size,
305+
resources: Vec::new(),
306+
finalizers: Vec::new(),
307+
})
308+
}
309+
310+
fn process_vgpu_extra(this: &mut Client<Self>, cmd: u8) -> Result<()> {
311+
if cmd != CROSS_DOMAIN_CMD_READ {
312+
return Err(Errno::EINVAL.into());
313+
}
314+
// SAFETY: vmm will put a valid cross domain message at that address
315+
let recv = unsafe {
316+
(this.gpu_ctx.channel_ring.address
317+
as *const CrossDomainReadWrite<[u8; mem::size_of::<u64>()]>)
318+
.as_ref()
319+
.unwrap()
320+
};
321+
if (recv.opaque_data_size as usize) < mem::size_of::<u64>() {
322+
return Err(Errno::EINVAL.into());
323+
}
324+
if let Some(efd) = this
325+
.protocol_handler
326+
.host_to_guest_eventfds
327+
.get(&recv.identifier)
328+
{
329+
efd.event_fd.write(u64::from_ne_bytes(recv.data))?;
330+
Ok(())
331+
} else {
332+
Err(Errno::ENOENT.into())
333+
}
334+
}
335+
}
336+
337+
pub fn start_pwbridge() {
338+
let sock_path = format!("{}/pipewire-0", env::var("XDG_RUNTIME_DIR").unwrap());
339+
340+
common::bridge_loop::<PipeWireProtocolHandler>(&sock_path)
341+
}

0 commit comments

Comments
 (0)