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