/* * 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 #include #include #include #include #include #include #include #include #include #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; ilen; 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; ientry_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)); }