twizzler_driver/dma/
pool.rs

1use std::sync::{Arc, Mutex};
2
3use twizzler::{
4    error::{GenericError, TwzError},
5    marker::{BaseType, Invariant},
6    object::ObjectBuilder,
7};
8use twizzler_abi::object::{MAX_SIZE, NULLPAGE_SIZE};
9
10use super::{Access, DeviceSync, DmaObject, DmaOptions, DmaRegion, DmaSliceRegion, DMA_PAGE_SIZE};
11
12#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
13pub(super) struct SplitPageRange {
14    start: usize,
15    len: usize,
16}
17
18pub(super) enum Split {
19    Single(SplitPageRange),
20    Multiple(SplitPageRange, SplitPageRange),
21}
22
23impl SplitPageRange {
24    fn new(start: usize, len: usize) -> Self {
25        Self { start, len }
26    }
27
28    fn split(self, newlen: usize) -> Split {
29        let start = self.start;
30        let len = self.len;
31        if newlen == 0 || newlen == len {
32            return Split::Single(Self { start, len });
33        }
34        Split::Multiple(
35            Self { start, len: newlen },
36            Self {
37                start: start + newlen,
38                len: len - newlen,
39            },
40        )
41    }
42
43    fn merge(self, other: Self) -> Self {
44        let (first, second) = if self.start < other.start {
45            (self, other)
46        } else {
47            (other, self)
48        };
49        assert!(first.adjacent_before(&second));
50
51        Self {
52            start: first.start,
53            len: first.len + second.len,
54        }
55    }
56
57    fn adjacent_before(&self, other: &Self) -> bool {
58        self.start < other.start && self.start + self.len == other.start
59    }
60
61    fn len(&self) -> usize {
62        self.len
63    }
64
65    #[cfg(test)]
66    fn start(&self) -> usize {
67        self.start
68    }
69
70    fn offset(&self) -> usize {
71        self.start * DMA_PAGE_SIZE
72    }
73}
74
75#[cfg(test)]
76pub mod tests_split_page_range {
77    use super::SplitPageRange;
78    use crate::dma::pool::compact_range_list;
79
80    #[test]
81    fn spr_split_multiple() {
82        let r = SplitPageRange::new(2, 7);
83        let split = r.split(4);
84        if let super::Split::Multiple(a, b) = split {
85            assert_eq!(a.len(), 4);
86            assert_eq!(a.start(), 2);
87            assert_eq!(b.len(), 3);
88            assert_eq!(b.start(), 6);
89        } else {
90            panic!("split broken");
91        }
92    }
93
94    #[test]
95    fn spr_split_single1() {
96        let r = SplitPageRange::new(2, 7);
97        let split = r.split(7);
98        if let super::Split::Single(r) = split {
99            assert_eq!(r.len(), 7);
100            assert_eq!(r.start(), 2);
101        } else {
102            panic!("split broken");
103        }
104    }
105
106    #[test]
107    fn spr_split_single2() {
108        let r = SplitPageRange::new(2, 7);
109        let split = r.split(0);
110        if let super::Split::Single(r) = split {
111            assert_eq!(r.len(), 7);
112            assert_eq!(r.start(), 2);
113        } else {
114            panic!("split broken");
115        }
116    }
117
118    #[test]
119    fn spr_merge() {
120        let a = SplitPageRange::new(2, 4);
121        let b = SplitPageRange::new(6, 3);
122        let r = a.merge(b);
123        assert_eq!(r.start(), 2);
124        assert_eq!(r.len(), 7);
125    }
126
127    #[test]
128    fn spr_adj() {
129        let a = SplitPageRange::new(2, 4);
130        let b = SplitPageRange::new(1, 1);
131        let c = SplitPageRange::new(6, 4);
132
133        assert!(!a.adjacent_before(&b));
134        assert!(b.adjacent_before(&a));
135        assert!(!a.adjacent_before(&a));
136        assert!(a.adjacent_before(&c));
137    }
138
139    #[test]
140    fn spr_merge_alg() {
141        let a = SplitPageRange::new(2, 4);
142        let b = SplitPageRange::new(0, 1);
143        let c = SplitPageRange::new(6, 4);
144        let x = SplitPageRange::new(2, 8);
145        let mut list = vec![a.clone(), b.clone(), c.clone()];
146        let single_list = vec![a.clone()];
147        let slw: Vec<_> = single_list.windows(2).collect();
148        assert!(slw.is_empty());
149
150        compact_range_list(&mut list);
151
152        assert_eq!(list, vec![b, x]);
153    }
154}
155
156pub(super) struct AllocatableDmaObject {
157    dma: DmaObject,
158    freelist: Mutex<Vec<SplitPageRange>>,
159}
160
161/// A pool for allocating DMA regions that all share a common access type and DMA options.
162pub struct DmaPool {
163    opts: DmaOptions,
164    spec: ObjectBuilder<()>,
165    access: Access,
166    objects: Mutex<Vec<Arc<AllocatableDmaObject>>>,
167}
168
169#[repr(C)]
170struct EmptyBase;
171
172unsafe impl Invariant for EmptyBase {}
173impl BaseType for EmptyBase {}
174
175// Merge adjacent regions by sorting, comparing pairs, and merging if they are adjacent.
176// Keep going until we cannot merge anymore.
177fn compact_range_list(list: &mut Vec<SplitPageRange>) {
178    list.sort();
179    loop {
180        let pairs: Vec<_> = list
181            .windows(2)
182            .enumerate()
183            .filter_map(|(idx, ranges)| {
184                if ranges[0].adjacent_before(&ranges[1]) {
185                    Some(idx)
186                } else {
187                    None
188                }
189            })
190            .collect();
191
192        if pairs.is_empty() {
193            break;
194        }
195
196        // Iterate in reverse to compact from top, so as to not mess up indices.
197        for pair in pairs.iter().rev() {
198            // Grab the second item first to not mess up indices.
199            let second = list.remove(pair + 1);
200            let new = list[*pair].clone().merge(second);
201            list[*pair] = new;
202        }
203    }
204}
205
206impl AllocatableDmaObject {
207    pub(super) fn dma_object(&self) -> &DmaObject {
208        &self.dma
209    }
210
211    pub(super) fn free(&self, range: SplitPageRange) {
212        let mut freelist = self.freelist.lock().unwrap();
213        freelist.push(range);
214
215        compact_range_list(&mut freelist);
216        // TODO: consider that, if the entire object get free'd, we could delete the object.
217    }
218
219    fn allocate(&self, len: usize) -> Option<SplitPageRange> {
220        let mut freelist = self.freelist.lock().unwrap();
221        let nr_pages = (len - 1) / DMA_PAGE_SIZE + 1;
222        let index = freelist.iter().position(|range| range.len() >= nr_pages)?;
223
224        let range = freelist.remove(index);
225        Some(match range.split(nr_pages) {
226            Split::Single(r) => r,
227            Split::Multiple(alloc, extra) => {
228                freelist.push(extra);
229                alloc
230            }
231        })
232    }
233
234    fn new(spec: ObjectBuilder<()>) -> Result<AllocatableDmaObject, TwzError> {
235        Ok(AllocatableDmaObject {
236            // TODO: automatic object deletion.
237            dma: DmaObject::new::<EmptyBase>(
238                spec.cast()
239                    .build(EmptyBase)
240                    .map_err(|_| GenericError::Other)?,
241            ),
242            freelist: Mutex::new(vec![SplitPageRange::new(
243                1,
244                (MAX_SIZE - NULLPAGE_SIZE * 2) / DMA_PAGE_SIZE,
245            )]),
246        })
247    }
248}
249
250impl DmaPool {
251    /// Create a new DmaPool with access and DMA options, where each created underlying Twizzler
252    /// object is created using the provided [ObjectBuilder]. If default (volatile) options are
253    /// acceptable for the create spec, use the [crate::dma::DmaPool::default_spec] function.
254    pub fn new(spec: ObjectBuilder<()>, access: Access, opts: DmaOptions) -> Self {
255        Self {
256            opts,
257            spec,
258            access,
259            objects: Mutex::new(vec![]),
260        }
261    }
262
263    pub fn default_spec() -> ObjectBuilder<()> {
264        ObjectBuilder::default()
265    }
266
267    fn new_object(&self) -> Result<Arc<AllocatableDmaObject>, TwzError> {
268        let obj = Arc::new(AllocatableDmaObject::new(self.spec.clone())?);
269        Ok(obj)
270    }
271
272    fn do_allocate(
273        &self,
274        len: usize,
275    ) -> Result<(Arc<AllocatableDmaObject>, SplitPageRange), TwzError> {
276        if len > MAX_SIZE - NULLPAGE_SIZE * 2 {
277            return Err(TwzError::INVALID_ARGUMENT);
278        }
279        let mut objects = self.objects.lock().unwrap();
280        for obj in &*objects {
281            if let Some(pagerange) = obj.allocate(len) {
282                return Ok((obj.clone(), pagerange));
283            }
284        }
285        let obj = self.new_object()?;
286        objects.push(obj);
287        drop(objects);
288        self.do_allocate(len)
289    }
290
291    /// Allocate a new `[DmaRegion]` from the pool. The region will be initialized with the
292    /// provided initial value.
293    pub fn allocate<'a, T: DeviceSync>(&'a self, init: T) -> Result<DmaRegion<T>, TwzError> {
294        let len = core::mem::size_of::<T>();
295        let (ado, range) = self.do_allocate(len)?;
296        let mut reg = DmaRegion::new(
297            len,
298            self.access,
299            self.opts,
300            range.offset(),
301            Some((ado.clone(), range)),
302        );
303        reg.fill(init);
304        Ok(reg)
305    }
306
307    /// Allocate a new `[DmaSliceRegion]` from the pool. Each entry in the region's slice will
308    /// be initialized with the provided initial value.
309    pub fn allocate_array<'a, T: DeviceSync + Clone>(
310        &'a self,
311        count: usize,
312        init: T,
313    ) -> Result<DmaSliceRegion<T>, TwzError> {
314        let len = core::mem::size_of::<T>() * count;
315        let (ado, range) = self.do_allocate(len)?;
316        let mut reg = DmaSliceRegion::new(
317            len,
318            self.access,
319            self.opts,
320            range.offset(),
321            count,
322            Some((ado.clone(), range)),
323        );
324        reg.fill(init);
325        Ok(reg)
326    }
327
328    /// Allocate a new `[DmaSliceRegion]` from the pool. Each entry in the region's slice will
329    /// be initialized by running the provided closure.
330    pub fn allocate_array_with<'a, T: DeviceSync>(
331        &'a self,
332        count: usize,
333        init: impl Fn() -> T,
334    ) -> Result<DmaSliceRegion<T>, TwzError> {
335        let len = core::mem::size_of::<T>() * count;
336        let (ado, range) = self.do_allocate(len)?;
337        let mut reg = DmaSliceRegion::new(
338            len,
339            self.access,
340            self.opts,
341            range.offset(),
342            count,
343            Some((ado.clone(), range)),
344        );
345        reg.fill_with(init);
346        Ok(reg)
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::DmaPool;
353    use crate::dma::{Access, DmaOptions};
354
355    #[test]
356    fn allocate() {
357        let pool = DmaPool::new(
358            DmaPool::default_spec(),
359            Access::BiDirectional,
360            DmaOptions::empty(),
361        );
362
363        let _res = pool.allocate(u32::MAX).unwrap();
364    }
365}