Skip to content

Allow GDT to be loaded with shared reference #381

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

Merged
merged 9 commits into from
Apr 16, 2022
Merged
62 changes: 1 addition & 61 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! and access to various system registers.

#![cfg_attr(not(test), no_std)]
#![cfg_attr(feature = "const_fn", feature(const_mut_refs))] // GDT add_entry()
#![cfg_attr(feature = "const_fn", feature(const_mut_refs))] // GDT::append()
#![cfg_attr(feature = "asm_const", feature(asm_const))]
#![cfg_attr(feature = "abi_x86_interrupt", feature(abi_x86_interrupt))]
#![cfg_attr(feature = "step_trait", feature(step_trait))]
Expand All @@ -11,9 +11,6 @@
#![deny(missing_debug_implementations)]
#![deny(unsafe_op_in_unsafe_fn)]

use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicBool, Ordering};

pub use crate::addr::{align_down, align_up, PhysAddr, VirtAddr};

pub mod addr;
Expand Down Expand Up @@ -66,60 +63,3 @@ impl PrivilegeLevel {
}
}
}

/// A wrapper that can be used to safely create one mutable reference `&'static mut T` from a static variable.
///
/// `SingleUseCell` is safe because it ensures that it only ever gives out one reference.
///
/// ``SingleUseCell<T>` is a safe alternative to `static mut` or a static `UnsafeCell<T>`.
#[derive(Debug)]
pub struct SingleUseCell<T> {
used: AtomicBool,
value: UnsafeCell<T>,
}

impl<T> SingleUseCell<T> {
/// Construct a new SingleUseCell.
pub const fn new(value: T) -> Self {
Self {
used: AtomicBool::new(false),
value: UnsafeCell::new(value),
}
}

/// Try to acquire a mutable reference to the wrapped value.
/// This will only succeed the first time the function is
/// called and fail on all following calls.
///
/// ```
/// use x86_64::SingleUseCell;
///
/// static FOO: SingleUseCell<i32> = SingleUseCell::new(0);
///
/// // Call `try_get_mut` for the first time and get a reference.
/// let first: &'static mut i32 = FOO.try_get_mut().unwrap();
/// assert_eq!(first, &0);
///
/// // Calling `try_get_mut` again will return `None`.
/// assert_eq!(FOO.try_get_mut(), None);
/// ```
pub fn try_get_mut(&self) -> Option<&mut T> {
let already_used = self.used.swap(true, Ordering::AcqRel);
if already_used {
None
} else {
Some(unsafe {
// SAFETY: no reference has been given out yet and we won't give out another.
&mut *self.value.get()
})
}
}
}

// SAFETY: Sharing a `SingleUseCell<T>` between threads is safe regardless of whether `T` is `Sync`
// because we only expose the inner value once to one thread. The `T: Send` bound makes sure that
// sending a unique reference to another thread is safe.
unsafe impl<T: Send> Sync for SingleUseCell<T> {}

// SAFETY: It's safe to send a `SingleUseCell<T>` to another thread if it's safe to send `T`.
unsafe impl<T: Send> Send for SingleUseCell<T> {}
153 changes: 117 additions & 36 deletions src/structures/gdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,79 @@ use crate::structures::tss::TaskStateSegment;
use crate::PrivilegeLevel;
use bit_field::BitField;
use bitflags::bitflags;
use core::fmt;
// imports for intra-doc links
#[cfg(doc)]
use crate::registers::segmentation::{Segment, CS, SS};

#[cfg(feature = "instructions")]
use core::sync::atomic::{AtomicU64 as EntryValue, Ordering};
#[cfg(not(feature = "instructions"))]
use u64 as EntryValue;

/// 8-byte entry in a descriptor table.
///
/// A [`GlobalDescriptorTable`] (or LDT) is an array of these entries, and
/// [`SegmentSelector`]s index into this array. Each [`Descriptor`] in the table
/// uses either 1 Entry (if it is a [`UserSegment`](Descriptor::UserSegment)) or
/// 2 Entries (if it is a [`SystemSegment`](Descriptor::SystemSegment)). This
/// type exists to give users access to the raw entry bits in a GDT.
#[repr(transparent)]
pub struct Entry(EntryValue);

impl Entry {
// Create a new Entry from a raw value.
const fn new(raw: u64) -> Self {
#[cfg(feature = "instructions")]
let raw = EntryValue::new(raw);
Self(raw)
}

/// The raw bits for this entry. Depending on the [`Descriptor`] type, these
/// bits may correspond to those in [`DescriptorFlags`].
pub fn raw(&self) -> u64 {
// TODO: Make this const fn when AtomicU64::load is const.
#[cfg(feature = "instructions")]
let raw = self.0.load(Ordering::SeqCst);
#[cfg(not(feature = "instructions"))]
let raw = self.0;
raw
}
}

impl Clone for Entry {
fn clone(&self) -> Self {
Self::new(self.raw())
}
}

impl PartialEq for Entry {
fn eq(&self, other: &Self) -> bool {
self.raw() == other.raw()
}
}

impl Eq for Entry {}

impl fmt::Debug for Entry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Display inner value as hex
write!(f, "Entry({:#018x})", self.raw())
}
}

/// A 64-bit mode global descriptor table (GDT).
///
/// In 64-bit mode, segmentation is not supported. The GDT is used nonetheless, for example for
/// switching between user and kernel mode or for loading a TSS.
///
/// The GDT has a fixed maximum size given by the `MAX` const generic parameter.
/// Trying to add more entries than this maximum via [`GlobalDescriptorTable::add_entry`]
/// will panic.
/// Overflowing this limit by adding too many [`Descriptor`]s via
/// [`GlobalDescriptorTable::append`] will panic.
///
/// You do **not** need to add a null segment descriptor yourself - this is already done
/// internally. This means you can add up to `MAX - 1` additional [`Descriptor`]s to
/// this table.
/// internally. This means you can add up to `MAX - 1` additional [`Entry`]s to
/// this table. Note that some [`Descriptor`]s may take up 2 [`Entry`]s.
///
/// Data segment registers in ring 0 can be loaded with the null segment selector. When running in
/// ring 3, the `ss` register must point to a valid data segment which can be obtained through the
Expand All @@ -40,16 +97,16 @@ use crate::registers::segmentation::{Segment, CS, SS};
/// use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor};
///
/// let mut gdt = GlobalDescriptorTable::new();
/// gdt.add_entry(Descriptor::kernel_code_segment());
/// gdt.add_entry(Descriptor::user_code_segment());
/// gdt.add_entry(Descriptor::user_data_segment());
/// gdt.append(Descriptor::kernel_code_segment());
/// gdt.append(Descriptor::user_code_segment());
/// gdt.append(Descriptor::user_data_segment());
///
/// // Add entry for TSS, call gdt.load() then update segment registers
/// ```

#[derive(Debug, Clone)]
pub struct GlobalDescriptorTable<const MAX: usize = 8> {
table: [u64; MAX],
table: [Entry; MAX],
len: usize,
}

Expand All @@ -61,57 +118,73 @@ impl GlobalDescriptorTable {
}

impl<const MAX: usize> GlobalDescriptorTable<MAX> {
/// Creates an empty GDT which can hold `MAX` number of [`Descriptor`]s.
/// Creates an empty GDT which can hold `MAX` number of [`Entry`]s.
#[inline]
pub const fn empty() -> Self {
// TODO: Replace with compiler error when feature(generic_const_exprs) is stable.
assert!(MAX > 0, "A GDT cannot have 0 entries");
assert!(MAX <= (1 << 13), "A GDT can only have at most 2^13 entries");

// TODO: Replace with inline_const when it's stable.
#[allow(clippy::declare_interior_mutable_const)]
const NULL: Entry = Entry::new(0);
Self {
table: [0; MAX],
table: [NULL; MAX],
len: 1,
}
}

/// Forms a GDT from a slice of `u64`.
///
/// # Safety
/// This method allows for creation of a GDT with malformed or invalid
/// entries. However, it is safe because loading a GDT with invalid
/// entires doesn't do anything until those entries are used. For example,
/// [`CS::set_reg`] and [`load_tss`](crate::instructions::tables::load_tss)
/// are both unsafe for this reason.
///
/// * The user must make sure that the entries are well formed
/// * Panics if the provided slice has more than `MAX` entries
/// Panics if:
/// * the provided slice has more than `MAX` entries
/// * the provided slice is empty
/// * the first entry is not zero
#[cfg_attr(not(feature = "instructions"), allow(rustdoc::broken_intra_doc_links))]
#[inline]
pub const unsafe fn from_raw_slice(slice: &[u64]) -> Self {
pub const fn from_raw_entries(slice: &[u64]) -> Self {
let len = slice.len();
let mut table = [0; MAX];
let mut table = Self::empty().table;
let mut idx = 0;

assert!(len > 0, "cannot initialize GDT with empty slice");
assert!(slice[0] == 0, "first GDT entry must be zero");
assert!(
len <= MAX,
"cannot initialize GDT with slice exceeding the maximum length"
);

while idx < len {
table[idx] = slice[idx];
table[idx] = Entry::new(slice[idx]);
idx += 1;
}

Self { table, len }
}

/// Get a reference to the internal table.
/// Get a reference to the internal [`Entry`] table.
///
/// The resulting slice may contain system descriptors, which span two `u64`s.
/// The resulting slice may contain system descriptors, which span two [`Entry`]s.
#[inline]
pub fn as_raw_slice(&self) -> &[u64] {
pub fn entries(&self) -> &[Entry] {
&self.table[..self.len]
}

/// Adds the given segment descriptor to the GDT, returning the segment selector.
/// Appends the given segment descriptor to the GDT, returning the segment selector.
///
/// Note that depending on the type of the [`Descriptor`] this may append
/// either one or two new [`Entry`]s to the table.
///
/// Panics if the GDT doesn't have enough free entries to hold the Descriptor.
/// Panics if the GDT doesn't have enough free entries.
#[inline]
#[cfg_attr(feature = "const_fn", rustversion::attr(all(), const))]
pub fn add_entry(&mut self, entry: Descriptor) -> SegmentSelector {
pub fn append(&mut self, entry: Descriptor) -> SegmentSelector {
let index = match entry {
Descriptor::UserSegment(value) => {
if self.len > self.table.len().saturating_sub(1) {
Expand Down Expand Up @@ -150,7 +223,7 @@ impl<const MAX: usize> GlobalDescriptorTable<MAX> {
/// [`SS::set_reg()`] and [`CS::set_reg()`].
#[cfg(feature = "instructions")]
#[inline]
pub fn load(&'static mut self) {
pub fn load(&'static self) {
// SAFETY: static lifetime ensures no modification after loading.
unsafe { self.load_unsafe() };
}
Expand All @@ -168,7 +241,7 @@ impl<const MAX: usize> GlobalDescriptorTable<MAX> {
///
#[cfg(feature = "instructions")]
#[inline]
pub unsafe fn load_unsafe(&mut self) {
pub unsafe fn load_unsafe(&self) {
use crate::instructions::tables::lgdt;
unsafe {
lgdt(&self.pointer());
Expand All @@ -179,7 +252,7 @@ impl<const MAX: usize> GlobalDescriptorTable<MAX> {
#[cfg_attr(feature = "const_fn", rustversion::attr(all(), const))]
fn push(&mut self, value: u64) -> usize {
let index = self.len;
self.table[index] = value;
self.table[index] = Entry::new(value);
self.len += 1;
index
}
Expand Down Expand Up @@ -378,11 +451,11 @@ mod tests {
// Makes a GDT that has two free slots
fn make_six_entry_gdt() -> GlobalDescriptorTable {
let mut gdt = GlobalDescriptorTable::new();
gdt.add_entry(Descriptor::kernel_code_segment());
gdt.add_entry(Descriptor::kernel_data_segment());
gdt.add_entry(Descriptor::UserSegment(DescriptorFlags::USER_CODE32.bits()));
gdt.add_entry(Descriptor::user_data_segment());
gdt.add_entry(Descriptor::user_code_segment());
gdt.append(Descriptor::kernel_code_segment());
gdt.append(Descriptor::kernel_data_segment());
gdt.append(Descriptor::UserSegment(DescriptorFlags::USER_CODE32.bits()));
gdt.append(Descriptor::user_data_segment());
gdt.append(Descriptor::user_code_segment());
assert_eq!(gdt.len, 6);
gdt
}
Expand All @@ -391,7 +464,7 @@ mod tests {

fn make_full_gdt() -> GlobalDescriptorTable {
let mut gdt = make_six_entry_gdt();
gdt.add_entry(Descriptor::tss_segment(&TSS));
gdt.append(Descriptor::tss_segment(&TSS));
assert_eq!(gdt.len, 8);
gdt
}
Expand All @@ -400,9 +473,9 @@ mod tests {
pub fn push_max_segments() {
// Make sure we don't panic with user segments
let mut gdt = make_six_entry_gdt();
gdt.add_entry(Descriptor::user_data_segment());
gdt.append(Descriptor::user_data_segment());
assert_eq!(gdt.len, 7);
gdt.add_entry(Descriptor::user_data_segment());
gdt.append(Descriptor::user_data_segment());
assert_eq!(gdt.len, 8);
// Make sure we don't panic with system segments
let _ = make_full_gdt();
Expand All @@ -412,15 +485,23 @@ mod tests {
#[should_panic]
pub fn panic_user_segment() {
let mut gdt = make_full_gdt();
gdt.add_entry(Descriptor::user_data_segment());
gdt.append(Descriptor::user_data_segment());
}

#[test]
#[should_panic]
pub fn panic_system_segment() {
let mut gdt = make_six_entry_gdt();
gdt.add_entry(Descriptor::user_data_segment());
gdt.append(Descriptor::user_data_segment());
// We have one free slot, but the GDT requires two
gdt.add_entry(Descriptor::tss_segment(&TSS));
gdt.append(Descriptor::tss_segment(&TSS));
}

#[test]
pub fn from_entries() {
let raw = [0, Flags::KERNEL_CODE64.bits(), Flags::KERNEL_DATA.bits()];
let gdt = GlobalDescriptorTable::<3>::from_raw_entries(&raw);
assert_eq!(gdt.table.len(), 3);
assert_eq!(gdt.entries().len(), 3);
}
}
Loading