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 {
86 Self::with_chunk_size(block_size, source, bti, contiguity, DEFAULT_PIN_CHUNK_SIZE)
87 }
88
89 pub fn with_chunk_size(
95 block_size: usize,
96 source: BufferSource,
97 bti: zx::Bti,
98 contiguity: u64,
99 chunk_size: usize,
100 ) -> Self {
101 assert!(!source.is_trusted(), "PinnedBufferAllocator cannot use a trusted buffer source");
102 assert!(chunk_size.is_power_of_two());
103 let contiguity = std::cmp::min(contiguity, chunk_size as u64);
104 let contiguity_usize = contiguity as usize;
105 assert!(chunk_size.is_multiple_of(contiguity_usize));
106 let num_chunks = source.size().div_ceil(chunk_size);
107 let total_paddrs = source.size().div_ceil(contiguity_usize);
108 let mut paddrs = Vec::with_capacity(total_paddrs);
109 for _ in 0..total_paddrs {
110 paddrs.push(UnsafeCell::new(0));
111 }
112 let mut chunks = Vec::with_capacity(num_chunks);
113 for _ in 0..num_chunks {
114 chunks.push(Chunk { pmt: None, ref_count: 0 });
115 }
116 let allocator = BufferAllocator::new(block_size, source);
117 let this = Self {
118 allocator,
119 bti,
120 contiguity,
121 chunk_size,
122 inner: Mutex::new(PinnedInner { chunks }),
123 paddrs: paddrs.into_boxed_slice(),
124 };
125 if num_chunks > 0 {
126 let mut inner = this.inner.lock();
127 this.pin_chunk_locked(&mut inner, 0);
128 }
129 this
130 }
131
132 pub fn block_size(&self) -> usize {
133 self.allocator.block_size()
134 }
135
136 pub fn chunk_size(&self) -> usize {
137 self.chunk_size
138 }
139
140 pub fn contiguity(&self) -> u64 {
141 self.contiguity
142 }
143
144 pub fn buffer_source(&self) -> &BufferSource {
145 self.allocator.buffer_source()
146 }
147
148 pub fn vmo(&self) -> Option<Arc<zx::Vmo>> {
150 self.allocator.vmo()
151 }
152
153 pub fn is_trusted(&self) -> bool {
154 false
155 }
156
157 pub fn clean_transfer_buffer(&self) {
159 self.allocator.clean_transfer_buffer();
160 }
161
162 fn pin_chunk_locked(&self, inner: &mut PinnedInner, chunk_idx: usize) {
163 let chunk = &mut inner.chunks[chunk_idx];
164 if chunk.pmt.is_some() {
165 return;
166 }
167 let chunk_offset = chunk_idx * self.chunk_size;
168 let chunk_len =
169 std::cmp::min(self.chunk_size, self.allocator.buffer_source().size() - chunk_offset);
170 let contiguity_usize = self.contiguity as usize;
171 let paddr_start = chunk_offset / contiguity_usize;
172 let num_paddrs = chunk_len.div_ceil(contiguity_usize);
173 let paddr_slice = unsafe {
176 let ptr = self.paddrs[paddr_start].get();
177 std::slice::from_raw_parts_mut(ptr, num_paddrs)
178 };
179 let options =
180 zx::BtiOptions::PERM_READ | zx::BtiOptions::PERM_WRITE | zx::BtiOptions::COMPRESS;
181 let vmo = self.allocator.vmo().expect("PinnedBufferAllocator requires an untrusted VMO");
182 let pmt = self
183 .bti
184 .pin(options, &vmo, chunk_offset as u64, chunk_len as u64, paddr_slice)
185 .unwrap_or_else(|status| {
186 panic!("Failed to pin chunk {chunk_idx}: {status:?}");
187 });
188 chunk.pmt = Some(pmt);
189 }
190
191 fn pin_range(&self, range: &Range<usize>) {
192 let start_chunk = range.start / self.chunk_size;
193 let end_chunk = (range.end + self.chunk_size - 1) / self.chunk_size;
194 let mut inner = self.inner.lock();
195 for chunk_idx in start_chunk..end_chunk {
196 self.pin_chunk_locked(&mut inner, chunk_idx);
197 inner.chunks[chunk_idx].ref_count += 1;
198 }
199 }
200
201 pub(crate) fn free_buffer(&self, range: Range<usize>) {
202 let start_chunk = range.start / self.chunk_size;
203 let end_chunk = (range.end + self.chunk_size - 1) / self.chunk_size;
204 let mut inner = self.inner.lock();
205 for chunk_idx in start_chunk..end_chunk {
206 let chunk = &mut inner.chunks[chunk_idx];
207 assert!(chunk.ref_count > 0);
208 chunk.ref_count -= 1;
209 if chunk.ref_count == 0 && chunk_idx > 0 {
211 if let Some(pmt) = chunk.pmt.take() {
212 let _ = unsafe { pmt.unpin() };
214 }
215 let chunk_offset = chunk_idx * self.chunk_size;
216 let chunk_len = std::cmp::min(
217 self.chunk_size,
218 self.allocator.buffer_source().size() - chunk_offset,
219 );
220 unsafe {
222 self.allocator
223 .buffer_source()
224 .clean_range(chunk_offset..chunk_offset + chunk_len);
225 }
226 }
227 }
228 drop(inner);
229 self.allocator.free_buffer(range);
230 }
231
232 pub fn paddrs(&self, range: &Range<usize>) -> Option<(&[zx_paddr_t], u64)> {
235 let contiguity_usize = self.contiguity as usize;
236 let start_page = range.start / contiguity_usize;
237 let end_page = (range.end + contiguity_usize - 1) / contiguity_usize;
238 let num_paddrs = end_page - start_page;
239 let slice = unsafe {
243 let ptr = self.paddrs[start_page].get();
244 std::slice::from_raw_parts(ptr, num_paddrs)
245 };
246 Some((slice, self.contiguity))
247 }
248
249 pub fn try_allocate_buffer(&self, size: usize) -> Result<PinnedBuffer<'_>, EventListener> {
250 let buffer = self.allocator.try_allocate_buffer(size)?;
251 let range = buffer.range();
252 self.pin_range(&range);
253 let slice = unsafe { self.allocator.buffer_source().subslice_ptr(&range) };
254 std::mem::forget(buffer);
257 Ok(BufferImpl::new(slice, range, self))
258 }
259
260 pub fn allocate_buffer_sync(&self, size: usize) -> PinnedBuffer<'_> {
261 <Self as TryAllocateBuffer>::allocate_buffer_sync(self, size)
262 }
263
264 pub fn allocate_buffer(&self, size: usize) -> PinnedBufferFuture<'_> {
265 BufferFuture::new(self, size)
266 }
267
268 pub fn try_allocate_buffer_owned(
269 self: &Arc<Self>,
270 size: usize,
271 ) -> Result<OwnedBuffer, EventListener> {
272 let buffer = self.allocator.try_allocate_buffer(size)?;
273 let range = buffer.range();
274 self.pin_range(&range);
275 let slice = unsafe { self.allocator.buffer_source().subslice_ptr_unbounded(&range) };
276 std::mem::forget(buffer);
279 Ok(BufferImpl::new(slice, range, self.clone()))
280 }
281
282 pub fn allocate_buffer_sync_owned(self: &Arc<Self>, size: usize) -> OwnedBuffer {
283 loop {
284 match self.try_allocate_buffer_owned(size) {
285 Ok(buffer) => return buffer,
286 Err(listener) => listener.wait(),
287 }
288 }
289 }
290}
291
292impl Drop for PinnedBufferAllocator {
293 fn drop(&mut self) {
294 let mut inner = self.inner.lock();
295 for chunk in &mut inner.chunks {
296 if let Some(pmt) = chunk.pmt.take() {
297 let _ = unsafe { pmt.unpin() };
299 }
300 }
301 }
302}
303
304impl BufferAllocatorTrait for PinnedBufferAllocator {
305 fn free_buffer(&self, range: Range<usize>) {
306 self.free_buffer(range);
307 }
308
309 fn identifier(&self) -> usize {
310 std::ptr::from_ref(self).addr()
311 }
312
313 fn is_trusted(&self) -> bool {
314 false
315 }
316
317 fn vmo(&self) -> Option<Arc<zx::Vmo>> {
318 self.vmo()
319 }
320
321 fn paddrs(&self, range: &Range<usize>) -> Option<(&[zx_paddr_t], u64)> {
322 self.paddrs(range)
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 use fake_bti::FakeBti;
330 use zx::Rights;
331
332 #[fuchsia::test]
333 async fn test_pinned_buffer() {
334 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
335 fake_bti.set_paddrs(&[4096, 8192]);
336 let source = BufferSource::new(8192);
337 let allocator = Arc::new(PinnedBufferAllocator::with_chunk_size(
339 512,
340 source,
341 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
342 4096,
343 4096,
344 ));
345
346 assert!(!allocator.is_trusted());
347 assert!(allocator.vmo().is_some());
348 assert!(BufferAllocatorTrait::vmo(allocator.as_ref()).is_some());
349
350 let buf = allocator.allocate_buffer(512).await;
352 assert_eq!(buf.range(), 0..512);
353 assert_eq!(buf.contiguity(), Some(4096));
354 assert_eq!(buf.paddrs(), Some(&[4096][..]));
355 assert!(buf.try_as_slice().is_none());
356 assert!(buf.vmo().is_some());
357
358 let buf2 = allocator.allocate_buffer_sync(512);
360 assert_eq!(buf2.range(), 512..1024);
361 assert_eq!(buf2.contiguity(), Some(4096));
362 assert_eq!(buf2.paddrs(), Some(&[4096][..]));
363
364 let buf_chunk1 = allocator.allocate_buffer_sync_owned(4096);
366 assert_eq!(buf_chunk1.range(), 4096..8192);
367 assert_eq!(buf_chunk1.contiguity(), Some(4096));
368 assert_eq!(buf_chunk1.paddrs(), Some(&[8192][..]));
369
370 std::mem::drop(buf_chunk1);
372
373 std::mem::drop(buf);
375 std::mem::drop(buf2);
376
377 let buf3 = allocator.allocate_buffer(512).await;
379 assert_eq!(buf3.range(), 0..512);
380 assert_eq!(buf3.contiguity(), Some(4096));
381 assert_eq!(buf3.paddrs(), Some(&[4096][..]));
382 }
383
384 #[fuchsia::test]
385 async fn test_spanning_chunks() {
386 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
387 fake_bti.set_paddrs(&[4096, 8192]);
388 let source = BufferSource::new(8192);
389 let allocator = PinnedBufferAllocator::with_chunk_size(
391 512,
392 source,
393 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
394 4096,
395 4096,
396 );
397
398 let mut buf = allocator.allocate_buffer(8192).await;
400 assert_eq!(buf.range(), 0..8192);
401 assert_eq!(buf.contiguity(), Some(4096));
402 assert_eq!(buf.paddrs(), Some(&[4096, 8192][..]));
403
404 buf.as_mut_ptr_slice().fill(0xab);
406 assert_eq!(buf.as_ptr_slice().to_vec(), vec![0xab; 8192]);
407
408 std::mem::drop(buf);
410
411 let buf_c0 = allocator.allocate_buffer_sync(512);
413 assert_eq!(buf_c0.range(), 0..512);
414 assert_eq!(buf_c0.paddrs(), Some(&[4096][..]));
415 }
416
417 #[fuchsia::test]
418 async fn test_multiple_chunks_independent_lifecycle() {
419 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
420 fake_bti.set_paddrs(&[0x1000, 0x2000, 0x3000, 0x4000]);
425 let source = BufferSource::new(16384);
426 let allocator = PinnedBufferAllocator::with_chunk_size(
428 512,
429 source,
430 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
431 4096,
432 4096,
433 );
434
435 let buf0 = allocator.allocate_buffer_sync(4096);
437 assert_eq!(buf0.paddrs(), Some(&[0x1000][..]));
438
439 let buf1 = allocator.allocate_buffer_sync(4096);
441 assert_eq!(buf1.paddrs(), Some(&[0x2000][..]));
442 let buf2 = allocator.allocate_buffer_sync(4096);
443 assert_eq!(buf2.paddrs(), Some(&[0x3000][..]));
444
445 std::mem::drop(buf1);
447 assert_eq!(buf2.paddrs(), Some(&[0x3000][..]));
448
449 let buf1_again = allocator.allocate_buffer_sync(4096);
451 assert_eq!(buf1_again.range(), 4096..8192);
452 assert_eq!(buf1_again.paddrs(), Some(&[0x4000][..]));
453
454 std::mem::drop(buf2);
456
457 std::mem::drop(buf1_again);
459
460 std::mem::drop(buf0);
462
463 let buf0_again = allocator.allocate_buffer_sync(512);
465 assert_eq!(buf0_again.paddrs(), Some(&[0x1000][..]));
466 }
467
468 #[fuchsia::test]
469 async fn test_clean_transfer_buffer() {
470 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
471 fake_bti.set_paddrs(&[4096, 8192]);
472 let source = BufferSource::new(8192);
473 let allocator = PinnedBufferAllocator::with_chunk_size(
474 512,
475 source,
476 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
477 4096,
478 4096,
479 );
480
481 let buf = allocator.allocate_buffer(4096).await;
482 allocator.clean_transfer_buffer();
484 assert_eq!(buf.paddrs(), Some(&[4096][..]));
486 }
487
488 #[fuchsia::test]
489 async fn test_concurrent_pinned_allocations() {
490 use fuchsia_async as fasync;
491 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
492 let source = BufferSource::new(16384);
493 let allocator = Arc::new(PinnedBufferAllocator::with_chunk_size(
494 512,
495 source,
496 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
497 4096,
498 4096,
499 ));
500
501 let mut tasks = Vec::new();
502 for i in 0..8 {
503 let alloc = allocator.clone();
504 tasks.push(async move {
505 let mut buf = alloc.allocate_buffer(512).await;
506 assert!(buf.paddrs().is_some());
507 buf.as_mut_ptr_slice().fill(i as u8);
508 fasync::Timer::new(std::time::Duration::from_millis(5)).await;
509 assert_eq!(buf.as_ptr_slice().to_vec(), vec![i as u8; 512]);
510 });
511 }
512 futures::future::join_all(tasks).await;
513 }
514
515 #[fuchsia::test]
516 async fn test_buffer_subslicing() {
517 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
518 fake_bti.set_paddrs(&[4096]);
519 let source = BufferSource::new(4096);
520 let allocator = PinnedBufferAllocator::new(
521 512,
522 source,
523 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
524 4096,
525 );
526
527 let mut buf = allocator.allocate_buffer(1024).await;
528 buf.as_mut_ptr_slice().fill(0x77);
529
530 let sub_ref = buf.subslice(100..200);
531 assert_eq!(sub_ref.len(), 100);
532 assert_eq!(sub_ref.to_vec(), vec![0x77; 100]);
533
534 let buf_ref = buf.as_ref();
535 let (left, right) = buf_ref.split_at(512);
536 assert_eq!(left.len(), 512);
537 assert_eq!(right.len(), 512);
538 }
539
540 #[fuchsia::test]
541 async fn test_u64_max_contiguity_clamped_to_chunk_size() {
542 let fake_bti = FakeBti::create().expect("failed to create fake BTI");
543 fake_bti.set_paddrs(&[0x22_0000, 0x35_0000]);
544 let source = BufferSource::new(8192);
545 let allocator = PinnedBufferAllocator::with_chunk_size(
546 512,
547 source,
548 fake_bti.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
549 u64::MAX,
550 4096,
551 );
552 assert_eq!(allocator.contiguity(), 4096);
553
554 let buf = allocator.allocate_buffer(8192).await;
555 assert_eq!(buf.range(), 0..8192);
556 assert_eq!(buf.contiguity(), Some(4096));
557 assert_eq!(buf.paddrs(), Some(&[0x22_0000, 0x35_0000][..]));
558 }
559}