blob: fb13db981b8be9afe20b50129aeac05d716b4ae2 [file] [log] [blame]
Imre Kis703482d2023-11-30 15:51:26 +01001// SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Module for handling physical and virtual memory regions
5//!
6//! A region is a continuous memory area in the given memory space.
7
8use alloc::vec::Vec;
9use log::debug;
10
11use super::{
12 page_pool::{PagePool, Pages},
13 region_pool::Region,
14};
15
16/// Physical region
17///
18/// A physical memory region can be in three different state
19/// * Unused
20/// * Points to a page pool allocated address
21/// * Points to a physical address without allocation
22pub enum PhysicalRegion {
23 Unused,
24 Allocated(PagePool, Pages),
25 PhysicalAddress(usize),
26}
27
28impl PhysicalRegion {
29 /// Get physical memory address
30 fn get_pa(&self) -> usize {
31 match self {
32 PhysicalRegion::Unused => panic!("Unused area has no PA"),
33 PhysicalRegion::Allocated(_page_pool, pages) => pages.get_pa(),
34 PhysicalRegion::PhysicalAddress(pa) => *pa,
35 }
36 }
37}
38
39/// Virtual region
40///
41/// A virtual memory region has a virtual address, a length and a physical region.
42pub struct VirtualRegion {
43 va: usize,
44 length: usize,
45 physical_region: PhysicalRegion,
46}
47
48impl VirtualRegion {
49 /// Create new virtual memory region without a physical region
50 pub fn new(va: usize, length: usize) -> Self {
51 Self::new_from_fields(va, length, PhysicalRegion::Unused)
52 }
53
54 /// Create virtual region with points to a given physical address
55 pub fn new_with_pa(pa: usize, va: usize, length: usize) -> Self {
56 Self::new_from_fields(va, length, PhysicalRegion::PhysicalAddress(pa))
57 }
58
59 /// Create virtual region by defining all the fields of the object
60 fn new_from_fields(va: usize, length: usize, physical_region: PhysicalRegion) -> Self {
61 Self {
62 va,
63 length,
64 physical_region,
65 }
66 }
67
68 /// Get the base address of the linked physical region
69 pub fn get_pa(&self) -> usize {
70 self.physical_region.get_pa()
71 }
72
73 /// Get physical address for a virtual address
74 pub fn get_pa_for_va(&self, va: usize) -> usize {
75 let offset = va.checked_sub(self.va).unwrap();
76
77 assert!(offset < self.length);
78 self.get_pa().checked_add(offset).unwrap()
79 }
80}
81
82impl Region for VirtualRegion {
83 type Resource = PhysicalRegion;
84
85 fn base(&self) -> usize {
86 self.va
87 }
88
89 fn length(&self) -> usize {
90 self.length
91 }
92
93 fn used(&self) -> bool {
94 !matches!(self.physical_region, PhysicalRegion::Unused)
95 }
96
97 fn contains(&self, base: usize, length: usize) -> bool {
98 if let (Some(end), Some(self_end)) =
99 (base.checked_add(length), self.va.checked_add(self.length))
100 {
101 self.va <= base && end <= self_end
102 } else {
103 false
104 }
105 }
106
107 fn try_append(&mut self, other: &Self) -> bool {
108 if let (Some(self_end), Some(new_length)) = (
109 self.va.checked_add(self.length),
110 self.length.checked_add(other.length),
111 ) {
112 if self_end == other.va {
113 // VA is consecutive
114 match (&self.physical_region, &other.physical_region) {
115 (PhysicalRegion::Unused, PhysicalRegion::Unused) => {
116 // Unused range can be merged without further conditions
117 self.length = new_length;
118 return true;
119 }
120 (
121 PhysicalRegion::PhysicalAddress(self_pa),
122 PhysicalRegion::PhysicalAddress(other_pa),
123 ) => {
124 // Used ranges can be only merged if the PA doesn't overflow and it is
125 // consecutive
126 if let Some(self_end_pa) = self_pa.checked_add(self.length) {
127 if self_end_pa == *other_pa {
128 self.length = new_length;
129 return true;
130 }
131 }
132 }
133
134 // PhyisicalRegion::Allocated instances cannot be merged at the moment. Not sure
135 // if it's a valid use case. If needed the pages has to be merged which might
136 // require tricks to invalidate the pages of 'other'.
137 _ => {}
138 }
139 }
140 }
141
142 false
143 }
144
145 fn create_split(
146 &self,
147 base: usize,
148 length: usize,
149 resource: Option<Self::Resource>,
150 ) -> (Self, Vec<Self>) {
151 assert!(self.contains(base, length));
152 assert!(self.used() != resource.is_some());
153
154 if let Some(physical_region) = resource {
155 // Self is unused, setting part of it to used
156 let pa = physical_region.get_pa();
157
158 let mut res = Vec::new();
159 if self.va != base {
160 res.push(Self::new(self.va, base.checked_sub(self.va).unwrap()));
161 }
162
163 res.push(Self::new_from_fields(base, length, physical_region));
164
165 let end = base.checked_add(length).unwrap();
166 let self_end = self.va.checked_add(self.length).unwrap();
167 if end != self_end {
168 res.push(Self::new(end, self_end.checked_sub(end).unwrap()));
169 }
170
171 (
172 Self::new_from_fields(base, length, PhysicalRegion::PhysicalAddress(pa)),
173 res,
174 )
175 } else {
176 // Self is used, mark part of it unused
177 let mut res = Vec::new();
178 if self.va != base {
179 let physical_region = match &self.physical_region {
180 PhysicalRegion::Allocated(_page_pool, _pages) => {
181 todo!("Implement Pages::split");
182 }
183 PhysicalRegion::PhysicalAddress(pa) => PhysicalRegion::PhysicalAddress(*pa),
184 _ => {
185 panic!("Splitting unused region by other unused")
186 }
187 };
188
189 res.push(Self::new_from_fields(
190 self.va,
191 base.checked_sub(self.va).unwrap(),
192 physical_region,
193 ));
194 }
195
196 res.push(Self::new(base, length));
197
198 let end = base.checked_add(length).unwrap();
199 let self_end = self.va.checked_add(self.length).unwrap();
200 if end != self_end {
201 let physical_region = match &self.physical_region {
202 PhysicalRegion::Allocated(_page_pool, _pages) => {
203 todo!("Implement Pages::split");
204 }
205 PhysicalRegion::PhysicalAddress(pa) => {
206 let offset = end.checked_sub(self.va).unwrap();
207 PhysicalRegion::PhysicalAddress(pa.checked_add(offset).unwrap())
208 }
209 _ => {
210 panic!("Splitting unused region by other unused")
211 }
212 };
213
214 res.push(Self::new_from_fields(
215 end,
216 self_end.checked_sub(end).unwrap(),
217 physical_region,
218 ));
219 }
220
221 (Self::new(base, length), res)
222 }
223 }
224}
225
226impl Drop for VirtualRegion {
227 /// If the virtual region has a linked physical region which was allocated then release the
228 /// allocated pages.
229 fn drop(&mut self) {
230 let mut physical_region = PhysicalRegion::Unused;
231
232 core::mem::swap(&mut self.physical_region, &mut physical_region);
233
234 if let PhysicalRegion::Allocated(page_pool, pages) = physical_region {
235 debug!(
236 "Dropping physical region with pages: PA={:#010x} VA={:#010x}",
237 pages.get_pa(),
238 self.base(),
239 );
240
241 page_pool.release_pages(pages).unwrap();
242 }
243 }
244}
245
246#[cfg(test)]
247use super::page_pool::PagePoolArea;
248
249#[test]
250#[should_panic]
251fn test_physical_region_unused() {
252 let region = PhysicalRegion::Unused;
253 region.get_pa();
254}
255
256#[test]
257fn test_physical_region() {
258 const PA: usize = 0x0123_4567_89ab_cdef;
259 const LENGTH: usize = 0x8000_0000_0000;
260
261 static PAGE_POOL_AREA: PagePoolArea<16> = PagePoolArea::new();
262 let region =
263 PhysicalRegion::Allocated(PagePool::new(&PAGE_POOL_AREA), Pages::new(PA, LENGTH, true));
264 assert_eq!(PA, region.get_pa());
265
266 let region = PhysicalRegion::PhysicalAddress(PA);
267 assert_eq!(PA, region.get_pa());
268}
269
270#[test]
271fn test_virtual_region() {
272 const VA: usize = 0x0123_4567_89ab_cdef;
273 const PA: usize = 0xfedc_ba98_7654_3210;
274 const LENGTH: usize = 0x8000_0000_0000;
275
276 let region = VirtualRegion::new(VA, LENGTH);
277 assert_eq!(VA, region.va);
278 assert_eq!(VA, region.base());
279 assert_eq!(LENGTH, region.length);
280 assert_eq!(LENGTH, region.length());
281 assert!(matches!(region.physical_region, PhysicalRegion::Unused));
282 assert!(!region.used());
283
284 let region = VirtualRegion::new_with_pa(PA, VA, LENGTH);
285 assert_eq!(VA, region.va);
286 assert_eq!(VA, region.base());
287 assert_eq!(LENGTH, region.length);
288 assert_eq!(LENGTH, region.length());
289 assert!(matches!(
290 region.physical_region,
291 PhysicalRegion::PhysicalAddress(_)
292 ));
293 assert_eq!(PA, region.get_pa());
294 assert!(region.used());
295}
296
297#[test]
298fn test_virtual_region_get_pa_for_va() {
299 let region = VirtualRegion::new_with_pa(0x8000_0000_0000_0000, 0x4000_0000_0000_0000, 0x1000);
300 assert_eq!(
301 0x8000_0000_0000_0000,
302 region.get_pa_for_va(0x4000_0000_0000_0000)
303 );
304 assert_eq!(
305 0x8000_0000_0000_0001,
306 region.get_pa_for_va(0x4000_0000_0000_0001)
307 );
308 assert_eq!(
309 0x8000_0000_0000_0fff,
310 region.get_pa_for_va(0x4000_0000_0000_0fff)
311 );
312}
313
314#[test]
315#[should_panic]
316fn test_virtual_region_get_pa_for_va_low_va() {
317 let region = VirtualRegion::new_with_pa(0x8000_0000_0000_0000, 0x4000_0000_0000_0000, 0x1000);
318 region.get_pa_for_va(0x3fff_ffff_ffff_ffff);
319}
320
321#[test]
322#[should_panic]
323fn test_virtual_region_get_pa_for_va_high_va() {
324 let region = VirtualRegion::new_with_pa(0x8000_0000_0000_0000, 0x4000_0000_0000_0000, 0x1000);
325 region.get_pa_for_va(0x4000_0000_0000_1000);
326}
327
328#[test]
329fn test_virtual_region_contains() {
330 const VA: usize = 0x8000_0000_0000_0000;
331 const LENGTH: usize = 0x8000_0000_0000;
332
333 let region_overflow_end = VirtualRegion::new(0x8000_0000_0000_0000, 0x8000_0000_0000_0000);
334 assert!(!region_overflow_end.contains(0x8000_0000_0000_0000, 1));
335
336 let region = VirtualRegion::new(0x4000_0000_0000_0000, 0x8000_0000_0000_0000);
337 assert!(!region.contains(0x8000_0000_0000_0000, 0x8000_0000_0000_0000));
338
339 assert!(!region.contains(0x4000_0000_0000_0000, 0x8000_0000_0000_0001));
340 assert!(!region.contains(0x3fff_ffff_ffff_ffff, 0x8000_0000_0000_0000));
341 assert!(region.contains(0x4000_0000_0000_0000, 0x8000_0000_0000_0000));
342 assert!(region.contains(0x4000_0000_0000_0000, 0x7fff_ffff_ffff_ffff));
343 assert!(region.contains(0x4000_0000_0000_0001, 0x7fff_ffff_ffff_ffff));
344}
345
346#[test]
347fn test_virtual_region_try_append() {
348 // Both unused
349 let mut region_unused0 = VirtualRegion::new(0x4000_0000, 0x1000);
350 let mut region_unused1 = VirtualRegion::new(0x4000_1000, 0x1000);
351
352 assert!(!region_unused1.try_append(&region_unused0));
353 assert_eq!(0x4000_0000, region_unused0.va);
354 assert_eq!(0x1000, region_unused0.length);
355 assert_eq!(0x4000_1000, region_unused1.va);
356 assert_eq!(0x1000, region_unused1.length);
357
358 assert!(region_unused0.try_append(&region_unused1));
359 assert_eq!(0x4000_0000, region_unused0.va);
360 assert_eq!(0x2000, region_unused0.length);
361 assert_eq!(0x4000_1000, region_unused1.va);
362 assert_eq!(0x1000, region_unused1.length);
363
364 // Unused and PA region
365 let mut region_unused = VirtualRegion::new(0x4000_0000, 0x1000);
366 let region_physical = VirtualRegion::new_with_pa(0x8000_0000, 0x4000_1000, 0x1000);
367 assert!(!region_unused.try_append(&region_physical));
368 assert_eq!(0x4000_0000, region_unused.va);
369 assert_eq!(0x1000, region_unused.length);
370 assert_eq!(0x4000_1000, region_physical.va);
371 assert_eq!(0x1000, region_physical.length);
372
373 // Both PA regions but non-consecutive PA ranges
374 let mut region_physical0 = VirtualRegion::new_with_pa(0x8000_0000, 0x4000_0000, 0x1000);
375 let region_physical1 = VirtualRegion::new_with_pa(0x9000_0000, 0x4000_1000, 0x1000);
376 assert!(!region_physical0.try_append(&region_physical1));
377 assert_eq!(0x4000_0000, region_physical0.va);
378 assert_eq!(0x1000, region_physical0.length);
379 assert_eq!(0x4000_1000, region_physical1.va);
380 assert_eq!(0x1000, region_physical1.length);
381
382 // Both PA regions with consecutive PA ranges
383 let mut region_physical0 = VirtualRegion::new_with_pa(0x8000_0000, 0x4000_0000, 0x1000);
384 let region_physical1 = VirtualRegion::new_with_pa(0x8000_1000, 0x4000_1000, 0x1000);
385 assert!(region_physical0.try_append(&region_physical1));
386 assert_eq!(0x4000_0000, region_physical0.va);
387 assert_eq!(0x2000, region_physical0.length);
388 assert_eq!(0x4000_1000, region_physical1.va);
389 assert_eq!(0x1000, region_physical1.length);
390
391 // VA overflow
392 let mut region_unused0 = VirtualRegion::new(0x8000_0000_0000_0000, 0x8000_0000_0000_0000);
393 let mut region_unused1 = VirtualRegion::new(0x4000_1000, 0x1000);
394
395 assert!(!region_unused0.try_append(&region_unused1));
396 assert_eq!(0x8000_0000_0000_0000, region_unused0.va);
397 assert_eq!(0x8000_0000_0000_0000, region_unused0.length);
398 assert_eq!(0x4000_1000, region_unused1.va);
399 assert_eq!(0x1000, region_unused1.length);
400
401 assert!(!region_unused1.try_append(&region_unused0));
402 assert_eq!(0x8000_0000_0000_0000, region_unused0.va);
403 assert_eq!(0x8000_0000_0000_0000, region_unused0.length);
404 assert_eq!(0x4000_1000, region_unused1.va);
405 assert_eq!(0x1000, region_unused1.length);
406
407 // PA overflow
408 let mut region_physical0 =
409 VirtualRegion::new_with_pa(0x8000_0000_0000_0000, 0x4000_0000, 0x8000_0000_0000_0000);
410 let region_physical1 = VirtualRegion::new_with_pa(0x9000_0000, 0x8000_0000_4000_0000, 0x1000);
411 assert!(!region_physical0.try_append(&region_physical1));
412 assert_eq!(0x4000_0000, region_physical0.va);
413 assert_eq!(0x8000_0000_0000_0000, region_physical0.length);
414 assert_eq!(0x8000_0000_4000_0000, region_physical1.va);
415 assert_eq!(0x1000, region_physical1.length);
416}
417
418#[test]
419fn test_virtual_region_create_split_by_used() {
420 let region_unused = VirtualRegion::new(0x4000_0000, 0x4000);
421
422 // New region at the start
423 let (new_region, splitted_regions) = region_unused.create_split(
424 0x4000_0000,
425 0x1000,
426 Some(PhysicalRegion::PhysicalAddress(0x8000_0000)),
427 );
428
429 assert_eq!(0x4000_0000, new_region.va);
430 assert_eq!(0x1000, new_region.length);
431 assert_eq!(0x8000_0000, new_region.get_pa());
432 assert!(matches!(
433 new_region.physical_region,
434 PhysicalRegion::PhysicalAddress(_)
435 ));
436
437 assert_eq!(0x4000_0000, splitted_regions[0].va);
438 assert_eq!(0x1000, splitted_regions[0].length);
439 assert_eq!(0x8000_0000, splitted_regions[0].get_pa());
440 assert!(matches!(
441 splitted_regions[0].physical_region,
442 PhysicalRegion::PhysicalAddress(_)
443 ));
444
445 assert_eq!(0x4000_1000, splitted_regions[1].va);
446 assert_eq!(0x3000, splitted_regions[1].length);
447 assert!(matches!(
448 splitted_regions[1].physical_region,
449 PhysicalRegion::Unused
450 ));
451
452 // New region in the middle
453 let (new_region, splitted_regions) = region_unused.create_split(
454 0x4000_1000,
455 0x1000,
456 Some(PhysicalRegion::PhysicalAddress(0x8000_0000)),
457 );
458
459 assert_eq!(0x4000_1000, new_region.va);
460 assert_eq!(0x1000, new_region.length);
461 assert_eq!(0x8000_0000, new_region.get_pa());
462 assert!(matches!(
463 new_region.physical_region,
464 PhysicalRegion::PhysicalAddress(_)
465 ));
466
467 assert_eq!(0x4000_0000, splitted_regions[0].va);
468 assert_eq!(0x1000, splitted_regions[0].length);
469 assert!(matches!(
470 splitted_regions[0].physical_region,
471 PhysicalRegion::Unused
472 ));
473
474 assert_eq!(0x4000_1000, splitted_regions[1].va);
475 assert_eq!(0x1000, splitted_regions[1].length);
476 assert_eq!(0x8000_0000, splitted_regions[1].get_pa());
477 assert!(matches!(
478 splitted_regions[1].physical_region,
479 PhysicalRegion::PhysicalAddress(_)
480 ));
481
482 assert_eq!(0x4000_2000, splitted_regions[2].va);
483 assert_eq!(0x2000, splitted_regions[2].length);
484 assert!(matches!(
485 splitted_regions[2].physical_region,
486 PhysicalRegion::Unused
487 ));
488
489 // New region at the end
490 let (new_region, splitted_regions) = region_unused.create_split(
491 0x4000_3000,
492 0x1000,
493 Some(PhysicalRegion::PhysicalAddress(0x8000_0000)),
494 );
495
496 assert_eq!(0x4000_3000, new_region.va);
497 assert_eq!(0x1000, new_region.length);
498 assert_eq!(0x8000_0000, new_region.get_pa());
499 assert!(matches!(
500 new_region.physical_region,
501 PhysicalRegion::PhysicalAddress(_)
502 ));
503
504 assert_eq!(0x4000_0000, splitted_regions[0].va);
505 assert_eq!(0x3000, splitted_regions[0].length);
506 assert!(matches!(
507 splitted_regions[0].physical_region,
508 PhysicalRegion::Unused
509 ));
510
511 assert_eq!(0x4000_3000, splitted_regions[1].va);
512 assert_eq!(0x1000, splitted_regions[1].length);
513 assert_eq!(0x8000_0000, splitted_regions[1].get_pa());
514 assert!(matches!(
515 splitted_regions[1].physical_region,
516 PhysicalRegion::PhysicalAddress(_)
517 ));
518}
519
520#[test]
521fn test_virtual_region_create_split_by_unused() {
522 let region_unused = VirtualRegion::new_with_pa(0x8000_0000, 0x4000_0000, 0x4000);
523
524 // New region at the start
525 let (new_region, splitted_regions) = region_unused.create_split(0x4000_0000, 0x1000, None);
526
527 assert_eq!(0x4000_0000, new_region.va);
528 assert_eq!(0x1000, new_region.length);
529 assert!(matches!(new_region.physical_region, PhysicalRegion::Unused));
530
531 assert_eq!(0x4000_0000, splitted_regions[0].va);
532 assert_eq!(0x1000, splitted_regions[0].length);
533 assert!(matches!(
534 splitted_regions[0].physical_region,
535 PhysicalRegion::Unused
536 ));
537
538 assert_eq!(0x4000_1000, splitted_regions[1].va);
539 assert_eq!(0x3000, splitted_regions[1].length);
540 assert_eq!(0x8000_1000, splitted_regions[1].get_pa());
541 assert!(matches!(
542 splitted_regions[1].physical_region,
543 PhysicalRegion::PhysicalAddress(_)
544 ));
545
546 // New region in the middle
547 let (new_region, splitted_regions) = region_unused.create_split(0x4000_1000, 0x1000, None);
548
549 assert_eq!(0x4000_1000, new_region.va);
550 assert_eq!(0x1000, new_region.length);
551 assert!(matches!(new_region.physical_region, PhysicalRegion::Unused));
552
553 assert_eq!(0x4000_0000, splitted_regions[0].va);
554 assert_eq!(0x1000, splitted_regions[0].length);
555 assert_eq!(0x8000_0000, splitted_regions[0].get_pa());
556 assert!(matches!(
557 splitted_regions[0].physical_region,
558 PhysicalRegion::PhysicalAddress(_)
559 ));
560
561 assert_eq!(0x4000_1000, splitted_regions[1].va);
562 assert_eq!(0x1000, splitted_regions[1].length);
563 assert!(matches!(
564 splitted_regions[1].physical_region,
565 PhysicalRegion::Unused
566 ));
567
568 assert_eq!(0x4000_2000, splitted_regions[2].va);
569 assert_eq!(0x2000, splitted_regions[2].length);
570 assert_eq!(0x8000_2000, splitted_regions[2].get_pa());
571 assert!(matches!(
572 splitted_regions[2].physical_region,
573 PhysicalRegion::PhysicalAddress(_)
574 ));
575
576 // New region at the end
577 let (new_region, splitted_regions) = region_unused.create_split(0x4000_3000, 0x1000, None);
578
579 assert_eq!(0x4000_3000, new_region.va);
580 assert_eq!(0x1000, new_region.length);
581 assert!(matches!(new_region.physical_region, PhysicalRegion::Unused));
582
583 assert_eq!(0x4000_0000, splitted_regions[0].va);
584 assert_eq!(0x3000, splitted_regions[0].length);
585 assert_eq!(0x8000_0000, splitted_regions[0].get_pa());
586 assert!(matches!(
587 splitted_regions[0].physical_region,
588 PhysicalRegion::PhysicalAddress(_)
589 ));
590
591 assert_eq!(0x4000_3000, splitted_regions[1].va);
592 assert_eq!(0x1000, splitted_regions[1].length);
593
594 assert!(matches!(
595 splitted_regions[1].physical_region,
596 PhysicalRegion::Unused
597 ));
598}
599
600#[test]
601#[should_panic]
602fn test_virtual_region_does_not_contain() {
603 let region = VirtualRegion::new(0x4000_0000, 0x1000);
604 region.create_split(
605 0x8000_0000,
606 0x1000,
607 Some(PhysicalRegion::PhysicalAddress(0xc000_0000)),
608 );
609}
610
611#[test]
612#[should_panic]
613fn test_virtual_region_create_split_same_used() {
614 let region = VirtualRegion::new(0x4000_0000, 0x1000);
615 region.create_split(0x4000_0000, 0x1000, Some(PhysicalRegion::Unused));
616}
617
618#[test]
619fn test_virtual_region_drop() {
620 const PA: usize = 0x0123_4567_89ab_cdef;
621 const LENGTH: usize = 0x8000_0000_0000;
622
623 static PAGE_POOL_AREA: PagePoolArea<8192> = PagePoolArea::new();
624 let page_pool = PagePool::new(&PAGE_POOL_AREA);
625 let page = page_pool.allocate_pages(4096).unwrap();
626
627 let physical_region = PhysicalRegion::Allocated(page_pool, page);
628
629 // Testing physical region drop through virtualregion
630 let virtual_region = VirtualRegion::new_from_fields(0x4000_0000, 1000, physical_region);
631 drop(virtual_region);
632}