Skip to content

Commit d95fa13

Browse files
committed
x86_64: init GDT and TSS. add UEFI example
1 parent 04eea3c commit d95fa13

File tree

13 files changed

+354
-113
lines changed

13 files changed

+354
-113
lines changed

examples/uefi/.cargo/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[build]
2+
target = "x86_64-unknown-uefi"

examples/uefi/.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OVMF.fd filter=lfs diff=lfs merge=lfs -text

examples/uefi/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "uefi"
3+
version = "0.1.0"
4+
authors = ["Runji Wang <[email protected]>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
log = "0.4"
11+
x86_64 = "0.8"
12+
uefi = "0.4.3"
13+
uefi-services = "0.2.1"
14+
raw-cpuid = "7.0.3"
15+
trapframe = { path = "../.." }

examples/uefi/Makefile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
MODE ?= debug
2+
EFI := target/x86_64-unknown-uefi/$(MODE)/uefi.efi
3+
OVMF := OVMF.fd
4+
ESP := target/esp
5+
BUILD_ARGS := -Z build-std=core,alloc
6+
QEMU_ARGS := -net none -nographic -cpu qemu64,fsgsbase
7+
OBJDUMP := rust-objdump
8+
9+
ifeq (${MODE}, release)
10+
BUILD_ARGS += --release
11+
endif
12+
13+
build:
14+
cargo build $(BUILD_ARGS)
15+
16+
run: build
17+
mkdir -p $(ESP)/EFI/Boot
18+
cp $(EFI) $(ESP)/EFI/Boot/BootX64.efi
19+
qemu-system-x86_64 \
20+
-bios ${OVMF} \
21+
-drive format=raw,file=fat:rw:${ESP} \
22+
$(QEMU_ARGS)
23+
24+
clippy:
25+
cargo clippy $(BUILD_ARGS)
26+
27+
fix:
28+
cargo fix $(BUILD_ARGS) --allow-dirty
29+
30+
header:
31+
$(OBJDUMP) -h $(EFI) | less
32+
33+
asm:
34+
$(OBJDUMP) -d $(EFI) | less

examples/uefi/OVMF.fd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:a2e2a2b2ff7d5ea1f112557a12436d42e560b71120a610407ba4efc9df12efd7
3+
size 2097152

examples/uefi/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Example on UEFI Application
2+
3+
```bash
4+
make run
5+
```

examples/uefi/src/main.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#![no_std]
2+
#![no_main]
3+
#![feature(abi_efiapi)]
4+
#![deny(warnings)]
5+
6+
extern crate alloc;
7+
8+
use log::*;
9+
use trapframe::GeneralRegs;
10+
use uefi::prelude::*;
11+
use x86_64::registers::control::*;
12+
use x86_64::structures::paging::{PageTable, PageTableFlags};
13+
14+
#[entry]
15+
fn efi_main(_image: Handle, st: SystemTable<Boot>) -> uefi::Status {
16+
uefi_services::init(&st).expect_success("Failed to initialize utilities");
17+
check_and_set_cpu_features();
18+
init_user_code();
19+
trapframe::init();
20+
21+
let mut regs = GeneralRegs {
22+
rax: 0,
23+
rbx: 1,
24+
rcx: 2,
25+
rdx: 3,
26+
rsi: 4,
27+
rdi: 5,
28+
rbp: 6,
29+
rsp: 7,
30+
r8: 8,
31+
r9: 9,
32+
r10: 10,
33+
r11: 11,
34+
r12: 12,
35+
r13: 13,
36+
r14: 14,
37+
r15: 15,
38+
rip: 0x1000,
39+
rflags: 0x202,
40+
fsbase: 18,
41+
gsbase: 19,
42+
};
43+
44+
info!("go to user");
45+
unsafe {
46+
trapframe::run_user(&mut regs);
47+
}
48+
info!("back from user: {:#x?}", regs);
49+
50+
unimplemented!()
51+
}
52+
53+
/// Initialize user code at 0x1000.
54+
fn init_user_code() {
55+
allow_user_access(USER_CODE_ADDR);
56+
const USER_CODE_ADDR: usize = 0x1000;
57+
const SYSCALL_OPCODE: u16 = 0x05_0f;
58+
unsafe {
59+
(USER_CODE_ADDR as *mut u16).write(SYSCALL_OPCODE);
60+
}
61+
}
62+
63+
/// Set user bit for 4-level PDEs of the `page`.
64+
/// This is a workaround since `x86_64` crate does not set user bit for PDEs.
65+
fn allow_user_access(vaddr: usize) {
66+
let mut page_table = Cr3::read().0.start_address().as_u64() as *mut PageTable;
67+
for level in 0..4 {
68+
let index = (vaddr >> (12 + (3 - level) * 9)) & 0o777;
69+
let entry = unsafe { &mut (&mut *page_table)[index] };
70+
let flags = entry.flags();
71+
entry.set_flags(flags | PageTableFlags::USER_ACCESSIBLE);
72+
if level == 3 || flags.contains(PageTableFlags::HUGE_PAGE) {
73+
return;
74+
}
75+
page_table = entry.frame().unwrap().start_address().as_u64() as *mut PageTable;
76+
}
77+
}
78+
79+
fn check_and_set_cpu_features() {
80+
assert!(raw_cpuid::CpuId::new()
81+
.get_extended_feature_info()
82+
.unwrap()
83+
.has_fsgsbase());
84+
unsafe {
85+
// Enable NX bit.
86+
Efer::update(|f| f.insert(EferFlags::NO_EXECUTE_ENABLE));
87+
88+
// By default the page of CR3 have write protect.
89+
// We have to remove that before editing page table.
90+
Cr0::update(|f| f.remove(Cr0Flags::WRITE_PROTECT));
91+
92+
// Enable `rdfsbase` series instructions.
93+
Cr4::update(|f| f.insert(Cr4Flags::FSGSBASE));
94+
}
95+
}

src/arch/x86_64/fast_syscall.rs

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,35 @@
11
use super::*;
22
use x86_64::registers::model_specific::*;
33

4+
global_asm!(include_str!("syscall.S"));
5+
46
pub fn init() {
57
unsafe {
8+
// enable `syscall` instruction
69
Efer::update(|flags| {
710
*flags |= EferFlags::SYSTEM_CALL_EXTENSIONS;
811
});
912

10-
let mut star = Msr::new(0xC0000081); // legacy mode SYSCALL target
13+
// let mut star = Msr::new(0xC0000081); // legacy mode SYSCALL target
1114
let mut lstar = Msr::new(0xC0000082); // long mode SYSCALL target
1215
let mut sfmask = Msr::new(0xC0000084); // EFLAGS mask for syscall
1316

1417
// flags to clear on syscall
1518
// copy from Linux 5.0
1619
// TF|DF|IF|IOPL|AC|NT
17-
let rflags_mask = 0x47700;
20+
const RFLAGS_MASK: u64 = 0x47700;
1821

19-
star.write(core::mem::transmute(STAR));
22+
// star.write(core::mem::transmute(STAR));
2023
lstar.write(syscall_entry as u64);
21-
sfmask.write(rflags_mask);
24+
sfmask.write(RFLAGS_MASK);
2225
}
2326
}
2427

25-
extern "C" {
28+
extern "sysv64" {
2629
fn syscall_entry();
2730
pub fn run_user(regs: &mut GeneralRegs);
2831
}
2932

30-
#[repr(packed)]
31-
struct StarMsr {
32-
eip: u32,
33-
kernel_cs: u16,
34-
user_cs: u16,
35-
}
36-
37-
const STAR: StarMsr = StarMsr {
38-
eip: 0, // ignored in 64 bit mode
39-
kernel_cs: KCODE_SELECTOR,
40-
user_cs: UCODE_SELECTOR,
41-
};
42-
4333
#[derive(Debug, Default, Clone, Copy)]
4434
#[repr(C)]
4535
pub struct GeneralRegs {

src/arch/x86_64/gdt.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//! Configure Global Descriptor Table (GDT)
2+
3+
use alloc::boxed::Box;
4+
use alloc::vec::Vec;
5+
use core::mem::size_of;
6+
7+
use x86_64::instructions::tables::{lgdt, load_tss};
8+
use x86_64::registers::model_specific::{GsBase, Msr};
9+
use x86_64::structures::gdt::{Descriptor, SegmentSelector};
10+
use x86_64::structures::tss::TaskStateSegment;
11+
use x86_64::structures::DescriptorTablePointer;
12+
use x86_64::{PrivilegeLevel, VirtAddr};
13+
14+
/// Init TSS & GDT.
15+
pub fn init() {
16+
// allocate stack for trap from user
17+
// set the stack top to TSS
18+
// so that when trap from ring3 to ring0, CPU can switch stack correctly
19+
let mut tss = Box::new(TaskStateSegment::new());
20+
let trap_stack_top = Box::leak(Box::new([0u8; 0x1000])).as_ptr() as u64 + 0x1000;
21+
tss.privilege_stack_table[0] = VirtAddr::new(trap_stack_top);
22+
let tss: &'static _ = Box::leak(tss);
23+
let (tss0, tss1) = match Descriptor::tss_segment(tss) {
24+
Descriptor::SystemSegment(tss0, tss1) => (tss0, tss1),
25+
_ => unreachable!(),
26+
};
27+
28+
unsafe {
29+
// get current GDT
30+
let gdtp = sgdt();
31+
let entry_count = (gdtp.limit + 1) as usize / size_of::<u64>();
32+
let old_gdt = core::slice::from_raw_parts(gdtp.base as *const u64, entry_count);
33+
34+
// allocate new GDT with 7 more entries
35+
//
36+
// NOTICE: for fast syscall:
37+
// STAR[47:32] = K_CS = K_SS - 8
38+
// STAR[63:48] = U_CS32 = U_SS32 - 8 = U_CS - 16
39+
let mut gdt = Vec::from(old_gdt);
40+
gdt.extend([tss0, tss1, KCODE64, KDATA64, UCODE32, UDATA32, UCODE64].iter());
41+
let gdt = Vec::leak(gdt);
42+
43+
// load new GDT and TSS
44+
lgdt(&DescriptorTablePointer {
45+
limit: gdt.len() as u16 * 8 - 1,
46+
base: gdt.as_ptr() as _,
47+
});
48+
load_tss(SegmentSelector::new(
49+
entry_count as u16,
50+
PrivilegeLevel::Ring0,
51+
));
52+
53+
// for fast syscall:
54+
// store address of TSS to kernel_gsbase
55+
GsBase::MSR.write(tss as *const _ as u64);
56+
57+
let mut star = Msr::new(0xC0000081); // legacy mode SYSCALL target
58+
star.write(core::mem::transmute(StarMsr {
59+
eip: 0,
60+
kernel_cs: SegmentSelector::new(entry_count as u16 + 2, PrivilegeLevel::Ring0).0,
61+
user_cs: SegmentSelector::new(entry_count as u16 + 4, PrivilegeLevel::Ring3).0,
62+
}));
63+
}
64+
}
65+
66+
/// Get current GDT register
67+
#[inline]
68+
unsafe fn sgdt() -> DescriptorTablePointer {
69+
let mut gdt = DescriptorTablePointer { limit: 0, base: 0 };
70+
asm!("sgdt ($0)" :: "r" (&mut gdt) : "memory" : "volatile");
71+
gdt
72+
}
73+
74+
#[allow(dead_code)]
75+
#[repr(packed)]
76+
struct StarMsr {
77+
/// ignored in 64 bit mode
78+
eip: u32,
79+
kernel_cs: u16,
80+
user_cs: u16,
81+
}
82+
83+
const KCODE64: u64 = 0x00209800_00000000; // EXECUTABLE | USER_SEGMENT | PRESENT | LONG_MODE
84+
const UCODE64: u64 = 0x0020F800_00000000; // EXECUTABLE | USER_SEGMENT | USER_MODE | PRESENT | LONG_MODE
85+
const KDATA64: u64 = 0x00009200_00000000; // DATA_WRITABLE | USER_SEGMENT | PRESENT
86+
const UDATA64: u64 = 0x0000F200_00000000; // DATA_WRITABLE | USER_SEGMENT | USER_MODE | PRESENT
87+
const UCODE32: u64 = 0x00cffa00_0000ffff; // EXECUTABLE | USER_SEGMENT | USER_MODE | PRESENT
88+
const UDATA32: u64 = 0x00cff200_0000ffff; // EXECUTABLE | USER_SEGMENT | USER_MODE | PRESENT

0 commit comments

Comments
 (0)