Skip to main content

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);