hyperlight_guest_bin/arch/amd64/machine.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use core::mem;
5
6use hyperlight_common::vmem::{BasicMapping, MappingKind, PAGE_SIZE};
7
8use super::layout::PROC_CONTROL_GVA;
9
10/// Entry in the Global Descriptor Table (GDT)
11/// For reference, see page 3-10 Vol. 3A of Intel 64 and IA-32
12/// Architectures Software Developer's Manual, figure 3-8
13/// (https://i.imgur.com/1i9xUmx.png).
14/// From the bottom, we have:
15/// - segment limit 15..0 = limit_low
16/// - base address 31..16 = base_low
17/// - base 23..16 = base_middle
18/// - p dpl s type 15..8 = access
19/// - p d/b l avl seg. limit 23..16 = flags_limit
20/// - base 31..24 = base_high
21#[derive(Copy, Clone)]
22#[repr(C, align(8))]
23pub(super) struct GdtEntry {
24 limit_low: u16,
25 base_low: u16,
26 base_middle: u8,
27 access: u8,
28 flags_limit: u8,
29 base_high: u8,
30}
31const _: () = assert!(mem::size_of::<GdtEntry>() == 0x8);
32
33impl GdtEntry {
34 /// Creates a new GDT entry.
35 pub const fn new(base: u32, limit: u32, access: u8, flags: u8) -> Self {
36 Self {
37 base_low: (base & 0xffff) as u16,
38 base_middle: ((base >> 16) & 0xff) as u8,
39 base_high: ((base >> 24) & 0xff) as u8,
40 limit_low: (limit & 0xffff) as u16,
41 flags_limit: (((limit >> 16) & 0x0f) as u8) | ((flags & 0x0f) << 4),
42 access,
43 }
44 }
45
46 /// Create a new entry that describes the Task State Segment
47 /// (TSS).
48 ///
49 /// The segment descriptor for the TSS needs to be wider than
50 /// other segments, because its base address is actually used &
51 /// must therefore be able to encode an entire 64-bit VA. Because
52 /// of this, it uses two adjacent descriptor entries.
53 ///
54 /// See AMD64 Architecture Programmer's Manual, Volume 2: System Programming
55 /// Section 4: Segmented Virtual Memory
56 /// §4.8: Long-Mod Segment Descriptors
57 /// §4.8.3: System Descriptors
58 /// for details of the layout
59 pub const fn tss(base: u64, limit: u32) -> [Self; 2] {
60 [
61 Self {
62 limit_low: (limit & 0xffff) as u16,
63 base_low: (base & 0xffff) as u16,
64 base_middle: ((base >> 16) & 0xff) as u8,
65 access: 0x89,
66 flags_limit: ((limit >> 16) & 0x0f) as u8,
67 base_high: ((base >> 24) & 0xff) as u8,
68 },
69 Self {
70 limit_low: ((base >> 32) & 0xffff) as u16,
71 base_low: ((base >> 48) & 0xffff) as u16,
72 base_middle: 0,
73 access: 0,
74 flags_limit: 0,
75 base_high: 0,
76 },
77 ]
78 }
79}
80
81/// GDTR (GDT pointer)
82///
83/// This contains the virtual address of the GDT. The GDT that it
84/// points to needs to remain mapped in memory at that address, but
85/// this structure itself does not.
86#[repr(C, packed)]
87pub(super) struct GdtPointer {
88 pub(super) limit: u16,
89 pub(super) base: u64,
90}
91
92/// Task State Segment
93///
94/// See AMD64 Architecture Programmer's Manual, Volume 2: System Programming
95/// Section 12: Task Management
96/// §12.2: Task-Management Resources
97/// §12.2.5: 64-bit Task State Segment
98#[allow(clippy::upper_case_acronyms)]
99#[repr(C, packed)]
100pub(super) struct TSS {
101 _rsvd0: [u8; 4],
102 _rsp0: u64,
103 _rsp1: u64,
104 _rsp2: u64,
105 _rsvd1: [u8; 8],
106 pub(super) ist1: u64,
107 _ist2: u64,
108 _ist3: u64,
109 _ist4: u64,
110 _ist5: u64,
111 _ist6: u64,
112 _ist7: u64,
113 _rsvd2: [u8; 8],
114}
115const _: () = assert!(mem::size_of::<TSS>() == 0x64);
116const _: () = assert!(mem::offset_of!(TSS, ist1) == 0x24);
117
118/// An entry in the Interrupt Descriptor Table (IDT)
119/// For reference, see page 7-20 Vol. 3A of Intel 64 and IA-32
120/// Architectures Software Developer's Manual, figure 7-8
121/// (i.e., https://i.imgur.com/N4rEjHj.png).
122/// From the bottom, we have:
123/// - offset 15..0 = offset_low
124/// - segment selector 31..16 = selector
125/// - 000 0 0 Interrupt Stack Table 7..0 = interrupt_stack_table_offset
126/// - p dpl 0 type 15..8 = type_attr
127/// - offset 31..16 = offset_mid
128/// - offset 63..32 = offset_high
129/// - reserved 31..0 = zero
130#[repr(C, align(16))]
131pub(crate) struct IdtEntry {
132 offset_low: u16, // Lower 16 bits of handler address
133 selector: u16, // code segment selector in GDT
134 interrupt_stack_table_offset: u8, // Interrupt Stack Table offset
135 type_attr: u8, // Gate type and flags
136 offset_mid: u16, // Middle 16 bits of handler address
137 offset_high: u32, // High 32 bits of handler address
138 _rsvd: u32, // Reserved, ignored
139}
140const _: () = assert!(mem::size_of::<IdtEntry>() == 0x10);
141
142impl IdtEntry {
143 pub(super) fn new(handler: u64) -> Self {
144 Self {
145 offset_low: (handler & 0xFFFF) as u16,
146 selector: 0x08, // Kernel Code Segment
147 interrupt_stack_table_offset: 1,
148 type_attr: 0x8E,
149 // 0x8E = 10001110b
150 // 1 00 0 1101
151 // 1 = Present
152 // 00 = Descriptor Privilege Level (0)
153 // 0 = Storage Segment (0)
154 // 1110 = Gate Type (0b1110 = 14 = 0xE)
155 // 0xE means it's an interrupt gate
156 offset_mid: ((handler >> 16) & 0xFFFF) as u16,
157 offset_high: ((handler >> 32) & 0xFFFFFFFF) as u32,
158 _rsvd: 0,
159 }
160 }
161}
162
163#[repr(C, packed)]
164pub(super) struct IdtPointer {
165 pub limit: u16,
166 pub base: u64,
167}
168const _: () = assert!(mem::size_of::<IdtPointer>() == 10);
169
170#[allow(clippy::upper_case_acronyms)]
171pub(super) type GDT = [GdtEntry; 5];
172#[allow(clippy::upper_case_acronyms)]
173#[repr(align(0x1000))]
174pub(super) struct IDT {
175 pub(super) entries: [IdtEntry; 256],
176}
177const _: () = assert!(mem::size_of::<IDT>() == 0x1000);
178
179const PADDING_BEFORE_TSS: usize = 64 - mem::size_of::<GDT>();
180/// A single structure containing all of the processor control
181/// structures that we use during early initialization, making it easy
182/// to keep them in an early-allocated physical page. Field alignment
183/// is chosen partly to lineup nicely with likely cache line
184/// boundaries (gdt, tss) and to keep the idt (which is 4k in size) on
185/// its own page.
186#[repr(C, align(0x1000))]
187pub(super) struct ProcCtrl {
188 pub(super) gdt: GDT,
189 _pad: mem::MaybeUninit<[u8; PADDING_BEFORE_TSS]>,
190 pub(super) tss: TSS,
191 pub(super) idt: IDT,
192}
193const _: () = assert!(mem::size_of::<ProcCtrl>() == 0x2000);
194const _: () = assert!(mem::size_of::<ProcCtrl>() <= PAGE_SIZE * 2);
195const _: () = assert!(mem::offset_of!(ProcCtrl, gdt) == 0);
196const _: () = assert!(mem::offset_of!(ProcCtrl, tss) == 64);
197const _: () = assert!(mem::offset_of!(ProcCtrl, idt) == 0x1000);
198
199impl ProcCtrl {
200 /// Create a copy of the ProcCtrl structure at its known
201 /// mapping.
202 ///
203 /// # Safety
204 /// This should only be called once, and before any of the
205 /// gdtr/tr/idtr pointing at its address have been loaded.
206 pub(super) unsafe fn init() -> *mut Self {
207 unsafe {
208 let ptr = PROC_CONTROL_GVA as *mut u8;
209 crate::paging::map_region(
210 hyperlight_guest::prim_alloc::alloc_phys_pages(2),
211 ptr,
212 PAGE_SIZE as u64 * 2,
213 MappingKind::Basic(BasicMapping {
214 readable: true,
215 writable: true,
216 executable: false,
217 }),
218 );
219 crate::paging::barrier::first_valid_same_ctx();
220 let ptr = ptr as *mut Self;
221 (&raw mut (*ptr).gdt).write_bytes(0u8, 1);
222 (&raw mut (*ptr).tss).write_bytes(0u8, 1);
223 (&raw mut (*ptr).idt).write_bytes(0u8, 1);
224 ptr
225 }
226 }
227}
228
229/// See AMD64 Architecture Programmer's Manual, Volume 2
230/// §8.9.3 Interrupt Stack Frame, pp. 283--284
231/// Figure 8-14: Long-Mode Stack After Interrupt---Same Privilege,
232/// Figure 8-15: Long-Mode Stack After Interrupt---Higher Privilege
233/// Subject to the proviso that we push a dummy error code of 0 for exceptions
234/// for which the processor does not provide one
235#[repr(C)]
236pub struct ExceptionInfo {
237 pub error_code: u64,
238 pub rip: u64,
239 pub cs: u64,
240 pub rflags: u64,
241 pub rsp: u64,
242 pub ss: u64,
243}
244const _: () = assert!(size_of::<ExceptionInfo>() == 8 * 6);
245const _: () = assert!(mem::offset_of!(ExceptionInfo, rip) == 8);
246const _: () = assert!(mem::offset_of!(ExceptionInfo, rsp) == 32);