1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
//! Support for the Page Attribute Table (PAT) feature on x86.
//!
//! PAT replaces the legacy Memory Type Range Registers (MTRRs) on x86.
//! PAT allows the system to assign memory types, i.e., specify their caching behavior,
//! to regions of "linear" (virtual) addresses.
//! This is in contrast to MTRRs, which operate on regions of physical addresses.
#![no_std]
use log::*;
use modular_bitfield::{specifiers::{B3, B5}, bitfield};
use msr::IA32_PAT;
use raw_cpuid::CpuId;
use spin::Once;
// use x86_64::registers::control::{Cr0, Cr0Flags};
/// Theseus's fixed [`PageAttributeTable`] has slots that align with
/// the default meaning of page table entry bits on x86_64.
///
/// | Bit Position | If Clear (0) | If Set (1) |
/// |:-------------|:--------------|:---------------|
/// | 0 (LSB) | write back | write through |
/// | 1 (middle) | cache enabled | cache disabled |
/// | 2 (MSB) | N/A | N/A |
///
/// Thus, the following slots are chosen to align with those bits:
/// * Slot 0 (`0b000`): [MemoryCachingType::WriteBack]
/// * Slot 1 (`0b001`): [MemoryCachingType::WriteThrough]
/// * Slot 2 (`0b010`): [MemoryCachingType::Uncacheable] (caching disabled)
/// * Slot 3 (`0b011`): unused -- write through and cache enabled doesn't make sense,
/// but this is set up as `Uncacheable` because the cache disabled flag takes priority.
///
/// The following slots are available for custom use,
/// and Theseus currently sets them up as such:
/// * Slot 4 (`0b100`): [MemoryCachingType::WriteProtected]
/// * Slot 5 (`0b101`): [MemoryCachingType::WriteCombining]
/// * Slot 6 (`0b110`): [MemoryCachingType::UncachedMinus]
/// * Slot 7 (`0b111`): [MemoryCachingType::UncachedMinus]
///
/// Currently, the difference between `Uncacheable` and
/// `UncachedMinus` is not clear, so we offer slots for both.
///
/// ## Usage
/// You cannot and do not need to use this type directly, as it is
/// pre-set up statically for you.
/// Instead, use [`MemoryCachingType::pat_slot_index()`] to obtain
/// the index of the PAT slot that has been set up for whatever
/// `MemoryCachingType` you need.
pub static FIXED_PAT: PageAttributeTable = PageAttributeTable::from_bytes([
//
// NOTE: this order must be kept in sync with `MemoryCachingType::pat_slot_index`.
//
MemoryCachingType::WriteBack as u8, // 0: 0b000
MemoryCachingType::WriteThrough as u8, // 1: 0b001
MemoryCachingType::Uncacheable as u8, // 2: 0b010
MemoryCachingType::Uncacheable as u8, // 3: 0b011
MemoryCachingType::WriteProtected as u8, // 4: 0b100
MemoryCachingType::WriteCombining as u8, // 5: 0b101
MemoryCachingType::UncachedMinus as u8, // 6: 0b110
MemoryCachingType::UncachedMinus as u8, // 7: 0b111
]);
/// The Page Attribute Table (PAT) consists of 8 "slots" that can each
/// be configured with a different [MemoryCachingType].
#[bitfield(bits = 64)]
#[repr(u64)]
#[derive(Copy, Clone)]
pub struct PageAttributeTable {
#[skip] pat_slot_0: B3,
#[skip] _reserved0: B5,
#[skip] pat_slot_1: B3,
#[skip] _reserved1: B5,
#[skip] pat_slot_2: B3,
#[skip] _reserved2: B5,
#[skip] pat_slot_3: B3,
#[skip] _reserved3: B5,
#[skip] pat_slot_4: B3,
#[skip] _reserved4: B5,
#[skip] pat_slot_5: B3,
#[skip] _reserved5: B5,
#[skip] pat_slot_6: B3,
#[skip] _reserved6: B5,
#[skip] pat_slot_7: B3,
#[skip] _reserved7: B5,
}
/// The various types of memory caching that x86 supports
/// for usage in the [`PageAttributeTable`].
///
/// The default is [`MemoryCachingType::WriteBack`],
/// which corresponds to the standard default caching mode
/// when no specific page table entry flags are set.
#[doc(alias("pat", "mtrr", "page attribute table"))]
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum MemoryCachingType {
Uncacheable = 0x00,
WriteCombining = 0x01,
// 0x02 and 0x03 are reserved.
WriteThrough = 0x04,
WriteProtected = 0x05,
WriteBack = 0x06,
UncachedMinus = 0x07,
// All other values are reserved.
}
impl MemoryCachingType {
/// Returns the index of the [`PageAttributeTable`] (PAT) slot
/// that has been pre-configured with this `MemoryCachingType`.
///
/// See the docs of [FIXED_PAT] for more info.
pub const fn pat_slot_index(self) -> u8 {
//
// NOTE: this must be kept in sync with the definition of `FIXED_PAT`.
//
match self {
Self::WriteBack => 0,
Self::WriteThrough => 1,
Self::Uncacheable => 2, // also 3
Self::WriteProtected => 4,
Self::WriteCombining => 5,
Self::UncachedMinus => 6, // also 6
}
}
}
/// Returns `true` if the Page Attribute Table is supported on this system.
pub fn is_supported() -> bool {
// Cache the result of CpuId
static PAT_SUPPORT: Once<bool> = Once::new();
*PAT_SUPPORT.call_once(|| CpuId::new()
.get_feature_info()
.map(|finfo| finfo.has_pat())
.unwrap_or(false)
)
}
/// An empty error type indicating that the Page Attribute Table
/// is not supported on this machine.
#[derive(Debug)]
pub struct PatNotSupported;
/// Sets up and enables the Page Attribute Table (PAT) for this (the current) CPU.
///
/// This works by setting the [`IA32_PAT`] MSR to the value
/// specified by [`FIXED_PAT`];
/// thus, it must be done separately on each and every CPU.
///
/// Returns `Ok(())` upon success, and `Err(PatNotSupported::Unsupported)` if PAT is unsupported.
pub fn init() -> Result<(), PatNotSupported> {
if !is_supported() {
return Err(PatNotSupported);
}
// TODO: do we need to disable the cache using CR0 first? or flush it?
let mut pat_msr = x86_64::registers::model_specific::Msr::new(IA32_PAT);
unsafe {
pat_msr.write(FIXED_PAT.into());
}
debug!("Enabled the Page Attribute Table for the current CPU.");
Ok(())
}