From b5216784a10705e446b4a765c1df94dfc6cfa41b Mon Sep 17 00:00:00 2001 From: NIMogen <91998100+NIMogen@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:16:44 -0700 Subject: [PATCH] Add `PageSize` trait for defining 4K, 2M, and 1G huge pages (#1031) * All chunk-related types are now parameterized with a `P: PageSize` type parameter: * `Page` and `Frame`, which ensure that the underlying virtual or physical address is always properly aligned to the specified size. * `PageRange` and `FrameRange`, which ensure that the range of pages or frames are correctly aligned and only can be created in granular chunks of the given `PageSize`. * When iterating over a range of huge pages or frames, each step will be at the granularity of one huge page, which makes it easy to iterate of huge pages and huge frames in lockstep. * There are various implementations of the conversion traits `From` and `TryFrom` for normal and huge pages/frames and ranges. * The `PageSize` parameter can only be one of 3 marker structs: 1. `Page4K`: a normal 4KiB page (P1-level), which is the default. 2. `Page2M`: a P2-level huge page. 3. `Page1G`: a P3-level huge page. * This is only relevant to x86_64 at the moment, though we may add aarch64 huge page sizes in the future too. Co-authored-by: Kevin Boos --- kernel/memory_structs/src/lib.rs | 347 ++++++++++++++++++++++-------- kernel/memory_structs/src/test.rs | 219 +++++++++++++++++++ 2 files changed, 481 insertions(+), 85 deletions(-) create mode 100644 kernel/memory_structs/src/test.rs diff --git a/kernel/memory_structs/src/lib.rs b/kernel/memory_structs/src/lib.rs index 3563eae66a..20181c16eb 100644 --- a/kernel/memory_structs/src/lib.rs +++ b/kernel/memory_structs/src/lib.rs @@ -11,19 +11,74 @@ #![allow(incomplete_features)] #![feature(adt_const_params)] +#[cfg(test)] +mod test; + use core::{ cmp::{min, max}, fmt, iter::Step, - marker::ConstParamTy, + marker::{ConstParamTy, PhantomData}, ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign}, }; -use kernel_config::memory::{MAX_PAGE_NUMBER, PAGE_SIZE}; +use kernel_config::memory::{MAX_PAGE_NUMBER, PAGE_SIZE, ENTRIES_PER_PAGE_TABLE}; use zerocopy::FromBytes; use paste::paste; use derive_more::*; use range_inclusive::{RangeInclusive, RangeInclusiveIterator}; +/// Enum used to indicate the size of a page or frame. +#[derive(Debug)] +pub enum MemChunkSize { + Normal4K, + Huge2M, + Huge1G, +} + +/// Trait that represents the size of a page or frame, i.e., for normal or huge pages. +/// +/// This is used to parameterize `Page`- and `Frame`-related types with a page size, +/// in order to define normal and huge pages in a generic manner. +pub trait PageSize: Ord + PartialOrd + Clone + Copy + private::Sealed { + const SIZE: MemChunkSize; + const NUM_4K_PAGES: usize; + const SIZE_IN_BYTES: usize; +} + +mod private { + pub trait Sealed { } +} + +/// Marker struct used to indicate the default page size of 4KiB. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Page4K; +impl private::Sealed for Page4K { } +impl PageSize for Page4K { + const SIZE: MemChunkSize = MemChunkSize::Normal4K; + const NUM_4K_PAGES: usize = 1; + const SIZE_IN_BYTES: usize = PAGE_SIZE; +} + +/// Marker struct used to indicate a page size of 2MiB. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Page2M; +impl private::Sealed for Page2M { } +impl PageSize for Page2M { + const SIZE: MemChunkSize = MemChunkSize::Huge2M; + const NUM_4K_PAGES: usize = Page4K::NUM_4K_PAGES * ENTRIES_PER_PAGE_TABLE; + const SIZE_IN_BYTES: usize = Self::NUM_4K_PAGES * Page4K::SIZE_IN_BYTES; +} + +/// Marker struct used to indicate a page size of 1GiB. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct Page1G; +impl private::Sealed for Page1G { } +impl PageSize for Page1G { + const SIZE: MemChunkSize = MemChunkSize::Huge1G; + const NUM_4K_PAGES: usize = Page2M::NUM_4K_PAGES * ENTRIES_PER_PAGE_TABLE; + const SIZE_IN_BYTES: usize = Self::NUM_4K_PAGES * Page4K::SIZE_IN_BYTES; +} + /// The possible states that a range of exclusively-owned pages or frames can be in. #[derive(PartialEq, Eq, ConstParamTy)] pub enum MemoryState { @@ -80,12 +135,12 @@ macro_rules! implement_address { self.0 } - #[doc = "Returns the offset from the " $chunk " boundary specified by this `" - $TypeName ".\n\n \ - For example, if the [`PAGE_SIZE`] is 4096 (4KiB), then this will return + #[doc = "Returns the offset from the 4K " $chunk " boundary specified by this `" + $TypeName ".\n\n \ + For example, for the address `0xFFFF_1578`, this will return `0x578`, the least significant 12 bits `(12:0]` of this `" $TypeName "`."] pub const fn [<$chunk _offset>](&self) -> usize { - self.0 & (PAGE_SIZE - 1) + self.0 & (Page4K::SIZE_IN_BYTES - 1) } } impl fmt::Debug for $TypeName { @@ -263,29 +318,21 @@ implement_address!( macro_rules! implement_page_frame { ($TypeName:ident, $desc:literal, $prefix:literal, $address:ident) => { paste! { // using the paste crate's macro for easy concatenation - - #[doc = "A `" $TypeName "` is a chunk of **" $desc "** memory aligned to a [`PAGE_SIZE`] boundary."] + #[doc = "A `" $TypeName "` is a chunk of **" $desc "** memory aligned to \ + a page boundary (default 4KiB) given by the `P` parameter."] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - pub struct $TypeName { + pub struct $TypeName { + /// A Page or Frame number is *always* given in terms of 4KiB pages/frames, + /// even for huge pages/frames. number: usize, + size: PhantomData::

, } - - impl $TypeName { - #[doc = "Returns the `" $address "` at the start of this `" $TypeName "`."] - pub const fn start_address(&self) -> $address { - $address::new_canonical(self.number * PAGE_SIZE) - } - - #[doc = "Returns the number of this `" $TypeName "`."] - #[inline(always)] - pub const fn number(&self) -> usize { - self.number - } - - #[doc = "Returns the `" $TypeName "` containing the given `" $address "`."] + impl $TypeName { + #[doc = "Returns the 4KiB `" $TypeName "` containing the given `" $address "`."] pub const fn containing_address(addr: $address) -> $TypeName { $TypeName { - number: addr.value() / PAGE_SIZE, + number: addr.value() / Page4K::SIZE_IN_BYTES, + size: PhantomData, } } @@ -294,59 +341,149 @@ macro_rules! implement_page_frame { #[doc(alias = "next_multiple_of")] pub const fn align_up(&self, alignment_4k_pages: usize) -> $TypeName { $TypeName { - number: self.number.next_multiple_of(alignment_4k_pages) + number: self.number.next_multiple_of(alignment_4k_pages), + size: PhantomData } } } - impl fmt::Debug for $TypeName { + impl $TypeName { + #[doc = "Returns the 2MiB huge `" $TypeName "` containing the given `" $address "`."] + pub const fn containing_address_2mb(addr: $address) -> $TypeName { + $TypeName { + number: (addr.value() / Page2M::SIZE_IN_BYTES) * Page2M::NUM_4K_PAGES, + size: PhantomData, + } + } + } + impl $TypeName { + #[doc = "Returns the 1GiB huge `" $TypeName "` containing the given `" $address "`."] + pub const fn containing_address_1gb(addr: $address) -> $TypeName { + $TypeName { + number: (addr.value() / Page1G::SIZE_IN_BYTES) * Page1G::NUM_4K_PAGES, + size: PhantomData, + } + } + } + impl $TypeName

{ + #[doc = "Returns the 4K-sized number of this `" $TypeName "`."] + #[inline(always)] + pub const fn number(&self) -> usize { + self.number + } + + #[doc = "Returns the `" $address "` at the start of this `" $TypeName "`."] + pub const fn start_address(&self) -> $address { + $address::new_canonical(self.number * Page4K::SIZE_IN_BYTES) + } + + #[doc = "Returns the size of this `" $TypeName "`."] + pub const fn page_size(&self) -> MemChunkSize { + P::SIZE + } + } + impl fmt::Debug for $TypeName

{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, concat!(stringify!($TypeName), "(", $prefix, "{:#X})"), self.start_address()) } } - impl Add for $TypeName { - type Output = $TypeName; - fn add(self, rhs: usize) -> $TypeName { + impl Add for $TypeName

{ + type Output = $TypeName

; + fn add(self, rhs: usize) -> $TypeName

{ // cannot exceed max page number (which is also max frame number) $TypeName { - number: core::cmp::min(MAX_PAGE_NUMBER, self.number.saturating_add(rhs)), + number: core::cmp::min( + MAX_PAGE_NUMBER, + self.number.saturating_add(rhs.saturating_mul(P::NUM_4K_PAGES)) + ), + size: self.size, } } } - impl AddAssign for $TypeName { + impl AddAssign for $TypeName

{ fn add_assign(&mut self, rhs: usize) { *self = $TypeName { - number: core::cmp::min(MAX_PAGE_NUMBER, self.number.saturating_add(rhs)), - }; + number: core::cmp::min( + MAX_PAGE_NUMBER, + self.number.saturating_add(rhs.saturating_mul(P::NUM_4K_PAGES)) + ), + size: self.size, + } } } - impl Sub for $TypeName { - type Output = $TypeName; - fn sub(self, rhs: usize) -> $TypeName { + impl Sub for $TypeName

{ + type Output = $TypeName

; + fn sub(self, rhs: usize) -> $TypeName

{ $TypeName { - number: self.number.saturating_sub(rhs), + number: self.number.saturating_sub(rhs.saturating_mul(P::NUM_4K_PAGES)), + size: self.size } } } - impl SubAssign for $TypeName { + impl SubAssign for $TypeName

{ fn sub_assign(&mut self, rhs: usize) { *self = $TypeName { - number: self.number.saturating_sub(rhs), - }; + number: self.number.saturating_sub(rhs.saturating_mul(P::NUM_4K_PAGES)), + size: self.size + } } } - #[doc = "Implementing `Step` allows `" $TypeName "` to be used in an [`Iterator`]."] - impl Step for $TypeName { + impl Step for $TypeName

{ #[inline] - fn steps_between(start: &$TypeName, end: &$TypeName) -> Option { + fn steps_between(start: &$TypeName

, end: &$TypeName

) -> Option { Step::steps_between(&start.number, &end.number) + .map(|n| n / P::NUM_4K_PAGES) } #[inline] - fn forward_checked(start: $TypeName, count: usize) -> Option<$TypeName> { - Step::forward_checked(start.number, count).map(|n| $TypeName { number: n }) + fn forward_checked(start: $TypeName

, count: usize) -> Option<$TypeName

> { + Step::forward_checked(start.number, count * P::NUM_4K_PAGES) + .map(|number| $TypeName { number, size: PhantomData }) } #[inline] - fn backward_checked(start: $TypeName, count: usize) -> Option<$TypeName> { - Step::backward_checked(start.number, count).map(|n| $TypeName { number: n }) + fn backward_checked(start: $TypeName

, count: usize) -> Option<$TypeName

> { + Step::backward_checked(start.number, count * P::NUM_4K_PAGES) + .map(|number| $TypeName { number, size: PhantomData }) + } + } + impl TryFrom<$TypeName> for $TypeName { + type Error = &'static str; + fn try_from(p: $TypeName) -> Result { + if p.number % Page2M::NUM_4K_PAGES == 0 { + Ok(Self { + number: p.number, + size: PhantomData, + }) + } else { + Err("Could not convert 4KiB to 2MiB page.") + } + } + } + impl TryFrom<$TypeName> for $TypeName { + type Error = &'static str; + fn try_from(p: $TypeName) -> Result { + if p.number % Page1G::NUM_4K_PAGES == 0 { + Ok(Self { + number: p.number, + size: PhantomData, + }) + } else { + Err("Could not convert 4KiB to 1GiB page.") + } + } + } + impl From<$TypeName> for $TypeName { + fn from(p: $TypeName) -> Self { + Self { + number: p.number, + size: PhantomData + } + } + } + impl From<$TypeName> for $TypeName { + fn from(p: $TypeName) -> Self { + Self { + number: p.number, + size: PhantomData + } } } } @@ -357,7 +494,7 @@ implement_page_frame!(Page, "virtual", "v", VirtualAddress); implement_page_frame!(Frame, "physical", "p", PhysicalAddress); // Implement other functions for the `Page` type that aren't relevant for `Frame. -impl Page { +impl Page

{ /// Returns the 9-bit part of this `Page`'s [`VirtualAddress`] that is the index into the P4 page table entries list. pub const fn p4_index(&self) -> usize { (self.number >> 27) & 0x1FF @@ -392,17 +529,12 @@ macro_rules! implement_page_frame_range { #[doc = "A range of [`" $chunk "`]s that are contiguous in " $desc " memory."] #[derive(Clone, PartialEq, Eq)] - pub struct $TypeName(RangeInclusive<$chunk>); - - impl $TypeName { - #[doc = "Creates a new range of [`" $chunk "`]s that spans from `start` to `end`, both inclusive bounds."] - pub const fn new(start: $chunk, end: $chunk) -> $TypeName { - $TypeName(RangeInclusive::new(start, end)) - } + pub struct $TypeName(RangeInclusive<$chunk::

>); + impl $TypeName { #[doc = "Creates a `" $TypeName "` that will always yield `None` when iterated."] - pub const fn empty() -> $TypeName { - $TypeName::new($chunk { number: 1 }, $chunk { number: 0 }) + pub const fn empty() -> Self { + Self::new($chunk { number: 1, size: PhantomData }, $chunk { number: 0, size: PhantomData }) } #[doc = "A convenience method for creating a new `" $TypeName "` that spans \ @@ -419,6 +551,12 @@ macro_rules! implement_page_frame_range { $TypeName::new(start, end) } } + } + impl $TypeName

{ + #[doc = "Creates a new range of [`" $chunk "`]s that spans from `start` to `end`, both inclusive bounds."] + pub const fn new(start: $chunk

, end: $chunk

) -> $TypeName

{ + $TypeName(RangeInclusive::new(start, end)) + } #[doc = "Returns the [`" $address "`] of the starting [`" $chunk "`] in this `" $TypeName "`."] pub const fn start_address(&self) -> $address { @@ -430,17 +568,19 @@ macro_rules! implement_page_frame_range { This is instant, because it doesn't need to iterate over each entry, unlike normal iterators."] pub const fn [](&self) -> usize { // add 1 because it's an inclusive range - (self.0.end().number + 1).saturating_sub(self.0.start().number) + (self.0.end().number + (1 * P::NUM_4K_PAGES)) + .saturating_sub(self.0.start().number) + / P::NUM_4K_PAGES } - /// Returns the size of this range in number of bytes. + #[doc = "Returns the size of this range in bytes."] pub const fn size_in_bytes(&self) -> usize { - self.[]() * PAGE_SIZE + self.[]() * P::SIZE_IN_BYTES } #[doc = "Returns `true` if this `" $TypeName "` contains the given [`" $address "`]."] pub const fn contains_address(&self, addr: $address) -> bool { - let c = $chunk::containing_address(addr); + let c = $chunk::::containing_address(addr); self.0.start().number <= c.number && c.number <= self.0.end().number } @@ -474,7 +614,7 @@ macro_rules! implement_page_frame_range { } #[doc = "Returns a new separate `" $TypeName "` that is extended to include the given [`" $chunk "`]."] - pub fn to_extended(&self, to_include: $chunk) -> $TypeName { + pub fn to_extended(&self, to_include: $chunk

) -> $TypeName

{ // if the current range was empty, return a new range containing only the given page/frame if self.is_empty() { return $TypeName::new(to_include.clone(), to_include); @@ -484,10 +624,18 @@ macro_rules! implement_page_frame_range { $TypeName::new(start.clone(), end.clone()) } + #[doc = "Returns `true` if the `other` `" $TypeName "` is fully contained within this `" $TypeName "`."] + pub fn contains_range(&self, other: &$TypeName

) -> bool { + !other.is_empty() + && (other.start() >= self.start()) + && (other.end() <= self.end()) + } + } + impl $TypeName

{ #[doc = "Returns an inclusive `" $TypeName "` representing the [`" $chunk "`]s that overlap \ across this `" $TypeName "` and the given other `" $TypeName "`.\n\n \ If there is no overlap between the two ranges, `None` is returned."] - pub fn overlap(&self, other: &$TypeName) -> Option<$TypeName> { + pub fn overlap(&self, other: &$TypeName

) -> Option<$TypeName

> { let starts = max(*self.start(), *other.start()); let ends = min(*self.end(), *other.end()); if starts <= ends { @@ -496,22 +644,15 @@ macro_rules! implement_page_frame_range { None } } - - #[doc = "Returns `true` if the `other` `" $TypeName "` is fully contained within this `" $TypeName "`."] - pub fn contains_range(&self, other: &$TypeName) -> bool { - !other.is_empty() - && (other.start() >= self.start()) - && (other.end() <= self.end()) - } } - impl fmt::Debug for $TypeName { + impl fmt::Debug for $TypeName

{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.0) } } - impl Deref for $TypeName { - type Target = RangeInclusive<$chunk>; - fn deref(&self) -> &RangeInclusive<$chunk> { + impl Deref for $TypeName

{ + type Target = RangeInclusive<$chunk

>; + fn deref(&self) -> &RangeInclusive<$chunk

> { &self.0 } } @@ -520,31 +661,67 @@ macro_rules! implement_page_frame_range { &mut self.0 } } - impl IntoIterator for $TypeName { - type Item = $chunk; - type IntoIter = RangeInclusiveIterator<$chunk>; + impl IntoIterator for $TypeName

{ + type Item = $chunk

; + type IntoIter = RangeInclusiveIterator<$chunk

>; fn into_iter(self) -> Self::IntoIter { self.0.iter() } } - #[doc = "A `" $TypeName "` that implements `Copy`"] + #[doc = "A `" $TypeName "` that implements `Copy`."] #[derive(Clone, Copy)] - pub struct [] { - start: $chunk, - end: $chunk, + pub struct [] { + start: $chunk

, + end: $chunk

, } - impl From<$TypeName> for [] { - fn from(r: $TypeName) -> Self { + impl From<$TypeName

> for []

{ + fn from(r: $TypeName

) -> Self { Self { start: *r.start(), end: *r.end() } } } - impl From<[]> for $TypeName { - fn from(cr: []) -> Self { + impl From<[]

> for $TypeName

{ + fn from(cr: []

) -> Self { Self::new(cr.start, cr.end) } } + impl From<$TypeName> for $TypeName { + fn from(r: $TypeName) -> Self { + Self::new($chunk::from(*r.start()), $chunk::from(*r.end())) + } + } + impl From<$TypeName> for $TypeName { + fn from(r: $TypeName) -> Self { + Self::new($chunk::from(*r.start()), $chunk::from(*r.end())) + } + } + impl TryFrom<$TypeName> for $TypeName { + type Error = &'static str; + fn try_from(p: $TypeName) -> Result { + if let Ok(aligned_upper_bound) = $chunk::::try_from(*p.end() + 1) { + return Ok(Self::new( + $chunk::::try_from(*p.start())?, + aligned_upper_bound - 1, + )); + } else { + return Err("Could not convert 4KiB page range into 2MiB page range."); + } + } + } + impl TryFrom<$TypeName> for $TypeName { + type Error = &'static str; + fn try_from(p: $TypeName) -> Result { + if let Ok(aligned_upper_bound) = $chunk::::try_from(*p.end() + 1) { + return Ok(Self::new( + $chunk::::try_from(*p.start())?, + aligned_upper_bound - 1, + )); + } else { + return Err("Could not convert 4KiB page range into 1GiB page range."); + } + } + } } }; } diff --git a/kernel/memory_structs/src/test.rs b/kernel/memory_structs/src/test.rs new file mode 100644 index 0000000000..134db5ddc0 --- /dev/null +++ b/kernel/memory_structs/src/test.rs @@ -0,0 +1,219 @@ +//! Tests sized variations of core paging types + +extern crate std; + +use super::*; + +#[test] +fn huge_2mb_range_size() { + let r = PageRange::::new( + Page::::containing_address_2mb(VirtualAddress::new(0x200000).unwrap()), + Page::::containing_address_2mb(VirtualAddress::new(0x400000 - 1).unwrap())); + + assert_eq!(r.end().number(), 512); + assert_eq!(r.start().number(), 512); + assert_eq!(r.size_in_pages(), 1); + assert_eq!(r.size_in_bytes(), 2097152); +} + +#[test] +fn huge_2mb_range_size2() { + let r: PageRange = PageRange::::new( + Page::::containing_address_2mb(VirtualAddress::new(0x200000).unwrap()), + Page::::containing_address_2mb(VirtualAddress::new(0x800000 - 1).unwrap())); + + assert_eq!(r.end().number(), 1536); + assert_eq!(r.start().number(), 512); + assert_eq!(r.size_in_pages(), 3); + assert_eq!(r.size_in_bytes(), 6291456); +} + +#[test] +fn huge_1gb_range_size() { + let r = PageRange::::new( + Page::::containing_address_1gb(VirtualAddress::new(0x40000000).unwrap()), + Page::::containing_address_1gb(VirtualAddress::new(0x80000000 - 1).unwrap())); + + assert_eq!(r.end().number(), 262144); + assert_eq!(r.start().number(), 262144); + assert_eq!(r.size_in_pages(), 1); + assert_eq!(r.size_in_bytes(), 1073741824); +} + +#[test] +fn huge_1gb_range_size2() { + let r = PageRange::::new( + Page::::containing_address_1gb(VirtualAddress::new(0x40000000).unwrap()), + Page::::containing_address_1gb(VirtualAddress::new(0x100000000 - 1).unwrap())); + + assert_eq!(r.end().number(), 786432); // 0xc0000000 + assert_eq!(r.start().number(), 262144); + assert_eq!(r.size_in_pages(), 3); + assert_eq!(r.size_in_bytes(), 3221225472); +} + +#[test] +fn huge_2mb_range_iteration1() { + let r = PageRange::::new( + Page::::containing_address_2mb(VirtualAddress::new(0x200000).unwrap()), + Page::::containing_address_2mb(VirtualAddress::new(0x400000 - 1).unwrap())); + let mut num_iters = 0; + for _ in r { + num_iters += 1; + } + assert_eq!(num_iters, 1); +} + +#[test] +fn huge_2mb_range_iteration2() { + let r = PageRange::::new( + Page::::containing_address_2mb(VirtualAddress::new(0x200000).unwrap()), + Page::::containing_address_2mb(VirtualAddress::new(0x800000 - 1).unwrap())); + let mut num_iters = 0; + // assert_eq!(r.start().number, 0x200000); + for _ in r { + num_iters += 1; + } + assert_eq!(num_iters, 3); +} + +#[test] +fn huge_2mb_range_iteration3() { + let r = PageRange::::new( + Page::::containing_address_2mb(VirtualAddress::new(0x200000).unwrap()), + Page::::containing_address_2mb(VirtualAddress::new(0x1000000 - 1).unwrap())); + let mut num_iters = 0; + for _ in r { + num_iters += 1; + } + assert_eq!(num_iters, 7); +} + +#[test] +fn huge_1gb_range_iteration() { + let r = PageRange::::new( + Page::::containing_address_1gb(VirtualAddress::new(0x40000000).unwrap()), + Page::::containing_address_1gb(VirtualAddress::new(0x80000000 - 1).unwrap())); + let mut num_iters = 0; + for _ in r { + num_iters += 1; + } + assert_eq!(num_iters, 1); +} + +#[test] +fn huge_1gb_range_iteration2() { + let r = PageRange::::new( + Page::::containing_address_1gb(VirtualAddress::new(0x40000000).unwrap()), + Page::::containing_address_1gb(VirtualAddress::new(0x100000000 - 1).unwrap())); + assert_eq!(r.size_in_pages(), 3); + + let mut num_iters = 0; + for _ in r { + num_iters += 1; + } + assert_eq!(num_iters, 3); +} + +#[test] +fn huge_1gb_from_4kb() { + let r = PageRange::new( + Page::containing_address(VirtualAddress::new(0x40000000).unwrap()), + Page::containing_address(VirtualAddress::new(0x80000000 - 1).unwrap())); + + + let new1gb = PageRange::::try_from(r).unwrap(); + + assert!(matches!(new1gb.start().page_size(), MemChunkSize::Huge1G)); + + let r = PageRange::new( + Page::containing_address(VirtualAddress::new(0x40000000).unwrap()), + Page::containing_address(VirtualAddress::new(0x42000000).unwrap())); + + let new1gb = PageRange::::try_from(r); + assert!(new1gb.is_err()); +} + +#[test] +fn huge_2gb_from_4kb() { + let r = PageRange::new( + Page::containing_address(VirtualAddress::new(0x200000).unwrap()), + Page::containing_address(VirtualAddress::new(0x800000 - 1).unwrap())); + + let new2mb = PageRange::::try_from(r).unwrap(); // r.into_2mb_range().unwrap(); + assert!(matches!(new2mb.start().page_size(), MemChunkSize::Huge2M)); + + let r = PageRange::new( + Page::containing_address(VirtualAddress::new(0x30000).unwrap()), + Page::containing_address(VirtualAddress::new(0x40000).unwrap())); + + let new2mb = PageRange::::try_from(r); + assert!(new2mb.is_err()); +} + +#[test] +fn standard_sized_from_1gb() { + let r = PageRange::::new( + Page::::containing_address_1gb(VirtualAddress::new(0x40000000).unwrap()), + Page::::containing_address_1gb(VirtualAddress::new(0x48000000 - 1).unwrap())); + + // Compiler needs to the size to be explicitly provided + let converted = PageRange::::from(r); + + assert!(matches!(converted.start().page_size(), MemChunkSize::Normal4K)); +} + +#[test] +fn standard_sized_from_2mb() { + let r = PageRange::::new( + Page::::containing_address_2mb(VirtualAddress::new(0x40000).unwrap()), + Page::::containing_address_2mb(VirtualAddress::new(0x80000 - 1).unwrap())); + + let converted = PageRange::::from(r); + + assert!(matches!(converted.start().page_size(), MemChunkSize::Normal4K)); +} + +#[test] +fn try_from_conversions() { + let r = PageRange::new( + Page::containing_address(VirtualAddress::new(0x200000).unwrap()), + Page::containing_address(VirtualAddress::new(0x800000 - 1).unwrap())); + assert_eq!(r.size_in_pages(), 1536); + + let new2mb = PageRange::::try_from(r).unwrap(); + assert!(matches!(new2mb.start().page_size(), MemChunkSize::Huge2M)); + assert_eq!(new2mb.size_in_pages(), 3); + + let r = PageRange::new( + Page::containing_address(VirtualAddress::new(0x40000000).unwrap()), + Page::containing_address(VirtualAddress::new(0x80000000 - 1).unwrap())); + assert_eq!(r.size_in_pages(), 262144); + + let new1gb = PageRange::::try_from(r).unwrap(); + assert!(matches!(new1gb.start().page_size(), MemChunkSize::Huge1G)); + assert_eq!(new1gb.size_in_pages(), 1); +} + +#[test] +fn test_chunk_addition() { + let page_2mb = Page::::containing_address_2mb(VirtualAddress::new(0x200000).unwrap()); + let page_1gb = Page::::containing_address_1gb(VirtualAddress::new(0x40000000).unwrap()); + + // original page num = 512. 512 + 1 = 1024 (which is the next huge page) + assert_eq!((page_2mb + 1).number(), 1024); + assert_eq!((page_1gb + 1).number(), 524288); + + assert_eq!((page_2mb + 2).number(), 1536); + assert_eq!((page_1gb + 2).number(), 786432); +} + +#[test] +fn test_chunk_subtraction() { + let page_2mb = Page::::containing_address_2mb(VirtualAddress::new(0x400000).unwrap()); + let page_1gb = Page::::containing_address_1gb(VirtualAddress::new(0x80000000).unwrap()); + + // original page num = 512. 512 + 1 = 1024 (which is the next huge page) + assert_eq!((page_2mb - 1).number(), 512); + assert_eq!((page_1gb - 1).number(), 262144); +} \ No newline at end of file