Use x86's TaskStateSegment and use usize for stack pointers

This commit is contained in:
Philipp Oppermann
2016-12-21 00:12:19 +01:00
parent d1fb1516fa
commit ceb44d9c2e
8 changed files with 31 additions and 178 deletions

View File

@@ -307,47 +307,29 @@ Finally, we create and return a new `Stack`, which we define as follows:
#[derive(Debug)] #[derive(Debug)]
pub struct Stack { pub struct Stack {
top: StackPointer, top: usize,
bottom: StackPointer, bottom: usize,
} }
impl Stack { impl Stack {
fn new(top: usize, bottom: usize) -> Stack { fn new(top: usize, bottom: usize) -> Stack {
assert!(top > bottom); assert!(top > bottom);
Stack { Stack {
top: StackPointer::new(top), top: top,
bottom: StackPointer::new(bottom), bottom: bottom,
} }
} }
pub fn top(&self) -> StackPointer { pub fn top(&self) -> StackPointer {
self.top self.top
} }
}
use core::nonzero::NonZero; pub fn bottom(&self) -> StackPointer {
self.bottom
#[derive(Debug, Clone, Copy)]
pub struct StackPointer(NonZero<usize>);
impl StackPointer {
fn new(ptr: usize) -> StackPointer {
assert!(ptr != 0);
StackPointer(unsafe { NonZero::new(ptr) })
}
}
impl Into<usize> for StackPointer {
fn into(self) -> usize {
*self.0
} }
} }
``` ```
The `Stack` struct describes a stack though its top and bottom pointers. A stack pointer can never be `0`, so we use the unstable [NonZero] wrapper for `StackPointer`. This wrapper is an optimization that tells the compiler that it can use the value `0` to differentiate enum variants. Thus, an `Option<StackPointer>` has always the same size as a bare `usize` (the value `0` is used to store the `None` case). We will require this feature when we create the Interrupt Stack Table later. The `Stack` struct describes a stack though its top and bottom addresses.
Since `NonZero` is unstable, we need to add `#![feature(nonzero)]` in our `lib.rs`.
[NonZero]: https://doc.rust-lang.org/nightly/core/nonzero/struct.NonZero.html
#### The Memory Controller #### The Memory Controller
Now we're able to allocate a new double fault stack. However, we add one more level of abstraction to make things easier. For that we add a new `MemoryController` type to our `memory` module: Now we're able to allocate a new double fault stack. However, we add one more level of abstraction to make things easier. For that we add a new `MemoryController` type to our `memory` module:
@@ -355,7 +337,7 @@ Now we're able to allocate a new double fault stack. However, we add one more le
```rust ```rust
// in src/memory/mod.rs // in src/memory/mod.rs
pub use self::stack_allocator::{Stack, StackPointer}; pub use self::stack_allocator::Stack;
pub struct MemoryController { pub struct MemoryController {
active_table: paging::ActivePageTable, active_table: paging::ActivePageTable,
@@ -475,82 +457,17 @@ I/O Map Base Address | `u16`
The _Privilege Stack Table_ is used by the CPU when the privilege level changes. For example, if an exception occurs while the CPU is in user mode (privilege level 3), the CPU normally switches to kernel mode (privilege level 0) before invoking the exception handler. In that case, the CPU would switch to the 0th stack in the Privilege Stack Table (since 0 is the target privilege level). We don't have any user mode programs yet, so we ignore this table for now. The _Privilege Stack Table_ is used by the CPU when the privilege level changes. For example, if an exception occurs while the CPU is in user mode (privilege level 3), the CPU normally switches to kernel mode (privilege level 0) before invoking the exception handler. In that case, the CPU would switch to the 0th stack in the Privilege Stack Table (since 0 is the target privilege level). We don't have any user mode programs yet, so we ignore this table for now.
Let's create a `TaskStateSegment` struct in a new `tss` submodule: #### Creating a TSS
Let's create a new TSS that contains our double fault stack in its Interrupt Stack Table. For that we need a TSS struct. Fortunately, the `x86` crate already contains a [`TaskStateSegment` struct] that we can use:
[`TaskStateSegment` struct]: https://docs.rs/x86/0.7.1/x86/task/struct.TaskStateSegment.html
```rust ```rust
// in src/interrupts/mod.rs // in src/interrupts/mod.rs
mod tss; use x86::task::TaskStateSegment;
// in src/interrupts/tss.rs
#[derive(Debug)]
#[repr(C, packed)]
pub struct TaskStateSegment {
reserved_0: u32,
pub privilege_stacks: PrivilegeStackTable,
reserved_1: u64,
pub interrupt_stacks: InterruptStackTable,
reserved_2: u64,
reserved_3: u16,
iomap_base: u16,
}
use memory::StackPointer;
#[derive(Debug)]
pub struct PrivilegeStackTable([Option<StackPointer>; 3]);
#[derive(Debug)]
pub struct InterruptStackTable([Option<StackPointer>; 7]);
``` ```
We use [repr\(C)] for the struct since the order is fields is important. We also use `[repr(packed)]` because otherwise the compiler might insert additional padding between the `reserved_0` and `privilege_stacks` fields.
The `PrivilegeStackTable` and `InterruptStackTable` types are just newtype wrappers for arrays of `Option<StackPointer>`. Here it becomes important that we implemented `NonZero` for `StackPointer`: Thus, an `Option<StackPointer>` still has the required size of 64 bits.
Let's add a `TaskStateSegment::new` function that creates an empty TSS:
```rust
impl TaskStateSegment {
pub fn new() -> TaskStateSegment {
TaskStateSegment {
privilege_stacks: PrivilegeStackTable([None, None, None]),
interrupt_stacks: InterruptStackTable(
[None, None, None, None, None, None, None]),
iomap_base: 0,
reserved_0: 0,
reserved_1: 0,
reserved_2: 0,
reserved_3: 0,
}
}
}
```
We also add a `InterruptStackTable::insert_stack` method, that inserts a given stack into a free table entry:
```rust
use memory::Stack;
impl InterruptStackTable {
pub fn insert_stack(&mut self, stack: Stack) -> Result<u8, Stack> {
// TSS index starts at 1, so we do a `zip(1..)`
for (entry, i) in self.0.iter_mut().zip(1..) {
if entry.is_none() {
*entry = Some(stack.top());
return Ok(i);
}
}
Err(stack)
}
}
```
The function iterates over the table and places the stack pointer in the first free entry. In the case of success, we return the table index of the inserted pointer. If there's no free entry left, we return the stack back to the caller as `Err`.
#### Creating a TSS
Let's build a new TSS that contains our double fault stack in its Interrupt Stack Table.
The Global Descriptor Table (again) The Global Descriptor Table (again)
Putting it together Putting it together
Whats next? Whats next?

View File

@@ -1,6 +1,6 @@
use bit_field::BitField; use bit_field::BitField;
use collections::vec::Vec; use collections::vec::Vec;
use interrupts::tss::TaskStateSegment; use x86::bits64::task::TaskStateSegment;
pub struct Gdt(Vec<u64>); pub struct Gdt(Vec<u64>);
@@ -26,11 +26,11 @@ impl Gdt {
} }
pub fn load(&'static self) { pub fn load(&'static self) {
use x86::dtables::{DescriptorTablePointer, lgdt}; use x86::shared::dtables::{DescriptorTablePointer, lgdt};
use core::mem::size_of; use core::mem::size_of;
let ptr = DescriptorTablePointer { let ptr = DescriptorTablePointer {
base: self.0.as_ptr() as u64, base: self.0.as_ptr() as *const ::x86::shared::segmentation::SegmentDescriptor,
limit: (self.0.len() * size_of::<u64>() - 1) as u16, limit: (self.0.len() * size_of::<u64>() - 1) as u16,
}; };

View File

@@ -109,7 +109,7 @@ impl EntryOptions {
#[allow(dead_code)] #[allow(dead_code)]
pub fn set_stack_index(&mut self, index: u8) -> &mut Self { pub fn set_stack_index(&mut self, index: u8) -> &mut Self {
self.0.set_range(0..3, index.into()); self.0.set_range(0..3, (index + 1).into());
self self
} }
} }

View File

@@ -9,9 +9,9 @@
use spin::Once; use spin::Once;
use memory::MemoryController; use memory::MemoryController;
use x86::bits64::task::TaskStateSegment;
mod idt; mod idt;
mod tss;
mod gdt; mod gdt;
macro_rules! save_scratch_registers { macro_rules! save_scratch_registers {
@@ -92,22 +92,18 @@ macro_rules! handler_with_error_code {
} }
static IDT: Once<idt::Idt> = Once::new(); static IDT: Once<idt::Idt> = Once::new();
static TSS: Once<tss::TaskStateSegment> = Once::new(); static TSS: Once<TaskStateSegment> = Once::new();
static GDT: Once<gdt::Gdt> = Once::new(); static GDT: Once<gdt::Gdt> = Once::new();
pub fn init(memory_controller: &mut MemoryController) { pub fn init(memory_controller: &mut MemoryController) {
let double_fault_stack = memory_controller.alloc_stack(1) let double_fault_stack = memory_controller.alloc_stack(1)
.expect("could not allocate double fault stack"); .expect("could not allocate double fault stack");
let mut double_fault_ist_index = 0; const DOUBLE_FAULT_IST_INDEX: u8 = 0;
let tss = TSS.call_once(|| { let tss = TSS.call_once(|| {
let mut tss = tss::TaskStateSegment::new(); let mut tss = TaskStateSegment::new();
tss.ist[DOUBLE_FAULT_IST_INDEX as usize] = double_fault_stack.top() as u64;
double_fault_ist_index = tss.interrupt_stacks
.insert_stack(double_fault_stack)
.expect("IST flush_all");
tss tss
}); });
@@ -134,7 +130,7 @@ pub fn init(memory_controller: &mut MemoryController) {
idt.set_handler(3, handler!(breakpoint_handler)); idt.set_handler(3, handler!(breakpoint_handler));
idt.set_handler(6, handler!(invalid_opcode_handler)); idt.set_handler(6, handler!(invalid_opcode_handler));
idt.set_handler(8, handler_with_error_code!(double_fault_handler)) idt.set_handler(8, handler_with_error_code!(double_fault_handler))
.set_stack_index(double_fault_ist_index); .set_stack_index(DOUBLE_FAULT_IST_INDEX);
idt.set_handler(14, handler_with_error_code!(page_fault_handler)); idt.set_handler(14, handler_with_error_code!(page_fault_handler));
idt idt

View File

@@ -1,46 +0,0 @@
use memory::{Stack, StackPointer};
#[derive(Debug)]
#[repr(C, packed)]
pub struct TaskStateSegment {
reserved_0: u32,
pub privilege_stacks: PrivilegeStackTable,
reserved_1: u64,
pub interrupt_stacks: InterruptStackTable,
reserved_2: u64,
reserved_3: u16,
iomap_base: u16,
}
impl TaskStateSegment {
pub fn new() -> TaskStateSegment {
TaskStateSegment {
privilege_stacks: PrivilegeStackTable([None, None, None]),
interrupt_stacks: InterruptStackTable([None, None, None, None, None, None, None]),
iomap_base: 0,
reserved_0: 0,
reserved_1: 0,
reserved_2: 0,
reserved_3: 0,
}
}
}
#[derive(Debug)]
pub struct PrivilegeStackTable([Option<StackPointer>; 3]);
#[derive(Debug)]
pub struct InterruptStackTable([Option<StackPointer>; 7]);
impl InterruptStackTable {
pub fn insert_stack(&mut self, stack: Stack) -> Result<u8, Stack> {
// TSS index starts at 1
for (entry, i) in self.0.iter_mut().zip(1..) {
if entry.is_none() {
*entry = Some(stack.top());
return Ok(i);
}
}
Err(stack)
}
}

View File

@@ -13,7 +13,6 @@
#![feature(asm)] #![feature(asm)]
#![feature(naked_functions)] #![feature(naked_functions)]
#![feature(core_intrinsics)] #![feature(core_intrinsics)]
#![feature(nonzero)]
#![feature(drop_types_in_const)] #![feature(drop_types_in_const)]
#![no_std] #![no_std]

View File

@@ -9,7 +9,7 @@
pub use self::area_frame_allocator::AreaFrameAllocator; pub use self::area_frame_allocator::AreaFrameAllocator;
pub use self::paging::remap_the_kernel; pub use self::paging::remap_the_kernel;
pub use self::stack_allocator::{Stack, StackPointer}; pub use self::stack_allocator::Stack;
use self::paging::PhysicalAddress; use self::paging::PhysicalAddress;
use multiboot2::BootInformation; use multiboot2::BootInformation;

View File

@@ -1,6 +1,5 @@
use memory::paging::{self, Page, PageIter, ActivePageTable}; use memory::paging::{self, Page, PageIter, ActivePageTable};
use memory::{PAGE_SIZE, FrameAllocator}; use memory::{PAGE_SIZE, FrameAllocator};
use core::nonzero::NonZero;
pub fn new_stack_allocator(page_range: PageIter) -> StackAllocator { pub fn new_stack_allocator(page_range: PageIter) -> StackAllocator {
StackAllocator { range: page_range } StackAllocator { range: page_range }
@@ -52,36 +51,24 @@ impl StackAllocator {
#[derive(Debug)] #[derive(Debug)]
pub struct Stack { pub struct Stack {
top: StackPointer, top: usize,
bottom: StackPointer, bottom: usize,
} }
impl Stack { impl Stack {
fn new(top: usize, bottom: usize) -> Stack { fn new(top: usize, bottom: usize) -> Stack {
assert!(top > bottom); assert!(top > bottom);
Stack { Stack {
top: StackPointer::new(top), top: top,
bottom: StackPointer::new(bottom), bottom: bottom,
} }
} }
pub fn top(&self) -> StackPointer { pub fn top(&self) -> usize {
self.top self.top
} }
}
#[derive(Debug, Clone, Copy)] pub fn bottom(&self) -> usize {
pub struct StackPointer(NonZero<usize>); self.bottom
impl StackPointer {
fn new(ptr: usize) -> StackPointer {
assert!(ptr != 0);
StackPointer(unsafe { NonZero::new(ptr) })
}
}
impl Into<usize> for StackPointer {
fn into(self) -> usize {
*self.0
} }
} }