|
| 1 | +//! Context switching for Proka Kernel |
| 2 | +//! |
| 3 | +//! Based on Redox OS context switching implementation. |
| 4 | +//! Uses ret instruction to jump to new thread via stack. |
| 5 | +
|
| 6 | +use super::thread::Context; |
| 7 | +use core::mem::offset_of; |
| 8 | + |
| 9 | +/// Initialize a new thread's context for its first run |
| 10 | +/// |
| 11 | +/// Sets up the stack so that when context switch "returns", |
| 12 | +/// it jumps to the entry point |
| 13 | +pub fn init_context( |
| 14 | + ctx: &mut Context, |
| 15 | + entry_point: usize, |
| 16 | + stack_top: usize, |
| 17 | + is_kernel_thread: bool, |
| 18 | +) { |
| 19 | + // Stack grows down. |
| 20 | + // System V ABI: stack must be 16-byte aligned before call. |
| 21 | + // Call pushes 8-byte return address. |
| 22 | + // So at function entry, (rsp + 8) is 16-byte aligned, i.e., rsp % 16 == 8. |
| 23 | + |
| 24 | + // We use stack_top - 16 for initial RSP. |
| 25 | + // We write entry_point at stack_top - 16. |
| 26 | + // When ret pops it, RSP becomes stack_top - 8. |
| 27 | + // Since stack_top is 4096-aligned, (stack_top - 8) % 16 == 8. |
| 28 | + let stack_ptr = (stack_top - 16) as *mut u64; |
| 29 | + |
| 30 | + // SAFETY: stack_ptr is valid (allocated by allocate_kernel_stack) |
| 31 | + unsafe { |
| 32 | + core::ptr::write(stack_ptr, entry_point as u64); |
| 33 | + } |
| 34 | + |
| 35 | + // Set up context |
| 36 | + ctx.rip = 0; // Not used - we use stack-based return |
| 37 | + ctx.rsp = (stack_top - 16) as u64; // Stack pointer points to entry_point |
| 38 | + ctx.rflags = 0x202; // IF flag set (interrupts enabled) |
| 39 | + |
| 40 | + if is_kernel_thread { |
| 41 | + ctx.cs = 0x08; // Kernel code segment |
| 42 | + ctx.ss = 0x10; // Kernel data segment |
| 43 | + } else { |
| 44 | + ctx.cs = 0x1B | 3; |
| 45 | + ctx.ss = 0x23 | 3; |
| 46 | + } |
| 47 | + |
| 48 | + // All registers start at zero |
| 49 | + ctx.rax = 0; |
| 50 | + ctx.rbx = 0; |
| 51 | + ctx.rcx = 0; |
| 52 | + ctx.rdx = 0; |
| 53 | + ctx.rsi = 0; |
| 54 | + ctx.rdi = 0; |
| 55 | + ctx.rbp = 0; |
| 56 | + ctx.r8 = 0; |
| 57 | + ctx.r9 = 0; |
| 58 | + ctx.r10 = 0; |
| 59 | + ctx.r11 = 0; |
| 60 | + ctx.r12 = 0; |
| 61 | + ctx.r13 = 0; |
| 62 | + ctx.r14 = 0; |
| 63 | + ctx.r15 = 0; |
| 64 | +} |
| 65 | + |
| 66 | +/// Context switch - save old context and restore new context |
| 67 | +/// |
| 68 | +/// # Safety |
| 69 | +/// Must be called with interrupts disabled. |
| 70 | +/// |
| 71 | +/// This function switches to the new context by: |
| 72 | +/// 1. Saving callee-saved registers to old context |
| 73 | +/// 2. Loading callee-saved registers from new context |
| 74 | +/// 3. Switching stack pointer |
| 75 | +/// 4. Returning (which pops the new RIP from the new stack) |
| 76 | +#[unsafe(naked)] |
| 77 | +pub unsafe extern "C" fn switch_context(_old_ctx: *mut Context, _new_ctx: *const Context) { |
| 78 | + use Context as Cx; |
| 79 | + |
| 80 | + core::arch::naked_asm!( |
| 81 | + // System V AMD64 ABI: |
| 82 | + // - Parameters in rdi (old_ctx), rsi (new_ctx) |
| 83 | + // - Callee-saved: rbx, r12-r15, rbp, rsp |
| 84 | + |
| 85 | + // Save callee-saved registers to old context |
| 86 | + "mov [rdi + {off_rbx}], rbx", |
| 87 | + "mov [rdi + {off_r12}], r12", |
| 88 | + "mov [rdi + {off_r13}], r13", |
| 89 | + "mov [rdi + {off_r14}], r14", |
| 90 | + "mov [rdi + {off_r15}], r15", |
| 91 | + "mov [rdi + {off_rbp}], rbp", |
| 92 | + |
| 93 | + // Save RSP (current stack pointer) |
| 94 | + "mov [rdi + {off_rsp}], rsp", |
| 95 | + |
| 96 | + // Save RFLAGS |
| 97 | + "pushfq", |
| 98 | + "pop QWORD PTR [rdi + {off_rflags}]", |
| 99 | + |
| 100 | + // Load callee-saved registers from new context |
| 101 | + "mov rbx, [rsi + {off_rbx}]", |
| 102 | + "mov r12, [rsi + {off_r12}]", |
| 103 | + "mov r13, [rsi + {off_r13}]", |
| 104 | + "mov r14, [rsi + {off_r14}]", |
| 105 | + "mov r15, [rsi + {off_r15}]", |
| 106 | + "mov rbp, [rsi + {off_rbp}]", |
| 107 | + |
| 108 | + // Switch stack pointer FIRST so that if interrupts are enabled by popfq, |
| 109 | + // they use the new thread's stack. |
| 110 | + "mov rsp, [rsi + {off_rsp}]", |
| 111 | + |
| 112 | + // Load RFLAGS |
| 113 | + "push QWORD PTR [rsi + {off_rflags}]", |
| 114 | + "popfq", |
| 115 | + |
| 116 | + // Return - this pops the return address from the new stack |
| 117 | + "ret", |
| 118 | + |
| 119 | + |
| 120 | + off_rbx = const(offset_of!(Cx, rbx)), |
| 121 | + off_r12 = const(offset_of!(Cx, r12)), |
| 122 | + off_r13 = const(offset_of!(Cx, r13)), |
| 123 | + off_r14 = const(offset_of!(Cx, r14)), |
| 124 | + off_r15 = const(offset_of!(Cx, r15)), |
| 125 | + off_rbp = const(offset_of!(Cx, rbp)), |
| 126 | + off_rsp = const(offset_of!(Cx, rsp)), |
| 127 | + off_rflags = const(offset_of!(Cx, rflags)), |
| 128 | + ); |
| 129 | +} |
| 130 | + |
| 131 | +/// First context switch from boot to the first thread |
| 132 | +/// |
| 133 | +/// # Safety |
| 134 | +/// This function does not return. It switches to the new context and |
| 135 | +/// never comes back (until that thread yields). |
| 136 | +#[unsafe(naked)] |
| 137 | +pub unsafe extern "C" fn first_context_switch(_new_ctx: *const Context) -> ! { |
| 138 | + use Context as Cx; |
| 139 | + |
| 140 | + core::arch::naked_asm!( |
| 141 | + // Load callee-saved registers from new context |
| 142 | + "mov rbx, [rdi + {off_rbx}]", |
| 143 | + "mov r12, [rdi + {off_r12}]", |
| 144 | + "mov r13, [rdi + {off_r13}]", |
| 145 | + "mov r14, [rdi + {off_r14}]", |
| 146 | + "mov r15, [rdi + {off_r15}]", |
| 147 | + "mov rbp, [rdi + {off_rbp}]", |
| 148 | + |
| 149 | + // Switch stack pointer |
| 150 | + "mov rsp, [rdi + {off_rsp}]", |
| 151 | + |
| 152 | + // Load RFLAGS |
| 153 | + "push QWORD PTR [rdi + {off_rflags}]", |
| 154 | + "popfq", |
| 155 | + |
| 156 | + // Return to start the new thread |
| 157 | + "ret", |
| 158 | + |
| 159 | + |
| 160 | + off_rbx = const(offset_of!(Cx, rbx)), |
| 161 | + off_r12 = const(offset_of!(Cx, r12)), |
| 162 | + off_r13 = const(offset_of!(Cx, r13)), |
| 163 | + off_r14 = const(offset_of!(Cx, r14)), |
| 164 | + off_r15 = const(offset_of!(Cx, r15)), |
| 165 | + off_rbp = const(offset_of!(Cx, rbp)), |
| 166 | + off_rsp = const(offset_of!(Cx, rsp)), |
| 167 | + off_rflags = const(offset_of!(Cx, rflags)), |
| 168 | + ); |
| 169 | +} |
0 commit comments