1use crate::buffer::{BufferAllocator as BufferAllocatorTrait, BufferImpl, OwnedBuffer};
6use crate::buffer_allocator::{BufferAllocator, BufferFuture, BufferSource, TryAllocateBuffer};
7use event_listener::{EventListener, Listener as _};
8use fuchsia_sync::Mutex;
9use std::cell::UnsafeCell;
10use std::fmt::Debug;
11use std::ops::Range;
12use std::sync::Arc;
13use zx::sys::zx_paddr_t;
14
15pub const DEFAULT_PIN_CHUNK_SIZE: usize = 1024 * 1024;
17
18#[derive(Debug)]
19struct Chunk {
20 pmt: Option<zx::Pmt>,
21 ref_count: usize,
23}
24
25#[derive(Debug)]
26struct PinnedInner {
27 chunks: Vec<Chunk>,
28}
29
30pub struct PinnedBufferAllocator {
41 allocator: BufferAllocator,
42 bti: zx::Bti,
43 contiguity: u64,
44 chunk_size: usize,
45 inner: Mutex<PinnedInner>,
46 paddrs: Box<[UnsafeCell<zx_paddr_t>]>,
47}
48
49unsafe impl Send for PinnedBufferAllocator {}
55unsafe impl Sync for PinnedBufferAllocator {}
56
57impl Debug for PinnedBufferAllocator {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 f.debug_struct("PinnedBufferAllocator")
60 .field("block_size", &self.block_size())
61 .field("chunk_size", &self.chunk_size)
62 .field("contiguity", &self.contiguity)
63 .finish_non_exhaustive()
64 }
65}
66
67pub type PinnedBuffer<'a> = BufferImpl<'a, &'a PinnedBufferAllocator, PinnedBufferAllocator>;
68
69pub type PinnedBufferFuture<'a> = BufferFuture<'a, PinnedBufferAllocator>;
70
71impl<'a> TryAllocateBuffer<'a> for PinnedBufferAllocator {
72 type Buffer = PinnedBuffer<'a>;
73
74 fn try_allocate_buffer(&'a self, size: usize) -> Result<PinnedBuffer<'a>, EventListener> {
75 self.try_allocate_buffer(size)
76 }
77}
78
79impl PinnedBufferAllocator {
80 pub fn new(block_size: usize, source: BufferSource, bti: zx::Bti, contiguity: u64) -> Self {
85 Self::with_chunk_size(block_size, source, bti, contiguity, DEFAULT_PIN_CHUNK_SIZE)
86 }
87
88 pub fn with_chunk_size(
93 block_size: usize,
94 source: BufferSource,
95 bti: zx::Bti,
96 contiguity: u64,
97 chunk_size: usize,
98 ) -> Self {
99 assert!(!source.is_trusted(), "PinnedBufferAllocator cannot use a trusted buffer source");
100 assert!(chunk_size.is_power_of_two());
101 assert!(chunk_size >= contiguity as usize);
102 assert!(chunk_size % contiguity as usize == 0);
103 let num_chunks = source.size().div_ceil(chunk_size);
104 let total_paddrs = source.size().div_ceil(contiguity as usize);
105 let mut paddrs = Vec::with_capacity(total_paddrs);
106 for _ in 0..total_paddrs {
107 paddrs.push(UnsafeCell::new(0));
108 }
109 let mut chunks = Vec::with_capacity(num_chunks);
110 for _ in 0..num_chunks {
111 chunks.push(Chunk { pmt: None, ref_count: 0 });
112 }
113 let allocator = BufferAllocator::new(block_size, source);
114 let this = Self {
115 allocator,
116 bti,
117 contiguity,
118 chunk_size,
119 inner: Mutex::new(PinnedInner { chunks }),
120 paddrs: paddrs.into_boxed_slice(),
121 };
122 if num_chunks > 0 {
123 let mut inner = this.inner.lock();
124 this.pin_chunk_locked(&mut inner, 0);
125 }
126 this
127 }
128
129 pub fn block_size(&self) -> usize {
130 self.allocator.block_size()
131 }
132
133 pub fn chunk_size(&self) -> usize {
134 self.chunk_size
135 }
136
137 pub fn contiguity(&self) -> u64 {
138 self.contiguity
139 }
140
141 pub fn buffer_source(&self) -> &BufferSource {
142 self.allocator.buffer_source()
143 }
144
145 pub fn vmo(&self) -> Option<Arc<zx::Vmo>> {
147 self.allocator.vmo()
148 }
149
150 pub fn is_trusted(&self) -> bool {
151 false
152 }
153
154 pub fn clean_transfer_buffer(&self) {
156 self.allocator.clean_transfer_buffer();
157 }
158
159 fn pin_chunk_locked(&self, inner: &mut PinnedInner, chunk_idx: usize) {
160 let chunk = &mut inner.chunks[chunk_idx];
161 if chunk.pmt.is_some() {
162 return;
163 }
164 let chunk_offset = chunk_idx * self.chunk_size;
165 let chunk_len =
166 std::cmp::min(self.chunk_size, self.allocator.buffer_source().size() - chunk_offset);
167 let contiguity_usize = self.contiguity as usize;
168 let paddr_start = chunk_offset / contiguity_usize;
169 let num_paddrs = chunk_len.div_ceil(contiguity_usize);
170 let paddr_slice = unsafe {
173 let ptr = self.paddrs[paddr_start].get();
174 std::slice::from_raw_parts_mut(ptr, num_paddrs)
175 };
176 let options =
177 zx::BtiOptions::PERM_READ | zx::BtiOptions::PERM_WRITE | zx::BtiOptions::COMPRESS;
178 let vmo = self.allocator.vmo().expect("PinnedBufferAllocator requires an untrusted VMO");
179 let pmt = self
180 .bti
181 .pin(options, &vmo, chunk_offset as u64, chunk_len as u64, paddr_slice)
182 .unwrap_or_else(|status| {
183 panic!("Failed to pin chunk {chunk_idx}: {status:?}");
184 });
185 chunk.pmt = Some(pmt);
186 }
187
188 fn pin_range(&self, range: &Range<usize>) {
189 let start_chunk = range.start / self.chunk_size;
190 let end_chunk = (range.end + self.chunk_size - 1) / self.chunk_size;
191 let mut inner = self.inner.lock();
192 for chunk_idx in start_chunk..end_chunk {
193 self.pin_chunk_locked(&mut inner, chunk_idx);
194 inner.chunks[chunk_idx].ref_count += 1;
195 }
196 }
197
198 pub(crate) fn free_buffer(&self, range: Range<usize>) {
199 let start_chunk = range.start / self.chunk_size;
200 let end_chunk = (range.end + self.chunk_size - 1) / self.chunk_size;
201 let mut inner = self.inner.lock();
202 for chunk_idx in start_chunk..end_chunk {
203 let chunk = &mut inner.chunks[chunk_idx];
204 assert!(chunk.ref_count > 0);
205 chunk.ref_count -= 1;
206 if chunk.ref_count == 0 && chunk_idx > 0 {
208 if let Some(pmt) = chunk.pmt.take() {
209 let _ = unsafe { pmt.unpin() };
211 }
212 let chunk_offset = chunk_idx * self.chunk_size;
213 let chunk_len = std::cmp::min(
214 self.chunk_size,
215 self.allocator.buffer_source().size() - chunk_offset,
216 );
217 unsafe {
219 self.allocator
220 .buffer_source()
221 .clean_range(chunk_offset..chunk_offset + chunk_len);
222 }
223 }
224 }
225 drop(inner);
226 self.allocator.free_buffer(range);
227 }
228
229 pub fn paddrs(&self, range: &Range<usize>) -> Option<(&[zx_paddr_t], u64)> {
232 let contiguity_usize = self.contiguity as usize;
233 let start_page = range.start / contiguity_usize;
234 let end_page = (range.end + contiguity_usize - 1) / contiguity_usize;
235 let num_paddrs = end_page - start_page;
236 let slice = unsafe {
240 let ptr = self.paddrs[start_page].get();
241 std::slice::from_raw_parts(ptr, num_paddrs)
242 };
243 Some((slice, self.contiguity))
244 }
245
246 pub fn try_allocate_buffer(&self, size: usize) -> Result<PinnedBuffer<'_>, EventListener> {
247 let buffer = self.allocator.try_allocate_buffer(size)?;
248 let range = buffer.range();
249 self.pin_range(&range);
250 let slice = unsafe { self.allocator.buffer_source().subslice_ptr(&range) };
251 std::mem::forget(buffer);
254 Ok(BufferImpl::new(slice, range, self))
255 }
256
257 pub fn allocate_buffer_sync(&self, size: usize) -> PinnedBuffer<'_> {
258 <Self as TryAllocateBuffer>::allocate_buffer_sync(self, size)
259 }
260
261 pub fn allocate_buffer(&self, size: usize) -> PinnedBufferFuture<'_> {
262 BufferFuture::new(self, size)
263 }
264
265 pub fn try_allocate_buffer_owned(
266 self: &Arc<Self>,
267 size: usize,
268 ) -> Result<OwnedBuffer, EventListener> {
269 let buffer = self.allocator.try_allocate_buffer(size)?;
270 let range = buffer.range();
271 self.pin_range(&range);
272 let slice = unsafe { self.allocator.buffer_source().subslice_ptr_unbounded(&range) };
273 std::mem::forget(buffer);
276 Ok(BufferImpl::new(slice, range, self.clone()))
277 }
278
279 pub fn allocate_buffer_sync_owned(self: &Arc<Self>, size: usize) -> OwnedBuffer {
280 loop {
281 match self.try_allocate_buffer_owned(size) {
282 Ok(buffer) => return buffer,
283 Err(listener) => listener.wait(),
284 }
285 }
286 }
287}
288
289impl Drop for PinnedBufferAllocator {
290 fn drop(&mut self) {
291 let mut inner = self.inner.lock();
292 for chunk in &mut inner.chunks {
293 if let Some(pmt) = chunk.pmt.take() {
294 let _ = unsafe { pmt.unpin() };
296 }
297 }
298 }
299}
300
301impl BufferAllocatorTrait for PinnedBufferAllocator {
302 fn free_buffer(&self, range: Range<usize>) {
303 self.free_buffer(range);
304 }
305
306 fn identifier(&self) -> usize {
307 std::ptr::from_ref(self).addr()
308 }
309
310 fn is_trusted(&self) -> bool {
311 false
312 }
313
314 fn vmo(&self) -> Option<Arc<zx::Vmo>> {
315 self.vmo()
316 }
317
318 fn paddrs(&self, range: &Range<usize>) -> Option<(&[zx_paddr_t], u64)> {
319 self.paddrs(range)
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use fake_bti::FakeBti;
327 use zx::Rights;
328
329 #[fuchsia::test]
330 async fn test_pinned_buffer() {
331 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
332 fake_bti.set_paddrs(&[4096, 8192]);
333 let source = BufferSource::new(8192);
334 let allocator = Arc::new(PinnedBufferAllocator::with_chunk_size(
336 512,
337 source,
338 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
339 4096,
340 4096,
341 ));
342
343 assert!(!allocator.is_trusted());
344 assert!(allocator.vmo().is_some());
345 assert!(BufferAllocatorTrait::vmo(allocator.as_ref()).is_some());
346
347 let buf = allocator.allocate_buffer(512).await;
349 assert_eq!(buf.range(), 0..512);
350 assert_eq!(buf.contiguity(), Some(4096));
351 assert_eq!(buf.paddrs(), Some(&[4096][..]));
352 assert!(buf.try_as_slice().is_none());
353 assert!(buf.vmo().is_some());
354
355 let buf2 = allocator.allocate_buffer_sync(512);
357 assert_eq!(buf2.range(), 512..1024);
358 assert_eq!(buf2.contiguity(), Some(4096));
359 assert_eq!(buf2.paddrs(), Some(&[4096][..]));
360
361 let buf_chunk1 = allocator.allocate_buffer_sync_owned(4096);
363 assert_eq!(buf_chunk1.range(), 4096..8192);
364 assert_eq!(buf_chunk1.contiguity(), Some(4096));
365 assert_eq!(buf_chunk1.paddrs(), Some(&[8192][..]));
366
367 std::mem::drop(buf_chunk1);
369
370 std::mem::drop(buf);
372 std::mem::drop(buf2);
373
374 let buf3 = allocator.allocate_buffer(512).await;
376 assert_eq!(buf3.range(), 0..512);
377 assert_eq!(buf3.contiguity(), Some(4096));
378 assert_eq!(buf3.paddrs(), Some(&[4096][..]));
379 }
380
381 #[fuchsia::test]
382 async fn test_spanning_chunks() {
383 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
384 fake_bti.set_paddrs(&[4096, 8192]);
385 let source = BufferSource::new(8192);
386 let allocator = PinnedBufferAllocator::with_chunk_size(
388 512,
389 source,
390 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
391 4096,
392 4096,
393 );
394
395 let mut buf = allocator.allocate_buffer(8192).await;
397 assert_eq!(buf.range(), 0..8192);
398 assert_eq!(buf.contiguity(), Some(4096));
399 assert_eq!(buf.paddrs(), Some(&[4096, 8192][..]));
400
401 buf.as_mut_ptr_slice().fill(0xab);
403 assert_eq!(buf.as_ptr_slice().to_vec(), vec![0xab; 8192]);
404
405 std::mem::drop(buf);
407
408 let buf_c0 = allocator.allocate_buffer_sync(512);
410 assert_eq!(buf_c0.range(), 0..512);
411 assert_eq!(buf_c0.paddrs(), Some(&[4096][..]));
412 }
413
414 #[fuchsia::test]
415 async fn test_multiple_chunks_independent_lifecycle() {
416 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
417 fake_bti.set_paddrs(&[0x1000, 0x2000, 0x3000, 0x4000]);
422 let source = BufferSource::new(16384);
423 let allocator = PinnedBufferAllocator::with_chunk_size(
425 512,
426 source,
427 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
428 4096,
429 4096,
430 );
431
432 let buf0 = allocator.allocate_buffer_sync(4096);
434 assert_eq!(buf0.paddrs(), Some(&[0x1000][..]));
435
436 let buf1 = allocator.allocate_buffer_sync(4096);
438 assert_eq!(buf1.paddrs(), Some(&[0x2000][..]));
439 let buf2 = allocator.allocate_buffer_sync(4096);
440 assert_eq!(buf2.paddrs(), Some(&[0x3000][..]));
441
442 std::mem::drop(buf1);
444 assert_eq!(buf2.paddrs(), Some(&[0x3000][..]));
445
446 let buf1_again = allocator.allocate_buffer_sync(4096);
448 assert_eq!(buf1_again.range(), 4096..8192);
449 assert_eq!(buf1_again.paddrs(), Some(&[0x4000][..]));
450
451 std::mem::drop(buf2);
453
454 std::mem::drop(buf1_again);
456
457 std::mem::drop(buf0);
459
460 let buf0_again = allocator.allocate_buffer_sync(512);
462 assert_eq!(buf0_again.paddrs(), Some(&[0x1000][..]));
463 }
464
465 #[fuchsia::test]
466 async fn test_clean_transfer_buffer() {
467 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
468 fake_bti.set_paddrs(&[4096, 8192]);
469 let source = BufferSource::new(8192);
470 let allocator = PinnedBufferAllocator::with_chunk_size(
471 512,
472 source,
473 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
474 4096,
475 4096,
476 );
477
478 let buf = allocator.allocate_buffer(4096).await;
479 allocator.clean_transfer_buffer();
481 assert_eq!(buf.paddrs(), Some(&[4096][..]));
483 }
484
485 #[fuchsia::test]
486 async fn test_concurrent_pinned_allocations() {
487 use fuchsia_async as fasync;
488 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
489 let source = BufferSource::new(16384);
490 let allocator = Arc::new(PinnedBufferAllocator::with_chunk_size(
491 512,
492 source,
493 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
494 4096,
495 4096,
496 ));
497
498 let mut tasks = Vec::new();
499 for i in 0..8 {
500 let alloc = allocator.clone();
501 tasks.push(async move {
502 let mut buf = alloc.allocate_buffer(512).await;
503 assert!(buf.paddrs().is_some());
504 buf.as_mut_ptr_slice().fill(i as u8);
505 fasync::Timer::new(std::time::Duration::from_millis(5)).await;
506 assert_eq!(buf.as_ptr_slice().to_vec(), vec![i as u8; 512]);
507 });
508 }
509 futures::future::join_all(tasks).await;
510 }
511
512 #[fuchsia::test]
513 async fn test_buffer_subslicing() {
514 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
515 fake_bti.set_paddrs(&[4096]);
516 let source = BufferSource::new(4096);
517 let allocator = PinnedBufferAllocator::new(
518 512,
519 source,
520 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
521 4096,
522 );
523
524 let mut buf = allocator.allocate_buffer(1024).await;
525 buf.as_mut_ptr_slice().fill(0x77);
526
527 let sub_ref = buf.subslice(100..200);
528 assert_eq!(sub_ref.len(), 100);
529 assert_eq!(sub_ref.to_vec(), vec![0x77; 100]);
530
531 let buf_ref = buf.as_ref();
532 let (left, right) = buf_ref.split_at(512);
533 assert_eq!(left.len(), 512);
534 assert_eq!(right.len(), 512);
535 }
536}