This patch adds a x86_64 architecture and qemu_x86_64 board to Zephyr. Only the basic architecture support needed to run 64 bit code is added; no drivers are added, though a low-level console exists and is wired to printk(). The support is built on top of a "X86 underkernel" layer, which can be built in isolation as a unit test on a Linux host. Limitations: + Right now the SDK lacks an x86_64 toolchain. The build will fall back to a host toolchain if it finds no cross compiler defined, which is tested to work on gcc 8.2.1 right now. + No x87/SSE/AVX usage is allowed. This is a stronger limitation than other architectures where the instructions work from one thread even if the context switch code doesn't support it. We are passing -no-sse to prevent gcc from automatically generating SSE instructions for non-floating-point purposes, which has the side effect of changing the ABI. Future work to handle the FPU registers will need to be combined with an "application" ABI distinct from the kernel one (or just to require USERSPACE). + Paging is enabled (it has to be in long mode), but is a 1:1 mapping of all memory. No MMU/USERSPACE support yet. + We are building with -mno-red-zone for stack size reasons, but this is a valuable optimization. Enabling it requires automatic stack switching, which requires a TSS, which means it has to happen after MMU support. + The OS runs in 64 bit mode, but for compatibility reasons is compiled to the 32 bit "X32" ABI. So while the full 64 bit registers and instruction set are available, C pointers are 32 bits long and Zephyr is constrained to run in the bottom 4G of memory. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
202 lines
4.8 KiB
C
202 lines
4.8 KiB
C
/*
|
|
* Copyright (c) 2018 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include "serial.h"
|
|
#include "vgacon.h"
|
|
#include "printf.h"
|
|
#include "xuk.h"
|
|
|
|
/* Tiny demonstration of the core64 code. Implements enough of an
|
|
* "OS" layer to do some simple unit testing.
|
|
*/
|
|
|
|
static void putchar(int c)
|
|
{
|
|
serial_putc(c);
|
|
vgacon_putc(c);
|
|
}
|
|
|
|
void test_timers(void)
|
|
{
|
|
/* Quickly calibrate the timers against each other. Note that
|
|
* the APIC is counting DOWN instead of up! Seems like on
|
|
* qemu, the APIC base frequency is 3.7x slower than the tsc.
|
|
* Looking at source, it seems like APIC is uniformly shifted
|
|
* down from a nominal 1Ghz reference
|
|
* (i.e. qemu_get_time_ns()), where the TSC is based on
|
|
* cpu_get_ticks() and thus pulls in wall clock time & such.
|
|
* If you specify "-icount shift=1", then they synchronize
|
|
* properly.
|
|
*/
|
|
int tsc0, apic0, tsc1, apic1;
|
|
|
|
__asm__ volatile("rdtsc" : "=a"(tsc0) : : "rdx");
|
|
apic0 = _apic.CURR_COUNT;
|
|
do {
|
|
/* Qemu misbehaves if I spam these registers. */
|
|
for (int i = 0; i < 1000; i++) {
|
|
__asm__ volatile("nop");
|
|
}
|
|
|
|
__asm__ volatile("rdtsc" : "=a"(tsc1) : : "rdx");
|
|
apic1 = _apic.CURR_COUNT;
|
|
} while ((tsc1 - tsc0) < 10000 || (apic0 - apic1) < 10000);
|
|
printf("tsc %d apic %d\n", tsc1 - tsc0, apic0 - apic1);
|
|
}
|
|
|
|
unsigned int _init_cpu_stack(int cpu)
|
|
{
|
|
return (long)alloc_page(0) + 4096;
|
|
}
|
|
|
|
void handler_timer(void *arg, int err)
|
|
{
|
|
printf("Timer expired on CPU%d\n", (int)(long)xuk_get_f_ptr());
|
|
}
|
|
|
|
void handler_f3(void *arg, int err)
|
|
{
|
|
printf("f3 handler on cpu%d arg %x, triggering INT 0xff\n",
|
|
(int)(long)xuk_get_f_ptr(), (int)(long)arg);
|
|
__asm__ volatile("int $0xff");
|
|
printf("end f3 handler\n");
|
|
}
|
|
|
|
void _unhandled_vector(int vector, int err, struct xuk_entry_frame *f)
|
|
{
|
|
(void)f;
|
|
_putchar = putchar;
|
|
printf("Unhandled vector %d (err %xh) on CPU%d\n",
|
|
vector, err, (int)(long)xuk_get_f_ptr());
|
|
}
|
|
|
|
void _isr_entry(void)
|
|
{
|
|
}
|
|
|
|
void *_isr_exit_restore_stack(void *interrupted)
|
|
{
|
|
/* Somewhat hacky test of the ISR exit modes. Two ways of
|
|
* specifying "this stack", one of which does the full spill
|
|
* and restore and one shortcuts that due to the NULL
|
|
* return
|
|
*/
|
|
if (rdtsc() & 1) {
|
|
return interrupted;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void *switch_back_to;
|
|
|
|
void switch_back(int arg1, int arg2, int arg3)
|
|
{
|
|
printf("Switching back (%d, %d, %d) sbt %xh\n",
|
|
arg1, arg2, arg3, (int)(long)switch_back_to);
|
|
xuk_switch(switch_back_to, &switch_back_to);
|
|
}
|
|
|
|
void test_switch(void)
|
|
{
|
|
static unsigned long long stack[256];
|
|
long args[] = { 5, 4, 3 };
|
|
int eflags = 0x20; /* interrupts disabled */
|
|
|
|
long handle = xuk_setup_stack((long)(sizeof(stack) + (char *)stack),
|
|
switch_back, eflags, args, 3);
|
|
|
|
printf("Switching to %xh (stack %xh)\n",
|
|
(int)handle, (int)(long)&stack[0]);
|
|
__asm__ volatile("cli");
|
|
xuk_switch((void *)handle, &switch_back_to);
|
|
__asm__ volatile("sti");
|
|
printf("Back from switch\n");
|
|
}
|
|
|
|
void local_ipi_handler(void *arg, int err)
|
|
{
|
|
printf("local IPI handler on CPU%d\n", (int)(long)xuk_get_f_ptr());
|
|
}
|
|
|
|
/* Sends an IPI to the current CPU and validates it ran */
|
|
void test_local_ipi(void)
|
|
{
|
|
printf("Testing a local IPI on CPU%d\n", (int)(long)xuk_get_f_ptr());
|
|
|
|
_apic.ICR_HI = (struct apic_icr_hi) {};
|
|
_apic.ICR_LO = (struct apic_icr_lo) {
|
|
.delivery_mode = FIXED,
|
|
.vector = 0x90,
|
|
.shorthand = SELF,
|
|
};
|
|
}
|
|
|
|
void _cpu_start(int cpu)
|
|
{
|
|
_putchar = putchar;
|
|
printf("Entering demo kernel\n");
|
|
|
|
/* Make sure the FS/GS pointers work, then set F to store our
|
|
* CPU ID
|
|
*/
|
|
xuk_set_f_ptr(cpu, (void *)(long)(0x19283700 + cpu));
|
|
xuk_set_g_ptr(cpu, (void *)(long)(0xabacad00 + cpu));
|
|
printf("fptr %p gptr %p\n", xuk_get_f_ptr(), xuk_get_g_ptr());
|
|
|
|
xuk_set_f_ptr(cpu, (void *)(long)cpu);
|
|
|
|
/* Set up this CPU's timer */
|
|
/* FIXME: this sets up a separate vector for every CPU's
|
|
* timer, and we'll run out. They should share the vector but
|
|
* still have individually-set APIC config. Probably wants a
|
|
* "timer" API
|
|
*/
|
|
xuk_set_isr(INT_APIC_LVT_TIMER, 10, handler_timer, 0);
|
|
_apic.INIT_COUNT = 5000000;
|
|
test_timers();
|
|
|
|
if (cpu == 0) {
|
|
xuk_set_isr(0x1f3, 0, (void *)handler_f3, (void *)0x12345678);
|
|
}
|
|
|
|
__asm__ volatile("int $0xf3");
|
|
|
|
/* Fire it all up */
|
|
printf("Enabling Interrupts\n");
|
|
__asm__ volatile("sti");
|
|
printf("Interrupts are unmasked (eflags %xh), here we go...\n",
|
|
eflags());
|
|
|
|
/* Wait a teeny bit then send an IPI to CPU0, which will hit
|
|
* the unhandled_vector handler
|
|
*/
|
|
if (cpu == 1) {
|
|
int t0 = rdtsc();
|
|
|
|
while (rdtsc() - t0 < 1000000) {
|
|
}
|
|
|
|
_apic.ICR_HI = (struct apic_icr_hi) {
|
|
.destination = 0
|
|
};
|
|
_apic.ICR_LO = (struct apic_icr_lo) {
|
|
.delivery_mode = FIXED,
|
|
.vector = 66,
|
|
};
|
|
while (_apic.ICR_LO.send_pending) {
|
|
}
|
|
}
|
|
|
|
test_switch();
|
|
|
|
xuk_set_isr(XUK_INT_RAW_VECTOR(0x90), -1, local_ipi_handler, 0);
|
|
test_local_ipi();
|
|
|
|
printf("CPU%d initialized, sleeping\n", cpu);
|
|
while (1) {
|
|
__asm__ volatile("hlt");
|
|
}
|
|
}
|