Support for SMP on x86_64.

No major changes to the kernel: just compiled in arch_smp.cpp and fixed the
IDT load in arch_cpu_init_percpu to use the correct limit for x86_64 (uses
sizeof(interrupt_descriptor)). In the boot loader, changed smp_boot_other_cpus
to construct a temporary GDT and get the page directory address from CR3, as
what's in kernel_args will be 64-bit stuff and will not work to switch the
CPUs into 32-bit mode in the trampoline code. Refactored 64-bit kernel entry
code to not use the stack after disabling paging, as the secondary CPUs are
given a 32-bit virtual stack address by the SMP trampoline code which will
no longer work.
This commit is contained in:
Alex Smith
2012-07-11 18:29:15 +01:00
parent 659bacac26
commit 76a1175dbe
13 changed files with 177 additions and 172 deletions
@@ -50,6 +50,10 @@ typedef struct segment_descriptor {
uint32 base_31_24 : 8; // 24 - 31
} segment_descriptor;
typedef struct interrupt_descriptor {
uint32 a, b;
} interrupt_descriptor;
struct tss {
uint16 prev_task;
uint16 unused0;
@@ -83,11 +83,6 @@ struct interrupt_frame {
uint32 eflags;
};
struct interrupt_descriptor {
uint32 a;
uint32 b;
};
static interrupt_descriptor sDebugIDT[DEBUG_IDT_SLOT_COUNT];
+46 -19
View File
@@ -23,6 +23,7 @@
#include <kernel.h>
#include "debug.h"
#include "smp.h"
#include "mmu.h"
@@ -31,6 +32,13 @@ static const uint64 kLargePageMappingFlags = 0x183;
static const uint64 kPageMappingFlags = 0x103;
// Global, R/W, Present
extern "C" void long_enter_kernel(int currentCPU, uint64 stackTop);
extern uint32 gLongPhysicalGDT;
extern uint64 gLongVirtualGDT;
extern uint32 gLongPhysicalPML4;
extern uint64 gLongKernelEntry;
/*! Convert a 32-bit address to a 64-bit address. */
static inline uint64
@@ -71,6 +79,10 @@ long_gdt_init()
DPL_USER);
set_segment_descriptor(&gdt[USER_DATA_SEG / 8], DT_DATA_WRITEABLE,
DPL_USER);
// Used by long_enter_kernel().
gLongPhysicalGDT = gKernelArgs.arch_args.phys_gdt;
gLongVirtualGDT = gKernelArgs.arch_args.vir_gdt;
}
@@ -222,6 +234,8 @@ long_mmu_init()
gKernelArgs.virtual_allocated_range[i].start,
gKernelArgs.virtual_allocated_range[i].size);
}
gLongPhysicalPML4 = gKernelArgs.arch_args.phys_pgdir;
}
@@ -275,12 +289,6 @@ convert_kernel_args()
gKernelArgs.kernel_args_range[i].size);
}
// Set correct kernel stack addresses.
for (uint32 i = 0; i < gKernelArgs.num_cpus; i++) {
gKernelArgs.cpu_kstack[i].start = fix_address(
gKernelArgs.cpu_kstack[i].start);
}
// Fix driver settings files.
driver_settings_file* file = gKernelArgs.driver_settings;
fix_address(gKernelArgs.driver_settings);
@@ -293,6 +301,27 @@ convert_kernel_args()
}
static void
long_smp_start_kernel(void)
{
uint32 cpu = smp_get_current_cpu();
// Important. Make sure supervisor threads can fault on read only pages...
asm("movl %%eax, %%cr0" : : "a" ((1 << 31) | (1 << 16) | (1 << 5) | 1));
asm("cld");
asm("fninit");
// Fix our kernel stack address.
gKernelArgs.cpu_kstack[cpu].start
= fix_address(gKernelArgs.cpu_kstack[cpu].start);
long_enter_kernel(cpu, gKernelArgs.cpu_kstack[cpu].start
+ gKernelArgs.cpu_kstack[cpu].size);
panic("Shouldn't get here");
}
void
long_start_kernel()
{
@@ -305,8 +334,7 @@ long_start_kernel()
preloaded_elf64_image *image = static_cast<preloaded_elf64_image *>(
gKernelArgs.kernel_image.Pointer());
// TODO: x86_64 SMP, disable for now.
gKernelArgs.num_cpus = 1;
smp_init_other_cpus();
long_gdt_init();
long_idt_init();
@@ -315,23 +343,22 @@ long_start_kernel()
debug_cleanup();
// Calculate the arguments for long_enter_kernel().
uint64 entry = image->elf_header.e_entry;
uint64 stackTop = gKernelArgs.cpu_kstack[0].start
+ gKernelArgs.cpu_kstack[0].size;
uint64 kernelArgs = (addr_t)&gKernelArgs;
// Save the kernel entry point address.
gLongKernelEntry = image->elf_header.e_entry;
dprintf("kernel entry at %#llx\n", gLongKernelEntry);
dprintf("kernel entry at %#llx, stack %#llx, args %#llx\n", entry,
stackTop, kernelArgs);
// Fix our kernel stack address.
gKernelArgs.cpu_kstack[0].start
= fix_address(gKernelArgs.cpu_kstack[0].start);
// We're about to enter the kernel -- disable console output.
stdout = NULL;
smp_boot_other_cpus(long_smp_start_kernel);
// Enter the kernel!
long_enter_kernel(gKernelArgs.arch_args.phys_pgdir,
gKernelArgs.arch_args.phys_gdt, gKernelArgs.arch_args.vir_gdt,
entry, stackTop, kernelArgs, 0);
long_enter_kernel(0, gKernelArgs.cpu_kstack[0].start
+ gKernelArgs.cpu_kstack[0].size);
panic("Shouldn't get here");
}
@@ -9,10 +9,6 @@
#include <SupportDefs.h>
extern "C" void long_enter_kernel(uint32 physPML4, uint32 physGDT,
uint64 virtGDT, uint64 entry, uint64 stackTop, uint64 kernelArgs,
int currentCPU);
extern void long_start_kernel();
+32 -21
View File
@@ -17,15 +17,17 @@
.code32
/*! void long_enter_kernel(uint32 physPML4, uint32 physGDT, uint64 virtGDT,
uint64 entry, uint64 stackTop, uint64 kernelArgs, int currentCPU);
*/
/*! void long_enter_kernel(int currentCPU, uint64 stackTop); */
FUNCTION(long_enter_kernel):
// Preserve the arguments. We may no longer be able to use the stack once
// paging is disabled.
movl 4(%esp), %ebx
movl 8(%esp), %edi
movl 12(%esp), %esi
// We're about to disable paging, so we need to load the the physical
// address of our GDT.
movl 8(%esp), %eax
movl %eax, (long_gdtr + 2)
lgdtl (long_gdtr)
lgdtl long_phys_gdtr
// Currently running with 32-bit paging tables at an identity mapped
// address. To switch to 64-bit paging we must first disable 32-bit paging,
@@ -40,11 +42,11 @@ FUNCTION(long_enter_kernel):
movl %eax, %cr4
// Point CR3 to the kernel's PML4.
movl 4(%esp), %eax
movl gLongPhysicalPML4, %eax
movl %eax, %cr3
// Enable long mode by setting EFER.LME.
movl $0xC0000080, %ecx
movl $0xc0000080, %ecx
rdmsr
orl $(1 << 8), %eax
wrmsr
@@ -68,32 +70,41 @@ FUNCTION(long_enter_kernel):
mov %ax, %fs
mov %ax, %gs
// Clear the high 32 bits of RSP.
movl %esp, %esp
// Load the virtual address of the GDT.
movq 12(%rsp), %rax
movq %rax, long_gdtr + 2(%rip)
lgdtq long_gdtr(%rip)
lgdtq long_virt_gdtr(%rip)
// Get the entry point address, arguments and new stack pointer.
movq 20(%rsp), %rax
movq 36(%rsp), %rdi
movl 44(%rsp), %esi
movq 28(%rsp), %rsp
// Set the stack pointer.
movl %edi, %esp
shl $32, %rsi
orq %rsi, %rsp
// Clear the stack frame/RFLAGS.
xorq %rbp, %rbp
push $0
popf
// Call the kernel entry point.
// Get arguments and call the kernel entry point.
leaq gKernelArgs(%rip), %rdi
movl %ebx, %esi
movq gLongKernelEntry(%rip), %rax
call *%rax
.data
long_gdtr:
long_phys_gdtr:
.word GDT_LIMIT - 1
SYMBOL(gLongPhysicalGDT):
.long 0
long_virt_gdtr:
.word GDT_LIMIT - 1
SYMBOL(gLongVirtualGDT):
.quad 0
SYMBOL(gLongPhysicalPML4):
.long 0
SYMBOL(gLongKernelEntry):
.quad 0
+29 -79
View File
@@ -20,8 +20,10 @@
#include <boot/menu.h>
#include <arch/x86/apic.h>
#include <arch/x86/arch_acpi.h>
#include <arch/x86/arch_cpu.h>
#include <arch/x86/arch_smp.h>
#include <arch/x86/arch_system_info.h>
#include <arch/x86/descriptors.h>
#include "mmu.h"
#include "acpi.h"
@@ -37,10 +39,6 @@
# define TRACE(x) ;
#endif
struct gdt_idt_descr {
uint16 a;
uint32 *b;
} _PACKED;
static struct scan_spots_struct smp_scan_spots[] = {
{ 0x9fc00, 0xa0000, 0xa0000 - 0x9fc00 },
@@ -54,9 +52,6 @@ extern "C" void smp_trampoline(void);
extern "C" void smp_trampoline_end(void);
static int smp_get_current_cpu(void);
static uint32
apic_read(uint32 offset)
{
@@ -71,22 +66,6 @@ apic_write(uint32 offset, uint32 data)
}
static int
smp_get_current_cpu(void)
{
if (gKernelArgs.arch_args.apic == NULL)
return 0;
uint8 apicID = apic_read(APIC_ID) >> 24;
for (uint32 i = 0; i < gKernelArgs.num_cpus; i++) {
if (gKernelArgs.arch_args.cpu_apic_id[i] == apicID)
return i;
}
return 0;
}
static mp_floating_struct *
smp_mp_probe(uint32 base, uint32 limit)
{
@@ -341,56 +320,6 @@ smp_do_acpi_config(void)
}
/*! Target function of the trampoline code.
The trampoline code should have the pgdir and a gdt set up for us,
along with us being on the final stack for this processor. We need
to set up the local APIC and load the global idt and gdt. When we're
done, we'll jump into the kernel with the cpu number as an argument.
*/
static int
smp_cpu_ready(void)
{
uint32 curr_cpu = smp_get_current_cpu();
struct gdt_idt_descr idt_descr;
struct gdt_idt_descr gdt_descr;
//TRACE(("smp_cpu_ready: entry cpu %ld\n", curr_cpu));
preloaded_elf32_image *image = static_cast<preloaded_elf32_image *>(
gKernelArgs.kernel_image.Pointer());
// Important. Make sure supervisor threads can fault on read only pages...
asm("movl %%eax, %%cr0" : : "a" ((1 << 31) | (1 << 16) | (1 << 5) | 1));
asm("cld");
asm("fninit");
// Set up the final idt
idt_descr.a = IDT_LIMIT - 1;
idt_descr.b = (uint32 *)(addr_t)gKernelArgs.arch_args.vir_idt;
asm("lidt %0;"
: : "m" (idt_descr));
// Set up the final gdt
gdt_descr.a = GDT_LIMIT - 1;
gdt_descr.b = (uint32 *)gKernelArgs.arch_args.vir_gdt;
asm("lgdt %0;"
: : "m" (gdt_descr));
asm("pushl %0; " // push the cpu number
"pushl %1; " // kernel args
"pushl $0x0;" // dummy retval for call to main
"pushl %2; " // this is the start address
"ret; " // jump.
: : "g" (curr_cpu), "g" (&gKernelArgs),
"g" (image->elf_header.e_entry));
// no where to return to
return 0;
}
static void
calculate_apic_timer_conversion_factor(void)
{
@@ -429,6 +358,22 @@ calculate_apic_timer_conversion_factor(void)
// #pragma mark -
int
smp_get_current_cpu(void)
{
if (gKernelArgs.arch_args.apic == NULL)
return 0;
uint8 apicID = apic_read(APIC_ID) >> 24;
for (uint32 i = 0; i < gKernelArgs.num_cpus; i++) {
if (gKernelArgs.arch_args.cpu_apic_id[i] == apicID)
return i;
}
return 0;
}
void
smp_init_other_cpus(void)
{
@@ -476,7 +421,7 @@ smp_init_other_cpus(void)
void
smp_boot_other_cpus(void)
smp_boot_other_cpus(void (*entryFunc)())
{
if (gKernelArgs.num_cpus < 2)
return;
@@ -516,7 +461,7 @@ smp_boot_other_cpus(void)
tempStack = (finalStack
+ (KERNEL_STACK_SIZE + KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE)
/ sizeof(uint32)) - 1;
*tempStack = (uint32)&smp_cpu_ready;
*tempStack = (uint32)entryFunc;
// set the trampoline stack up
tempStack = (uint32 *)(trampolineStack + B_PAGE_SIZE - 4);
@@ -525,15 +470,20 @@ smp_boot_other_cpus(void)
+ KERNEL_STACK_GUARD_PAGES * B_PAGE_SIZE - sizeof(uint32);
tempStack--;
// page dir
*tempStack = gKernelArgs.arch_args.phys_pgdir;
*tempStack = x86_read_cr3() & 0xfffff000;
// put a gdt descriptor at the bottom of the stack
*((uint16 *)trampolineStack) = 0x18 - 1; // LIMIT
*((uint32 *)(trampolineStack + 2)) = trampolineStack + 8;
// put the gdt at the bottom
memcpy(&((uint32 *)trampolineStack)[2],
(void *)gKernelArgs.arch_args.vir_gdt, 6 * 4);
// construct a temporary gdt at the bottom
segment_descriptor* tempGDT
= (segment_descriptor*)&((uint32 *)trampolineStack)[2];
clear_segment_descriptor(&tempGDT[0]);
set_segment_descriptor(&tempGDT[1], 0, 0xffffffff, DT_CODE_READABLE,
DPL_KERNEL);
set_segment_descriptor(&tempGDT[2], 0, 0xffffffff, DT_DATA_WRITEABLE,
DPL_KERNEL);
/* clear apic errors */
if (gKernelArgs.arch_args.cpu_apic_version[i] & 0xf0) {
+3 -1
View File
@@ -19,7 +19,9 @@ extern "C" {
extern void smp_init(void);
extern void smp_init_other_cpus(void);
extern void smp_boot_other_cpus(void);
extern void smp_boot_other_cpus(void (*entryFunc)());
extern int smp_get_current_cpu(void);
#ifdef __cplusplus
}
+50 -1
View File
@@ -73,6 +73,55 @@ platform_boot_options(void)
}
/*! Target function of the SMP trampoline code.
The trampoline code should have the pgdir and a gdt set up for us,
along with us being on the final stack for this processor. We need
to set up the local APIC and load the global idt and gdt. When we're
done, we'll jump into the kernel with the cpu number as an argument.
*/
static void
smp_start_kernel(void)
{
uint32 curr_cpu = smp_get_current_cpu();
struct gdt_idt_descr idt_descr;
struct gdt_idt_descr gdt_descr;
//TRACE(("smp_cpu_ready: entry cpu %ld\n", curr_cpu));
preloaded_elf32_image *image = static_cast<preloaded_elf32_image *>(
gKernelArgs.kernel_image.Pointer());
// Important. Make sure supervisor threads can fault on read only pages...
asm("movl %%eax, %%cr0" : : "a" ((1 << 31) | (1 << 16) | (1 << 5) | 1));
asm("cld");
asm("fninit");
// Set up the final idt
idt_descr.limit = IDT_LIMIT - 1;
idt_descr.base = (uint32 *)(addr_t)gKernelArgs.arch_args.vir_idt;
asm("lidt %0;"
: : "m" (idt_descr));
// Set up the final gdt
gdt_descr.limit = GDT_LIMIT - 1;
gdt_descr.base = (uint32 *)gKernelArgs.arch_args.vir_gdt;
asm("lgdt %0;"
: : "m" (gdt_descr));
asm("pushl %0; " // push the cpu number
"pushl %1; " // kernel args
"pushl $0x0;" // dummy retval for call to main
"pushl %2; " // this is the start address
"ret; " // jump.
: : "g" (curr_cpu), "g" (&gKernelArgs),
"g" (image->elf_header.e_entry));
panic("kernel returned!\n");
}
extern "C" void
platform_start_kernel(void)
{
@@ -99,7 +148,7 @@ platform_start_kernel(void)
// We're about to enter the kernel -- disable console output.
stdout = NULL;
smp_boot_other_cpus();
smp_boot_other_cpus(smp_start_kernel);
dprintf("kernel entry at %lx\n", image->elf_header.e_entry);
+4 -7
View File
@@ -62,10 +62,7 @@ static const int kInterruptNameCount = 20;
#define MAX_ARGS 16
typedef struct {
uint32 a, b;
} desc_table;
static desc_table* sIDTs[B_MAX_CPU_COUNT];
static interrupt_descriptor* sIDTs[B_MAX_CPU_COUNT];
// table with functions handling respective interrupts
typedef void interrupt_handler_function(struct iframe* frame);
@@ -80,7 +77,7 @@ extern void hardware_interrupt(struct iframe* frame);
/*! Initializes a descriptor in an IDT.
*/
static void
set_gate(desc_table *gate_addr, addr_t addr, int type, int dpl)
set_gate(interrupt_descriptor *gate_addr, addr_t addr, int type, int dpl)
{
unsigned int gate1; // first byte of gate desc
unsigned int gate2; // second byte of gate desc
@@ -353,7 +350,7 @@ arch_int_init(struct kernel_args *args)
interrupt_handler_function** table;
// set the global sIDT variable
sIDTs[0] = (desc_table *)(addr_t)args->arch_args.vir_idt;
sIDTs[0] = (interrupt_descriptor *)(addr_t)args->arch_args.vir_idt;
// setup the standard programmable interrupt controller
pic_init();
@@ -668,7 +665,7 @@ arch_int_init_post_vm(struct kernel_args *args)
int32 cpuCount = smp_get_num_cpus();
if (cpuCount > 0) {
size_t areaSize = ROUNDUP(cpuCount * idtSize, B_PAGE_SIZE);
desc_table* idt;
interrupt_descriptor* idt;
virtual_address_restrictions virtualRestrictions = {};
virtualRestrictions.address_specification = B_ANY_KERNEL_ADDRESS;
physical_address_restrictions physicalRestrictions = {};
-28
View File
@@ -270,34 +270,6 @@ arch_debug_call_with_fault_handler(cpu_ent* cpu, jmp_buf jumpBuffer,
}
status_t
arch_smp_init(kernel_args *args)
{
return B_OK;
}
status_t
arch_smp_per_cpu_init(kernel_args *args, int32 cpu)
{
return B_OK;
}
void
arch_smp_send_broadcast_ici(void)
{
}
void
arch_smp_send_ici(int32 target_cpu)
{
}
// The software breakpoint instruction (int3).
const uint8 kX86SoftwareBreakpoint[1] = { 0xcc };
+1 -1
View File
@@ -48,7 +48,6 @@ if $(TARGET_ARCH) = x86_64 {
arch_commpage.cpp
arch_debug.cpp
arch_smp.cpp
arch_user_debugger.cpp
apm.cpp
bios.cpp
@@ -83,6 +82,7 @@ local archGenericSources =
arch_int.cpp
arch_platform.cpp
arch_real_time_clock.cpp
arch_smp.cpp
arch_system_info.cpp
arch_thread.cpp
arch_timer.cpp
+3 -2
View File
@@ -349,7 +349,7 @@ x86_init_fpu(void)
static void
load_tss(int cpu)
{
short seg = ((TSS_BASE_SEGMENT + cpu) << 3) | DPL_KERNEL;
short seg = (TSS_SEGMENT(cpu) << 3) | DPL_KERNEL;
asm("ltr %%ax" : : "a" (seg));
}
@@ -789,7 +789,8 @@ arch_cpu_init_percpu(kernel_args* args, int cpu)
uint16 limit;
void* address;
} _PACKED descriptor = {
256 * 8 - 1, // 256 descriptors, 8 bytes each (-1 for "limit")
256 * sizeof(interrupt_descriptor) - 1,
// 256 descriptors (-1 for "limit")
x86_get_idt(cpu)
};
+5 -4
View File
@@ -52,7 +52,7 @@ static int32
x86_spurious_interrupt(void *data)
{
// spurious interrupt
TRACE(("spurious interrupt on cpu %ld\n", smp_get_current_cpu()));
TRACE(("spurious interrupt on cpu %" B_PRId32 "\n", smp_get_current_cpu()));
// spurious interrupts must not be acknowledged as it does not expect
// a end of interrupt - if we still do it we would loose the next best
@@ -65,7 +65,7 @@ static int32
x86_smp_error_interrupt(void *data)
{
// smp error interrupt
TRACE(("smp error interrupt on cpu %ld\n", smp_get_current_cpu()));
TRACE(("smp error interrupt on cpu %" B_PRId32 "\n", smp_get_current_cpu()));
return B_HANDLED_INTERRUPT;
}
@@ -104,7 +104,8 @@ status_t
arch_smp_per_cpu_init(kernel_args *args, int32 cpu)
{
// set up the local apic on the current cpu
TRACE(("arch_smp_init_percpu: setting up the apic on cpu %ld\n", cpu));
TRACE(("arch_smp_init_percpu: setting up the apic on cpu %" B_PRId32 "\n",
cpu));
apic_per_cpu_init(args, cpu);
// setup FPU and SSE if supported
@@ -154,7 +155,7 @@ arch_smp_send_ici(int32 target_cpu)
asm volatile ("pause;");
if (timeout == 0)
panic("arch_smp_send_ici: timeout, target_cpu %ld", target_cpu);
panic("arch_smp_send_ici: timeout, target_cpu %" B_PRId32, target_cpu);
restore_interrupts(state);
}