Skip to main content

fake_bti/
lib.rs

1// Copyright 2025 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#![warn(missing_docs)]
6
7//! Fake BTI for testing without access to hardware resources
8
9use anyhow::{Error, anyhow};
10use std::collections::BTreeMap;
11use std::ops::{Deref, Range};
12use std::sync::Arc;
13use zerocopy::{FromBytes, Immutable, IntoBytes};
14use zx::sys::{zx_handle_t, zx_paddr_t};
15
16mod ffi;
17
18/// A collection of pinned VMOs returned by [`FakeBti::get_pinned_vmo`].
19pub struct FakeBtiPinnedVmos {
20    map: BTreeMap<usize, (Arc<zx::Vmo>, Range<u64>)>,
21}
22
23impl FakeBtiPinnedVmos {
24    /// Looks up a physical address in the pinned VMOs.
25    ///
26    /// Returns the VMO and the range of offsets within that VMO starting at the offset
27    /// corresponding to `physical_address` and ending at the end of the contiguous pinned block.
28    pub fn find_phys(&self, physical_address: usize) -> Option<(Arc<zx::Vmo>, Range<u64>)> {
29        let (&start_paddr, (vmo, offset_range)) =
30            self.map.range(..=physical_address).next_back()?;
31        let diff = (physical_address - start_paddr) as u64;
32        let len = offset_range.end - offset_range.start;
33        if diff < len {
34            Some((vmo.clone(), (offset_range.start + diff)..offset_range.end))
35        } else {
36            None
37        }
38    }
39
40    /// Reads bytes from physical memory.
41    ///
42    /// The read can span across multiple non-contiguous pinned ranges.
43    pub fn read_bytes(&self, mut phys_addr: usize, len: usize) -> Result<Vec<u8>, Error> {
44        let mut buf = vec![0u8; len];
45        let mut read_offset = 0;
46
47        while read_offset < len {
48            let (vmo, range) = self
49                .find_phys(phys_addr)
50                .ok_or_else(|| anyhow!("Physical address {phys_addr:#x} not pinned"))?;
51            let available = (range.end - range.start) as usize;
52            let to_read = std::cmp::min(available, len - read_offset);
53            vmo.read(&mut buf[read_offset..read_offset + to_read], range.start)?;
54            read_offset += to_read;
55            phys_addr += to_read;
56        }
57
58        Ok(buf)
59    }
60
61    /// Reads T from physical memory.
62    pub fn read<T: FromBytes>(&self, phys_addr: usize) -> Result<T, Error> {
63        Ok(T::read_from_bytes(&self.read_bytes(phys_addr, std::mem::size_of::<T>())?).unwrap())
64    }
65
66    /// Writes bytes to physical memory.
67    ///
68    /// The write can span across multiple non-contiguous pinned ranges.
69    pub fn write_bytes(&self, mut phys_addr: usize, mut data: &[u8]) -> Result<(), Error> {
70        while !data.is_empty() {
71            let (vmo, range) = self
72                .find_phys(phys_addr)
73                .ok_or_else(|| anyhow!("Physical address {phys_addr:#x} not pinned"))?;
74            let amount = std::cmp::min(data.len(), (range.end - range.start) as usize);
75            vmo.write(data.split_off(..amount).unwrap(), range.start)?;
76            phys_addr += amount;
77        }
78        Ok(())
79    }
80
81    /// Writes T to physical memory.
82    pub fn write<T: IntoBytes + Immutable>(&self, phys_addr: usize, value: T) -> Result<(), Error> {
83        self.write_bytes(phys_addr, value.as_bytes())
84    }
85}
86
87/// A fake BTI object which can be created without having access to hardware resources.
88#[derive(Debug)]
89pub struct FakeBti(zx::Bti);
90
91impl Deref for FakeBti {
92    type Target = zx::Bti;
93
94    fn deref(&self) -> &Self::Target {
95        &self.0
96    }
97}
98
99impl FakeBti {
100    /// All physical addresses returned by zx_bti_pin with a fake BTI will be set to this value.
101    #[inline(always)]
102    pub fn phys_addr() -> usize {
103        // SAFETY: This is just a constant with static storage.
104        unsafe { ffi::g_fake_bti_phys_addr }
105    }
106
107    /// Creates a new FakeBti.
108    pub fn create() -> Result<Self, zx::Status> {
109        let handle = {
110            let mut raw_handle = zx_handle_t::default();
111            // SAFETY: `raw_handle` is valid
112            unsafe {
113                zx::ok(ffi::fake_bti_create(&mut raw_handle))?;
114            }
115            // SAFETY: `fake_bti_create` returned a valid handle on success
116            unsafe { zx::NullableHandle::from_raw(raw_handle) }
117        };
118        Ok(Self(handle.into()))
119    }
120
121    /// Sets the paddrs used by the fake BTI.  Whenever [`zx::Bti::pin`] is called, these static
122    /// paddrs will be written out into the resulting array.  If more physical addresses are needed
123    /// than are available in paddrs, `pin` will return an error.
124    pub fn set_paddrs(&self, paddrs: &[zx_paddr_t]) {
125        // SAFETY: `paddrs` is valid
126        unsafe {
127            // OK to unwrap -- we know that `self.0` is a valid handle to a fake BTI
128            zx::ok(ffi::fake_bti_set_paddrs(self.0.raw_handle(), paddrs.as_ptr(), paddrs.len()))
129                .unwrap();
130        }
131    }
132
133    /// Retrieves the mappings for all pinned VMOs.
134    pub fn get_pinned_vmo(&self) -> FakeBtiPinnedVmos {
135        let mut actual = 64;
136        let vmo_infos = loop {
137            let mut vmo_infos = Vec::with_capacity(actual);
138            // SAFETY: `vmo_infos` has enough capacity
139            unsafe {
140                zx::ok(ffi::fake_bti_get_pinned_vmo(
141                    self.0.raw_handle(),
142                    vmo_infos.as_mut_ptr(),
143                    vmo_infos.capacity(),
144                    &mut actual as *mut usize,
145                ))
146                .unwrap();
147
148                if actual <= vmo_infos.capacity() {
149                    vmo_infos.set_len(actual);
150                    break vmo_infos;
151                }
152                vmo_infos.set_len(vmo_infos.capacity());
153                // Release all the vmos.
154                for info in vmo_infos {
155                    zx::NullableHandle::from_raw(info.vmo);
156                }
157                actual += 16; // Add some spare.
158            }
159        };
160
161        let page_size = zx::system_get_page_size() as usize;
162        let mut map = BTreeMap::new();
163        for info in vmo_infos {
164            let mut actual_paddrs = 64;
165            let paddrs = loop {
166                let mut paddrs = Vec::with_capacity(actual_paddrs);
167                // SAFETY: `paddrs` has enough capacity
168                unsafe {
169                    match zx::ok(ffi::fake_bti_get_vmo_phys_address(
170                        self.0.raw_handle(),
171                        &info,
172                        paddrs.as_mut_ptr(),
173                        paddrs.capacity(),
174                        &mut actual_paddrs,
175                    )) {
176                        Ok(()) => {}
177                        Err(zx::Status::NOT_FOUND) => {
178                            // This is possible due to races: the VMO could have been unpinned since
179                            // we asked for all the VMOs above.
180                            break vec![];
181                        }
182                        Err(status) => panic!("Unexpected error: {status:?}"),
183                    }
184                    if actual_paddrs <= paddrs.capacity() {
185                        paddrs.set_len(actual_paddrs);
186                        break paddrs;
187                    }
188                    actual_paddrs += 16; // Add some spare.
189                }
190            };
191
192            // SAFETY: The handle returned by the FFI is owned by us now.
193            let vmo: Arc<zx::Vmo> =
194                Arc::new(unsafe { zx::NullableHandle::from_raw(info.vmo).into() });
195
196            let mut paddrs = paddrs.into_iter();
197            if let Some(a) = paddrs.next() {
198                let mut offset = 0;
199                let mut range = a..a + page_size;
200                for a in paddrs {
201                    if a == range.end {
202                        range.end += page_size;
203                    } else {
204                        let len = (range.end - range.start) as u64;
205                        map.insert(range.start, (vmo.clone(), offset..offset + len));
206                        offset += len;
207                        range = a..a + page_size;
208                    }
209                }
210                let len = (range.end - range.start) as u64;
211                map.insert(range.start, (vmo, offset..offset + len));
212            }
213        }
214
215        FakeBtiPinnedVmos { map }
216    }
217}
218
219impl From<zx::Bti> for FakeBti {
220    fn from(bti: zx::Bti) -> Self {
221        Self(bti)
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[fuchsia::test]
230    fn create() {
231        let bti = FakeBti::create().expect("create failed");
232        let info = bti.info().expect("info failed");
233        assert!(info.minimum_contiguity > 0);
234    }
235
236    #[fuchsia::test]
237    fn pin_vmo() {
238        let bti = FakeBti::create().expect("create failed");
239        let vmo = zx::Vmo::create(16384).expect("create VMO failed");
240        let mut paddrs = [zx_paddr_t::default(); 4];
241        let _pmt = bti
242            .pin(zx::BtiOptions::PERM_READ, &vmo, 0, 16384, &mut paddrs[..])
243            .expect("pin failed");
244
245        let expected = FakeBti::phys_addr();
246        assert_eq!(paddrs, [expected, expected, expected, expected]);
247    }
248
249    #[fuchsia::test]
250    fn create_and_pin_contiguous_vmo() {
251        let bti = FakeBti::create().expect("create failed");
252        let vmo = zx::Vmo::create_contiguous(&bti, 16384, 0).expect("create contiguous VMO failed");
253        let mut paddr = [zx_paddr_t::default()];
254        let _pmt = bti
255            .pin(
256                zx::BtiOptions::PERM_READ | zx::BtiOptions::CONTIGUOUS,
257                &vmo,
258                0,
259                16384,
260                &mut paddr[..],
261            )
262            .expect("pin failed");
263
264        assert_eq!(paddr, [FakeBti::phys_addr()]);
265    }
266
267    #[fuchsia::test]
268    fn pin_with_paddrs() {
269        let bti = FakeBti::create().expect("create failed");
270        bti.set_paddrs(&[4096, 16384, 65536]);
271        let vmo = zx::Vmo::create(3 * 4096).expect("create VMO failed");
272        {
273            let mut paddrs = [zx_paddr_t::default(); 3];
274            let _pmt = bti
275                .pin(zx::BtiOptions::PERM_READ, &vmo, 0, 3 * 4096, &mut paddrs[..])
276                .expect("pin failed");
277            assert_eq!(paddrs, [4096, 16384, 65536]);
278        }
279        bti.set_paddrs(&[4096, 8192]);
280        {
281            let mut paddrs = [zx_paddr_t::default(); 2];
282            let _pmt = bti
283                .pin(zx::BtiOptions::PERM_READ, &vmo, 0, 2 * 4096, &mut paddrs[..])
284                .expect("pin failed");
285            assert_eq!(paddrs, [4096, 8192]);
286        }
287    }
288
289    #[fuchsia::test]
290    fn test_get_pinned_vmo() {
291        let bti = FakeBti::create().expect("create failed");
292        bti.set_paddrs(&[0x1000, 0x2000, 0x3000, 0x4000]);
293
294        let vmo1 = zx::Vmo::create(0x2000).expect("create VMO1 failed");
295        let mut paddrs1 = [zx_paddr_t::default(); 2];
296        let _pmt1 = bti
297            .pin(zx::BtiOptions::PERM_READ, &vmo1, 0, 0x2000, &mut paddrs1[..])
298            .expect("pin1 failed");
299
300        let vmo2 = zx::Vmo::create(0x2000).expect("create VMO2 failed");
301        let mut paddrs2 = [zx_paddr_t::default(); 2];
302        let _pmt2 = bti
303            .pin(zx::BtiOptions::PERM_READ, &vmo2, 0, 0x2000, &mut paddrs2[..])
304            .expect("pin2 failed");
305
306        let pinned = bti.get_pinned_vmo();
307
308        let koid1 = vmo1.koid().unwrap();
309        let koid2 = vmo2.koid().unwrap();
310
311        let (v1, r1) = pinned.find_phys(0x1000).expect("lookup 0x1000 failed");
312        assert_eq!(v1.koid().unwrap(), koid1);
313        assert_eq!(r1, 0..0x2000);
314
315        let (v1_2, r1_2) = pinned.find_phys(0x2000).expect("lookup 0x2000 failed");
316        assert_eq!(v1_2.koid().unwrap(), koid1);
317        assert_eq!(r1_2, 0x1000..0x2000);
318
319        let (v1_3, r1_3) = pinned.find_phys(0x2fff).expect("lookup 0x2fff failed");
320        assert_eq!(v1_3.koid().unwrap(), koid1);
321        assert_eq!(r1_3, 0x1fff..0x2000);
322
323        let (v2, r2) = pinned.find_phys(0x3000).expect("lookup 0x3000 failed");
324        assert_eq!(v2.koid().unwrap(), koid2);
325        assert_eq!(r2, 0..0x2000);
326
327        let (v2_2, r2_2) = pinned.find_phys(0x4000).expect("lookup 0x4000 failed");
328        assert_eq!(v2_2.koid().unwrap(), koid2);
329        assert_eq!(r2_2, 0x1000..0x2000);
330
331        assert!(pinned.find_phys(0x0fff).is_none());
332        assert!(pinned.find_phys(0x5000).is_none());
333    }
334
335    #[fuchsia::test]
336    fn test_read() {
337        let bti = FakeBti::create().expect("create failed");
338
339        {
340            bti.set_paddrs(&[0x1000, 0x2000]); // Contiguous in physical space
341            let vmo = zx::Vmo::create(0x2000).expect("create VMO failed");
342            let data = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
343            vmo.write(&data, 0xffc).expect("write failed");
344
345            let mut paddrs = [0; 2];
346            let _pmt = bti
347                .pin(zx::BtiOptions::PERM_READ, &vmo, 0, 0x2000, &mut paddrs)
348                .expect("pin failed");
349
350            let pinned = bti.get_pinned_vmo();
351
352            let val: u64 = pinned.read(0x1ffc).expect("read failed");
353            assert_eq!(val, 0x8877665544332211);
354        }
355
356        // Test reading across non-contiguous physical ranges
357        {
358            let vmo = zx::Vmo::create(0x2000).expect("create VMO failed");
359            let data = [0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
360            vmo.write(&data, 0xffc).expect("write failed");
361            vmo.write(&data, 0x1000).expect("write failed");
362
363            // We must use distinct paddrs to avoid the fake-bti GetVmoPhysAddress bug.
364            bti.set_paddrs(&[0x14000, 0x16000]);
365            let mut paddrs2 = [0; 2];
366            let _pmt2 = bti
367                .pin(zx::BtiOptions::PERM_READ, &vmo, 0, 0x2000, &mut paddrs2)
368                .expect("pin failed");
369            assert_eq!(paddrs2, [0x14000, 0x16000]);
370
371            let pinned2 = bti.get_pinned_vmo();
372            // This should fail because of the gap at 0x15000
373            assert!(pinned2.read::<u64>(0x14ffc).is_err());
374
375            // But reading at the boundaries should work
376            let val1: u32 = pinned2.read(0x14ffc).expect("read boundary 1 failed");
377            assert_eq!(val1, 0x44332211);
378            let val2: u32 = pinned2.read(0x16000).expect("read boundary 2 failed");
379            assert_eq!(val2, 0x44332211);
380        }
381    }
382}