1use std::borrow::Borrow;
7
8pub struct DataRegion {
12 pub addr: u64,
13 pub len: u64,
14}
15
16pub fn data_regions(
22 payloads: &[crate::queue::VirtioQueuePayload],
23 writable: bool,
24 skip_bytes: u64,
25 data_len: u64,
26) -> DataRegions<'_> {
27 DataRegions {
28 payloads: payloads.iter(),
29 writable,
30 skip: skip_bytes,
31 remaining: data_len,
32 }
33}
34
35pub struct DataRegions<'a> {
39 payloads: core::slice::Iter<'a, crate::queue::VirtioQueuePayload>,
40 writable: bool,
41 skip: u64,
42 remaining: u64,
43}
44
45impl Iterator for DataRegions<'_> {
46 type Item = DataRegion;
47
48 fn next(&mut self) -> Option<DataRegion> {
49 while self.remaining > 0 {
50 let payload = self.payloads.next()?;
51 if payload.writeable != self.writable {
52 continue;
53 }
54 let mut addr = payload.address;
55 let mut plen = payload.length as u64;
56 if self.skip > 0 {
57 let s = self.skip.min(plen);
58 addr += s;
59 plen -= s;
60 self.skip -= s;
61 }
62 if plen == 0 {
63 continue;
64 }
65 let chunk = plen.min(self.remaining);
66 self.remaining -= chunk;
67 return Some(DataRegion { addr, len: chunk });
68 }
69 None
70 }
71}
72
73pub fn try_build_gpn_list(
80 regions: impl IntoIterator<Item = impl Borrow<DataRegion>>,
81) -> Option<(Vec<u64>, usize, usize)> {
82 const PAGE_SIZE: u64 = guestmem::PAGE_SIZE as u64;
83
84 let mut gpns = Vec::new();
85 let mut total_len: u64 = 0;
86 let mut first_offset: Option<usize> = None;
87 let mut prev_end: Option<u64> = None;
88
89 for region in regions {
90 let region = region.borrow();
91 let addr = region.addr;
92 let len = region.len;
93 if len == 0 {
94 continue;
95 }
96
97 let end = addr.checked_add(len)?;
98 let first_gpn = addr / PAGE_SIZE;
99 let last_gpn = (end - 1) / PAGE_SIZE;
100
101 if let Some(pe) = prev_end {
102 if addr == pe {
103 let last_gpn_in_list = *gpns.last().unwrap();
106 if first_gpn == last_gpn_in_list {
107 for gpn in (first_gpn + 1)..=last_gpn {
109 gpns.push(gpn);
110 }
111 } else {
112 for gpn in first_gpn..=last_gpn {
115 gpns.push(gpn);
116 }
117 }
118 } else {
119 if pe % PAGE_SIZE != 0 || addr % PAGE_SIZE != 0 {
123 return None;
124 }
125 for gpn in first_gpn..=last_gpn {
126 gpns.push(gpn);
127 }
128 }
129 } else {
130 first_offset = Some((addr % PAGE_SIZE) as usize);
132 for gpn in first_gpn..=last_gpn {
133 gpns.push(gpn);
134 }
135 }
136
137 prev_end = Some(end);
138 total_len += len;
139 }
140
141 let offset = first_offset.unwrap_or(0);
142 Some((gpns, offset, total_len as usize))
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::queue::VirtioQueuePayload;
149 use guestmem::ranges::PagedRange;
150
151 fn payload(writeable: bool, address: u64, length: u32) -> VirtioQueuePayload {
152 VirtioQueuePayload {
153 writeable,
154 address,
155 length,
156 }
157 }
158
159 #[test]
162 fn data_regions_read_single_descriptor() {
163 let payloads = vec![payload(true, 0x1000, 4097)];
165 let regions: Vec<_> = data_regions(&payloads, true, 0, 4096).collect();
166 assert_eq!(regions.len(), 1);
167 assert_eq!(regions[0].addr, 0x1000);
168 assert_eq!(regions[0].len, 4096);
169 }
170
171 #[test]
172 fn data_regions_write_skips_header() {
173 let payloads = vec![
175 payload(false, 0x1000, 16), payload(false, 0x2000, 512), ];
178 let regions: Vec<_> = data_regions(&payloads, false, 16, 512).collect();
179 assert_eq!(regions.len(), 1);
180 assert_eq!(regions[0].addr, 0x2000);
181 assert_eq!(regions[0].len, 512);
182 }
183
184 #[test]
185 fn data_regions_write_header_spans_descriptors() {
186 let payloads = vec![
188 payload(false, 0x1000, 8), payload(false, 0x2000, 520), ];
191 let regions: Vec<_> = data_regions(&payloads, false, 16, 512).collect();
192 assert_eq!(regions.len(), 1);
193 assert_eq!(regions[0].addr, 0x2008); assert_eq!(regions[0].len, 512);
195 }
196
197 #[test]
198 fn data_regions_filters_by_direction() {
199 let payloads = vec![
201 payload(false, 0x1000, 16), payload(true, 0x3000, 4097), ];
204 let regions: Vec<_> = data_regions(&payloads, true, 0, 4096).collect();
206 assert_eq!(regions.len(), 1);
207 assert_eq!(regions[0].addr, 0x3000);
208 assert_eq!(regions[0].len, 4096);
209 }
210
211 #[test]
212 fn data_regions_empty_payload() {
213 let payloads: Vec<VirtioQueuePayload> = vec![];
214 let regions: Vec<_> = data_regions(&payloads, true, 0, 4096).collect();
215 assert!(regions.is_empty());
216 }
217
218 #[test]
221 fn gpn_list_single_page_aligned_region() {
222 let regions = vec![DataRegion {
223 addr: 0x1000,
224 len: 4096,
225 }];
226 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
227 assert_eq!(gpns, vec![1]); assert_eq!(offset, 0);
229 assert_eq!(len, 4096);
230 }
231
232 #[test]
233 fn gpn_list_single_region_with_offset() {
234 let regions = vec![DataRegion {
235 addr: 0x1200,
236 len: 512,
237 }];
238 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
239 assert_eq!(gpns, vec![1]);
240 assert_eq!(offset, 0x200);
241 assert_eq!(len, 512);
242 }
243
244 #[test]
245 fn gpn_list_single_region_spanning_pages() {
246 let regions = vec![DataRegion {
248 addr: 0x2000,
249 len: 8192,
250 }];
251 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
252 assert_eq!(gpns, vec![2, 3]);
253 assert_eq!(offset, 0);
254 assert_eq!(len, 8192);
255 }
256
257 #[test]
258 fn gpn_list_two_page_aligned_non_contiguous_regions() {
259 let regions = vec![
261 DataRegion {
262 addr: 0x1000,
263 len: 4096,
264 },
265 DataRegion {
266 addr: 0x5000,
267 len: 4096,
268 },
269 ];
270 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
271 assert_eq!(gpns, vec![1, 5]);
272 assert_eq!(offset, 0);
273 assert_eq!(len, 8192);
274 }
275
276 #[test]
277 fn gpn_list_two_gpa_contiguous_regions() {
278 let regions = vec![
280 DataRegion {
281 addr: 0x1000,
282 len: 4096,
283 },
284 DataRegion {
285 addr: 0x2000,
286 len: 4096,
287 },
288 ];
289 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
290 assert_eq!(gpns, vec![1, 2]);
291 assert_eq!(offset, 0);
292 assert_eq!(len, 8192);
293 }
294
295 #[test]
296 fn gpn_list_contiguous_mid_page_boundary() {
297 let regions = vec![
299 DataRegion {
300 addr: 0x1000,
301 len: 4608,
302 }, DataRegion {
304 addr: 0x2200,
305 len: 3584,
306 }, ];
308 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
309 assert_eq!(gpns, vec![1, 2]);
310 assert_eq!(offset, 0);
311 assert_eq!(len, 8192);
312 }
313
314 #[test]
315 fn gpn_list_non_contiguous_non_aligned_fails() {
316 let regions = vec![
318 DataRegion {
319 addr: 0x1000,
320 len: 4608,
321 }, DataRegion {
323 addr: 0x5200,
324 len: 512,
325 }, ];
327 assert!(try_build_gpn_list(®ions).is_none());
328 }
329
330 #[test]
331 fn gpn_list_non_contiguous_first_aligned_second_not() {
332 let regions = vec![
334 DataRegion {
335 addr: 0x1000,
336 len: 4096,
337 }, DataRegion {
339 addr: 0x5200,
340 len: 512,
341 }, ];
343 assert!(try_build_gpn_list(®ions).is_none());
344 }
345
346 #[test]
347 fn gpn_list_non_contiguous_first_not_aligned_second_aligned() {
348 let regions = vec![
350 DataRegion {
351 addr: 0x1000,
352 len: 4608,
353 }, DataRegion {
355 addr: 0x5000,
356 len: 4096,
357 }, ];
359 assert!(try_build_gpn_list(®ions).is_none());
360 }
361
362 #[test]
363 fn gpn_list_empty_regions() {
364 let regions: Vec<DataRegion> = vec![];
365 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
366 assert!(gpns.is_empty());
367 assert_eq!(offset, 0);
368 assert_eq!(len, 0);
369 }
370
371 #[test]
372 fn gpn_list_region_wrapping_64_bits_is_rejected() {
373 let regions = vec![DataRegion {
374 addr: 0xffff_ffff_ffff_ff00,
375 len: 512,
376 }];
377 assert!(try_build_gpn_list(®ions).is_none());
378 }
379
380 #[test]
381 fn gpn_list_chain_after_wrapping_region_is_rejected() {
382 let regions = vec![
383 DataRegion {
384 addr: 0xffff_ffff_ffff_ff00,
385 len: 512,
386 },
387 DataRegion {
388 addr: 0x100,
389 len: 512,
390 },
391 ];
392 assert!(try_build_gpn_list(®ions).is_none());
393 }
394
395 #[test]
396 fn gpn_list_three_page_aligned_regions() {
397 let regions = vec![
399 DataRegion {
400 addr: 0x1000,
401 len: 4096,
402 },
403 DataRegion {
404 addr: 0x3000,
405 len: 4096,
406 },
407 DataRegion {
408 addr: 0x7000,
409 len: 4096,
410 },
411 ];
412 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
413 assert_eq!(gpns, vec![1, 3, 7]);
414 assert_eq!(offset, 0);
415 assert_eq!(len, 12288);
416 }
417
418 #[test]
419 fn gpn_list_first_region_with_offset_second_page_aligned() {
420 let regions = vec![
423 DataRegion {
424 addr: 0x1800,
425 len: 2048,
426 }, DataRegion {
428 addr: 0x5000,
429 len: 4096,
430 }, ];
432 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
433 assert_eq!(gpns, vec![1, 5]);
434 assert_eq!(offset, 0x800);
435 assert_eq!(len, 6144);
436 }
437
438 #[test]
439 fn gpn_list_validates_paged_range_construction() {
440 let regions = vec![
442 DataRegion {
443 addr: 0x1000,
444 len: 4096,
445 },
446 DataRegion {
447 addr: 0x5000,
448 len: 8192,
449 },
450 ];
451 let (gpns, offset, len) = try_build_gpn_list(®ions).unwrap();
452 let range = PagedRange::new(offset, len, &gpns);
453 assert!(range.is_some());
454 assert_eq!(range.unwrap().len(), 12288);
455 }
456}