metalsvm/arch/x86/kernel/apic.c
2010-11-26 18:15:09 +00:00

281 lines
7.4 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 Timer Register
#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 lapic = 0;
static uint32_t ncores = 1;
static inline void apic_write(uint32_t addr, uint32_t value)
{
asm volatile ("movl (%%eax), %%edx; movl %%ebx, (%%eax)" :: "a"(addr), "b"(value) : "%edx");
}
#ifndef CONFIG_MULTIBOOT
static unsigned int* search_apic(unsigned int base, unsigned int limit) {
uint32_t* ptr;
for (ptr = (uint32_t*) base; (uint32_t) ptr < limit; ptr++) {
if (*ptr == MP_FLT_SIGNATURE) {
if (!(((apic_mp_t*)ptr)->version > 4) && ((apic_mp_t*)ptr)->features[0])
return ptr;
}
}
return NULL;
}
#endif
/*
* Send a 'End of Interrupt' command to the APIC
*/
void apic_eoi(void)
{
if (lapic)
apic_write(lapic+APIC_EOI, 0);
}
/*
* detects the timer frequency of the APIC and restart
* the APIC timer with the correct period
*/
int apic_calibration(void)
{
#ifndef CONFIG_ROCKCREEK
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)
;
apic_write(lapic+APIC_DCR, 0xB); // set it to 1 clock increments
apic_write(lapic+APIC_LVT_T, 0x20030); // connects the timer to 48 and enables it
apic_write(lapic+APIC_ICR, 0xFFFFFFFF);
/* wait 3 time slices to determine a ICR */
while(get_clock_tick() - ticks < 3)
;
diff = 0xFFFFFFFF - *((uint32_t*) (lapic+APIC_CCR));
apic_write(lapic+APIC_DCR, 0xB); // set it to 1 clock increments
apic_write(lapic+APIC_LVT_T, 0x20030); // connects the timer to 48 and enables it
apic_write(lapic+APIC_ICR, diff / 3);
// Now, MetalSVM is able to use the APIC => Therefore, we disable the PIC
outportb(0xA1, 0xFF);
outportb(0x21, 0xFF);
#else
/*
* On the SCC, we already know the processor frequency
* and possess no PIC timer. Therfore, we use the rdtsc to
* to calibrate the APIC timer.
*/
uint64_t start, end, ticks;
uint32_t diff;
if (!has_apic())
return -ENXIO;
apic_write(lapic+APIC_DCR, 0xB); // set it to 1 clock increments
apic_write(lapic+APIC_LVT_T, 0x20030); // connects the timer to 48 and enables it
apic_write(lapic+APIC_ICR, 0xFFFFFFFF);
/* wait 3 time slices to determine a ICR */
start = rdtsc();
do {
flush_pipeline();
end = rdtsc();
ticks = end > start ? end - start : start - end;
} while(ticks < 3*scc_info.tile_frequency*1000000 / TIMER_FREQ);
diff = 0xFFFFFFFF - *((uint32_t*) (lapic+APIC_CCR));
apic_write(lapic+APIC_DCR, 0xB); // set it to 1 clock increments
apic_write(lapic+APIC_LVT_T, 0x20030); // connects the timer to 48 and enables it
apic_write(lapic+APIC_ICR, diff / 3);
#endif
kprintf("APIC calibration detects an ICR of 0x%x\n", diff / 3);
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 (*((uint32_t*) addr) == MP_FLT_SIGNATURE) {
apic_mp = (apic_mp_t*) addr;
if (!(apic_mp->version > 4) && apic_mp->features[0])
goto found_mp;
}
}
}
mmap++;
}
}
found_mp:
#else
apic_mp = (apic_mp_t*) search_apic(0xF0000, 0x100000);
if (!apic_mp)
apic_mp = (apic_mp_t*) search_apic(0x9F000, 0xA0000);
#endif
if (!apic_mp)
goto no_mp;
kprintf("System uses Multiprocessing Specification 1.%u\n", apic_mp->version);
kprintf("MP features 1: %u\n", apic_mp->features[0]);
if (apic_mp->features[0]) {
kputs("Currently, MetalSVM supports only multiprocessing via the MP config tables!\n");
goto no_mp;
}
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 no_mp;
}
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 %u cores\n", count);
if (count > MAX_CORES) {
kputs("Found too many cores! Increase the macro MAX_CORES!\n");
goto no_mp;
}
ncores = count;
check_lapic:
if (apic_config) {
lapic = apic_config->lapic;
} else {
uint32_t eax, edx, dummy;
cpuid(0x1, &eax, &dummy, &dummy, &edx);
if (edx & (1 << 9))
lapic = 0xFEE00000;
kprintf("Processor familiy %u, model %u, stepping %u\n", (eax>>8)&0xF, (eax>>4)&0xF, eax&0xF);
}
if (!lapic)
goto out;
i = *((uint32_t*) (lapic+APIC_VERSION));
kprintf("Found APIC at 0x%x\n", lapic);
kprintf("Maximum LVT Entry: 0x%x\n", (i >> 16) & 0xFF);
kprintf("APIC Version: 0x%x\n", i & 0xFF);
return 0;
out:
apic_mp = NULL;
apic_config = NULL;
lapic = 0;
ncores = 1;
return -ENXIO;
no_mp:
apic_mp = NULL;
apic_config = NULL;
ncores = 1;
goto check_lapic;
}
int apic_init(void)
{
int ret;
ret = apic_probe();
if (!ret)
return ret;
apic_write(lapic+APIC_TPR, 0x00); // allow all interrupts
apic_write(lapic+APIC_LVT_T, 0x10000); // disable timer interrupt
apic_write(lapic+APIC_LVT_PMC, 0x10000);// disable performance counter interrupt
apic_write(lapic+APIC_LINT0, 0x31); // connect LINT0 to idt entry 49
apic_write(lapic+APIC_LINT1, 0x32); // connect LINT1 to idt entry 50
apic_write(lapic+APIC_LVT_ER, 0x33); // connect error to idt entry 51
apic_write(lapic+APIC_SVR, 0x17F); // enable the apic and connect to the idt entry 207
return 0;
}
int has_apic(void)
{
return (lapic != 0);
}