- 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
This commit is contained in:
stefan 2010-11-04 20:15:39 +00:00
parent 141c2c194e
commit c8c3035a4d
11 changed files with 246 additions and 61 deletions

View file

@ -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

View file

@ -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

View file

@ -22,24 +22,92 @@
#include <metalsvm/string.h>
#include <metalsvm/errno.h>
#include <metalsvm/processor.h>
#include <metalsvm/time.h>
#include <asm/irq.h>
#include <asm/irqflags.h>
#include <asm/apic.h>
#include <asm/multiboot.h>
#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));
}

View file

@ -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

View file

@ -22,6 +22,7 @@
#include <asm/irq.h>
#include <asm/idt.h>
#include <asm/isrs.h>
#include <asm/apic.h>
/*
* 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);

View file

@ -22,7 +22,6 @@
#include <metalsvm/tasks.h>
#include <metalsvm/time.h>
#include <metalsvm/processor.h>
#include <asm/apic.h>
#include <asm/irq.h>
#include <asm/irqflags.h>
#include <asm/gdt.h>
@ -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;
}

View file

@ -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)

View file

@ -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); }

View file

@ -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));

View file

@ -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;

View file

@ -110,8 +110,9 @@ int mmu_init(void)
}
} else {
kputs("Unable to initialize the memory management subsystem\n");
while(1)
while(1) {
NOP8;
}
}
/*