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