1use fidl_fuchsia_starnix_runner as fstarnixrunner;
10use futures::TryStreamExt;
11use starnix_logging::{log_debug, log_error, log_warn, with_zx_name};
12use starnix_sync::{LockDepMutex, PagerFilesByInodeLock, PagerFilesystemsLock};
13use starnix_uapi::errors::Errno;
14use starnix_uapi::{errno, error};
15use std::collections::HashMap;
16use std::collections::hash_map::Entry;
17use std::ops::Range;
18use std::sync::Arc;
19use std::sync::atomic::{AtomicU32, Ordering};
20use zx::sys::zx_page_request_command_t::{ZX_PAGER_VMO_COMPLETE, ZX_PAGER_VMO_READ};
21
22const PAGER_THREADS: usize = 1;
25const TRANSFER_VMO_SIZE: u64 = 1 * 1024 * 1024;
26const ZERO_VMO_SIZE: u64 = 1 * 1024 * 1024;
27
28const CATEGORY_STARNIX_PAGER: &'static str = "starnix:pager";
30
31pub async fn run_pager(
32 pager_request: fstarnixrunner::ManagerCreatePagerRequest,
33 pager: Arc<Pager>,
34) {
35 let fstarnixrunner::ManagerCreatePagerRequest {
36 backing_vmo: Some(backing_vmo),
37 block_size: Some(block_size),
38 pager: Some(pager_server),
39 ..
40 } = pager_request
41 else {
42 log_error!("Invalid create pager request");
43 return;
44 };
45
46 let filesystem = Arc::new(match Filesystem::new(pager.clone(), backing_vmo, block_size) {
47 Ok(filesystem) => filesystem,
48 Err(error) => {
49 log_error!("Unable to register filesystem {error}");
50 return;
51 }
52 });
53
54 pager.add_filesystem(filesystem.clone());
55
56 let mut stream = pager_server.into_stream();
57 'outer: while let Ok(Some(event)) = stream.try_next().await {
58 match event {
59 fstarnixrunner::PagerRequest::RegisterFile {
60 payload:
61 fstarnixrunner::PagerRegisterFileRequest {
62 name: Some(name),
63 inode_num: Some(inode_num),
64 size: Some(size),
65 extents: Some(extents),
66 ..
67 },
68 responder,
69 ..
70 } => {
71 fuchsia_trace::instant!(
72 CATEGORY_STARNIX_PAGER,
73 "file_register",
74 fuchsia_trace::Scope::Thread
75 );
76 match filesystem.register(
77 &name,
78 inode_num,
79 size,
80 extents
81 .iter()
82 .map(|e| PagerExtent {
83 logical: e.logical_start..e.logical_end,
84 physical_block: e.physical_block,
85 })
86 .collect(),
87 ) {
88 Ok(vmo) => {
89 match responder.send(Ok(fstarnixrunner::PagerRegisterFileResponse {
90 vmo: Some(vmo),
91 ..Default::default()
92 })) {
93 Ok(_) => {}
94 Err(e) => {
95 log_error!("Error sending pager response {:?}", e);
96 break 'outer;
97 }
98 }
99 }
100 Err(e) => match responder.send(Err(e.into_raw())) {
101 Ok(_) => {}
102 Err(e) => {
103 log_error!("Error sending pager error response {:?}", e);
104 break 'outer;
105 }
106 },
107 };
108 }
109 fstarnixrunner::PagerRequest::RegisterFile { .. } => {
110 log_error!("Invalid RegisterFile request");
111 break 'outer;
112 }
113 _ => {}
114 }
115 }
116 pager.remove_filesystem(&*filesystem);
117}
118
119pub struct Pager {
121 pager: zx::Pager,
122 port: zx::Port,
123 zero_vmo: zx::Vmo,
124 next_filesystem_id: AtomicU32,
125 filesystems: LockDepMutex<HashMap<u32, Arc<Filesystem>>, PagerFilesystemsLock>,
126}
127
128impl Pager {
129 pub fn new() -> Result<Self, Errno> {
130 Ok(Self {
131 pager: zx::Pager::create(zx::PagerOptions::empty()).map_err(|error| {
132 log_error!(error:?; "Pager::create failed");
133 errno!(EINVAL)
134 })?,
135 port: zx::Port::create(),
136 zero_vmo: with_zx_name(
137 zx::Vmo::create(ZERO_VMO_SIZE).map_err(|_| errno!(EINVAL))?,
138 b"starnix:ext4",
139 ),
140 next_filesystem_id: AtomicU32::new(1),
141 filesystems: Default::default(),
142 })
143 }
144
145 pub fn start_threads(self: &Arc<Self>) {
147 for i in 0..PAGER_THREADS {
148 let this = self.clone();
149 let _ = std::thread::Builder::new().name(format!("pager-{}", i)).spawn(move || {
150 this.run_pager_thread();
151 });
152 }
153 }
154
155 pub fn run_pager_thread(&self) {
158 let transfer_vmo = with_zx_name(
159 zx::Vmo::create(TRANSFER_VMO_SIZE).expect("unable to create transfer vmo"),
160 b"starnix:ext4",
161 );
162 let transfer_vmo_addr = fuchsia_runtime::vmar_root_self()
163 .map(
164 0,
165 &transfer_vmo,
166 0,
167 TRANSFER_VMO_SIZE as usize,
168 zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE | zx::VmarFlags::ALLOW_FAULTS,
169 )
170 .expect("unable to map transfer vmo");
171 scopeguard::defer!({
172 let _ = unsafe {
174 fuchsia_runtime::vmar_root_self()
175 .unmap(transfer_vmo_addr, TRANSFER_VMO_SIZE as usize)
176 };
177 });
178 let split_key = |key: u64| -> (u32, u32) { ((key >> 32) as u32, key as u32) };
179 loop {
180 match self.port.wait(zx::MonotonicInstant::INFINITE) {
181 Ok(packet) => {
182 match packet.contents() {
183 zx::PacketContents::Pager(contents)
184 if contents.command() == ZX_PAGER_VMO_READ =>
185 {
186 fuchsia_trace::duration!(CATEGORY_STARNIX_PAGER, "vmo_read");
187 let (filesystem_num, inode_num) = split_key(packet.key());
188 let filesystem = self
189 .filesystems
190 .lock()
191 .get(&filesystem_num)
192 .cloned()
193 .expect("Unexpected packet key");
194 filesystem.receive_pager_packet(
195 inode_num,
196 contents,
197 &transfer_vmo,
198 transfer_vmo_addr,
199 );
200 }
201 zx::PacketContents::Pager(contents)
202 if contents.command() == ZX_PAGER_VMO_COMPLETE =>
203 {
204 fuchsia_trace::duration!(CATEGORY_STARNIX_PAGER, "vmo_complete");
205 }
208 zx::PacketContents::SignalOne(signals)
209 if signals.observed().contains(zx::Signals::VMO_ZERO_CHILDREN) =>
210 {
211 fuchsia_trace::duration!(
212 CATEGORY_STARNIX_PAGER,
213 "signal_zero_children"
214 );
215 let (filesystem_num, inode_num) = split_key(packet.key());
216 let filesystem = self.filesystems.lock().get(&filesystem_num).cloned();
220 if let Some(filesystem) = filesystem {
221 filesystem.on_zero_children(inode_num).expect("on_zero_children");
222 }
223 }
224 zx::PacketContents::User(_) => break,
225 _ => log_error!("Unexpected port packet: {:?}", packet.contents()),
226 }
227 }
228 Err(error) => log_error!(error:?; "Port::wait failed"),
229 }
230 }
231 log_debug!("Pager thread terminating");
232 }
233
234 fn create_pager_vmo(&self, key: u64, size: u64) -> Result<zx::Vmo, zx::Status> {
235 self.pager.create_vmo(zx::VmoOptions::RESIZABLE, &self.port, key, size)
236 }
237
238 fn allocate_filesystem_id(&self) -> u32 {
239 self.next_filesystem_id.fetch_add(1, Ordering::Relaxed)
240 }
241
242 fn add_filesystem(&self, filesystem: Arc<Filesystem>) {
243 self.filesystems.lock().insert(filesystem.id(), filesystem);
244 }
245
246 fn remove_filesystem(&self, filesystem: &Filesystem) {
247 self.filesystems.lock().remove(&filesystem.id());
248 }
249
250 pub fn terminate(&self) {
252 let up = zx::UserPacket::from_u8_array([0; 32]);
253 let packet = zx::Packet::from_user_packet(0, 0, up);
254 for _ in 0..PAGER_THREADS {
255 self.port.queue(&packet).unwrap();
256 }
257 }
258}
259
260pub struct Filesystem {
262 pager: Arc<Pager>,
263 backing_vmo: zx::Vmo,
264 block_size: u64,
265 files_by_inode: LockDepMutex<HashMap<u32, Arc<PagedFile>>, PagerFilesByInodeLock>,
266 id: u32,
267}
268
269impl Filesystem {
270 pub fn new(pager: Arc<Pager>, backing_vmo: zx::Vmo, block_size: u64) -> Result<Self, Errno> {
273 if block_size > 1024 * 1024 || !block_size.is_power_of_two() {
274 return error!(EINVAL, "Bad block size {block_size}");
275 }
276 let id = pager.allocate_filesystem_id();
277 Ok(Self { pager, backing_vmo, block_size, files_by_inode: Default::default(), id })
278 }
279
280 pub fn register(
282 &self,
283 name: &str,
284 inode_num: u32,
285 size: u64,
286 extents: Box<[PagerExtent]>,
287 ) -> Result<zx::Vmo, zx::Status> {
288 let (file, did_create) = {
289 match self.files_by_inode.lock().entry(inode_num) {
290 Entry::Occupied(o) => (o.get().clone(), false),
291 Entry::Vacant(v) => (
292 v.insert(Arc::new(PagedFile {
293 vmo: self
294 .pager
295 .create_pager_vmo(self.port_key_for_inode(inode_num), size)?,
296 extents,
297 }))
298 .clone(),
299 true,
300 ),
301 }
302 };
303 let child_vmo = file.vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0);
304 if did_create {
305 let set_up_vmo = |vmo| -> Result<(), zx::Status> {
306 self.watch_for_zero_children(vmo, inode_num)?;
307 vmo.set_name(&zx::Name::new_lossy(&format!("ext4!{}", name)))?;
308 Ok(())
309 };
310
311 if let Err(e) = set_up_vmo(&file.vmo) {
312 self.files_by_inode.lock().remove(&inode_num);
313 return Err(e);
314 }
315 }
316 child_vmo
317 }
318
319 fn receive_pager_packet(
320 &self,
321 inode_num: u32,
322 contents: zx::PagerPacket,
323 transfer_vmo: &zx::Vmo,
324 transfer_vmo_addr: usize,
325 ) {
326 let Some(file) = self.files_by_inode.lock().get(&inode_num).cloned() else {
327 return;
328 };
329
330 let requested_range = contents.range();
331
332 const ALIGNMENT: u64 = 128 * 1024;
334 let readahead_start = (requested_range.start / ALIGNMENT) * ALIGNMENT;
335 let mut readahead_end = requested_range.end.next_multiple_of(ALIGNMENT);
336
337 let vmo_size = match file.vmo.get_size() {
339 Ok(size) => size,
340 Err(status) => {
341 log_error!("Failed to get VMO size: {:?}", status);
342 let _ = self.pager.pager.op_range(
343 zx::PagerOp::Fail(zx::Status::IO),
344 &file.vmo,
345 contents.range(),
346 );
347 return;
348 }
349 };
350 readahead_end = std::cmp::min(readahead_end, vmo_size);
351
352 if readahead_end <= readahead_start {
353 return;
354 }
355
356 let start_block = (readahead_start / self.block_size) as u32;
357 let mut ix = file.extents.partition_point(|e| e.logical.end <= start_block);
358
359 let buf = unsafe {
362 std::slice::from_raw_parts_mut(transfer_vmo_addr as *mut u8, TRANSFER_VMO_SIZE as usize)
363 };
364
365 let mut current_offset = readahead_start;
366 let mut supply_helper =
367 SupplyHelper::new(transfer_vmo, buf, &file.vmo, current_offset, &*self.pager);
368
369 while ix < file.extents.len() && current_offset < readahead_end {
370 let extent = &file.extents[ix];
371
372 let logical_start = extent.logical.start as u64 * self.block_size;
373
374 if current_offset < logical_start {
376 if let Err(e) = supply_helper.zero(logical_start - current_offset) {
377 supply_helper.fail_to(readahead_end, e);
378 return;
379 }
380 current_offset = logical_start;
381 }
382
383 let end = std::cmp::min(extent.logical.end as u64 * self.block_size, readahead_end);
384
385 while current_offset < end {
386 let phys_offset =
387 extent.physical_block * self.block_size + current_offset - logical_start;
388
389 match supply_helper.fill_buf(|buf| {
390 let amount = std::cmp::min(buf.len() as u64, end - current_offset) as usize;
391 self.backing_vmo.read(&mut buf[..amount], phys_offset)?;
392 Ok(amount)
393 }) {
394 Ok(amount) => {
395 let _ = self.backing_vmo.op_range(
398 zx::VmoOp::DONT_NEED,
399 phys_offset,
400 amount as u64,
401 );
402 current_offset += amount as u64;
403 }
404 Err(e) => {
405 supply_helper.fail_to(readahead_end, e);
406 return;
407 }
408 }
409 }
410
411 ix += 1;
412 }
413
414 if let Err(e) = supply_helper.finish(readahead_end) {
415 supply_helper.fail_to(readahead_end, e);
416 }
417 }
418
419 fn watch_for_zero_children(&self, vmo: &zx::Vmo, inode_num: u32) -> Result<(), zx::Status> {
420 vmo.wait_async(
421 &self.pager.port,
422 self.port_key_for_inode(inode_num),
423 zx::Signals::VMO_ZERO_CHILDREN,
424 zx::WaitAsyncOpts::empty(),
425 )
426 }
427
428 fn on_zero_children(&self, inode_num: u32) -> Result<(), Errno> {
429 let mut files = self.files_by_inode.lock();
430 let file = files.entry(inode_num);
431 if let Entry::Occupied(o) = file {
432 let vmo = &o.get().vmo;
433 match vmo.info() {
434 Ok(info) => {
435 if info.num_children == 0 {
436 o.remove();
438 } else {
439 if let Err(error) = self.watch_for_zero_children(vmo, inode_num) {
442 log_error!(
443 error:?;
444 "watch_for_zero_children failed"
445 );
446 }
447 }
448 }
449 Err(error) => log_error!(error:?; "Vmo::info failed"),
450 }
451 }
452 Ok(())
453 }
454
455 fn port_key_for_inode(&self, inode_num: u32) -> u64 {
456 (self.id as u64) << 32 | inode_num as u64
457 }
458
459 fn id(&self) -> u32 {
460 self.id
461 }
462}
463
464struct PagedFile {
466 vmo: zx::Vmo,
468
469 extents: Box<[PagerExtent]>,
472}
473
474pub struct PagerExtent {
476 pub logical: Range<u32>,
477 pub physical_block: u64,
478}
479
480struct SupplyHelper<'a> {
482 transfer_vmo: &'a zx::Vmo,
483 buffer: &'a mut [u8],
484 target_vmo: &'a zx::Vmo,
485 offset: u64,
486 pager: &'a Pager,
487 page_size: u64,
488 buf_len: usize,
489}
490
491impl<'a> SupplyHelper<'a> {
492 fn new(
493 transfer_vmo: &'a zx::Vmo,
494 buffer: &'a mut [u8],
495 target_vmo: &'a zx::Vmo,
496 offset: u64,
497 pager: &'a Pager,
498 ) -> Self {
499 Self {
500 transfer_vmo,
501 buffer,
502 target_vmo,
503 offset,
504 pager,
505 page_size: *starnix_core::mm::PAGE_SIZE,
506 buf_len: 0,
507 }
508 }
509
510 fn zero(&mut self, mut len: u64) -> Result<(), zx::Status> {
512 let unaligned = self.buf_len as u64 % self.page_size;
513 if unaligned > 0 {
514 let amount = std::cmp::min(self.page_size - unaligned, len);
515 self.buffer[self.buf_len..self.buf_len + amount as usize].fill(0);
516 self.buf_len += amount as usize;
517 len -= amount;
518 self.supply_pages()?;
519 }
520 while len >= self.page_size {
522 let amount =
523 if len >= ZERO_VMO_SIZE { ZERO_VMO_SIZE } else { len - len % self.page_size };
524 self.pager.pager.supply_pages(
525 self.target_vmo,
526 self.offset..self.offset + amount,
527 &self.pager.zero_vmo,
528 0,
529 )?;
530 self.offset += amount;
531 len -= amount;
532 }
533 self.buffer[self.buf_len..self.buf_len + len as usize].fill(0);
535 self.buf_len += len as usize;
536 Ok(())
537 }
538
539 fn supply_pages(&mut self) -> Result<(), zx::Status> {
541 if self.buf_len as u64 >= self.page_size {
542 let len = self.buf_len - self.buf_len % self.page_size as usize;
543 self.pager.pager.supply_pages(
544 self.target_vmo,
545 self.offset..self.offset + len as u64,
546 self.transfer_vmo,
547 0,
548 )?;
549 self.buffer.copy_within(len..self.buf_len, 0);
551 self.buf_len -= len;
552 self.offset += len as u64;
553 }
554 Ok(())
555 }
556
557 fn fill_buf(
559 &mut self,
560 f: impl FnOnce(&mut [u8]) -> Result<usize, zx::Status>,
561 ) -> Result<usize, zx::Status> {
562 let amount = f(&mut self.buffer[self.buf_len..])?;
563 self.buf_len += amount;
564 self.supply_pages()?;
565 Ok(amount)
566 }
567
568 fn finish(&mut self, mut end: u64) -> Result<(), zx::Status> {
570 let byte_offset = self.offset + self.buf_len as u64;
571 end = std::cmp::max(end, byte_offset);
572 end = end + self.page_size - 1;
573 end -= end % self.page_size;
574 self.zero(end - byte_offset)
575 }
576
577 fn fail_to(&mut self, end: u64, error: zx::Status) {
579 if self.offset < end {
580 log_warn!(error:?; "Failing page-in, range: {:?}", self.offset..end);
581 match self.pager.pager.op_range(
583 zx::PagerOp::Fail(zx::Status::IO),
584 self.target_vmo,
585 self.offset..end,
586 ) {
587 Ok(()) => {}
588 Err(error) => log_error!(error:?; "Failed to report error"),
589 }
590 self.offset = end;
591 self.buf_len = 0;
592 }
593 }
594}
595
596#[cfg(test)]
597mod tests {
598 use super::{Filesystem, Pager, PagerExtent};
599
600 use std::sync::Arc;
601 use std::time::Duration;
602
603 #[::fuchsia::test]
604 async fn test_pager() {
605 let backing_vmo = zx::Vmo::create(1 * 1024 * 1024).expect("Vmo::craete failed");
606 let backing_vmo_clone =
607 backing_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("failed handle dup");
608
609 let pager = Arc::new(Pager::new().expect("Pager::new failed"));
610 let filesystem = Arc::new(
611 Filesystem::new(pager.clone(), backing_vmo_clone, 1024)
612 .expect("Filesystem::new failed"),
613 );
614
615 pager.add_filesystem(filesystem.clone());
616
617 {
618 pager.start_threads();
619
620 let vmo = filesystem.register("a".into(), 1, 5, Box::new([])).expect("register failed");
622
623 let mut buf = vec![1; 5];
624 vmo.read(&mut buf, 0).expect("read failed");
625
626 assert_eq!(&buf, &[0; 5]);
627
628 let vmo = filesystem
630 .register(
631 "b".into(),
632 2,
633 5,
634 Box::new([PagerExtent { logical: 0..1, physical_block: 0 }]),
635 )
636 .expect("register failed");
637 backing_vmo.write(b"hello", 0).expect("write failed");
638 vmo.read(&mut buf, 0).expect("read failed");
639
640 assert_eq!(&buf, b"hello");
641
642 let file_size = (6 + 1 + 5 + 4) * 1024 + 100;
645 let vmo = filesystem
646 .register(
647 "c".into(),
648 3,
649 file_size,
650 Box::new([
651 PagerExtent { logical: 6..7, physical_block: 0 },
652 PagerExtent { logical: 12..13, physical_block: 1 },
653 ]),
654 )
655 .expect("register failed");
656 backing_vmo.write(b"there", 1024).expect("write failed");
657 let mut buf = vec![1; file_size as usize];
658 vmo.read(&mut buf, 0).expect("read failed");
659
660 let mut expected = vec![0; file_size as usize];
661 expected[6 * 1024..6 * 1024 + 5].copy_from_slice(b"hello");
662 expected[12 * 1024..12 * 1024 + 5].copy_from_slice(b"there");
663 assert_eq!(&buf, &expected);
664
665 let vmo = filesystem
667 .register(
668 "d".into(),
669 4,
670 file_size,
671 Box::new([
672 PagerExtent { logical: 6..7, physical_block: 0 },
673 PagerExtent { logical: 12..13, physical_block: 1 },
674 ]),
675 )
676 .expect("register failed");
677
678 let offset = 9000;
679 let mut buf = vec![1; (file_size - offset) as usize];
680 vmo.read(&mut buf, offset).expect("read failed");
681
682 assert_eq!(&buf, &expected[offset as usize..]);
683 }
684
685 loop {
687 if filesystem.files_by_inode.lock().is_empty() {
688 break;
689 }
690 std::thread::sleep(Duration::from_millis(10));
692 }
693 }
694}