Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Architecture-dependent Page Table Entry (PTE) flags for x86_64 and aarch64 #699

Merged
merged 11 commits into from
Nov 25, 2022

Conversation

kevinaboos
Copy link
Member

@kevinaboos kevinaboos commented Nov 19, 2022

For purposes of comparison with #696

Remaining TODO: add #[test] cases for the four conversion routines. Done

Copy link
Member

@NathanRoyer NathanRoyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, other than the two other comments I left on specific lines.

Not so important: PteFlags has the flags that best match x86_64 & aarch64; if we want to support risc-v in the future, would we change it based on flags common to aarch64, x86_64 & risc-v? I guess we'll see in the future

kernel/pte_flags/src/pte_flags_aarch64.rs Outdated Show resolved Hide resolved
kernel/pte_flags/src/lib.rs Show resolved Hide resolved
@kevinaboos kevinaboos marked this pull request as ready for review November 22, 2022 02:33
@kevinaboos
Copy link
Member Author

kevinaboos commented Nov 22, 2022

ok this should be completely ready now. @NathanRoyer please re-review and also see how I did the cool cfg stuff with docs

@NathanRoyer
Copy link
Member

NathanRoyer commented Nov 22, 2022

ok this should be completely ready now. @NathanRoyer please re-review

Your solution for ACCESSED will work. However, regarding PAGE_DESCRIPTOR, I'm not sure I understand why we shouldn't put it by default like ACCESSED because it's also a requirement for things to work... Without PAGE_DESCRIPTOR, all the other flags will be interpreted as if they were in a block descriptor

edit: we solved this during our meeting

also see how I did the cool cfg stuff with docs

Very nice, didn't know about this trick

@NathanRoyer
Copy link
Member

Also, should we add a dummy PteFlags::is_huge(self) -> bool method? one that would always returns false?
(For the moment I commented out the calls to that method in memory)

@NathanRoyer
Copy link
Member

By manually setting the bit, the descriptor was accepted into the TLB and I encountered no page fault. Maybe this was because of my configuration of translation control registers though? I'll check. It could also be qemu not implementing FEAT_HAFDBS.

Something I forgot: TCR_EL1 (the translation control register) has two flags that control the behaviour of the CPU regarding the ACCESSED and DIRTY flags: HA (for ACCESSED) and HD (for DIRTY). However, even with these enabled, qemu triggers an access fault when the bit is initially cleared.

Regarding the output address size: I was using 48-bit OA mode in my experimental code; I assumed we could only address up to 48 bits (4 levels * 9 bits + 12 bits).

However, actually one of the following configurations can be selected (using TCR_ELn::IPS and TCR_ELn::DS - weirdly, the latter is not exposed by cortex_a... I assumed the DS field was just not present in TCR_EL1):

image

image

As we can see, the 52-bit output address mode is a bit more complex as some bits of the OA are shifted to the middle of lower attributes. This allows the dirty bit to always occupy the 51st bit. In 48-bit OA mode, bits 48 & 49 are reserved and bit 50 is the Guarded Page bit - implementation is optional.

In 52-bit OA mode, the shareability attributes are stored in the TCR_EL1 register, specifcially in field SH0 for TTBR0 page table walks and in field SH1 for TTBR1 page table walks. This limits us to two shareability profiles and also complexifies page table management a bit since that requires us to maintain two page tables at all time.

Apparently long mode x86_64 also supports 52-bit OA mode; I don't know how this is configured however, but maybe you know.

@kevinaboos
Copy link
Member Author

Also, should we add a dummy PteFlags::is_huge(self) -> bool method? one that would always returns false? (For the moment I commented out the calls to that method in memory)

whoa, definitely not. That would be invalid and could lead to us completely misinterpreting what PTE values mean. For a concrete example of why, in the initial bootstrap assembly code (boot.asm) we actually do use huge pages to efficiently and easily map large regions of the very lowest and very highest addresses before jumping to Rust code. If we attempted to call translate() on one of those addresses early on, it would completely fail because it would unconditionally consider it a non-huge page PTE.

In any case, why do something like that which could confuse the caller? It also acts as a sanity check if we ever do come across a mapping that uses huge pages, or in the future when we add proper support for them.

@kevinaboos
Copy link
Member Author

By manually setting the bit, the descriptor was accepted into the TLB and I encountered no page fault. Maybe this was because of my configuration of translation control registers though? I'll check. It could also be qemu not implementing FEAT_HAFDBS.

Something I forgot: TCR_EL1 (the translation control register) has two flags that control the behaviour of the CPU regarding the ACCESSED and DIRTY flags: HA (for ACCESSED) and HD (for DIRTY). However, even with these enabled, qemu triggers an access fault when the bit is initially cleared.

That's fine, it seems that those bits just allow hardware to set the access flag (as is the default behavior on x86) instead of the OS always doing it (?). Looks like the Access Flag Fault always occurs regardless of HA/HD value, so let's just always set the ACCESSED bit for now, but also implement an Access Flag Fault handler such that we know when it happens (and can handle those faults in the future if/when we support swapping pages to disk).

@kevinaboos
Copy link
Member Author

Regarding the output address size: I was using 48-bit OA mode in my experimental code; I assumed we could only address up to 48 bits (4 levels * 9 bits + 12 bits).

48-bit is fine with me, we can use that for now and if it presents problems we can move to 52-bit.

As we can see, the 52-bit output address mode is a bit more complex as some bits of the OA are shifted to the middle of lower attributes. This allows the dirty bit to always occupy the 51st bit. In 48-bit OA mode, bits 48 & 49 are reserved and bit 50 is the Guarded Page bit - implementation is optional.

I was wondering about the DIRTY bit, that makes sense. Glad we resolved that.

In 52-bit OA mode, the shareability attributes are stored in the TCR_EL1 register, specifcially in field SH0 for TTBR0 page table walks and in field SH1 for TTBR1 page table walks. This limits us to two shareability profiles and also complexifies page table management a bit since that requires us to maintain two page tables at all time.

We'd only ever use OUTER_SHAREABLE anyway, so that's not a problem. We could just use the same page tables for both TTBRs, i think.

Apparently long mode x86_64 also supports 52-bit OA mode; I don't know how this is configured however, but maybe you know.

We don't use it, but it's much simpler on x86 -- the 52-bit physical address can pretty much always be used.

@kevinaboos
Copy link
Member Author

@NathanRoyer I have addressed all of our comments, please double-check everything again.

Important Note: in the memory_structs crate, you will need to implement aarch64-specific canonicalization for 48-bit physical addresses, since they now differ from the 52-bit physical addresses we use on x86_64.
Also, I assume that virtual addresses on aarch64 are also canonicalized in a similar fashion to x86; if not, you'll need to handle that too.
This stuff is on L129-159 of memory_structs/src/lib.rs

@NathanRoyer
Copy link
Member

@NathanRoyer I have addressed all of our comments, please double-check everything again.

About PteFlagsAarch64::_CONTIGUOUS:

If not set, this translation table is not contiguous with the previous one in memory.

I think it's not necessarily the previous one; from the DDI0487:

The Contiguous bit identifies a descriptor as belonging to a group of adjacent translation table entries that point to a contiguous OA range.

But anyway, the nuance is small and we don't use it so I'll leave it as is.

Important Note: in the memory_structs crate, you will need to implement aarch64-specific canonicalization for 48-bit physical addresses, since they now differ from the 52-bit physical addresses we use on x86_64. Also, I assume that virtual addresses on aarch64 are also canonicalized in a similar fashion to x86; if not, you'll need to handle that too. This stuff is on L129-159 of memory_structs/src/lib.rs

I responded to this on Discord: the most significant bits of an address are typically used for tagged addresses (ASID) or ignored, when that mechanism is disabled.

I will approve this, you can merge it in.

@kevinaboos
Copy link
Member Author

About PteFlagsAarch64::_CONTIGUOUS:

If not set, this translation table is not contiguous with the previous one in memory.

I think it's not necessarily the previous one; from the DDI0487:

The Contiguous bit identifies a descriptor as belonging to a group of adjacent translation table entries that point to a contiguous OA range.

Right, thanks for pointing that out. I'll fix it.

@kevinaboos kevinaboos merged commit 29ffb09 into theseus-os:theseus_main Nov 25, 2022
@kevinaboos kevinaboos deleted the arch_generic_pte_flags branch November 25, 2022 20:25
github-actions bot pushed a commit that referenced this pull request Nov 25, 2022
…4` (#699)

* The `pte_flags` crate will replace `entryflags` and supports both `aarch64` & `x86_64`

* There are "lower-level" (architecture-specific) PTE flag types that can be converted to and from a "higher-level" (architecture-independent) `PteFlags` type.

* Currently unused in Theseus, but will be used in an upcoming PR.

Co-authored-by: Nathan Royer <[email protected]> 29ffb09
kevinaboos added a commit that referenced this pull request Dec 5, 2022
* The `pte_flags` crate offers the following improvements:
  * Builder-style functions to set or clear each flag.
  * A unified interface for checking or setting PTE flags
    across all architectures. 
  * The ability to be as generic or as specific to an architecture
    as you like.
  * Lossless conversions from the generic `PteFlags` to the
    arch-specific `PteFlags<Arch>`, and lossy conversions from
    the arch-specific `PteFlags<Arch>` to the generic `PteFlags`.
  * See #699 and #712 for more details.

* All memory mapping functions now accept the PTE flags
  parameter as an instance of `Into<PteFlagsArch>`,
  which allows both arch-generic `PteFlags` and arch-specific
  `PteFlagsX86_64` or `PteFlagsAarch64` to be used.
github-actions bot pushed a commit that referenced this pull request Dec 5, 2022
* The `pte_flags` crate offers the following improvements:
  * Builder-style functions to set or clear each flag.
  * A unified interface for checking or setting PTE flags
    across all architectures.
  * The ability to be as generic or as specific to an architecture
    as you like.
  * Lossless conversions from the generic `PteFlags` to the
    arch-specific `PteFlags<Arch>`, and lossy conversions from
    the arch-specific `PteFlags<Arch>` to the generic `PteFlags`.
  * See #699 and #712 for more details.

* All memory mapping functions now accept the PTE flags
  parameter as an instance of `Into<PteFlagsArch>`,
  which allows both arch-generic `PteFlags` and arch-specific
  `PteFlagsX86_64` or `PteFlagsAarch64` to be used. e7847d5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants