zx/
pager.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Type-safe bindings for Zircon pager objects.
6
7use crate::{
8    AsHandleRef, HandleBased, HandleRef, NullableHandle, Port, Status, Vmo, VmoOptions, ok, sys,
9};
10use bitflags::bitflags;
11
12/// An object representing a Zircon
13/// [pager](https://fuchsia.dev/fuchsia-src/concepts/objects/pager.md).
14///
15/// As essentially a subtype of `NullableHandle`, it can be freely interconverted.
16#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
17#[repr(transparent)]
18pub struct Pager(NullableHandle);
19impl_handle_based!(Pager);
20
21bitflags! {
22    /// Options that may be used when creating a pager.
23    #[repr(transparent)]
24    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
25    pub struct PagerOptions: u32 {
26    }
27}
28
29bitflags! {
30    #[repr(transparent)]
31    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
32    pub struct PagerWritebackBeginOptions: u64 {
33        const DIRTY_RANGE_IS_ZERO = sys::ZX_VMO_DIRTY_RANGE_IS_ZERO;
34    }
35}
36
37pub enum PagerOp {
38    Fail(Status),
39    Dirty,
40    WritebackBegin(PagerWritebackBeginOptions),
41    WritebackEnd,
42}
43
44impl Pager {
45    /// See [zx_pager_create](https://fuchsia.dev/fuchsia-src/reference/syscalls/pager_create)
46    pub fn create(options: PagerOptions) -> Result<Pager, Status> {
47        let mut out = 0;
48        let status = unsafe { sys::zx_pager_create(options.bits(), &mut out) };
49        ok(status)?;
50        Ok(Pager::from(unsafe { NullableHandle::from_raw(out) }))
51    }
52
53    /// See [zx_pager_create_vmo](https://fuchsia.dev/fuchsia-src/reference/syscalls/pager_create_vmo)
54    pub fn create_vmo(
55        &self,
56        options: VmoOptions,
57        port: &Port,
58        key: u64,
59        size: u64,
60    ) -> Result<Vmo, Status> {
61        let mut out = 0;
62        let status = unsafe {
63            sys::zx_pager_create_vmo(
64                self.raw_handle(),
65                options.bits(),
66                port.raw_handle(),
67                key,
68                size,
69                &mut out,
70            )
71        };
72        ok(status)?;
73        Ok(Vmo::from(unsafe { NullableHandle::from_raw(out) }))
74    }
75
76    /// See [zx_pager_detach_vmo](https://fuchsia.dev/fuchsia-src/reference/syscalls/pager_detach_vmo)
77    pub fn detach_vmo(&self, vmo: &Vmo) -> Result<(), Status> {
78        let status = unsafe { sys::zx_pager_detach_vmo(self.raw_handle(), vmo.raw_handle()) };
79        ok(status)
80    }
81
82    /// See [zx_pager_supply_pages](https://fuchsia.dev/fuchsia-src/reference/syscalls/pager_supply_pages)
83    pub fn supply_pages(
84        &self,
85        vmo: &Vmo,
86        range: std::ops::Range<u64>,
87        aux_vmo: &Vmo,
88        aux_offset: u64,
89    ) -> Result<(), Status> {
90        let status = unsafe {
91            sys::zx_pager_supply_pages(
92                self.raw_handle(),
93                vmo.raw_handle(),
94                range.start,
95                range.end - range.start,
96                aux_vmo.raw_handle(),
97                aux_offset,
98            )
99        };
100        ok(status)
101    }
102
103    /// See [zx_pager_op_range](https://fuchsia.dev/fuchsia-src/reference/syscalls/pager_op_range)
104    pub fn op_range(
105        &self,
106        op: PagerOp,
107        pager_vmo: &Vmo,
108        range: std::ops::Range<u64>,
109    ) -> Result<(), Status> {
110        let (op, data) = match op {
111            PagerOp::Fail(status) => (sys::ZX_PAGER_OP_FAIL, status.into_raw() as u64),
112            PagerOp::Dirty => (sys::ZX_PAGER_OP_DIRTY, 0),
113            PagerOp::WritebackBegin(options) => (sys::ZX_PAGER_OP_WRITEBACK_BEGIN, options.bits()),
114            PagerOp::WritebackEnd => (sys::ZX_PAGER_OP_WRITEBACK_END, 0),
115        };
116        let status = unsafe {
117            sys::zx_pager_op_range(
118                self.raw_handle(),
119                op,
120                pager_vmo.raw_handle(),
121                range.start,
122                range.end - range.start,
123                data,
124            )
125        };
126        ok(status)
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use crate as zx;
133    use std::sync::Arc;
134
135    const KEY: u64 = 5;
136
137    #[test]
138    fn create_vmo() {
139        let port = zx::Port::create();
140        let pager = zx::Pager::create(zx::PagerOptions::empty()).unwrap();
141        let vmo = pager.create_vmo(zx::VmoOptions::RESIZABLE, &port, KEY, /*size=*/ 100).unwrap();
142        let vmo_info = vmo.info().unwrap();
143        assert!(vmo_info.flags.contains(zx::VmoInfoFlags::PAGER_BACKED));
144        assert!(vmo_info.flags.contains(zx::VmoInfoFlags::RESIZABLE));
145    }
146
147    #[test]
148    fn detach_vmo() {
149        let port = zx::Port::create();
150        let pager = zx::Pager::create(zx::PagerOptions::empty()).unwrap();
151        let vmo = pager.create_vmo(zx::VmoOptions::empty(), &port, KEY, /*size=*/ 100).unwrap();
152        pager.detach_vmo(&vmo).unwrap();
153
154        // If the vmo was not detached then the test will time out waiting for the page to be
155        // supplied.
156        let mut data = [0u8; 100];
157        let e = vmo.read(&mut data, 0).expect_err("A detached vmo should fail reads");
158        assert_eq!(e, zx::Status::BAD_STATE);
159    }
160
161    #[test]
162    fn supply_pages() {
163        let page_size: u64 = zx::system_get_page_size().into();
164        let port = zx::Port::create();
165        let pager = zx::Pager::create(zx::PagerOptions::empty()).unwrap();
166        let vmo =
167            Arc::new(pager.create_vmo(zx::VmoOptions::empty(), &port, KEY, page_size).unwrap());
168
169        let read_thread = std::thread::spawn({
170            let vmo = vmo.clone();
171            move || {
172                let mut data = [0u8; 100];
173                vmo.read(&mut data, 0).unwrap();
174            }
175        });
176
177        let packet = port.wait(zx::MonotonicInstant::INFINITE).unwrap();
178        assert_eq!(packet.key(), KEY);
179        match packet.contents() {
180            zx::PacketContents::Pager(request) => {
181                assert_eq!(
182                    request.command(),
183                    zx::sys::zx_page_request_command_t::ZX_PAGER_VMO_READ
184                );
185                assert_eq!(request.range(), 0..page_size);
186                let aux_vmo = zx::Vmo::create(page_size).unwrap();
187                pager.supply_pages(vmo.as_ref(), request.range(), &aux_vmo, 0).unwrap();
188            }
189            packet => panic!("Unexpected packet: {:?}", packet),
190        }
191        read_thread.join().unwrap();
192    }
193
194    #[test]
195    fn fail_page_request() {
196        let page_size: u64 = zx::system_get_page_size().into();
197        let port = zx::Port::create();
198        let pager = zx::Pager::create(zx::PagerOptions::empty()).unwrap();
199        let vmo =
200            Arc::new(pager.create_vmo(zx::VmoOptions::empty(), &port, KEY, page_size).unwrap());
201
202        let read_thread = std::thread::spawn({
203            let vmo = vmo.clone();
204            move || {
205                let mut data = [0u8; 100];
206                let e = vmo.read(&mut data, 0).expect_err("Request should have failed");
207                assert_eq!(e, zx::Status::NO_SPACE);
208            }
209        });
210
211        let packet = port.wait(zx::MonotonicInstant::INFINITE).unwrap();
212        assert_eq!(packet.key(), KEY);
213        match packet.contents() {
214            zx::PacketContents::Pager(request) => {
215                assert_eq!(
216                    request.command(),
217                    zx::sys::zx_page_request_command_t::ZX_PAGER_VMO_READ
218                );
219                assert_eq!(request.range(), 0..page_size);
220                pager
221                    .op_range(
222                        zx::PagerOp::Fail(zx::Status::NO_SPACE),
223                        vmo.as_ref(),
224                        request.range(),
225                    )
226                    .unwrap();
227            }
228            packet => panic!("Unexpected packet: {:?}", packet),
229        }
230        read_thread.join().unwrap();
231    }
232
233    #[test]
234    fn pager_writeback() {
235        let page_size: u64 = zx::system_get_page_size().into();
236        let port = zx::Port::create();
237        let pager = zx::Pager::create(zx::PagerOptions::empty()).unwrap();
238        let vmo =
239            Arc::new(pager.create_vmo(zx::VmoOptions::TRAP_DIRTY, &port, KEY, page_size).unwrap());
240
241        let write_thread = std::thread::spawn({
242            let vmo = vmo.clone();
243            move || {
244                let data = [0u8; 100];
245                vmo.write(&data, 0).unwrap();
246            }
247        });
248
249        let packet = port.wait(zx::MonotonicInstant::INFINITE).unwrap();
250        assert_eq!(packet.key(), KEY);
251        match packet.contents() {
252            zx::PacketContents::Pager(request) => {
253                assert_eq!(
254                    request.command(),
255                    zx::sys::zx_page_request_command_t::ZX_PAGER_VMO_READ
256                );
257                assert_eq!(request.range(), 0..page_size);
258                let aux_vmo = zx::Vmo::create(page_size).unwrap();
259                pager.supply_pages(vmo.as_ref(), request.range(), &aux_vmo, 0).unwrap();
260            }
261            packet => panic!("Unexpected packet: {:?}", packet),
262        }
263
264        let packet = port.wait(zx::MonotonicInstant::INFINITE).unwrap();
265        assert_eq!(packet.key(), KEY);
266        match packet.contents() {
267            zx::PacketContents::Pager(request) => {
268                assert_eq!(
269                    request.command(),
270                    zx::sys::zx_page_request_command_t::ZX_PAGER_VMO_DIRTY
271                );
272                assert_eq!(request.range(), 0..page_size);
273                pager.op_range(zx::PagerOp::Dirty, vmo.as_ref(), request.range()).unwrap();
274            }
275            packet => panic!("Unexpected packet: {:?}", packet),
276        }
277
278        write_thread.join().unwrap();
279
280        // TODO(https://fxbug.dev/42142550) Verify that the first page is dirty with `query_dirty_ranges`.
281        pager
282            .op_range(
283                zx::PagerOp::WritebackBegin(zx::PagerWritebackBeginOptions::empty()),
284                vmo.as_ref(),
285                0..page_size,
286            )
287            .unwrap();
288        pager.op_range(zx::PagerOp::WritebackEnd, vmo.as_ref(), 0..page_size).unwrap();
289        // TODO(https://fxbug.dev/42142550) Verify that the first page is now clean with `query_dirty_ranges`.
290    }
291
292    #[test]
293    fn pager_writeback_begin_with_dirtied_zero_range() {
294        let page_size: u64 = zx::system_get_page_size().into();
295        let port = zx::Port::create();
296        let pager = zx::Pager::create(zx::PagerOptions::empty()).unwrap();
297        let vmo = pager.create_vmo(zx::VmoOptions::RESIZABLE, &port, KEY, page_size).unwrap();
298        let aux_vmo = zx::Vmo::create(page_size).unwrap();
299        pager.supply_pages(&vmo, 0..page_size, &aux_vmo, 0).unwrap();
300        vmo.set_size(page_size * 3).unwrap();
301
302        // TODO(https://fxbug.dev/42142550) Verify that pages 2, and 3 are dirty and zero with
303        // `query_dirty_ranges`.
304
305        vmo.write(&[0, 1, 2, 3], page_size * 2).unwrap();
306        pager
307            .op_range(
308                zx::PagerOp::WritebackBegin(zx::PagerWritebackBeginOptions::DIRTY_RANGE_IS_ZERO),
309                &vmo,
310                page_size..(page_size * 2),
311            )
312            .unwrap();
313        pager.op_range(zx::PagerOp::WritebackEnd, &vmo, page_size..(page_size * 2)).unwrap();
314
315        // TODO(https://fxbug.dev/42142550) Verify that page 2 is still dirty and not zero with
316        // `query_dirty_ranges`.
317    }
318}