Skip to main content

strat9_kernel/process/
usertest.rs

1//! Minimal Ring 3 test task for Strat9-OS.
2//!
3//! Creates a user address space with a tiny machine code blob that:
4//! 1. Calls SYS_DEBUG_LOG to print "Hello Ring 3!" to serial
5//! 2. Calls SYS_PROC_EXIT to cleanly exit
6//!
7//! This tests the full SYSCALL/SYSRET pipeline without needing an ELF loader.
8
9use alloc::sync::Arc;
10use x86_64::VirtAddr;
11
12use crate::{
13    memory::address_space::{AddressSpace, VmaFlags, VmaType},
14    process::{
15        task::{CpuContext, KernelStack, SyncUnsafeCell, Task, TaskPriority},
16        TaskState,
17    },
18};
19
20/// User code virtual address (must be in lower half, page-aligned).
21const USER_CODE_ADDR: u64 = 0x40_0000;
22
23/// User stack virtual address (page-aligned).
24const USER_STACK_ADDR: u64 = 0x80_0000;
25
26/// User stack top (stack grows down, so this is base + 4096).
27const USER_STACK_TOP: u64 = USER_STACK_ADDR + 0x1000;
28
29/// Create and schedule a minimal Ring 3 test task.
30///
31/// This function:
32/// 1. Creates a user address space (PML4 with kernel half cloned)
33/// 2. Maps a code page at USER_CODE_ADDR (RX, user-accessible)
34/// 3. Maps a stack page at USER_STACK_ADDR (RW, user-accessible)
35/// 4. Writes a small machine code blob into the code page
36/// 5. Creates a kernel task whose entry point does IRETQ to Ring 3
37pub fn create_user_test_task() {
38    log::info!("Creating Ring 3 test task...");
39
40    // Step 1: Create user address space
41    let user_as = match AddressSpace::new_user() {
42        Ok(a) => Arc::new(a),
43        Err(e) => {
44            log::error!("Failed to create user address space: {}", e);
45            return;
46        }
47    };
48
49    // Step 2: Map code page (USER_ACCESSIBLE | RX)
50    let code_flags = VmaFlags {
51        readable: true,
52        writable: false,
53        executable: true,
54        user_accessible: true,
55    };
56    if let Err(e) = user_as.map_region(
57        USER_CODE_ADDR,
58        1,
59        code_flags,
60        VmaType::Code,
61        crate::memory::address_space::VmaPageSize::Small,
62    ) {
63        log::error!("Failed to map user code page: {}", e);
64        return;
65    }
66
67    // Step 3: Map stack page (USER_ACCESSIBLE | RW)
68    let stack_flags = VmaFlags {
69        readable: true,
70        writable: true,
71        executable: false,
72        user_accessible: true,
73    };
74    if let Err(e) = user_as.map_region(
75        USER_STACK_ADDR,
76        1,
77        stack_flags,
78        VmaType::Stack,
79        crate::memory::address_space::VmaPageSize::Small,
80    ) {
81        log::error!("Failed to map user stack page: {}", e);
82        return;
83    }
84
85    // Step 4: Write machine code into the code page.
86    // We need to write via the HHDM mapping since the user AS isn't active.
87    // Translate user vaddr → phys → HHDM virt for writing.
88    let code_phys = user_as.translate(VirtAddr::new(USER_CODE_ADDR));
89    let code_phys = match code_phys {
90        Some(p) => p,
91        None => {
92            log::error!("Failed to translate user code page");
93            return;
94        }
95    };
96    let code_virt = crate::memory::phys_to_virt(code_phys.as_u64());
97
98    // Write the user code blob
99    write_user_code(code_virt as *mut u8);
100
101    // Step 5: Create a kernel task that will IRETQ to Ring 3.
102    // We store the user_as Arc and entry parameters in statics since the
103    // trampoline is a simple extern "C" fn.
104    // SAFETY: Single-threaded setup, task not yet scheduled.
105    unsafe {
106        let ptr = &raw mut USER_TASK_AS;
107        *ptr = Some(user_as.clone());
108    }
109
110    // Create a kernel task with the IRETQ trampoline as entry point
111    let kernel_stack = match KernelStack::allocate(Task::DEFAULT_STACK_SIZE) {
112        Ok(s) => s,
113        Err(e) => {
114            log::error!("Failed to allocate kernel stack for user task: {}", e);
115            return;
116        }
117    };
118
119    let context = CpuContext::new(ring3_trampoline as *const () as u64, &kernel_stack);
120    let (pid, tid, tgid) = Task::allocate_process_ids();
121    let fpu_state = crate::process::task::ExtendedState::new();
122    let xcr0_mask = fpu_state.xcr0_mask;
123
124    let task = Arc::new(Task {
125        id: crate::process::TaskId::new(),
126        pid,
127        tid,
128        tgid,
129        pgid: core::sync::atomic::AtomicU32::new(pid),
130        sid: core::sync::atomic::AtomicU32::new(pid),
131        uid: core::sync::atomic::AtomicU32::new(0),
132        euid: core::sync::atomic::AtomicU32::new(0),
133        gid: core::sync::atomic::AtomicU32::new(0),
134        egid: core::sync::atomic::AtomicU32::new(0),
135        state: core::sync::atomic::AtomicU8::new(TaskState::Ready as u8),
136        priority: TaskPriority::Normal,
137        context: SyncUnsafeCell::new(context),
138        resume_kind: SyncUnsafeCell::new(crate::process::task::ResumeKind::RetFrame),
139        interrupt_rsp: core::sync::atomic::AtomicU64::new(0),
140        kernel_stack,
141        user_stack: None,
142
143        name: "test-user-ring3",
144        process: alloc::sync::Arc::new(crate::process::process::Process::new(pid, user_as)),
145        pending_signals: super::signal::SignalSet::new(),
146
147        blocked_signals: super::signal::SignalSet::new(),
148        irq_signal_delivery_blocked: core::sync::atomic::AtomicBool::new(false),
149        signal_stack: SyncUnsafeCell::new(None),
150        itimers: super::timer::ITimers::new(),
151        wake_pending: core::sync::atomic::AtomicBool::new(false),
152        wake_deadline_ns: core::sync::atomic::AtomicU64::new(0),
153        trampoline_entry: core::sync::atomic::AtomicU64::new(0),
154        trampoline_stack_top: core::sync::atomic::AtomicU64::new(0),
155        trampoline_arg0: core::sync::atomic::AtomicU64::new(0),
156        ticks: core::sync::atomic::AtomicU64::new(0),
157        sched_policy: crate::process::task::SyncUnsafeCell::new(Task::default_sched_policy(
158            TaskPriority::Normal,
159        )),
160        home_cpu: core::sync::atomic::AtomicUsize::new(usize::MAX),
161        vruntime: core::sync::atomic::AtomicU64::new(0),
162        fair_rq_generation: core::sync::atomic::AtomicU64::new(0),
163        fair_on_rq: core::sync::atomic::AtomicBool::new(false),
164        clear_child_tid: core::sync::atomic::AtomicU64::new(0),
165        user_fs_base: core::sync::atomic::AtomicU64::new(0),
166        fpu_state: crate::process::task::SyncUnsafeCell::new(fpu_state),
167        xcr0_mask: core::sync::atomic::AtomicU64::new(xcr0_mask),
168        rt_link: intrusive_collections::LinkedListLink::new(),
169    });
170
171    task.seed_interrupt_frame(crate::syscall::SyscallFrame {
172        r15: 0,
173        r14: 0,
174        r13: 0,
175        r12: 0,
176        rbp: 0,
177        rbx: 0,
178        r11: 0x202,
179        r10: 0,
180        r9: 0,
181        r8: 0,
182        rsi: 0,
183        rdi: 0,
184        rdx: 0,
185        rcx: USER_CODE_ADDR,
186        rax: 0,
187        iret_rip: USER_CODE_ADDR,
188        iret_cs: crate::arch::x86_64::gdt::user_code_selector().0 as u64,
189        iret_rflags: 0x202,
190        iret_rsp: USER_STACK_TOP,
191        iret_ss: crate::arch::x86_64::gdt::user_data_selector().0 as u64,
192    });
193
194    crate::process::add_task(task);
195    log::info!(
196        "Ring 3 test task created: code@{:#x}, stack@{:#x}",
197        USER_CODE_ADDR,
198        USER_STACK_TOP,
199    );
200}
201
202/// Static storage for the user address space (accessed by the trampoline).
203static mut USER_TASK_AS: Option<Arc<AddressSpace>> = None;
204
205/// Trampoline that switches to user address space and does IRETQ to Ring 3.
206///
207/// This runs as a kernel task entry point. It:
208/// 1. Switches CR3 to the user address space
209/// 2. Pushes an IRET frame (SS, RSP, RFLAGS, CS, RIP)
210/// 3. Executes IRETQ to jump to Ring 3 code
211extern "C" fn ring3_trampoline() -> ! {
212    use crate::arch::x86_64::gdt;
213
214    // Switch to user address space
215    // SAFETY: The user AS was set up with the kernel half cloned.
216    unsafe {
217        let user_as = &*(&raw const USER_TASK_AS);
218        if let Some(ref as_ref) = user_as {
219            as_ref.switch_to();
220
221            // Diagnostic: verify the code page is mapped before IRETQ
222            let phys = as_ref.translate(x86_64::VirtAddr::new(USER_CODE_ADDR));
223            crate::serial_println!(
224                "[ring3-tramp] CR3={:#x}, translate({:#x})={:?}",
225                as_ref.cr3().as_u64(),
226                USER_CODE_ADDR,
227                phys,
228            );
229
230            // Also verify via a direct CR3 read + manual walk
231            let (cr3_frame, _) = x86_64::registers::control::Cr3::read();
232            let cr3_phys = cr3_frame.start_address().as_u64();
233            let hhdm = crate::memory::hhdm_offset();
234            crate::serial_println!(
235                "[ring3-tramp] Active CR3={:#x} (expected {:#x})",
236                cr3_phys,
237                as_ref.cr3().as_u64(),
238            );
239
240            // Manual PML4[0] check
241            let l4_ptr = (cr3_phys + hhdm) as *const u64;
242            let l4_entry = *l4_ptr; // PML4[0]
243            crate::serial_println!(
244                "[ring3-tramp] PML4[0]={:#x} (present={})",
245                l4_entry,
246                l4_entry & 1,
247            );
248        } else {
249            crate::serial_println!("[ring3-tramp] ERROR: USER_TASK_AS is None!");
250        }
251    }
252
253    let user_cs = gdt::user_code_selector().0 as u64;
254    let user_ss = gdt::user_data_selector().0 as u64;
255    let user_rip = USER_CODE_ADDR;
256    let user_rsp = USER_STACK_TOP;
257    let user_rflags: u64 = 0x202; // IF=1, reserved bit 1 = 1
258
259    crate::serial_println!(
260        "[ring3-tramp] IRETQ: CS={:#x} RIP={:#x} SS={:#x} RSP={:#x} RFLAGS={:#x}",
261        user_cs,
262        user_rip,
263        user_ss,
264        user_rsp,
265        user_rflags,
266    );
267
268    // SAFETY: We've set up valid user mappings. IRETQ will switch to Ring 3.
269    unsafe {
270        core::arch::asm!(
271            "push {ss}",       // SS
272            "push {rsp}",      // RSP
273            "push {rflags}",   // RFLAGS
274            "push {cs}",       // CS
275            "push {rip}",      // RIP
276            "swapgs",
277            "iretq",
278            ss = in(reg) user_ss,
279            rsp = in(reg) user_rsp,
280            rflags = in(reg) user_rflags,
281            cs = in(reg) user_cs,
282            rip = in(reg) user_rip,
283            options(noreturn),
284        );
285    }
286}
287
288/// Write the user-mode test program into the code page.
289///
290/// The program:
291/// ```asm
292/// ; SYS_DEBUG_LOG(buf_ptr, buf_len)
293/// mov rax, 600           ; SYS_DEBUG_LOG
294/// lea rdi, [rip + msg]   ; buffer pointer (rdi = arg1)
295/// mov rsi, 13            ; length (rsi = arg2)
296/// syscall
297///
298/// ; SYS_PROC_EXIT(0)
299/// mov rax, 300           ; SYS_PROC_EXIT
300/// xor rdi, rdi           ; exit code 0
301/// syscall
302///
303/// ; Safety: should never reach here
304/// hlt
305/// jmp $-1
306///
307/// msg: db "Hello Ring 3!"
308/// ```
309fn write_user_code(dest: *mut u8) {
310    // Hand-assembled x86_64 machine code
311    let code: &[u8] = &[
312        // mov rax, 600 (SYS_DEBUG_LOG)
313        0x48, 0xC7, 0xC0, 0x58, 0x02, 0x00, 0x00, // mov rax, 0x258 (600)
314        // lea rdi, [rip + offset_to_msg]
315        // This instruction is at offset 7, length 7, so next IP at offset 14.
316        // msg is at offset 38 (after hlt+jmp = 35+1+2 = 38).
317        // disp32 = 38 - 14 = 24 = 0x18
318        0x48, 0x8D, 0x3D, 0x18, 0x00, 0x00, 0x00, // lea rdi, [rip+0x18]
319        // mov rsi, 13
320        0x48, 0xC7, 0xC6, 0x0D, 0x00, 0x00, 0x00, // mov rsi, 13
321        // syscall
322        0x0F, 0x05, // syscall
323        // mov rax, 300 (SYS_PROC_EXIT)
324        0x48, 0xC7, 0xC0, 0x2C, 0x01, 0x00, 0x00, // mov rax, 0x12C (300)
325        // xor rdi, rdi
326        0x48, 0x31, 0xFF, // xor rdi, rdi
327        // syscall
328        0x0F, 0x05, // syscall
329        // hlt + infinite loop (safety)
330        0xF4, // hlt
331        0xEB, 0xFD, // jmp $-1 (back to hlt)
332        // "Hello Ring 3!" (13 bytes)
333        b'H', b'e', b'l', b'l', b'o', b' ', b'R', b'i', b'n', b'g', b' ', b'3', b'!',
334    ];
335
336    // SAFETY: dest points to a freshly allocated and zeroed page.
337    unsafe {
338        core::ptr::copy_nonoverlapping(code.as_ptr(), dest, code.len());
339    }
340}