memory_pinning/
shadow_process.rs1use fidl::HandleBased;
6use fidl_fuchsia_scheduler::{
7 RoleManagerMarker, RoleManagerSetRoleRequest, RoleManagerSynchronousProxy, RoleName, RoleTarget,
8};
9use starnix_logging::log_warn;
10use starnix_uapi::errors::Errno;
11use starnix_uapi::from_status_like_fdio;
12use std::sync::{Arc, Weak};
13
14const MEMORY_ROLE: &str = "fuchsia.starnix.pinned_memory";
17
18#[derive(Debug)]
25pub struct ShadowProcess {
26 process: zx::Process,
28 vmar: Arc<zx::Vmar>,
29}
30
31impl ShadowProcess {
32 pub fn new(name: zx::Name) -> Result<Self, zx::Status> {
35 let role_manager =
36 fuchsia_component::client::connect_to_protocol_sync::<RoleManagerMarker>()
37 .expect("this can only fail if a process' namespace is broken");
38 Self::from_role_manager(name, role_manager)
39 }
40
41 fn from_role_manager(
42 name: zx::Name,
43 role_manager: RoleManagerSynchronousProxy,
44 ) -> Result<Self, zx::Status> {
45 let (process, vmar) =
46 zx::Process::create(&fuchsia_runtime::job_default(), name, Default::default())?;
47 let vmar_dupe = vmar.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
48 if let Err(e) = role_manager.set_role(
49 RoleManagerSetRoleRequest {
50 target: Some(RoleTarget::Vmar(vmar_dupe)),
51 role: Some(RoleName { role: MEMORY_ROLE.to_string() }),
52 ..Default::default()
53 },
54 zx::MonotonicInstant::INFINITE,
55 ) {
56 log_warn!(e:%, name:%; "Unable to set role for memory pin shadow process' vmar.");
57 }
58
59 Ok(Self { process, vmar: Arc::new(vmar) })
60 }
61
62 pub fn pin_pages(
65 &self,
66 vmo: &zx::Vmo,
67 offset: u64,
68 length: usize,
69 ) -> Result<Arc<PinnedMapping>, Errno> {
70 let base = self
71 .vmar
72 .map(0, vmo, offset, length, zx::VmarFlags::PERM_READ)
73 .map_err(|e| from_status_like_fdio!(e))?;
74 Ok(Arc::new(PinnedMapping { vmar: Arc::downgrade(&self.vmar), base, length }))
75 }
76
77 pub fn vmar(&self) -> Arc<zx::Vmar> {
79 self.vmar.clone()
80 }
81}
82
83impl Drop for ShadowProcess {
84 fn drop(&mut self) {
85 use zx::Task;
86
87 self.process.kill().expect("must be able to kill process we created");
89 }
90}
91
92#[derive(Clone, Debug)]
94pub struct PinnedMapping {
95 vmar: Weak<zx::Vmar>,
96 base: usize,
97 length: usize,
98}
99
100impl Drop for PinnedMapping {
101 fn drop(&mut self) {
102 if let Some(vmar) = self.vmar.upgrade() {
103 if let Err(e) = unsafe { vmar.unmap(self.base, self.length) } {
107 log_warn!(e:%; "Failed to unmap mlock() pin mapping.");
108 }
109 }
110 }
111}
112
113impl std::cmp::PartialEq for PinnedMapping {
114 fn eq(&self, rhs: &Self) -> bool {
115 Weak::ptr_eq(&self.vmar, &rhs.vmar) && self.base == rhs.base && self.length == rhs.length
116 }
117}
118impl std::cmp::Eq for PinnedMapping {}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use fidl_fuchsia_scheduler::{RoleManagerRequest, RoleManagerSetRoleResponse};
124 use futures::StreamExt;
125
126 #[fuchsia::test]
127 fn create_without_role_manager_succeeds() {
128 let _shadow_process = ShadowProcess::new(zx::Name::new_lossy("noop")).unwrap();
130 }
131
132 #[fuchsia::test]
133 async fn creation_sets_role() {
134 let (role_manager_client, mut role_manager_server) =
135 fidl::endpoints::create_sync_proxy_and_stream::<RoleManagerMarker>();
136
137 let (send_vmar_koid, recv_vmar_koid) = futures::channel::oneshot::channel();
140 std::thread::spawn(move || {
141 let shadow_process = ShadowProcess::from_role_manager(
142 zx::Name::new_lossy("role_manager_test"),
143 role_manager_client,
144 )
145 .unwrap();
146 send_vmar_koid.send(shadow_process.vmar.koid().unwrap()).unwrap();
147 });
148
149 match role_manager_server.next().await.unwrap().unwrap() {
150 RoleManagerRequest::SetRole { payload, responder } => {
151 responder.send(Ok(RoleManagerSetRoleResponse::default())).unwrap();
152 let shadow_vmar_koid: zx::Koid = recv_vmar_koid.await.unwrap();
153
154 let received_vmar_koid = match &payload.target {
155 Some(RoleTarget::Vmar(vmar)) => vmar.koid().unwrap(),
156 other => panic!("unexpected SetRole target {other:#?}"),
157 };
158 assert_eq!(shadow_vmar_koid, received_vmar_koid);
159 assert_eq!(payload.role, Some(RoleName { role: MEMORY_ROLE.to_string() }),);
160 }
161 other => panic!("unexpected SetRole request {other:?}"),
162 }
163 }
164
165 #[fuchsia::test]
166 fn vmo_is_mapped_in_shadow_vmar() {
167 let shadow_process = ShadowProcess::new(zx::Name::new_lossy("vmo_mapping_test")).unwrap();
168
169 let get_shadow_mappings = || {
170 shadow_process
171 .vmar
172 .maps_vec()
173 .unwrap()
174 .into_iter()
175 .filter_map(|info| info.details().as_mapping().map(ToOwned::to_owned))
176 .collect::<Vec<_>>()
177 };
178 assert_eq!(get_shadow_mappings(), &[], "VMAR should be empty before any pinning");
179
180 let to_map = zx::Vmo::create(8192).unwrap();
182 to_map.write(&[1u8; 8192][..], 0).unwrap();
183
184 let pinned_mapping = shadow_process.pin_pages(&to_map, 0, 8192).unwrap();
186 let mappings_after_pinning = get_shadow_mappings();
187 assert_eq!(mappings_after_pinning.len(), 1, "there should only be one mapping in VMAR");
188
189 let pinned_mapping_info = &mappings_after_pinning[0];
192 assert_eq!(pinned_mapping_info.mmu_flags, zx::VmarFlagsExtended::PERM_READ);
193 assert_eq!(pinned_mapping_info.vmo_koid, to_map.koid().unwrap());
194 assert_eq!(pinned_mapping_info.vmo_offset, 0);
195 assert_eq!(pinned_mapping_info.committed_bytes, 8192);
196 assert_eq!(pinned_mapping_info.populated_bytes, 8192);
197
198 drop(pinned_mapping);
199 assert_eq!(get_shadow_mappings(), &[], "dropping PinnedMap must clean up");
200 }
201}