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::dt::DtError;
7use memory_range::MemoryRange;
8
9/// The start address of MMIO high range.
10const MMIO_HIGH_RANGE_START: u64 = 1 << 32;
11
12/// Select the mmio range that VTL2 should use from looking at VTL0 mmio
13/// ranges.
14///
15/// VTL2 MMIO is partitioned such that:
16/// - All MMIO low range is assigned to VTL0.
17/// - VTL2_MMIO_HIGH_RANGE_SIZE bytes from the end of the high range is
18/// assigned to VTL2.
19/// - The remaining high range is assigned to VTL0.
20///
21/// Assumes input ranges are non-overlapping and in increasing address
22/// order.
23///
24/// On success, returns the mmio that VTL2 should use.
25///
26/// Returns an error if the input VTL0 MMIO range is invalid or if the VTL2
27/// allocation amount was not satisfied due to a lack of high MMIO assigned
28/// to VTL0.
29pub fn select_vtl2_mmio_range(
30 mmio: &[MemoryRange],
31 vtl2_size: u64,
32) -> Result<MemoryRange, DtError> {
33 // Iterate over the list of MMIO ranges in reverse address order so that
34 // the VTL2 range is carved out from the end.
35 for range in mmio.iter().rev() {
36 // Do not select low MMIO ranges for VTL2.
37 if range.start() < MMIO_HIGH_RANGE_START {
38 continue;
39 }
40
41 // Compute the length of the VTL2 subrange. If there is not enough
42 // mmio, give up.
43 if range.len() < vtl2_size {
44 return Err(DtError::NotEnoughMmio);
45 }
46
47 let vtl2_range_start = range.end() - vtl2_size;
48
49 return Ok(MemoryRange::new(vtl2_range_start..range.end()));
50 }
51
52 Err(DtError::NotEnoughMmio)
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 result = select_vtl2_mmio_range(&tc.mmio, VTL2_MMIO_HIGH_RANGE_SIZE);
186
187 assert_eq!(
188 tc.succeeds,
189 result.is_ok(),
190 "test case #{i}: unexpected result"
191 );
192
193 if tc.succeeds {
194 let vtl2_mmio = result.unwrap();
195 let vtl0_mmio =
196 subtract_ranges(tc.mmio.iter().cloned(), [vtl2_mmio]).collect::<Vec<_>>();
197
198 assert_eq!(
199 tc.vtl0_range.as_slice(),
200 vtl0_mmio.as_slice(),
201 "test case #{i}: vtl0 was assigned an unexpected mmio range"
202 );
203 assert_eq!(
204 tc.vtl2_range, vtl2_mmio,
205 "test case #{i}: vtl1 was assigned an unexpected mmio range"
206 );
207 }
208 }
209 }
210}