Skip to main content

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