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