openhcl_boot/host_params/
mmio.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Manages MMIO range partitioning between VTLs.
5
6use super::PartitionInfo;
7use super::dt::DtError;
8use memory_range::MemoryRange;
9
10/// The start address of MMIO high range.
11const MMIO_HIGH_RANGE_START: u64 = 1 << 32;
12
13impl PartitionInfo {
14    /// Select the mmio range that VTL2 should use from looking at VTL0 mmio
15    /// ranges.
16    ///
17    /// VTL2 MMIO is partitioned such that:
18    /// - All MMIO low range is assigned to VTL0.
19    /// - VTL2_MMIO_HIGH_RANGE_SIZE bytes from the end of the high range is
20    ///   assigned to VTL2.
21    /// - The remaining high range is assigned to VTL0.
22    ///
23    /// Assumes input ranges are non-overlapping and in increasing address
24    /// order.
25    ///
26    /// On success, returns the mmio that VTL2 should use.
27    ///
28    /// Returns an error if the input VTL0 MMIO range is invalid or if the VTL2
29    /// allocation amount was not satisfied due to a lack of high MMIO assigned
30    /// to VTL0.
31    pub fn select_vtl2_mmio_range(&self, vtl2_size: u64) -> Result<MemoryRange, DtError> {
32        // Iterate over the list of MMIO ranges in reverse address order so that
33        // the VTL2 range is carved out from the end.
34        for range in self.vmbus_vtl0.mmio.iter().rev() {
35            // Do not select low MMIO ranges for VTL2.
36            if range.start() < MMIO_HIGH_RANGE_START {
37                continue;
38            }
39
40            // Compute the length of the VTL2 subrange. If there is not enough
41            // mmio, give up.
42            if range.len() < vtl2_size {
43                return Err(DtError::NotEnoughMmio);
44            }
45
46            let vtl2_range_start = range.end() - vtl2_size;
47
48            return Ok(MemoryRange::new(vtl2_range_start..range.end()));
49        }
50
51        Err(DtError::NotEnoughMmio)
52    }
53}
54
55#[cfg(test)]
56mod test {
57    use super::*;
58    use arrayvec::ArrayVec;
59    use memory_range::subtract_ranges;
60
61    /// The size (in bytes) of MMIO high range assigned to VTL2.
62    const VTL2_MMIO_HIGH_RANGE_SIZE: u64 = 128 * (1 << 20);
63
64    // Tests that MMIO range is partitioned correctly between VTL0 and VTL2
65    // for a variety of input ranges.
66    #[test]
67    fn mmio_range_partitioned_correctly() {
68        #[derive(Debug)]
69        struct TestCase {
70            // Input
71            mmio: ArrayVec<MemoryRange, 2>,
72            // Expected output
73            succeeds: bool,
74            vtl0_range: ArrayVec<MemoryRange, 2>,
75            vtl2_range: MemoryRange,
76        }
77
78        let testcases = vec![
79            TestCase {
80                // No MMIO range is provided, fails.
81                mmio: ArrayVec::new(),
82                succeeds: false,
83                vtl0_range: ArrayVec::new(),
84                vtl2_range: MemoryRange::EMPTY,
85            },
86            TestCase {
87                // Only low mmio, fails.
88                mmio: ArrayVec::from([
89                    MemoryRange::new(0x3000_0000..0x4000_0000),
90                    MemoryRange::new(0x4000_0000..0x5000_0000),
91                ]),
92                succeeds: false,
93                vtl0_range: ArrayVec::new(),
94                vtl2_range: MemoryRange::EMPTY,
95            },
96            TestCase {
97                // MMIO high range is less than what VTL2 requested.
98                mmio: ArrayVec::from([
99                    MemoryRange::new(0x3000_0000..0x4000_0000),
100                    MemoryRange::new(
101                        MMIO_HIGH_RANGE_START
102                            ..(MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE / 2),
103                    ),
104                ]),
105                succeeds: false,
106                vtl0_range: ArrayVec::new(),
107                vtl2_range: MemoryRange::EMPTY,
108            },
109            TestCase {
110                // MMIO high range is just enough for VTL2.
111                // Low range should be assigned to VTL0.
112                // High range should be assigned to VTL2.
113                mmio: ArrayVec::from([
114                    MemoryRange::new(0x3000_0000..0x4000_0000),
115                    MemoryRange::new(
116                        MMIO_HIGH_RANGE_START..(MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE),
117                    ),
118                ]),
119                succeeds: true,
120                vtl0_range: [MemoryRange::new(0x3000_0000..0x4000_0000)]
121                    .into_iter()
122                    .collect(),
123                vtl2_range: MemoryRange::new(
124                    MMIO_HIGH_RANGE_START..(MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE),
125                ),
126            },
127            TestCase {
128                // MMIO high range is more than what VTL2 requested.
129                // VTL2 should be assigned SIZE from the end of the high range.
130                // VTL0 should be assigned the remaining high range.
131                mmio: ArrayVec::from([
132                    MemoryRange::new(0x3000_0000..0x4000_0000),
133                    MemoryRange::new(
134                        MMIO_HIGH_RANGE_START
135                            ..(MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE * 4),
136                    ),
137                ]),
138                succeeds: true,
139                vtl0_range: ArrayVec::from([
140                    MemoryRange::new(0x3000_0000..0x4000_0000),
141                    MemoryRange::new(
142                        MMIO_HIGH_RANGE_START
143                            ..(MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE * 3),
144                    ),
145                ]),
146                vtl2_range: MemoryRange::new(
147                    MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE * 3
148                        ..(MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE * 4),
149                ),
150            },
151            TestCase {
152                // Multiple MMIO high ranges are provided.
153                // VTL2 should be assigned SIZE from the very end of the high range.
154                // VTL0 should be assigned all the remaining high ranges.
155                mmio: ArrayVec::from([
156                    MemoryRange::new(
157                        MMIO_HIGH_RANGE_START
158                            ..(MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE * 2),
159                    ),
160                    MemoryRange::new(
161                        (MMIO_HIGH_RANGE_START * 2)
162                            ..(MMIO_HIGH_RANGE_START * 2 + VTL2_MMIO_HIGH_RANGE_SIZE * 2),
163                    ),
164                ]),
165                succeeds: true,
166                vtl0_range: ArrayVec::from([
167                    MemoryRange::new(
168                        MMIO_HIGH_RANGE_START
169                            ..(MMIO_HIGH_RANGE_START + VTL2_MMIO_HIGH_RANGE_SIZE * 2),
170                    ),
171                    MemoryRange::new(
172                        (MMIO_HIGH_RANGE_START * 2)
173                            ..(MMIO_HIGH_RANGE_START * 2 + VTL2_MMIO_HIGH_RANGE_SIZE),
174                    ),
175                ]),
176                vtl2_range: MemoryRange::new(
177                    (MMIO_HIGH_RANGE_START * 2 + VTL2_MMIO_HIGH_RANGE_SIZE)
178                        ..(MMIO_HIGH_RANGE_START * 2 + VTL2_MMIO_HIGH_RANGE_SIZE * 2),
179                ),
180            },
181        ];
182
183        // Run all test cases.
184        for (i, tc) in testcases.iter().enumerate() {
185            let mut vtl2_info = PartitionInfo::new();
186            vtl2_info.vmbus_vtl0.mmio.clone_from(&tc.mmio);
187
188            let result = vtl2_info.select_vtl2_mmio_range(VTL2_MMIO_HIGH_RANGE_SIZE);
189
190            assert_eq!(
191                tc.succeeds,
192                result.is_ok(),
193                "test case #{i}: unexpected result"
194            );
195
196            if tc.succeeds {
197                let vtl2_mmio = result.unwrap();
198                let vtl0_mmio =
199                    subtract_ranges(tc.mmio.iter().cloned(), [vtl2_mmio]).collect::<Vec<_>>();
200
201                assert_eq!(
202                    tc.vtl0_range.as_slice(),
203                    vtl0_mmio.as_slice(),
204                    "test case #{i}: vtl0 was assigned an unexpected mmio range"
205                );
206                assert_eq!(
207                    tc.vtl2_range, vtl2_mmio,
208                    "test case #{i}: vtl1 was assigned an unexpected mmio range"
209                );
210            }
211        }
212    }
213}