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}