1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 10:37:45 +00:00

Kernel: Implement software context switching and Processor structure

Moving certain globals into a new Processor structure for
each CPU allows us to eventually run an instance of the
scheduler on each CPU.
This commit is contained in:
Tom 2020-06-27 13:42:28 -06:00 committed by Andreas Kling
parent 10407061d2
commit fb41d89384
22 changed files with 1002 additions and 513 deletions

View file

@ -252,15 +252,6 @@ apic_ap_start:
mov %cs, %ax
mov %ax, %ds
/* Generate a new processor id. This is not the APIC id. We just
need a way to find ourselves a stack without stomping on other
APs that may be doing this concurrently. */
xor %ax, %ax
mov %ax, %bp
inc %ax
lock; xaddw %ax, %ds:(ap_cpu_id - apic_ap_start)(%bp) /* avoid relocation entries */
mov %ax, %bx
xor %ax, %ax
mov %ax, %sp
@ -281,14 +272,18 @@ apic_ap_start32:
mov %ax, %es
mov %ax, %fs
mov %ax, %gs
movl $0x8000, %ebp
/* generate a unique ap cpu id (0 means 1st ap, not bsp!) */
xorl %eax, %eax
incl %eax
lock; xaddl %eax, (ap_cpu_id - apic_ap_start)(%ebp) /* avoid relocation entries */
movl %eax, %esi
/* find our allocated stack based on the generated id */
andl 0x0000FFFF, %ebx
movl %ebx, %esi
movl (ap_cpu_init_stacks - apic_ap_start)(%ebp, %ebx, 4), %esp
movl (ap_cpu_init_stacks - apic_ap_start)(%ebp, %eax, 4), %esp
/* check if we support NX and enable it if we do */
movl $0x80000001, %eax
cpuid
@ -319,8 +314,8 @@ apic_ap_start32:
lgdt (ap_cpu_gdtr_initial2 - apic_ap_start + 0xc0008000)
/* jump above 3GB into our identity mapped area now */
ljmp $8, $(1f - apic_ap_start + 0xc0008000)
1:
ljmp $8, $(apic_ap_start32_2 - apic_ap_start + 0xc0008000)
apic_ap_start32_2:
/* flush the TLB */
movl %cr3, %eax
movl %eax, %cr3
@ -338,13 +333,20 @@ apic_ap_start32:
movl %eax, %cr0
movl (ap_cpu_init_cr4 - apic_ap_start)(%ebp), %eax
movl %eax, %cr4
/* push the Processor pointer this CPU is going to use */
movl (ap_cpu_init_processor_info_array - apic_ap_start)(%ebp), %eax
addl $0xc0000000, %eax
movl 0(%eax, %esi, 4), %eax
push %eax
/* push the cpu id, 0 representing the bsp and call into c++ */
incl %esi
push %esi
xor %ebp, %ebp
cld
/* push the arbitrary cpu id, 0 representing the bsp and call into c++ */
inc %esi
push %esi
/* We are in identity mapped P0x8000 and the BSP will unload this code
once all APs are initialized, so call init_ap but return to our
infinite loop */
@ -356,7 +358,7 @@ apic_ap_start32:
apic_ap_start_size:
.2byte end_apic_ap_start - apic_ap_start
ap_cpu_id:
.2byte 0x0
.4byte 0x0
ap_cpu_gdt:
/* null */
.8byte 0x0
@ -388,6 +390,9 @@ ap_cpu_init_cr3:
.global ap_cpu_init_cr4
ap_cpu_init_cr4:
.4byte 0x0 /* will be set at runtime */
.global ap_cpu_init_processor_info_array
ap_cpu_init_processor_info_array:
.4byte 0x0 /* will be set at runtime */
.global ap_cpu_init_stacks
ap_cpu_init_stacks:
/* array of allocated stack pointers */

View file

@ -38,42 +38,28 @@
#include <Kernel/Interrupts/UnhandledInterruptHandler.h>
#include <Kernel/KSyms.h>
#include <Kernel/Process.h>
#include <Kernel/SpinLock.h>
#include <Kernel/Thread.h>
#include <Kernel/VM/MemoryManager.h>
#include <Kernel/VM/PageDirectory.h>
#include <Kernel/IO.h>
#include <LibC/mallocdefs.h>
//#define PAGE_FAULT_DEBUG
//#define CONTEXT_SWITCH_DEBUG
namespace Kernel {
static DescriptorTablePointer s_idtr;
static DescriptorTablePointer s_gdtr;
static Descriptor s_idt[256];
static Descriptor s_gdt[256];
static GenericInterruptHandler* s_interrupt_handler[GENERIC_INTERRUPT_HANDLERS_COUNT];
static Vector<u16>* s_gdt_freelist;
static u16 s_gdt_length;
u16 gdt_alloc_entry()
{
ASSERT(s_gdt_freelist);
ASSERT(!s_gdt_freelist->is_empty());
return s_gdt_freelist->take_last();
}
void gdt_free_entry(u16 entry)
{
s_gdt_freelist->append(entry);
}
extern "C" void handle_interrupt(RegisterState);
extern "C" void handle_interrupt(TrapFrame*);
#define EH_ENTRY(ec, title) \
extern "C" void title##_asm_entry(); \
extern "C" void title##_handler(RegisterState); \
extern "C" void title##_handler(TrapFrame*); \
asm( \
".globl " #title "_asm_entry\n" \
"" #title "_asm_entry: \n" \
@ -83,22 +69,21 @@ extern "C" void handle_interrupt(RegisterState);
" pushl %fs\n" \
" pushl %gs\n" \
" pushl %ss\n" \
" mov $0x10, %ax\n" \
" mov $" __STRINGIFY(GDT_SELECTOR_DATA0) ", %ax\n" \
" mov %ax, %ds\n" \
" mov %ax, %es\n" \
" mov $" __STRINGIFY(GDT_SELECTOR_PROC) ", %ax\n" \
" mov %ax, %fs\n" \
" pushl %esp \n" /* set TrapFrame::regs */ \
" subl $" __STRINGIFY(TRAP_FRAME_SIZE - 4) ", %esp \n" \
" pushl %esp \n" \
" cld\n" \
" call enter_trap_no_irq \n" \
" call " #title "_handler\n" \
" add $0x4, %esp \n" \
" popl %gs\n" \
" popl %fs\n" \
" popl %es\n" \
" popl %ds\n" \
" popa\n" \
" add $0x4, %esp\n" \
" iret\n");
" jmp common_trap_exit \n");
#define EH_ENTRY_NO_CODE(ec, title) \
extern "C" void title##_handler(RegisterState); \
extern "C" void title##_handler(TrapFrame*); \
extern "C" void title##_asm_entry(); \
asm( \
".globl " #title "_asm_entry\n" \
@ -110,19 +95,18 @@ extern "C" void handle_interrupt(RegisterState);
" pushl %fs\n" \
" pushl %gs\n" \
" pushl %ss\n" \
" mov $0x10, %ax\n" \
" mov $" __STRINGIFY(GDT_SELECTOR_DATA0) ", %ax\n" \
" mov %ax, %ds\n" \
" mov %ax, %es\n" \
" mov $" __STRINGIFY(GDT_SELECTOR_PROC) ", %ax\n" \
" mov %ax, %fs\n" \
" pushl %esp \n" /* set TrapFrame::regs */ \
" subl $" __STRINGIFY(TRAP_FRAME_SIZE - 4) ", %esp \n" \
" pushl %esp \n" \
" cld\n" \
" call enter_trap_no_irq \n" \
" call " #title "_handler\n" \
" add $0x4, %esp\n" \
" popl %gs\n" \
" popl %fs\n" \
" popl %es\n" \
" popl %ds\n" \
" popa\n" \
" add $0x4, %esp\n" \
" iret\n");
" jmp common_trap_exit \n");
static void dump(const RegisterState& regs)
{
@ -172,7 +156,7 @@ void handle_crash(RegisterState& regs, const char* description, int signal, bool
// make sure we switch back to the right page tables.
MM.enter_process_paging_scope(*Process::current);
klog() << "CRASH: " << description << ". Ring " << (Process::current->is_ring0() ? 0 : 3) << ".";
klog() << "CRASH: CPU #" << Processor::current().id() << " " << description << ". Ring " << (Process::current->is_ring0() ? 0 : 3) << ".";
dump(regs);
if (Process::current->is_ring0()) {
@ -186,29 +170,29 @@ void handle_crash(RegisterState& regs, const char* description, int signal, bool
}
EH_ENTRY_NO_CODE(6, illegal_instruction);
void illegal_instruction_handler(RegisterState regs)
void illegal_instruction_handler(TrapFrame* trap)
{
clac();
handle_crash(regs, "Illegal instruction", SIGILL);
handle_crash(*trap->regs, "Illegal instruction", SIGILL);
}
EH_ENTRY_NO_CODE(0, divide_error);
void divide_error_handler(RegisterState regs)
void divide_error_handler(TrapFrame* trap)
{
clac();
handle_crash(regs, "Divide error", SIGFPE);
handle_crash(*trap->regs, "Divide error", SIGFPE);
}
EH_ENTRY(13, general_protection_fault);
void general_protection_fault_handler(RegisterState regs)
void general_protection_fault_handler(TrapFrame* trap)
{
clac();
handle_crash(regs, "General protection fault", SIGSEGV);
handle_crash(*trap->regs, "General protection fault", SIGSEGV);
}
// 7: FPU not available exception
EH_ENTRY_NO_CODE(7, fpu_exception);
void fpu_exception_handler(RegisterState)
void fpu_exception_handler(TrapFrame*)
{
// Just clear the TS flag. We've already restored the FPU state eagerly.
// FIXME: It would be nice if we didn't have to do this at all.
@ -217,10 +201,11 @@ void fpu_exception_handler(RegisterState)
// 14: Page Fault
EH_ENTRY(14, page_fault);
void page_fault_handler(RegisterState regs)
void page_fault_handler(TrapFrame* trap)
{
clac();
auto& regs = *trap->regs;
u32 fault_address;
asm("movl %%cr2, %%eax"
: "=a"(fault_address));
@ -294,9 +279,10 @@ void page_fault_handler(RegisterState regs)
}
EH_ENTRY_NO_CODE(1, debug);
void debug_handler(RegisterState regs)
void debug_handler(TrapFrame* trap)
{
clac();
auto& regs = *trap->regs;
if (!Process::current || (regs.cs & 3) == 0) {
klog() << "Debug Exception in Ring0";
hang();
@ -314,9 +300,10 @@ void debug_handler(RegisterState regs)
}
EH_ENTRY_NO_CODE(3, breakpoint);
void breakpoint_handler(RegisterState regs)
void breakpoint_handler(TrapFrame* trap)
{
clac();
auto& regs = *trap->regs;
if (!Process::current || (regs.cs & 3) == 0) {
klog() << "Breakpoint Trap in Ring0";
hang();
@ -356,80 +343,11 @@ EH(12, "Stack exception")
EH(15, "Unknown error")
EH(16, "Coprocessor error")
static void write_raw_gdt_entry(u16 selector, u32 low, u32 high)
{
u16 i = (selector & 0xfffc) >> 3;
s_gdt[i].low = low;
s_gdt[i].high = high;
if (i > s_gdt_length)
s_gdtr.limit = (s_gdt_length + 1) * 8 - 1;
}
void write_gdt_entry(u16 selector, Descriptor& descriptor)
{
write_raw_gdt_entry(selector, descriptor.low, descriptor.high);
}
Descriptor& get_gdt_entry(u16 selector)
{
u16 i = (selector & 0xfffc) >> 3;
return *(Descriptor*)(&s_gdt[i]);
}
void flush_gdt()
{
s_gdtr.address = s_gdt;
s_gdtr.limit = (s_gdt_length * 8) - 1;
asm("lgdt %0" ::"m"(s_gdtr)
: "memory");
}
const DescriptorTablePointer& get_gdtr()
{
return s_gdtr;
}
const DescriptorTablePointer& get_idtr()
{
return s_idtr;
}
void gdt_init()
{
s_gdt_length = 5;
s_gdt_freelist = new Vector<u16>();
s_gdt_freelist->ensure_capacity(256);
for (size_t i = s_gdt_length; i < 256; ++i)
s_gdt_freelist->append(i * 8);
s_gdt_length = 256;
s_gdtr.address = s_gdt;
s_gdtr.limit = (s_gdt_length * 8) - 1;
write_raw_gdt_entry(0x0000, 0x00000000, 0x00000000);
write_raw_gdt_entry(0x0008, 0x0000ffff, 0x00cf9a00);
write_raw_gdt_entry(0x0010, 0x0000ffff, 0x00cf9200);
write_raw_gdt_entry(0x0018, 0x0000ffff, 0x00cffa00);
write_raw_gdt_entry(0x0020, 0x0000ffff, 0x00cff200);
flush_gdt();
asm volatile(
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"mov %%ax, %%fs\n"
"mov %%ax, %%gs\n"
"mov %%ax, %%ss\n" ::"a"(0x10)
: "memory");
// Make sure CS points to the kernel code descriptor.
asm volatile(
"ljmpl $0x8, $sanity\n"
"sanity:\n");
}
static void unimp_trap()
{
klog() << "Unhandled IRQ.";
@ -514,7 +432,7 @@ void flush_idt()
asm("lidt %0" ::"m"(s_idtr));
}
void idt_init()
static void idt_init()
{
s_idtr.address = s_idt;
s_idtr.limit = 0x100 * 8 - 1;
@ -683,21 +601,32 @@ void load_task_register(u16 selector)
asm("ltr %0" ::"r"(selector));
}
u32 g_in_irq;
void handle_interrupt(RegisterState regs)
void handle_interrupt(TrapFrame* trap)
{
clac();
++g_in_irq;
auto& regs = *trap->regs;
ASSERT(regs.isr_number >= IRQ_VECTOR_BASE && regs.isr_number <= (IRQ_VECTOR_BASE + GENERIC_INTERRUPT_HANDLERS_COUNT));
u8 irq = (u8)(regs.isr_number - 0x50);
ASSERT(s_interrupt_handler[irq]);
s_interrupt_handler[irq]->handle_interrupt(regs);
s_interrupt_handler[irq]->increment_invoking_counter();
--g_in_irq;
s_interrupt_handler[irq]->eoi();
}
void enter_trap_no_irq(TrapFrame* trap)
{
Processor::current().enter_trap(*trap, false);
}
void enter_trap(TrapFrame* trap)
{
Processor::current().enter_trap(*trap, true);
}
void exit_trap(TrapFrame* trap)
{
return Processor::current().exit_trap(*trap);
}
void sse_init()
{
asm volatile(
@ -740,9 +669,10 @@ void cpu_detect()
g_cpu_supports_rdseed = (extended_features.ebx() & (1 << 18));
}
void cpu_setup()
void cpu_setup(u32 cpu)
{
cpu_detect();
if (cpu == 0)
cpu_detect();
if (g_cpu_supports_sse) {
sse_init();
@ -863,6 +793,424 @@ u32 read_dr6()
return dr6;
}
FPUState Processor::s_clean_fpu_state;
void Processor::initialize(u32 cpu)
{
m_self = this;
m_cpu = cpu;
m_in_irq = 0;
gdt_init();
if (cpu == 0)
idt_init();
else
flush_idt();
ASSERT(&current() == this); // sanity check
if (cpu == 0) {
ASSERT((FlatPtr(&s_clean_fpu_state) & 0xF) == 0);
asm volatile("fninit");
asm volatile("fxsave %0"
: "=m"(s_clean_fpu_state));
}
klog() << "CPU #" << cpu << " using Processor at " << VirtualAddress(FlatPtr(this));
}
void Processor::write_raw_gdt_entry(u16 selector, u32 low, u32 high)
{
u16 i = (selector & 0xfffc) >> 3;
u32 prev_gdt_length = m_gdt_length;
if (i > m_gdt_length) {
m_gdt_length = i + 1;
ASSERT(m_gdt_length <= sizeof(m_gdt) / sizeof(m_gdt[0]));
m_gdtr.limit = (m_gdt_length + 1) * 8 - 1;
}
m_gdt[i].low = low;
m_gdt[i].high = high;
// clear selectors we may have skipped
while (i < prev_gdt_length) {
m_gdt[i].low = 0;
m_gdt[i].high = 0;
i++;
}
}
void Processor::write_gdt_entry(u16 selector, Descriptor& descriptor)
{
write_raw_gdt_entry(selector, descriptor.low, descriptor.high);
}
Descriptor& Processor::get_gdt_entry(u16 selector)
{
u16 i = (selector & 0xfffc) >> 3;
return *(Descriptor*)(&m_gdt[i]);
}
void Processor::flush_gdt()
{
m_gdtr.address = m_gdt;
m_gdtr.limit = (m_gdt_length * 8) - 1;
asm volatile("lgdt %0" ::"m"(m_gdtr)
: "memory");
}
const DescriptorTablePointer& Processor::get_gdtr()
{
return m_gdtr;
}
extern "C" void enter_thread_context(Thread* from_thread, Thread* to_thread)
{
ASSERT(from_thread == to_thread || from_thread->state() != Thread::Running);
ASSERT(to_thread->state() == Thread::Running);
auto& from_tss = from_thread->tss();
auto& to_tss = to_thread->tss();
asm volatile("fxsave %0"
: "=m"(from_thread->fpu_state()));
from_tss.fs = get_fs();
from_tss.gs = get_gs();
set_fs(to_tss.fs);
set_gs(to_tss.gs);
auto& tls_descriptor = Processor::current().get_gdt_entry(GDT_SELECTOR_TLS);
tls_descriptor.set_base(to_thread->thread_specific_data().as_ptr());
tls_descriptor.set_limit(to_thread->thread_specific_region_size());
if (from_tss.cr3 != to_tss.cr3)
write_cr3(to_tss.cr3);
asm volatile("fxrstor %0"
::"m"(to_thread->fpu_state()));
// TODO: debug registers
// TODO: ioperm?
}
#define ENTER_THREAD_CONTEXT_ARGS_SIZE (2 * 4) // to_thread, from_thread
void Processor::switch_context(Thread* from_thread, Thread* to_thread)
{
ASSERT(!in_irq());
ASSERT(is_kernel_mode());
#ifdef CONTEXT_SWITCH_DEBUG
dbg() << "switch_context --> switching out of: " << *from_thread;
#endif
// Switch to new thread context, passing from_thread and to_thread
// through to the new context using registers edx and eax
asm volatile(
// NOTE: changing how much we push to the stack affects
// SWITCH_CONTEXT_TO_STACK_SIZE and thread_context_first_enter()!
"pushfl \n"
"pushl %%ebx \n"
"pushl %%esi \n"
"pushl %%edi \n"
"pushl %%ebp \n"
"movl %%esp, %[from_esp] \n"
"movl $1f, %[from_eip] \n"
"movl %[to_esp0], %%ebx \n"
"movl %%ebx, %[tss_esp0] \n"
"movl %[to_esp], %%esp \n"
"pushl %[to_thread] \n"
"pushl %[from_thread] \n"
"pushl %[to_eip] \n"
"cld \n"
"jmp enter_thread_context \n"
"1: \n"
"popl %%edx \n"
"popl %%eax \n"
"popl %%ebp \n"
"popl %%edi \n"
"popl %%esi \n"
"popl %%ebx \n"
"popfl \n"
: [from_esp] "=m" (from_thread->tss().esp),
[from_eip] "=m" (from_thread->tss().eip),
[tss_esp0] "=m" (m_tss.esp0),
"=d" (from_thread), // needed so that from_thread retains the correct value
"=a" (to_thread) // needed so that to_thread retains the correct value
: [to_esp] "g" (to_thread->tss().esp),
[to_esp0] "g" (to_thread->tss().esp0),
[to_eip] "c" (to_thread->tss().eip),
[from_thread] "d" (from_thread),
[to_thread] "a" (to_thread)
);
#ifdef CONTEXT_SWITCH_DEBUG
dbg() << "switch_context <-- from " << *from_thread << " to " << *to_thread;
#endif
}
extern "C" void context_first_init(Thread* from_thread, Thread* to_thread, TrapFrame* trap)
{
ASSERT(!are_interrupts_enabled());
ASSERT(is_kernel_mode());
(void)from_thread;
(void)to_thread;
(void)trap;
#ifdef CONTEXT_SWITCH_DEBUG
dbg() << "switch_context <-- from " << *from_thread << " to " << *to_thread << " (context_first_init)";
#endif
}
extern "C" void thread_context_first_enter(void);
asm(
// enter_thread_context returns to here first time a thread is executing
".globl thread_context_first_enter \n"
"thread_context_first_enter: \n"
// switch_context will have pushed from_thread and to_thread to our new
// stack prior to thread_context_first_enter() being called, and the
// pointer to TrapFrame was the top of the stack before that
" movl 8(%esp), %ebx \n" // save pointer to TrapFrame
" cld \n"
" call context_first_init \n"
" addl $" __STRINGIFY(ENTER_THREAD_CONTEXT_ARGS_SIZE) ", %esp \n"
" movl %ebx, 0(%esp) \n" // push pointer to TrapFrame
" jmp common_trap_exit \n"
);
u32 Processor::init_context(Thread& thread)
{
ASSERT(is_kernel_mode());
const u32 kernel_stack_top = thread.kernel_stack_top();
u32 stack_top = kernel_stack_top;
// TODO: handle NT?
ASSERT((cpu_flags() & 0x24000) == 0); // Assume !(NT | VM)
auto& tss = thread.tss();
bool return_to_user = (tss.cs & 3) != 0;
// make room for an interrupt frame
if (!return_to_user) {
// userspace_esp and userspace_ss are not popped off by iret
// unless we're switching back to user mode
stack_top -= sizeof(RegisterState) - 2 * sizeof(u32);
} else {
stack_top -= sizeof(RegisterState);
}
// we want to end up 16-byte aligned, %esp + 4 should be aligned
stack_top -= sizeof(u32);
*reinterpret_cast<u32*>(kernel_stack_top - 4) = 0;
// set up the stack so that after returning from thread_context_first_enter()
// we will end up either in kernel mode or user mode, depending on how the thread is set up
// However, the first step is to always start in kernel mode with thread_context_first_enter
RegisterState& iretframe = *reinterpret_cast<RegisterState*>(stack_top);
iretframe.ss = tss.ss;
iretframe.gs = tss.gs;
iretframe.fs = tss.fs;
iretframe.es = tss.es;
iretframe.ds = tss.ds;
iretframe.edi = tss.edi;
iretframe.esi = tss.esi;
iretframe.ebp = tss.ebp;
iretframe.esp = 0;
iretframe.ebx = tss.ebx;
iretframe.edx = tss.edx;
iretframe.ecx = tss.ecx;
iretframe.eax = tss.eax;
iretframe.eflags = tss.eflags;
iretframe.eip = tss.eip;
iretframe.cs = tss.cs;
if (return_to_user) {
iretframe.userspace_esp = tss.esp;
iretframe.userspace_ss = tss.ss;
}
// make space for a trap frame
stack_top -= sizeof(TrapFrame);
TrapFrame& trap = *reinterpret_cast<TrapFrame*>(stack_top);
trap.regs = &iretframe;
trap.prev_irq_level = 0;
stack_top -= sizeof(u32); // pointer to TrapFrame
*reinterpret_cast<u32*>(stack_top) = stack_top + 4;
#ifdef CONTEXT_SWITCH_DEBUG
dbg() << "init_context " << thread << " set up to execute at eip: " << VirtualAddress(tss.eip) << " esp: " << VirtualAddress(tss.esp) << " stack top: " << VirtualAddress(stack_top);
#endif
// make switch_context() always first return to thread_context_first_enter()
// in kernel mode, so set up these values so that we end up popping iretframe
// off the stack right after the context switch completed, at which point
// control is transferred to what iretframe is pointing to.
tss.eip = FlatPtr(&thread_context_first_enter);
tss.esp0 = kernel_stack_top;
tss.esp = stack_top;
tss.cs = GDT_SELECTOR_CODE0;
tss.ds = GDT_SELECTOR_DATA0;
tss.es = GDT_SELECTOR_DATA0;
tss.gs = GDT_SELECTOR_DATA0;
tss.ss = GDT_SELECTOR_DATA0;
tss.fs = GDT_SELECTOR_PROC;
return stack_top;
}
extern "C" u32 do_init_context(Thread* thread)
{
return Processor::init_context(*thread);
}
extern "C" void do_assume_context(Thread* thread);
asm(
".global do_assume_context \n"
"do_assume_context: \n"
" movl 4(%esp), %ebx \n"
// We're going to call Processor::init_context, so just make sure
// we have enough stack space so we don't stomp over it
" subl $(" __STRINGIFY(4 + REGISTER_STATE_SIZE + TRAP_FRAME_SIZE + 4) "), %esp \n"
" pushl %ebx \n"
" cld \n"
" call do_init_context \n"
" addl $4, %esp \n"
" movl %eax, %esp \n" // move stack pointer to what Processor::init_context set up for us
" pushl %ebx \n" // push to_thread
" pushl %ebx \n" // push from_thread
" pushl $thread_context_first_enter \n" // should be same as tss.eip
" jmp enter_thread_context \n"
);
void Processor::assume_context(Thread& thread)
{
do_assume_context(&thread);
ASSERT_NOT_REACHED();
}
void Processor::initialize_context_switching(Thread& initial_thread)
{
ASSERT(initial_thread.process().is_ring0());
auto& tss = initial_thread.tss();
m_tss = tss;
m_tss.esp0 = tss.esp0;
m_tss.ss0 = GDT_SELECTOR_DATA0;
// user mode needs to be able to switch to kernel mode:
m_tss.cs = m_tss.ds = m_tss.es = m_tss.gs = m_tss.ss = GDT_SELECTOR_CODE0 | 3;
m_tss.fs = GDT_SELECTOR_PROC | 3;
asm volatile(
"movl %[new_esp], %%esp \n" // swich to new stack
"pushl %[from_to_thread] \n" // to_thread
"pushl %[from_to_thread] \n" // from_thread
"pushl $" __STRINGIFY(GDT_SELECTOR_CODE0) " \n"
"pushl %[new_eip] \n" // save the entry eip to the stack
"movl %%esp, %%ebx \n"
"addl $20, %%ebx \n" // calculate pointer to TrapFrame
"pushl %%ebx \n"
"cld \n"
"call enter_trap_no_irq \n"
"addl $4, %%esp \n"
"lret \n"
:: [new_esp] "g" (tss.esp),
[new_eip] "a" (tss.eip),
[from_to_thread] "b" (&initial_thread)
);
ASSERT_NOT_REACHED();
}
void Processor::enter_trap(TrapFrame& trap, bool raise_irq)
{
InterruptDisabler disabler;
trap.prev_irq_level = m_in_irq;
if (raise_irq)
m_in_irq++;
}
void Processor::exit_trap(TrapFrame& trap)
{
InterruptDisabler disabler;
ASSERT(m_in_irq >= trap.prev_irq_level);
m_in_irq = trap.prev_irq_level;
if (m_invoke_scheduler_async && !m_in_irq) {
m_invoke_scheduler_async = false;
Scheduler::invoke_async();
}
}
void Processor::gdt_init()
{
m_gdt_length = 0;
m_gdtr.address = nullptr;
m_gdtr.limit = 0;
write_raw_gdt_entry(0x0000, 0x00000000, 0x00000000);
write_raw_gdt_entry(GDT_SELECTOR_CODE0, 0x0000ffff, 0x00cf9a00); // code0
write_raw_gdt_entry(GDT_SELECTOR_DATA0, 0x0000ffff, 0x00cf9200); // data0
write_raw_gdt_entry(GDT_SELECTOR_CODE3, 0x0000ffff, 0x00cffa00); // code3
write_raw_gdt_entry(GDT_SELECTOR_DATA3, 0x0000ffff, 0x00cff200); // data3
Descriptor tls_descriptor;
tls_descriptor.low = tls_descriptor.high = 0;
tls_descriptor.dpl = 3;
tls_descriptor.segment_present = 1;
tls_descriptor.granularity = 0;
tls_descriptor.zero = 0;
tls_descriptor.operation_size = 1;
tls_descriptor.descriptor_type = 1;
tls_descriptor.type = 2;
write_gdt_entry(GDT_SELECTOR_TLS, tls_descriptor); // tls3
Descriptor fs_descriptor;
fs_descriptor.set_base(this);
fs_descriptor.set_limit(sizeof(Processor));
fs_descriptor.dpl = 0;
fs_descriptor.segment_present = 1;
fs_descriptor.granularity = 0;
fs_descriptor.zero = 0;
fs_descriptor.operation_size = 1;
fs_descriptor.descriptor_type = 1;
fs_descriptor.type = 2;
write_gdt_entry(GDT_SELECTOR_PROC, fs_descriptor); // fs0
Descriptor tss_descriptor;
tss_descriptor.set_base(&m_tss);
tss_descriptor.set_limit(sizeof(TSS32));
tss_descriptor.dpl = 0;
tss_descriptor.segment_present = 1;
tss_descriptor.granularity = 0;
tss_descriptor.zero = 0;
tss_descriptor.operation_size = 1;
tss_descriptor.descriptor_type = 0;
tss_descriptor.type = 9;
write_gdt_entry(GDT_SELECTOR_TSS, tss_descriptor); // tss
flush_gdt();
load_task_register(GDT_SELECTOR_TSS);
asm volatile(
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"mov %%ax, %%gs\n"
"mov %%ax, %%ss\n" ::"a"(GDT_SELECTOR_DATA0)
: "memory");
set_fs(GDT_SELECTOR_PROC);
// Make sure CS points to the kernel code descriptor.
asm volatile(
"ljmpl $" __STRINGIFY(GDT_SELECTOR_CODE0) ", $sanity\n"
"sanity:\n");
}
void Processor::set_thread_specific(u8* data, size_t len)
{
auto& descriptor = get_gdt_entry(GDT_SELECTOR_TLS);
descriptor.set_base(data);
descriptor.set_limit(len);
}
}
#ifdef DEBUG

View file

@ -106,6 +106,14 @@ union [[gnu::packed]] Descriptor
TrapGate_32bit = 0xf,
};
void* get_base() const
{
u32 b = base_lo;
b |= base_hi << 16;
b |= base_hi2 << 24;
return reinterpret_cast<void*>(b);
}
void set_base(void* b)
{
base_lo = (u32)(b)&0xffff;
@ -256,8 +264,6 @@ struct RegisterState;
const DescriptorTablePointer& get_gdtr();
const DescriptorTablePointer& get_idtr();
void gdt_init();
void idt_init();
void sse_init();
void register_interrupt_handler(u8 number, void (*f)());
void register_user_callable_interrupt_handler(u8 number, void (*f)());
@ -267,12 +273,7 @@ void replace_single_handler_with_shared(GenericInterruptHandler&);
void replace_shared_handler_with_single(GenericInterruptHandler&);
void unregister_generic_interrupt_handler(u8 number, GenericInterruptHandler&);
void flush_idt();
void flush_gdt();
void load_task_register(u16 selector);
u16 gdt_alloc_entry();
void gdt_free_entry(u16);
Descriptor& get_gdt_entry(u16 selector);
void write_gdt_entry(u16 selector, Descriptor&);
void handle_crash(RegisterState&, const char* description, int signal, bool out_of_memory = false);
[[noreturn]] static inline void hang()
@ -303,6 +304,39 @@ inline u32 cpu_flags()
return flags;
}
inline void set_fs(u32 segment)
{
asm volatile(
"movl %%eax, %%fs" :: "a"(segment)
: "memory"
);
}
inline void set_gs(u32 segment)
{
asm volatile(
"movl %%eax, %%gs" :: "a"(segment)
: "memory"
);
}
inline u32 get_fs()
{
u32 fs;
asm("mov %%fs, %%eax"
: "=a"(fs));
return fs;
}
inline u32 get_gs()
{
u32 gs;
asm("mov %%gs, %%eax"
: "=a"(gs));
return gs;
}
inline u32 read_fs_u32(u32 offset)
{
u32 val;
@ -460,6 +494,9 @@ struct [[gnu::packed]] RegisterState
u32 userspace_ss;
};
#define REGISTER_STATE_SIZE (19 * 4)
static_assert(REGISTER_STATE_SIZE == sizeof(RegisterState));
struct [[gnu::aligned(16)]] FPUState
{
u8 buffer[512];
@ -492,6 +529,15 @@ u32 read_cr4();
u32 read_dr6();
static inline bool is_kernel_mode()
{
u32 cs;
asm volatile (
"movl %%cs, %[cs] \n"
: [cs] "=g" (cs));
return (cs & 3) == 0;
}
class CPUID {
public:
CPUID(u32 function) { asm volatile("cpuid"
@ -552,6 +598,94 @@ private:
SplitQword m_start;
};
class Thread;
struct TrapFrame;
#define GDT_SELECTOR_CODE0 0x08
#define GDT_SELECTOR_DATA0 0x10
#define GDT_SELECTOR_CODE3 0x18
#define GDT_SELECTOR_DATA3 0x20
#define GDT_SELECTOR_TLS 0x28
#define GDT_SELECTOR_PROC 0x30
#define GDT_SELECTOR_TSS 0x38
class Processor {
Processor* m_self; // must be first field (%fs offset 0x0)
DescriptorTablePointer m_gdtr;
Descriptor m_gdt[256];
u32 m_gdt_length;
u32 m_cpu;
u32 m_in_irq;
TSS32 m_tss;
static FPUState s_clean_fpu_state;
bool m_invoke_scheduler_async;
void gdt_init();
void write_raw_gdt_entry(u16 selector, u32 low, u32 high);
void write_gdt_entry(u16 selector, Descriptor& descriptor);
public:
void initialize(u32 cpu);
Descriptor& get_gdt_entry(u16 selector);
void flush_gdt();
const DescriptorTablePointer& get_gdtr();
ALWAYS_INLINE static Processor& current()
{
return *(Processor*)read_fs_u32(0);
}
ALWAYS_INLINE static u32 id()
{
return current().m_cpu;
}
ALWAYS_INLINE u32& in_irq()
{
return m_in_irq;
}
ALWAYS_INLINE const FPUState& clean_fpu_state() const
{
return s_clean_fpu_state;
}
void invoke_scheduler_async() { m_invoke_scheduler_async = true; }
void enter_trap(TrapFrame& trap, bool raise_irq);
void exit_trap(TrapFrame& trap);
[[noreturn]] void initialize_context_switching(Thread& initial_thread);
void switch_context(Thread* from_thread, Thread* to_thread);
[[noreturn]] static void assume_context(Thread& thread);
static u32 init_context(Thread& thread);
void set_thread_specific(u8* data, size_t len);
};
struct TrapFrame {
u32 prev_irq_level;
RegisterState* regs; // must be last
TrapFrame() = delete;
TrapFrame(const TrapFrame&) = delete;
TrapFrame(TrapFrame&&) = delete;
TrapFrame& operator=(const TrapFrame&) = delete;
TrapFrame& operator=(TrapFrame&&) = delete;
};
#define TRAP_FRAME_SIZE (2 * 4)
static_assert(TRAP_FRAME_SIZE == sizeof(TrapFrame));
extern "C" void enter_trap_no_irq(TrapFrame*);
extern "C" void enter_trap(TrapFrame*);
extern "C" void exit_trap(TrapFrame*);
class MSR {
uint32_t m_msr;
@ -583,7 +717,8 @@ public:
}
};
void cpu_setup();
void cpu_setup(u32 cpu);
extern bool g_cpu_supports_nx;
extern bool g_cpu_supports_pae;
extern bool g_cpu_supports_pge;
@ -629,6 +764,4 @@ private:
u32 m_flags;
};
extern u32 g_in_irq;
}

View file

@ -27,6 +27,8 @@
#pragma once
#include <AK/Types.h>
#include <AK/Assertions.h>
#include <Kernel/Arch/i386/CPU.h>
extern "C" void interrupt_common_asm_entry();
@ -47,16 +49,35 @@ asm(
" pushl %fs\n"
" pushl %gs\n"
" pushl %ss\n"
" mov $0x10, %ax\n"
" mov $" __STRINGIFY(GDT_SELECTOR_DATA0) ", %ax\n"
" mov %ax, %ds\n"
" mov %ax, %es\n"
" mov $" __STRINGIFY(GDT_SELECTOR_PROC) ", %ax\n"
" mov %ax, %fs\n"
" pushl %esp \n" // set TrapFrame::regs
" subl $" __STRINGIFY(TRAP_FRAME_SIZE - 4) ", %esp \n"
" movl %esp, %ebx \n" // save pointer to TrapFrame
" pushl %ebx \n"
" cld\n"
" call enter_trap \n"
" movl %ebx, 0(%esp) \n" // push pointer to TrapFrame
" call handle_interrupt\n"
" add $0x4, %esp\n" // "popl %ss"
" movl %ebx, 0(%esp) \n" // push pointer to TrapFrame
".globl common_trap_exit \n"
"common_trap_exit: \n"
// another thread may have handled this trap at this point, so don't
// make assumptions about the stack other than there's a TrapFrame
// and a pointer to it.
" call exit_trap \n"
" addl $" __STRINGIFY(TRAP_FRAME_SIZE + 4) ", %esp\n" // pop TrapFrame and pointer to it
".globl interrupt_common_asm_exit \n"
"interrupt_common_asm_exit: \n"
" addl $4, %esp\n" // pop %ss
" popl %gs\n"
" popl %fs\n"
" popl %es\n"
" popl %ds\n"
" popa\n"
" add $0x4, %esp\n"
" iret\n");
" addl $0x4, %esp\n" // skip exception_code, isr_number
" iret\n"
);