metalsvm/arch/x86/kernel/apic.c
stefan c8c3035a4d - 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
2010-11-04 20:15:39 +00:00

227 lines
6.2 KiB
C

/*
* Copyright 2010 Stefan Lankes, Chair for Operating Systems,
* RWTH Aachen University
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is part of MetalSVM.
*/
#include <metalsvm/stddef.h>
#include <metalsvm/stdio.h>
#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 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;
#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;
// searching MP signature in the reserved memory areas
#ifdef CONFIG_MULTIBOOT
if (mb_info && (mb_info->flags & (1 << 6))) {
multiboot_memory_map_t* mmap = (multiboot_memory_map_t*) mb_info->mmap_addr;
multiboot_memory_map_t* mmap_end = (void*) ((size_t) mb_info->mmap_addr + mb_info->mmap_length);
while (mmap < mmap_end) {
if (mmap->type == MULTIBOOT_MEMORY_RESERVED) {
addr = mmap->addr;
for(i=0; i<mmap->len; i++, addr++) {
if (strncmp((void*)addr, "_MP_", 4) == 0) {
apic_mp = (apic_mp_t*) addr;
goto found_mp;
}
}
}
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:
if (!apic_mp)
goto out;
kprintf("System uses Multiprocessing Specification 1.%d\n", apic_mp->version);
kprintf("MP features 1: %d\n", apic_mp->features[0]);
if (apic_mp->features[0]) {
kputs("Currently, MetalSVM supports only multiprocessing via the MP config tables!\n");
goto out;
}
apic_config = (apic_config_table_t*) apic_mp->mp_config;
if (!apic_config || strncmp((void*) &apic_config->signature, "PCMP", 4) !=0) {
kputs("Invalid MP config table\n");
goto out;
}
addr = (size_t) apic_config;
addr += sizeof(apic_config_table_t);
if (addr % 4)
addr += 4 - addr % 4;
for(i=0, count=0; i<apic_config->entry_count; i++) {
if (*((uint8_t*) addr) == 0) {
count++;
addr += 20;
} else addr += 8;
}
kprintf("Found %d cores\n", count);
if (count > MAX_CORES) {
kputs("Found too many cores! Increase the macro MAX_CORES!\n");
goto out;
}
ncores = count;
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("APIC Version: 0x%x\n", i & 0xFF);
cpuid(0x1, &i);
if (!(i & (1 << 5))) {
kputs("Unable to use Machine-Specific Registers (MSR)\n");
goto out;
}
if (!(rdmsr(0x1B) & (1 << 11))) {
kputs("Unable to use APIC Global Enable flag!\n");
goto out;
}
return 0;
out:
apic_mp = NULL;
apic_config = NULL;
ncores = 1;
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) && (apic_config != NULL));
}