diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bits32/mod.rs | 1 | ||||
-rw-r--r-- | src/bits32/segmentation.rs | 40 | ||||
-rw-r--r-- | src/bits64/irq.rs | 40 | ||||
-rw-r--r-- | src/bits64/mod.rs | 16 | ||||
-rw-r--r-- | src/bits64/paging.rs | 1 | ||||
-rw-r--r-- | src/bits64/segmentation.rs | 56 | ||||
-rw-r--r-- | src/bits64/task.rs | 7 | ||||
-rw-r--r-- | src/shared/control_regs.rs | 36 | ||||
-rw-r--r-- | src/shared/descriptor.rs | 2 | ||||
-rw-r--r-- | src/shared/dtables.rs | 24 | ||||
-rw-r--r-- | src/shared/segmentation.rs | 70 |
11 files changed, 192 insertions, 101 deletions
diff --git a/src/bits32/mod.rs b/src/bits32/mod.rs index e98b862..2ffe79f 100644 --- a/src/bits32/mod.rs +++ b/src/bits32/mod.rs @@ -1,4 +1,5 @@ pub mod irq; +pub mod segmentation; pub mod task; #[inline(always)] diff --git a/src/bits32/segmentation.rs b/src/bits32/segmentation.rs new file mode 100644 index 0000000..31547b0 --- /dev/null +++ b/src/bits32/segmentation.rs @@ -0,0 +1,40 @@ +use core::mem::size_of; + +use bits32::task::*; +use shared::descriptor; +use shared::PrivilegeLevel; +pub use shared::segmentation::*; + +/// Reload code segment register. +/// Note this is special since we can not directly move +/// to %cs. Instead we push the new segment selector +/// and return value on the stack and use lretl +/// to reload cs and continue at 1:. +pub unsafe fn set_cs(sel: SegmentSelector) { + asm!("pushl $0; \ + pushl $$1f; \ + lretl; \ + 1:" :: "ri" (sel.bits() as usize) : "memory"); +} + +impl SegmentDescriptor { + pub fn new_memory(base: u32, limit: u32, ty: Type, accessed: bool, dpl: PrivilegeLevel) -> SegmentDescriptor { + let ty1 = descriptor::Type::SegmentDescriptor { + ty: ty, + accessed: accessed, + }; + let flags = FLAGS_DB; + let seg = SegmentDescriptor::memory_or_tss(base, limit, ty1, dpl, flags); + seg + } + + pub fn new_tss(tss: &TaskStateSegment, dpl: PrivilegeLevel) -> SegmentDescriptor { + let tss_ptr = tss as *const TaskStateSegment; + let ty1 = descriptor::Type::SystemDescriptor { + size: true, + ty: descriptor::SystemType::TssAvailable, + }; + let seg = SegmentDescriptor::memory_or_tss(tss_ptr as u32, size_of::<TaskStateSegment>() as u32, ty1, dpl, Flags::empty()); + seg + } +} diff --git a/src/bits64/irq.rs b/src/bits64/irq.rs index 84ee0e6..0f8f9ce 100644 --- a/src/bits64/irq.rs +++ b/src/bits64/irq.rs @@ -2,6 +2,7 @@ use core::fmt; +use bits64::segmentation::SegmentSelector; use shared::descriptor::*; use shared::paging::VAddr; use shared::PrivilegeLevel; @@ -9,16 +10,16 @@ use shared::PrivilegeLevel; /// An interrupt gate descriptor. /// /// See Intel manual 3a for details, specifically section "6.14.1 64-Bit Mode -/// IDT" and "Table 3-2. System-Segment and Gate-Descriptor Types". +/// IDT" and "Figure 6-7. 64-Bit IDT Gate Descriptors". #[derive(Debug, Clone, Copy)] #[repr(C, packed)] pub struct IdtEntry { /// Lower 16 bits of ISR. pub base_lo: u16, /// Segment selector. - pub selector: u16, + pub selector: SegmentSelector, /// This must always be zero. - pub reserved0: u8, + pub ist_index: u8, /// Flags. pub flags: Flags, /// The upper 48 bits of ISR (the last 16 bits must be zero). @@ -27,6 +28,20 @@ pub struct IdtEntry { pub reserved1: u16, } +pub enum Type { + InterruptGate, + TrapGate, +} + +impl Type { + pub fn pack(self) -> Flags { + match self { + Type::InterruptGate => FLAGS_TYPE_SYS_NATIVE_INTERRUPT_GATE, + Type::TrapGate => FLAGS_TYPE_SYS_NATIVE_TRAP_GATE, + } + } +} + impl IdtEntry { /// A "missing" IdtEntry. /// @@ -35,8 +50,8 @@ impl IdtEntry { /// some other data stored in the error code. pub const MISSING: IdtEntry = IdtEntry { base_lo: 0, - selector: 0, - reserved0: 0, + selector: SegmentSelector::from_raw(0), + ist_index: 0, flags: Flags::BLANK, base_hi: 0, reserved1: 0, @@ -49,20 +64,17 @@ impl IdtEntry { /// /// The "Present" flag set, which is the most common case. If you need /// something else, you can construct it manually. - pub const fn new(handler: VAddr, gdt_code_selector: u16, - dpl: PrivilegeLevel, block: bool) -> IdtEntry { + pub fn new(handler: VAddr, gdt_code_selector: SegmentSelector, + dpl: PrivilegeLevel, ty: Type, ist_index: u8) -> IdtEntry { + assert!(ist_index < 0b1000); IdtEntry { base_lo: ((handler.as_usize() as u64) & 0xFFFF) as u16, base_hi: handler.as_usize() as u64 >> 16, selector: gdt_code_selector, - reserved0: 0, - // Nice bitflags operations don't work in const fn, hence these - // ad-hoc methods. + ist_index: ist_index, flags: Flags::from_priv(dpl) - .const_or(FLAGS_TYPE_SYS_NATIVE_INTERRUPT_GATE - .const_mux(FLAGS_TYPE_SYS_NATIVE_TRAP_GATE, - block)) - .const_or(FLAGS_PRESENT), + | ty.pack() + | FLAGS_PRESENT, reserved1: 0, } } diff --git a/src/bits64/mod.rs b/src/bits64/mod.rs index b6694f5..7409435 100644 --- a/src/bits64/mod.rs +++ b/src/bits64/mod.rs @@ -15,23 +15,9 @@ macro_rules! check_flag { ) } -macro_rules! is_bit_set { - ($field:expr, $bit:expr) => ( - $field & (1 << $bit) > 0 - ) -} - -macro_rules! check_bit_fn { - ($doc:meta, $fun:ident, $field:ident, $bit:expr) => ( - #[$doc] - pub fn $fun(&self) -> bool { - is_bit_set!(self.$field, $bit) - } - ) -} - pub mod irq; pub mod paging; +pub mod segmentation; pub mod task; pub mod syscall; pub mod sgx; diff --git a/src/bits64/paging.rs b/src/bits64/paging.rs index 47ac8ed..8858de8 100644 --- a/src/bits64/paging.rs +++ b/src/bits64/paging.rs @@ -74,6 +74,7 @@ pub type PD = [PDEntry; 512]; pub type PT = [PTEntry; 512]; /// Given virtual address calculate corresponding entry in PML4. +#[inline] pub fn pml4_index(addr: VAddr) -> usize { (addr.as_usize() >> 39) & 0b111111111 } diff --git a/src/bits64/segmentation.rs b/src/bits64/segmentation.rs new file mode 100644 index 0000000..529efb9 --- /dev/null +++ b/src/bits64/segmentation.rs @@ -0,0 +1,56 @@ +use core::mem::size_of; + +use bits64::task::*; +use shared::descriptor; +use shared::PrivilegeLevel; +pub use shared::segmentation::*; + +/// Reload code segment register. +/// Note this is special since we can not directly move +/// to %cs. Instead we push the new segment selector +/// and return value on the stack and use lretq +/// to reload cs and continue at 1:. +pub unsafe fn set_cs(sel: SegmentSelector) { + asm!("pushq $0; \ + leaq 1f(%rip), %rax; \ + pushq %rax; \ + lretq; \ + 1:" :: "ri" (sel.bits() as usize) : "rax" "memory"); +} + +pub enum SegmentBitness { + Bits32, + Bits64, +} + +impl SegmentBitness { + pub fn pack(self) -> Flags { + match self { + SegmentBitness::Bits32 => FLAGS_DB, + SegmentBitness::Bits64 => FLAGS_L, + } + } +} + +impl SegmentDescriptor { + pub fn new_memory(base: u32, limit: u32, ty: Type, accessed: bool, dpl: PrivilegeLevel, bitness: SegmentBitness) -> SegmentDescriptor { + let ty1 = descriptor::Type::SegmentDescriptor { + ty: ty, + accessed: accessed, + }; + let flags = bitness.pack(); + let seg = SegmentDescriptor::memory_or_tss(base, limit, ty1, dpl, flags); + seg + } + + pub fn new_tss(tss: &TaskStateSegment, dpl: PrivilegeLevel) -> [SegmentDescriptor; 2] { + let tss_ptr = tss as *const TaskStateSegment; + let ty1 = descriptor::Type::SystemDescriptor { + size: true, + ty: descriptor::SystemType::TssAvailable, + }; + let seg1 = SegmentDescriptor::memory_or_tss(tss_ptr as u32, size_of::<TaskStateSegment>() as u32, ty1, dpl, Flags::empty()); + let seg2 = SegmentDescriptor::high(tss_ptr as u64); + [seg1, seg2] + } +} diff --git a/src/bits64/task.rs b/src/bits64/task.rs index 1d98bad..c7dc781 100644 --- a/src/bits64/task.rs +++ b/src/bits64/task.rs @@ -1,16 +1,11 @@ //! Helpers to program the task state segment. //! See Intel 3a, Chapter 7, Section 7 -use shared::segmentation; - -pub type TaskStateDescriptorLow = segmentation::SegmentDescriptor; -pub type TaskStateDescriptorHigh = u64; - /// In 64-bit mode the TSS holds information that is not /// directly related to the task-switch mechanism, /// but is used for finding kernel level stack /// if interrupts arrive while in kernel mode. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] #[repr(C, packed)] pub struct TaskStateSegment { pub reserved: u32, diff --git a/src/shared/control_regs.rs b/src/shared/control_regs.rs index 306e52f..1a1f6a3 100644 --- a/src/shared/control_regs.rs +++ b/src/shared/control_regs.rs @@ -1,5 +1,5 @@ //! Functions to read and write control registers. -//! See Intel Vol. 3a Section 2.5, especially Figure 2.6. +//! See Intel Vol. 3a Section 2.5, especially Figure 2-7. bitflags! { pub flags Cr0: usize { @@ -19,12 +19,15 @@ bitflags! { bitflags! { pub flags Cr4: usize { + const CR4_ENABLE_PROTECTION_KEY = 1 << 22, const CR4_ENABLE_SMAP = 1 << 21, const CR4_ENABLE_SMEP = 1 << 20, const CR4_ENABLE_OS_XSAVE = 1 << 18, const CR4_ENABLE_PCID = 1 << 17, + const CR4_ENABLE_FSGSBASE = 1 << 16, const CR4_ENABLE_SMX = 1 << 14, const CR4_ENABLE_VMX = 1 << 13, + const CR4_ENABLE_UMIP = 1 << 11, const CR4_UNMASKED_SSE = 1 << 10, const CR4_ENABLE_SSE = 1 << 9, const CR4_ENABLE_PPMC = 1 << 8, @@ -39,6 +42,20 @@ bitflags! { } } +bitflags! { + pub flags Xcr0: u64 { + const XCR0_PKRU_STATE = 1 << 9, + const XCR0_HI16_ZMM_STATE = 1 << 7, + const XCR0_ZMM_HI256_STATE = 1 << 6, + const XCR0_OPMASK_STATE = 1 << 5, + const XCR0_BNDCSR_STATE = 1 << 4, + const XCR0_BNDREG_STATE = 1 << 3, + const XCR0_AVX_STATE = 1 << 2, + const XCR0_SSE_STATE = 1 << 1, + const XCR0_FPU_MMX_STATE = 1 << 0, + } +} + /// Read cr0 pub unsafe fn cr0() -> Cr0 { @@ -82,3 +99,20 @@ pub unsafe fn cr4() -> Cr4 { pub unsafe fn cr4_write(val: Cr4) { asm!("mov $0, %cr4" :: "r" (val.bits) : "memory"); } + +/// Read Extended Control Register XCR0. +/// Only supported if CR4_ENABLE_OS_XSAVE is set. +pub unsafe fn xcr0() -> Xcr0 { + let high: u32; + let low: u32; + asm!("xgetbv" : "={eax}"(low), "={edx}"(high) : "{ecx}"(0)); + Xcr0::from_bits_truncate((high as u64) << 32 | low as u64) +} + +/// Write to Extended Control Register XCR0. +/// Only supported if CR4_ENABLE_OS_XSAVE is set. +pub unsafe fn xcr0_write(val: Xcr0) { + let high: u32 = (val.bits >> 32) as u32; + let low: u32 = val.bits as u32; + asm!("xsetbv" :: "{eax}"(low), "{ecx}"(0), "{edx}"(high)); +} diff --git a/src/shared/descriptor.rs b/src/shared/descriptor.rs index 7fdf098..231e7b6 100644 --- a/src/shared/descriptor.rs +++ b/src/shared/descriptor.rs @@ -45,7 +45,7 @@ impl Type { Type::SystemDescriptor { size, ty } => (size as u8) << 3 | (ty as u8) | FLAGS_TYPE_SYS.bits, Type::SegmentDescriptor { ty, accessed } => - (accessed as u8) | ty.pack() | FLAGS_TYPE_SYS.bits, + (accessed as u8) | ty.pack() | FLAGS_TYPE_SEG.bits, } } } diff --git a/src/shared/dtables.rs b/src/shared/dtables.rs index c5337fb..5ec2117 100644 --- a/src/shared/dtables.rs +++ b/src/shared/dtables.rs @@ -17,8 +17,11 @@ pub struct DescriptorTablePointer<Entry> { } impl<T> DescriptorTablePointer<T> { - fn new(slice: &[T]) -> Self { - let len = slice.len() * size_of::<T>(); + pub fn new(slice: &[T]) -> Self { + // GDT, LDT, and IDT all expect the limit to be set to "one less". + // See Intel 3a, Section 3.5.1 "Segment Descriptor Tables" and + // Section 6.10 "Interrupt Descriptor Table (IDT)". + let len = slice.len() * size_of::<T>() - 1; assert!(len < 0x10000); DescriptorTablePointer { base: slice.as_ptr(), @@ -27,23 +30,6 @@ impl<T> DescriptorTablePointer<T> { } } -impl DescriptorTablePointer<SegmentDescriptor> { - pub fn new_gdtp(gdt: &[SegmentDescriptor]) -> Self { - let mut p = Self::new(gdt); - p.limit -= 1; - p - } - pub fn new_ldtp(ldt: &[SegmentDescriptor]) -> Self { - Self::new(ldt) - } -} - -impl DescriptorTablePointer<IdtEntry> { - pub fn new_idtp(idt: &[IdtEntry]) -> Self { - Self::new(idt) - } -} - /// Load GDT table. pub unsafe fn lgdt(gdt: &DescriptorTablePointer<SegmentDescriptor>) { diff --git a/src/shared/segmentation.rs b/src/shared/segmentation.rs index fbbaf34..8aafd31 100644 --- a/src/shared/segmentation.rs +++ b/src/shared/segmentation.rs @@ -24,36 +24,6 @@ bitflags! { } } -/// Reload code segment register. -/// Note this is special since we can not directly move -/// to %cs. Instead we push the new segment selector -/// and return value on the stack and use lretq -/// to reload cs and continue at 1:. -pub unsafe fn set_cs(sel: SegmentSelector) { - - #[cfg(target_arch="x86")] - #[inline(always)] - unsafe fn inner(sel: SegmentSelector) { - asm!("pushl $0; \ - pushl $$1f; \ - lretl; \ - 1:" :: "ri" (sel.bits() as usize) : "rax" "memory"); - } - - #[cfg(target_arch="x86_64")] - #[inline(always)] - unsafe fn inner(sel: SegmentSelector) { - asm!("pushq $0; \ - leaq 1f(%rip), %rax; \ - pushq %rax; \ - lretq; \ - 1:" :: "ri" (sel.bits() as usize) : "rax" "memory"); - } - - inner(sel) -} - - impl SegmentSelector { /// Create a new SegmentSelector /// @@ -196,21 +166,22 @@ pub struct SegmentDescriptor { base3: u8, } -/// This is data-structure is a ugly mess thing so we provide some + +/// This data-structure is an ugly mess thing so we provide some /// convenience function to program it. impl SegmentDescriptor { pub const NULL: SegmentDescriptor = SegmentDescriptor { + limit1: 0, base1: 0, base2: 0, - base3: 0, access: descriptor::Flags::BLANK, - limit1: 0, limit2_flags: Flags::BLANK, + base3: 0, }; - pub fn new(base: u32, limit: u32, - ty: Type, accessed: bool, dpl: PrivilegeLevel) -> SegmentDescriptor - { + /// Outputs a memory or TSS descriptor. + /// For a TSS descriptor on x86-64, you also need a high descriptor as second entry (see below). + pub(crate) fn memory_or_tss(base: u32, limit: u32, ty: descriptor::Type, dpl: PrivilegeLevel, flags: Flags) -> SegmentDescriptor { let fine_grained = limit < 0x100000; let (limit1, limit2) = if fine_grained { ((limit & 0xFFFF) as u16, ((limit & 0xF0000) >> 16) as u8) @@ -220,21 +191,30 @@ impl SegmentDescriptor { } (((limit & 0xFFFF000) >> 12) as u16, ((limit & 0xF0000000) >> 28) as u8) }; - let ty1 = descriptor::Type::SegmentDescriptor { - ty: ty, - accessed: accessed - }; SegmentDescriptor { + limit1: limit1, base1: base as u16, base2: ((base as usize & 0xFF0000) >> 16) as u8, - base3: ((base as usize & 0xFF000000) >> 24) as u8, - access: descriptor::Flags::from_type(ty1) + access: descriptor::Flags::from_type(ty) | descriptor::Flags::from_priv(dpl) | descriptor::FLAGS_PRESENT, - limit1: limit1, - limit2_flags: FLAGS_DB - | if fine_grained { Flags::empty() } else { FLAGS_G } + limit2_flags: if fine_grained { Flags::empty() } else { FLAGS_G } + | flags | Flags::from_limit2(limit2), + base3: ((base as usize & 0xFF000000) >> 24) as u8, + } + } + + /// Outputs a descriptor containing the high 32 bits of a memory address. + /// Serves as the second entry for descriptors that consume 2 table entries in x86-64. + pub(crate) const fn high(address: u64) -> SegmentDescriptor { + SegmentDescriptor { + limit1: (address >> 32) as u16, + base1: (address >> 48) as u16, + base2: 0, + access: descriptor::Flags::BLANK, + limit2_flags: Flags::BLANK, + base3: 0, } } } |