From c8c3035a4d84c17fbcb440c3dbe500e8118e4758 Mon Sep 17 00:00:00 2001 From: stefan Date: Thu, 4 Nov 2010 20:15:39 +0000 Subject: [PATCH] - complete redesign of the APIC code - MetalSVM detects the APIC and initializes the APIC timer - If an a APIC is available, the PIC timer will be disabled - SMP is currently not supported git-svn-id: http://svn.lfbs.rwth-aachen.de/svn/scc/trunk/MetalSVM@233 315a16e6-25f9-4109-90ae-ca3045a26c18 --- arch/x86/include/asm/apic.h | 2 + arch/x86/include/asm/processor.h | 7 ++ arch/x86/kernel/apic.c | 134 ++++++++++++++++++++++++------ arch/x86/kernel/entry.asm | 51 +++++++++++- arch/x86/kernel/irq.c | 58 +++++++++---- arch/x86/kernel/timer.c | 35 ++++---- include/metalsvm/config.h.example | 2 +- include/metalsvm/time.h | 1 + kernel/main.c | 3 +- kernel/processor.c | 11 ++- mm/memory.c | 3 +- 11 files changed, 246 insertions(+), 61 deletions(-) diff --git a/arch/x86/include/asm/apic.h b/arch/x86/include/asm/apic.h index 3c8cc549..0d318c85 100644 --- a/arch/x86/include/asm/apic.h +++ b/arch/x86/include/asm/apic.h @@ -72,6 +72,8 @@ typedef struct { } __attribute__ ((packed)) apic_io_entry_t; int apic_init(void); +void apic_eoi(void); +int apic_calibration(void); int has_apic(void); #ifdef __cplusplus diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h index 83b0e808..e653e083 100644 --- a/arch/x86/include/asm/processor.h +++ b/arch/x86/include/asm/processor.h @@ -98,6 +98,13 @@ inline static int system_init(void) return 0; } +inline static int system_calibration(void) +{ + apic_calibration(); + + return 0; +} + #ifdef __cplusplus } #endif diff --git a/arch/x86/kernel/apic.c b/arch/x86/kernel/apic.c index 95c0f8e2..273545d1 100644 --- a/arch/x86/kernel/apic.c +++ b/arch/x86/kernel/apic.c @@ -22,24 +22,92 @@ #include #include #include +#include +#include +#include #include #include -#define LAPIC_VERSION 0x0030 -#define LAPIC_SVR 0x00F0 - -// 8259 controllers' I/O ports -#define MAST_8259_APORT 0x20 -#define MAST_8259_DPORT 0x21 -#define SLAV_8259_APORT 0xA0 -#define SLAV_8259_DPORT 0xA1 +#define MP_FLT_SIGNATURE 0x5f504d5f +#define APIC_VERSION 0x0030 // Local APIC Version Register +#define APIC_TPR 0x0080 // Task Priority Regster +#define APIC_EOI 0x00B0 // EOI Register +#define APIC_SVR 0x00F0 // Spurious Interrupt Vector Register +#define APIC_LVT_T 0x0320 // LVT Thermal Sensor Register (P4/Xeon only) +#define APIC_LVT_PMC 0x0340 // LVT Performance Monitoring Counters Register +#define APIC_LINT0 0x0350 // LVT LINT0 Register +#define APIC_LINT1 0x0360 // LVT LINT1 Register +#define APIC_LVT_ER 0x0370 // LVT Error Register +#define APIC_ICR 0x0380 // Initial Count Register +#define APIC_CCR 0x0390 // Current Count Register +#define APIC_DCR 0x03E0 // Divide Configuration Register static apic_mp_t* apic_mp = NULL; static apic_config_table_t* apic_config = NULL; static uint32_t ncores = 1; -int apic_init(void) +#ifndef CONFIG_MULTIBOOT +static unsigned int* search_apic(unsigned int base, unsigned int limit) { + unsigned int *ptr; + + for (ptr = (unsigned int *) base; (unsigned int) ptr < limit; ptr++) { + if (*ptr == MP_FLT_SIGNATURE) + return ptr; + } + + return NULL; +} +#endif + +/* + * Send a 'End of Interrupt' command to the APIC + */ +void apic_eoi(void) +{ + *((uint32_t*) (apic_config->lapic+APIC_EOI)) = 0; +} + +/* + * detects the timer frequency of the APIC and restart + * the APIC timer with the correct period + */ +int apic_calibration(void) +{ + uint64_t ticks, old; + uint32_t diff; + + if (!has_apic()) + return -ENXIO; + + old = get_clock_tick(); + + /* wait for the next time slice */ + while((ticks = get_clock_tick()) - old == 0) + ; + + *((uint32_t*) (apic_config->lapic+APIC_DCR)) = 0xB; // set it to 1 clock increments + *((uint32_t*) (apic_config->lapic+APIC_LVT_T)) = 0x20030; // connects the timer to 48 and enables it + *((uint32_t*) (apic_config->lapic+APIC_ICR)) = 0xFFFFFFFF; + + /* wait 3 time slices to determine a ICR */ + while(get_clock_tick() - ticks < 3) + ; + + diff = 0xFFFFFFFF - *((uint32_t*) (apic_config->lapic+APIC_CCR)); + + *((uint32_t*) (apic_config->lapic+APIC_DCR)) = 0xB; // set it to 1 clock increments + *((uint32_t*) (apic_config->lapic+APIC_LVT_T)) = 0x20030; // connects the timer to 48 and enables it + *((uint32_t*) (apic_config->lapic+APIC_ICR)) = diff / 3; + + // Now, MetalSVM is able to use the APIC => Therefore, we disable the PIC + outportb(0xA1, 0xFF); + outportb(0x21, 0xFF); + + return 0; +} + +static int apic_probe(void) { size_t addr; uint32_t i, count; @@ -65,6 +133,10 @@ int apic_init(void) mmap++; } } +#else + apic_mp = (apic_mp_t*) search_apic(0xF0000, 0x100000); + if (!apic_mp) + apic_mp = (apic_mp_t*) search_apic(0x9F000, 0xA0000); #endif found_mp: @@ -104,12 +176,11 @@ found_mp: } ncores = count; - addr = apic_config->lapic; - i = *((uint32_t*) (addr+LAPIC_VERSION)); - kprintf("Found LAPIC at 0x%x\n", addr); + i = *((uint32_t*) (apic_config->lapic+APIC_VERSION)); + kprintf("Found APIC at 0x%x\n", apic_config->lapic); kprintf("Maximum LVT Entry: 0x%x\n", (i >> 16) & 0xFF); - kprintf("LAPIC Version: 0x%x\n", i & 0xFF); -#if 0 + kprintf("APIC Version: 0x%x\n", i & 0xFF); + cpuid(0x1, &i); if (!(i & (1 << 5))) { kputs("Unable to use Machine-Specific Registers (MSR)\n"); @@ -121,27 +192,36 @@ found_mp: goto out; } - i = *((uint32_t*) (addr+LAPIC_SVR)); - kprintf("Supurious Interrupt Vector Register: %x\n", i); - i = i | (1 << 8); - *((uint32_t*) (addr+LAPIC_SVR)) = i; - - // Disable the 8259's because we are going to use the IOAPIC for interrupt processing - outportb(MAST_8259_DPORT, 0xFF); // OCW1 master: inhibit all interrupts - udelay(100); - outportb(SLAV_8259_DPORT, 0xFF); // OCW1 slave: inhibit all interrupts - return 0; -#endif + out: apic_mp = NULL; apic_config = NULL; ncores = 1; - return -EINVAL; + return -ENXIO; +} + +int apic_init(void) +{ + int ret; + + ret = apic_probe(); + if (!ret) + return ret; + + *((uint32_t*) (apic_config->lapic+APIC_TPR)) = 0x20; // inhibit softint delivery + *((uint32_t*) (apic_config->lapic+APIC_LVT_T)) = 0x10000; // disable timer interrupt + *((uint32_t*) (apic_config->lapic+APIC_LVT_PMC)) = 0x10000; // disable performance counter interrupt + *((uint32_t*) (apic_config->lapic+APIC_LINT0)) = 0x31; // connect LINT0 to idt entry 49 + *((uint32_t*) (apic_config->lapic+APIC_LINT1)) = 0x32; // connect LINT1 to idt entry 50 + *((uint32_t*) (apic_config->lapic+APIC_LVT_ER)) = 0x33; // connect error to idt entry 51 + *((uint32_t*) (apic_config->lapic+APIC_SVR)) = 0x134; // enable the apic and connect to the idt entry 52 + + return 0; } int has_apic(void) { - return (apic_mp != NULL); + return ((apic_mp != NULL) && (apic_config != NULL)); } diff --git a/arch/x86/kernel/entry.asm b/arch/x86/kernel/entry.asm index 062f8fd3..94016d6b 100644 --- a/arch/x86/kernel/entry.asm +++ b/arch/x86/kernel/entry.asm @@ -36,7 +36,7 @@ mboot: ; Multiboot macros to make a few lines more readable later MULTIBOOT_PAGE_ALIGN equ 1<<0 MULTIBOOT_MEMORY_INFO equ 1<<1 - MULTIBOOT_AOUT_KLUDGE equ 1<<16 + ; MULTIBOOT_AOUT_KLUDGE equ 1<<16 MULTIBOOT_HEADER_MAGIC equ 0x1BADB002 MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO ; | MULTIBOOT_AOUT_KLUDGE MULTIBOOT_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) @@ -471,6 +471,11 @@ global irq12 global irq13 global irq14 global irq15 +global apic_timer +global apic_lint0 +global apic_lint1 +global apic_error +global apic_svr extern irq_handler extern get_current_task @@ -513,6 +518,7 @@ irq0: push byte 32 pusha +Lirq0: push esp call irq_handler add esp, 4 @@ -673,6 +679,49 @@ irq15: push byte 47 jmp irq_common_stub +apic_timer: + ; apic timer is registered as "Interrupt Gate" + ; Therefore, the interrupt flag (IF) is already cleared. + ; cli + push byte 0 + push byte 48 + pusha + ; we reuse code of the "traditional" timer interrupt (PIC) + jmp Lirq0 + +apic_lint0: + ; lint0 is registered as "Interrupt Gate" + ; Therefore, the interrupt flag (IF) is already cleared. + ; cli + push byte 0 + push byte 49 + jmp irq_common_stub + +apic_lint1: + ; lint1 is registered as "Interrupt Gate" + ; Therefore, the interrupt flag (IF) is already cleared. + ; cli + push byte 0 + push byte 50 + jmp irq_common_stub + +apic_error: + ; LVT error interrupt is registered as "Interrupt Gate" + ; Therefore, the interrupt flag (IF) is already cleared. + ; cli + push byte 0 + push byte 51 + jmp irq_common_stub + +apic_svr: + ; SVR is registered as "Interrupt Gate" + ; Therefore, the interrupt flag (IF) is already cleared. + ; cli + push byte 0 + push byte 52 + jmp irq_common_stub + + irq_common_stub: pusha diff --git a/arch/x86/kernel/irq.c b/arch/x86/kernel/irq.c index ef5adbe1..0fadd789 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -22,6 +22,7 @@ #include #include #include +#include /* * These are our own ISRs that point to our special IRQ handler @@ -43,12 +44,17 @@ extern void irq12(void); extern void irq13(void); extern void irq14(void); extern void irq15(void); +extern void apic_timer(void); +extern void apic_lint0(void); +extern void apic_lint1(void); +extern void apic_error(void); +extern void apic_svr(void); /* * This array is actually an array of function pointers. We use * this to handle custom IRQ handlers for a given IRQ */ -static void *irq_routines[16] = {[0 ... 15] = NULL }; +static void *irq_routines[32] = {[0 ... 31] = NULL }; /* This installs a custom IRQ handler for the given IRQ */ void irq_install_handler(unsigned int irq, irq_handler_t handler) @@ -94,6 +100,7 @@ static void irq_remap(void) static void irq_install(void) { irq_remap(); + idt_set_gate(32, (unsigned)irq0, KERNEL_CODE_SELECTOR, IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP); idt_set_gate(33, (unsigned)irq1, KERNEL_CODE_SELECTOR, @@ -126,6 +133,19 @@ static void irq_install(void) IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP); idt_set_gate(47, (unsigned)irq15, KERNEL_CODE_SELECTOR, IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP); + + if (has_apic()) { + idt_set_gate(48, (unsigned)apic_timer, KERNEL_CODE_SELECTOR, + IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP); + idt_set_gate(49, (unsigned)apic_lint0, KERNEL_CODE_SELECTOR, + IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP); + idt_set_gate(50, (unsigned)apic_lint1, KERNEL_CODE_SELECTOR, + IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP); + idt_set_gate(51, (unsigned)apic_error, KERNEL_CODE_SELECTOR, + IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP); + idt_set_gate(52, (unsigned)apic_svr, KERNEL_CODE_SELECTOR, + IDT_FLAG_PRESENT|IDT_FLAG_RING0|IDT_FLAG_32BIT|IDT_FLAG_INTTRAP); + } } void irq_init(void) @@ -139,12 +159,12 @@ void irq_init(void) * Each of the IRQ ISRs point to this function, rather than * the 'fault_handler' in 'isrs.c'. The IRQ Controllers need * to be told when you are done servicing them, so you need - * to send them an "End of Interrupt" command (0x20). There - * are two 8259 chips: The first one exists at 0x20, the second - * one exists at 0xA0. If the second controller (an IRQ from 8 to - * 15) gets an interrupt, you need to acknowledge the - * interrupt at BOTH controllers, otherwise, you only send - * an EOI command to the first controller. If you don't send + * to send them an "End of Interrupt" command. If we use the PIC + * instead of the APIC, we have two 8259 chips: The first one + * exists at 0x20, the second one exists at 0xA0. If the second + * controller (an IRQ from 8 to 15) gets an interrupt, you need to + * acknowledge the interrupt at BOTH controllers, otherwise, you + * only send an EOI command to the first controller. If you don't send * an EOI, you won't raise any more IRQs */ void irq_handler(struct state *s) @@ -156,22 +176,30 @@ void irq_handler(struct state *s) * Find out if we have a custom handler to run for this * IRQ and then finally, run it */ + handler = irq_routines[s->int_no - 32]; - if (handler) { + if (handler) handler(s); - } /* - * If the IDT entry that was invoked was greater than 40 - * (meaning IRQ8 - 15), then we need to send an EOI to - * the slave controller + * If the IDT entry that was invoked was greater than 48, + * then we use the APIC */ - if (s->int_no >= 40) { - outportb(0xA0, 0x20); + if (s->int_no >= 48) { + apic_eoi(); + return; } /* - * In either case, we need to send an EOI to the master + * If the IDT entry that was invoked was greater than 40 + * and lower than 48 (meaning IRQ8 - 15), then we need to + * send an EOI to the slave controller (PIC) + */ + if (s->int_no >= 40) + outportb(0xA0, 0x20); + + /* + * In either case, we need to send an EOI to the master (PIC) * interrupt controller too */ outportb(0x20, 0x20); diff --git a/arch/x86/kernel/timer.c b/arch/x86/kernel/timer.c index 9a2f19c0..f9211e14 100644 --- a/arch/x86/kernel/timer.c +++ b/arch/x86/kernel/timer.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -35,6 +34,11 @@ */ static volatile uint64_t timer_ticks = 0; +uint64_t get_clock_tick(void) +{ + return timer_ticks; +} + /* * Handles the timer. In this case, it's very simple: We * increment the 'timer_ticks' variable every time the @@ -76,21 +80,24 @@ int timer_init(void) { uint64_t start; - /* Installs 'timer_handler' to IRQ0 */ + /* + * Installs 'timer_handler' for the PIC (0) and APIC timer (16), + * only one handler will be later used. + */ irq_install_handler(0, timer_handler); + irq_install_handler(16, timer_handler); - if (!has_apic()) { - /* set default timer */ - outportb(0x43, 0x34); - start = rdtsc(); - while(rdtsc() - start < 1000000) - ; - outportb(0x40, LATCH(TIMER_FREQ) & 0xFF); /* low byte */ - start = rdtsc(); - while(rdtsc() - start < 1000000) - ; - outportb(0x40, LATCH(TIMER_FREQ) >> 8); /* high byte */ - } + outportb(0x43, 0x34); + /* before we write to 0x40, we wait some time */ + start = rdtsc(); + while(rdtsc() - start < 1000000) + ; + outportb(0x40, LATCH(TIMER_FREQ) & 0xFF); /* low byte */ + /* before we write to 0x40, we wait some time */ + start = rdtsc(); + while(rdtsc() - start < 1000000) + ; + outportb(0x40, LATCH(TIMER_FREQ) >> 8); /* high byte */ return 0; } diff --git a/include/metalsvm/config.h.example b/include/metalsvm/config.h.example index 45d99102..b006fa7e 100644 --- a/include/metalsvm/config.h.example +++ b/include/metalsvm/config.h.example @@ -32,6 +32,7 @@ extern "C" { #define KERNEL_STACK_SIZE 8192 #define KMSG_SIZE (128*1024) #define PAGE_SIZE 4096 +#define CACHE_LINE 64 #define MAILBOX_SIZE 2 #define TIMER_FREQ 100 /* in HZ */ #define CLOCK_TICK_RATE 1193182 /* 8254 chip's internal oscillator frequency */ @@ -51,7 +52,6 @@ extern "C" { #define CONFIG_KEYBOARD #define CONFIG_MULTIBOOT //#define CONFIG_ROCKCREEK -//#define CONFIG_LAPICTIMER #define BUILTIN_EXPECT(exp, b) __builtin_expect((exp), (b)) //#define BUILTIN_EXPECT(exp, b) (exp) diff --git a/include/metalsvm/time.h b/include/metalsvm/time.h index 69815785..b793169d 100644 --- a/include/metalsvm/time.h +++ b/include/metalsvm/time.h @@ -26,6 +26,7 @@ extern "C" { int timer_init(void); void timer_wait(unsigned int); +uint64_t get_clock_tick(void); static inline void sleep(unsigned int i) { timer_wait(i*TIMER_FREQ); } diff --git a/kernel/main.c b/kernel/main.c index 95b40101..713b65e6 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -160,7 +160,8 @@ int main(void) kprintf("Kernel starts at %p and ends at %p\n", &kernel_start, &kernel_end); - detect_cpu_frequency(); + system_calibration(); + kprintf("Processor frequency: %d MHz\n", get_cpu_frequency()/1000000); kprintf("Total memory: %u MBytes\n", atomic_int32_read(&total_pages)/((1024*1024)/PAGE_SIZE)); kprintf("Current allocated memory: %u KBytes\n", atomic_int32_read(&total_allocated_pages)*(PAGE_SIZE/1024)); diff --git a/kernel/processor.c b/kernel/processor.c index ea4779e5..a9461bce 100644 --- a/kernel/processor.c +++ b/kernel/processor.c @@ -27,9 +27,18 @@ static int cpu_freq = 0; int detect_cpu_frequency(void) { uint64_t start, end; + uint64_t ticks, old; + + old = get_clock_tick(); + + /* wait for the next time slice */ + while((ticks = get_clock_tick()) - old == 0) + ; start = rdtsc(); - timer_wait(1*TIMER_FREQ); + /* wait 5 time slices to determine the frequency */ + while(get_clock_tick() - ticks < TIMER_FREQ) + ; end = rdtsc(); cpu_freq = end - start; diff --git a/mm/memory.c b/mm/memory.c index 467430e0..fd664f07 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -110,8 +110,9 @@ int mmu_init(void) } } else { kputs("Unable to initialize the memory management subsystem\n"); - while(1) + while(1) { NOP8; + } } /*