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