rustubs/arch/x86_64/gdt.rs
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
//! the x86 gdt struct is so obscure and it's not worth the lines of code to
//! write proper high level representaion. Also since it only needs to be
//! written to once or twice, I'll live with the hard coded stuffs in this mod.
//! You need to suffer all this pain exists because intel/amd doens't want to
//! ditch segmentation due to backward compatibility. THIS REALLY SUCKS.
use crate::defs::P2V;
use bit_field::BitField;
use core::mem::size_of;
use core::{arch::asm, slice::from_raw_parts_mut};
// these are 32 bit low-address symbols. we need to promote them to high
// address mapping.
extern "C" {
fn gdt();
// tss_desc is part of the gdt, this tag is only for convenience
fn tss_desc();
fn gdt_80();
// tss0 is already reserved in high memory
fn tss0();
}
/// promote the gdt to high address. unsafe: only call this before dropping low
/// memory mapping. and only call this once
pub unsafe fn init() {
let gdtd = unsafe { &mut *(gdt_80 as *mut GDTDescriptor) };
// sanity check
debug_assert!(gdtd.table_size == 7 * 8 - 1);
gdtd.table_addr = P2V(gdt as u64).unwrap();
unsafe { asm!("lgdt [{}]", in (reg) P2V(gdt_80 as u64).unwrap()) }
// set up tss
let tssd = from_raw_parts_mut(tss_desc as *mut u64, 2);
let (low, high) = to_tss_desc(tss0 as u64);
tssd[0] = low;
tssd[1] = high;
// load tss. Fuck you x86 why this one don't need to minus one?
// 0x28 for the 6th entry in gdt.
asm!("ltr {0:x}", in(reg) 0x28, options(nostack, preserves_flags));
}
pub unsafe fn set_tss_ksp(ksp: u64) {
let tss = tss0 as *mut TaskStateSegment;
(*tss).privilege_stack_table[0] = ksp;
}
/// gdtd describes the gdt, don't be confused, gdtd is not a gdt entdy (segment
/// descriptor)
#[repr(C)]
#[repr(packed)]
struct GDTDescriptor {
pub table_size: u16,
pub table_addr: u64,
}
// takes the address of the tss segment and returns its descriptor in the
// gdt(high, low). The low desc contains only the PRESENT bit
fn to_tss_desc(tss_addr: u64) -> (u64, u64) {
// present
let mut low: u64 = 1 << 47;
// base
low.set_bits(16..40, tss_addr.get_bits(0..24));
low.set_bits(56..64, tss_addr.get_bits(24..32));
// limit (the `-1` in needed since the bound is inclusive)
low.set_bits(0..16, (size_of::<TaskStateSegment>() - 1) as u64);
// type (0b1001 = available 64-bit tss)
low.set_bits(40..44, 0b1001);
let mut high: u64 = 0;
high.set_bits(0..32, tss_addr.get_bits(32..64));
(low, high)
}
// below are copied from:
// https://docs.rs/x86_64/0.15.1/src/x86_64/structures/tss.rs.html#12-23
// TODO: add attributions
#[derive(Debug, Clone, Copy)]
#[repr(C, packed(4))]
pub struct TaskStateSegment {
reserved_1: u32,
/// The full 64-bit canonical forms of the stack pointers (RSP) for
/// privilege levels 0-2.
pub privilege_stack_table: [u64; 3],
reserved_2: u64,
/// The full 64-bit canonical forms of the interrupt stack table (IST)
/// pointers.
pub interrupt_stack_table: [u64; 7],
reserved_3: u64,
reserved_4: u16,
/// The 16-bit offset to the I/O permission bit map from the 64-bit TSS
/// base.
pub iomap_base: u16,
}
impl TaskStateSegment {
/// Creates a new TSS with zeroed privilege and interrupt stack table and an
/// empty I/O-Permission Bitmap.
///
/// As we always set the TSS segment limit to
/// `size_of::<TaskStateSegment>() - 1`, this means that `iomap_base` is
/// initialized to `size_of::<TaskStateSegment>()`.
#[inline]
pub const fn new() -> TaskStateSegment {
TaskStateSegment {
privilege_stack_table: [0; 3],
interrupt_stack_table: [0; 7],
iomap_base: size_of::<TaskStateSegment>() as u16,
reserved_1: 0,
reserved_2: 0,
reserved_3: 0,
reserved_4: 0,
}
}
}