x86/segmentation.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
4
5//! Facilities for managing memory segmentation
6//!
7//! ## Segmentation Model
8//!
9//! The memory model implemented by this module is often referred to as _flat segmentation_, because
10//! the entire address space of 4Gb is contained in a single unbroken ("flat") memory segment. This
11//! technique effectively disables segmentation altogether, allowing the kernel to manage memory
12//! using page tables exclusively.
13//!
14//! To this end, the GDT created by this module contains the following entries:
15//!
16//! * The null segment
17//! * Kernel code segment - DPL of 0. Starts at address zero and extends the full 4Gb.
18//! * Kernel data segment - Same as kernel code segment, but with data segment type.
19//! * Global TSS - Single task state segment used for both kernel and user contexts.
20//! * User code segment - DPL of 3. Starts at address zero and extends the full 4Gb. Memory
21//! protection is done using paging, not segmentation.
22//! * User data segment - Same as user code segment, but with data segment type.
23//!
24//! Segment registers are also populated as follows:
25//!
26//! * CS register -> kernel code segment
27//! * SS/DS/ES/FS/GS registers -> kernel data segment
28//!
29//! ## Task State Segment
30//!
31//! This module initializes a single, global TSS to help with context switching.
32//!
33//! The sole purpose of this TSS is to enable stack switching when transitioning from user to kernel
34//! mode. When an interrupt occurs in user mode, the hardware uses the `ss0` and `esp0` fields of
35//! the TSS to locate the kernel's stack.
36//!
37//! Apart from that, the global TSS is unused. In particular, this crate does _not_ use hardware
38//! task management.
39
40use core::mem;
41use core::ptr;
42
43use crate::registers::bits32::task::TaskStateSegment;
44use crate::registers::dtables::{self, DescriptorTablePointer};
45use crate::registers::ring::Ring;
46use crate::registers::segmentation::{
47 self, BuildDescriptor, CodeSegmentType, DataSegmentType, Descriptor, DescriptorBuilder,
48 GateDescriptorBuilder, SegmentDescriptorBuilder, SegmentSelector,
49};
50use crate::registers::task;
51
52use kernel::static_init;
53
54/// Total number of descriptors in the GDT
55const NUM_DESCRIPTORS: usize = 6;
56
57/// Selector for the kernel's code segment
58pub const KERNEL_CODE: SegmentSelector = SegmentSelector::new(1, Ring::Ring0);
59
60/// Selector for the kernel's data segment
61pub const KERNEL_DATA: SegmentSelector = SegmentSelector::new(2, Ring::Ring0);
62
63/// Selector for the global TSS
64pub const GLOBAL_TSS: SegmentSelector = SegmentSelector::new(3, Ring::Ring0);
65
66/// Selector for the user mode code segment
67pub const USER_CODE: SegmentSelector = SegmentSelector::new(4, Ring::Ring3);
68
69/// Selector for the user mode data segment
70pub const USER_DATA: SegmentSelector = SegmentSelector::new(5, Ring::Ring3);
71
72/// Static storage for a global TSS
73static mut TSS_INSTANCE: TaskStateSegment = TaskStateSegment::new();
74
75/// Initializes the global TSS instance and returns a descriptor for it.
76///
77/// ## Safety
78///
79/// Must never be called more than once, as this would cause the TSS to be reinitialized
80/// unexpectedly.
81unsafe fn init_tss() -> Descriptor {
82 unsafe {
83 TSS_INSTANCE.ss0 = KERNEL_DATA.bits();
84 }
85
86 let tss_base = ptr::addr_of!(TSS_INSTANCE) as u64;
87 let tss_limit = mem::size_of::<TaskStateSegment>() as u64;
88
89 <DescriptorBuilder as GateDescriptorBuilder<u32>>::tss_descriptor(tss_base, tss_limit, true)
90 .dpl(Ring::Ring0)
91 .present()
92 .avl()
93 .finish()
94}
95
96/// Sets the stack pointer to use for handling interrupts from user mode.
97///
98/// ## Safety
99///
100/// When handling interrupts that occur during user mode, the context switching logic has very
101/// specific expectations about the layout of the kernel's stack frame. See _return_from_user.rs_ for
102/// complete details.
103///
104/// When calling this function, the stack frame referenced by `esp` must meet these expectations.
105// `allow(unsupported_calling_conventions)`: cdecl is not valid when testing
106// this code on an x86_64 machine. This avoids a warning until a more permanent
107// fix is decided. See: https://github.com/tock/tock/pull/4662
108#[allow(unsupported_calling_conventions)]
109#[no_mangle]
110pub unsafe extern "cdecl" fn set_tss_esp0(esp: u32) {
111 unsafe {
112 TSS_INSTANCE.esp0 = esp;
113 }
114}
115
116/// Performs global initialization of memory segmentation.
117///
118/// ## Safety
119///
120/// Memory must be identity-mapped before this function is called. Otherwise the kernel's code/data
121/// will suddenly be re-mapped to different addresses, likely resulting in a spectacular crash.
122///
123/// The GDT created by this function is allocated from static program memory. This function must
124/// never be called more than once, or the static GDT will be overwritten.
125pub unsafe fn init() {
126 let gdt = static_init!(
127 [Descriptor; NUM_DESCRIPTORS],
128 [Descriptor::NULL; NUM_DESCRIPTORS]
129 );
130
131 let kernel_code_desc =
132 DescriptorBuilder::code_descriptor(0, u32::MAX, CodeSegmentType::ExecuteRead)
133 .present()
134 .limit_granularity_4kb()
135 .db()
136 .finish();
137 gdt[KERNEL_CODE.index() as usize] = kernel_code_desc;
138
139 let kernel_data_desc =
140 DescriptorBuilder::data_descriptor(0, u32::MAX, DataSegmentType::ReadWrite)
141 .present()
142 .limit_granularity_4kb()
143 .db()
144 .finish();
145 gdt[KERNEL_DATA.index() as usize] = kernel_data_desc;
146
147 let tss_desc = unsafe { init_tss() };
148 gdt[GLOBAL_TSS.index() as usize] = tss_desc;
149
150 let user_code_desc =
151 DescriptorBuilder::code_descriptor(0, u32::MAX, CodeSegmentType::ExecuteRead)
152 .present()
153 .limit_granularity_4kb()
154 .db()
155 .dpl(Ring::Ring3)
156 .finish();
157 gdt[USER_CODE.index() as usize] = user_code_desc;
158
159 let user_data_desc =
160 DescriptorBuilder::data_descriptor(0, u32::MAX, DataSegmentType::ReadWrite)
161 .present()
162 .limit_granularity_4kb()
163 .db()
164 .dpl(Ring::Ring3)
165 .finish();
166 gdt[USER_DATA.index() as usize] = user_data_desc;
167
168 unsafe {
169 dtables::lgdt(&DescriptorTablePointer::new_from_slice(gdt));
170 segmentation::load_cs(KERNEL_CODE);
171 segmentation::load_ss(KERNEL_DATA);
172 segmentation::load_ds(KERNEL_DATA);
173 segmentation::load_es(KERNEL_DATA);
174 segmentation::load_fs(KERNEL_DATA);
175 segmentation::load_gs(KERNEL_DATA);
176 task::load_tr(GLOBAL_TSS);
177 }
178}