451a6890d6
- Currently the cpu time quantum is timer-ticks based. Thus the remaining quantum is decreased only if the processes is interrupted by a timer tick. As processes block a lot this typically does not happen for normal user processes. Also the quantum depends on the frequency of the timer. - This change makes the quantum miliseconds based. Internally the miliseconds are translated into cpu cycles. Everytime userspace execution is interrupted by kernel the cycles just consumed by the current process are deducted from the remaining quantum. - It makes the quantum system timer frequency independent. - The boot processes quantum is loosely derived from the tick-based quantas and 60Hz timer and subject to future change - the 64bit arithmetics is a little ugly, will be changes once we have compiler support for 64bit integers (soon)
491 lines
12 KiB
C
491 lines
12 KiB
C
/*
|
|
* APIC handling routines. APIC is a requirement for SMP
|
|
*/
|
|
#include "kernel/kernel.h"
|
|
|
|
#include <unistd.h>
|
|
#include <minix/portio.h>
|
|
|
|
#include <minix/syslib.h>
|
|
|
|
#include "kernel/proc.h"
|
|
#include "kernel/glo.h"
|
|
#include "proto.h"
|
|
|
|
#include <minix/u64.h>
|
|
|
|
#include "apic.h"
|
|
#include "apic_asm.h"
|
|
#include "kernel/clock.h"
|
|
#include "glo.h"
|
|
|
|
#ifdef CONFIG_WATCHDOG
|
|
#include "kernel/watchdog.h"
|
|
#endif
|
|
|
|
#define IA32_APIC_BASE 0x1b
|
|
#define IA32_APIC_BASE_ENABLE_BIT 11
|
|
|
|
/* currently only 2 interrupt priority levels are used */
|
|
#define SPL0 0x0
|
|
#define SPLHI 0xF
|
|
|
|
/*
|
|
* to make APIC work if SMP is not configured, we need to set the maximal number
|
|
* of CPUS to 1, cpuid to return 0 and the current cpu is always BSP
|
|
*/
|
|
#define CONFIG_MAX_CPUS 1
|
|
#define cpu_is_bsp(x) 1
|
|
|
|
#define lapic_write_icr1(val) lapic_write(LAPIC_ICR1, val)
|
|
#define lapic_write_icr2(val) lapic_write(LAPIC_ICR2, val)
|
|
|
|
#define lapic_read_icr1(x) lapic_read(LAPIC_ICR1)
|
|
#define lapic_read_icr2(x) lapic_read(LAPIC_ICR2)
|
|
|
|
#define VERBOSE_APIC(x) x
|
|
|
|
PRIVATE int reboot_type;
|
|
PRIVATE int ioapic_enabled;
|
|
PRIVATE u32_t ioapic_id_mask[8];
|
|
PRIVATE u32_t lapic_id_mask[8];
|
|
PUBLIC u32_t lapic_addr_vaddr;
|
|
PUBLIC vir_bytes lapic_addr;
|
|
PUBLIC vir_bytes lapic_eoi_addr;
|
|
PRIVATE u32_t lapic_taskpri_addr;
|
|
|
|
PRIVATE volatile int probe_ticks;
|
|
PRIVATE u64_t tsc0, tsc1;
|
|
PRIVATE u32_t lapic_tctr0, lapic_tctr1;
|
|
|
|
/* FIXME: this is only accessed from assembly, never from C. Move to asm? */
|
|
PUBLIC u8_t apicid2cpuid[MAX_NR_APICIDS+1]; /* Accessed from asm */
|
|
|
|
PRIVATE unsigned apic_imcrp;
|
|
PRIVATE unsigned nioapics;
|
|
PRIVATE unsigned nbuses;
|
|
PRIVATE unsigned nintrs;
|
|
PRIVATE const unsigned nlints = 0;
|
|
|
|
/*
|
|
* FIXME this should be a cpulocal variable but there are some problems with
|
|
* arch specific cpulocals. As this variable is write-once-read-only it is ok to
|
|
* have at as an array until we resolve the cpulocals properly
|
|
*/
|
|
PRIVATE u32_t lapic_bus_freq[CONFIG_MAX_CPUS];
|
|
/* the probe period will be roughly 100ms */
|
|
#define PROBE_TICKS (system_hz / 10)
|
|
|
|
PRIVATE u32_t pci_config_intr_data;
|
|
PRIVATE u32_t ioapic_extint_assigned = 0;
|
|
PRIVATE int lapic_extint_assigned = 0;
|
|
|
|
PRIVATE int calib_clk_handler(irq_hook_t * UNUSED(hook))
|
|
{
|
|
u32_t tcrt;
|
|
u64_t tsc;
|
|
|
|
probe_ticks++;
|
|
read_tsc_64(&tsc);
|
|
tcrt = lapic_read(LAPIC_TIMER_CCR);
|
|
|
|
|
|
if (probe_ticks == 1) {
|
|
lapic_tctr0 = tcrt;
|
|
tsc0 = tsc;
|
|
}
|
|
else if (probe_ticks == PROBE_TICKS) {
|
|
lapic_tctr1 = tcrt;
|
|
tsc1 = tsc;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
PRIVATE void apic_calibrate_clocks(void)
|
|
{
|
|
u32_t lvtt, val, lapic_delta;
|
|
u64_t tsc_delta;
|
|
u64_t cpu_freq;
|
|
|
|
irq_hook_t calib_clk;
|
|
|
|
BOOT_VERBOSE(printf("Calibrating clock\n"));
|
|
/*
|
|
* Set Initial count register to the highest value so it does not
|
|
* underflow during the testing period
|
|
* */
|
|
val = 0xffffffff;
|
|
lapic_write (LAPIC_TIMER_ICR, val);
|
|
|
|
/* Set Current count register */
|
|
val = 0;
|
|
lapic_write (LAPIC_TIMER_CCR, val);
|
|
|
|
lvtt = lapic_read(LAPIC_TIMER_DCR) & ~0x0b;
|
|
/* Set Divide configuration register to 1 */
|
|
lvtt = APIC_TDCR_1;
|
|
lapic_write(LAPIC_TIMER_DCR, lvtt);
|
|
|
|
/*
|
|
* mask the APIC timer interrupt in the LVT Timer Register so that we
|
|
* don't get an interrupt upon underflow which we don't know how to
|
|
* handle right know. If underflow happens, the system will not continue
|
|
* as something is wrong with the clock IRQ 0 and we cannot calibrate
|
|
* the clock which mean that we cannot run processes
|
|
*/
|
|
lvtt = lapic_read (LAPIC_LVTTR);
|
|
lvtt |= APIC_LVTT_MASK;
|
|
lapic_write (LAPIC_LVTTR, lvtt);
|
|
|
|
/* set the probe, we use the legacy timer, IRQ 0 */
|
|
put_irq_handler(&calib_clk, CLOCK_IRQ, calib_clk_handler);
|
|
|
|
/* set the PIC timer to get some time */
|
|
intr_enable();
|
|
init_8253A_timer(system_hz);
|
|
|
|
/* loop for some time to get a sample */
|
|
while(probe_ticks < PROBE_TICKS) {
|
|
intr_enable();
|
|
}
|
|
|
|
intr_disable();
|
|
stop_8253A_timer();
|
|
|
|
/* remove the probe */
|
|
rm_irq_handler(&calib_clk);
|
|
|
|
lapic_delta = lapic_tctr0 - lapic_tctr1;
|
|
tsc_delta = sub64(tsc1, tsc0);
|
|
|
|
lapic_bus_freq[cpuid] = system_hz * lapic_delta / (PROBE_TICKS - 1);
|
|
BOOT_VERBOSE(printf("APIC bus freq %lu MHz\n",
|
|
lapic_bus_freq[cpuid] / 1000000));
|
|
cpu_freq = mul64(div64u64(tsc_delta, PROBE_TICKS - 1), make64(system_hz, 0));
|
|
cpu_set_freq(cpuid, cpu_freq);
|
|
BOOT_VERBOSE(cpu_print_freq(cpuid));
|
|
}
|
|
|
|
PRIVATE void lapic_set_timer_one_shot(const u32_t value)
|
|
{
|
|
/* sleep in micro seconds */
|
|
u32_t lvtt;
|
|
u32_t ticks_per_us;
|
|
const u8_t cpu = cpuid;
|
|
|
|
ticks_per_us = lapic_bus_freq[cpu] / 1000000;
|
|
|
|
/* calculate divisor and count from value */
|
|
lvtt = APIC_TDCR_1;
|
|
lapic_write(LAPIC_TIMER_DCR, lvtt);
|
|
|
|
/* configure timer as one-shot */
|
|
lvtt = APIC_TIMER_INT_VECTOR;
|
|
lapic_write(LAPIC_LVTTR, lvtt);
|
|
|
|
lapic_write(LAPIC_TIMER_ICR, value * ticks_per_us);
|
|
}
|
|
|
|
PUBLIC void lapic_set_timer_periodic(const unsigned freq)
|
|
{
|
|
/* sleep in micro seconds */
|
|
u32_t lvtt;
|
|
u32_t lapic_ticks_per_clock_tick;
|
|
const u8_t cpu = cpuid;
|
|
|
|
lapic_ticks_per_clock_tick = lapic_bus_freq[cpu] / freq;
|
|
|
|
lvtt = APIC_TDCR_1;
|
|
lapic_write(LAPIC_TIMER_DCR, lvtt);
|
|
|
|
/* configure timer as periodic */
|
|
lvtt = APIC_LVTT_TM | APIC_TIMER_INT_VECTOR;
|
|
lapic_write(LAPIC_LVTTR, lvtt);
|
|
|
|
lapic_write(LAPIC_TIMER_ICR, lapic_ticks_per_clock_tick);
|
|
}
|
|
|
|
PUBLIC void lapic_stop_timer(void)
|
|
{
|
|
u32_t lvtt;
|
|
lvtt = lapic_read(LAPIC_LVTTR);
|
|
lapic_write(LAPIC_LVTTR, lvtt | APIC_LVTT_MASK);
|
|
}
|
|
|
|
PRIVATE void lapic_microsec_sleep(unsigned count)
|
|
{
|
|
lapic_set_timer_one_shot(count);
|
|
while (lapic_read (LAPIC_TIMER_CCR));
|
|
}
|
|
|
|
PRIVATE u32_t lapic_errstatus(void)
|
|
{
|
|
lapic_write(LAPIC_ESR, 0);
|
|
return lapic_read(LAPIC_ESR);
|
|
}
|
|
|
|
PRIVATE void lapic_disable(void)
|
|
{
|
|
/* Disable current APIC and close interrupts from PIC */
|
|
u32_t val;
|
|
|
|
if (!lapic_addr)
|
|
return;
|
|
{
|
|
/* leave it enabled if imcr is not set */
|
|
val = lapic_read(LAPIC_LINT0);
|
|
val &= ~(APIC_ICR_DM_MASK|APIC_ICR_INT_MASK);
|
|
val |= APIC_ICR_DM_EXTINT; /* ExtINT at LINT0 */
|
|
lapic_write (LAPIC_LINT0, val);
|
|
return;
|
|
}
|
|
|
|
val = lapic_read(LAPIC_LINT0) & 0xFFFE58FF;
|
|
val |= APIC_ICR_INT_MASK;
|
|
lapic_write (LAPIC_LINT0, val);
|
|
|
|
val = lapic_read(LAPIC_LINT1) & 0xFFFE58FF;
|
|
val |= APIC_ICR_INT_MASK;
|
|
lapic_write (LAPIC_LINT1, val);
|
|
|
|
val = lapic_read(LAPIC_SIVR) & 0xFFFFFF00;
|
|
val &= ~APIC_ENABLE;
|
|
lapic_write(LAPIC_SIVR, val);
|
|
}
|
|
|
|
PRIVATE void lapic_enable_no_lints(void)
|
|
{
|
|
u32_t val;
|
|
|
|
val = lapic_read(LAPIC_LINT0);
|
|
lapic_extint_assigned = (val & APIC_ICR_DM_MASK) == APIC_ICR_DM_EXTINT;
|
|
val &= ~(APIC_ICR_DM_MASK|APIC_ICR_INT_MASK);
|
|
|
|
if (!ioapic_enabled && cpu_is_bsp(cpuid))
|
|
val |= (APIC_ICR_DM_EXTINT); /* ExtINT at LINT0 */
|
|
else
|
|
val |= (APIC_ICR_DM_EXTINT|APIC_ICR_INT_MASK); /* Masked ExtINT at LINT0 */
|
|
|
|
lapic_write (LAPIC_LINT0, val);
|
|
|
|
val = lapic_read(LAPIC_LINT1);
|
|
val &= ~(APIC_ICR_DM_MASK|APIC_ICR_INT_MASK);
|
|
|
|
if (!ioapic_enabled && cpu_is_bsp(cpuid))
|
|
val |= APIC_ICR_DM_NMI;
|
|
else
|
|
val |= (APIC_ICR_DM_NMI | APIC_ICR_INT_MASK); /* NMI at LINT1 */
|
|
lapic_write (LAPIC_LINT1, val);
|
|
}
|
|
|
|
PRIVATE int lapic_enable_in_msr(void)
|
|
{
|
|
u64_t msr;
|
|
u32_t addr;
|
|
|
|
ia32_msr_read(IA32_APIC_BASE, &msr.hi, &msr.lo);
|
|
|
|
/*
|
|
* FIXME if the location is different (unlikely) then the one we expect,
|
|
* update it
|
|
*/
|
|
addr = (msr.lo >> 12) | ((msr.hi & 0xf) << 20);
|
|
if (phys2vir(addr) != (lapic_addr >> 12)) {
|
|
if (msr.hi & 0xf) {
|
|
printf("ERROR : APIC address needs more then 32 bits\n");
|
|
return 0;
|
|
}
|
|
lapic_addr = phys2vir(msr.lo & ~((1 << 12) - 1));
|
|
}
|
|
|
|
msr.lo |= (1 << IA32_APIC_BASE_ENABLE_BIT);
|
|
ia32_msr_write(IA32_APIC_BASE, msr.hi, msr.lo);
|
|
|
|
return 1;
|
|
}
|
|
|
|
PUBLIC int lapic_enable(void)
|
|
{
|
|
u32_t val, nlvt;
|
|
unsigned cpu = cpuid;
|
|
|
|
if (!lapic_addr)
|
|
return 0;
|
|
|
|
cpu_has_tsc = _cpufeature(_CPUF_I386_TSC);
|
|
if (!cpu_has_tsc) {
|
|
printf("CPU lacks timestamp counter, "
|
|
"cannot calibrate LAPIC timer\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!lapic_enable_in_msr())
|
|
return 0;
|
|
|
|
lapic_eoi_addr = LAPIC_EOI;
|
|
/* clear error state register. */
|
|
val = lapic_errstatus ();
|
|
|
|
/* Enable Local APIC and set the spurious vector to 0xff. */
|
|
|
|
val = lapic_read(LAPIC_SIVR) & 0xFFFFFF00;
|
|
val |= APIC_ENABLE | APIC_SPURIOUS_INT_VECTOR;
|
|
val &= ~APIC_FOCUS_DISABLED;
|
|
lapic_write(LAPIC_SIVR, val);
|
|
lapic_read(LAPIC_SIVR);
|
|
|
|
*((u32_t *)lapic_eoi_addr) = 0;
|
|
|
|
cpu = cpuid;
|
|
|
|
/* Program Logical Destination Register. */
|
|
val = lapic_read(LAPIC_LDR) & ~0xFF000000;
|
|
val |= (cpu & 0xFF) << 24;
|
|
lapic_write(LAPIC_LDR, val);
|
|
|
|
/* Program Destination Format Register for Flat mode. */
|
|
val = lapic_read(LAPIC_DFR) | 0xF0000000;
|
|
lapic_write (LAPIC_DFR, val);
|
|
|
|
if (nlints == 0) {
|
|
lapic_enable_no_lints();
|
|
}
|
|
|
|
val = lapic_read (LAPIC_LVTER) & 0xFFFFFF00;
|
|
lapic_write (LAPIC_LVTER, val);
|
|
|
|
nlvt = (lapic_read(LAPIC_VERSION)>>16) & 0xFF;
|
|
|
|
if(nlvt >= 4) {
|
|
val = lapic_read(LAPIC_LVTTMR);
|
|
lapic_write(LAPIC_LVTTMR, val | APIC_ICR_INT_MASK);
|
|
}
|
|
|
|
if(nlvt >= 5) {
|
|
val = lapic_read(LAPIC_LVTPCR);
|
|
lapic_write(LAPIC_LVTPCR, val | APIC_ICR_INT_MASK);
|
|
}
|
|
|
|
/* setup TPR to allow all interrupts. */
|
|
val = lapic_read (LAPIC_TPR);
|
|
/* accept all interrupts */
|
|
lapic_write (LAPIC_TPR, val & ~0xFF);
|
|
|
|
lapic_read (LAPIC_SIVR);
|
|
*((u32_t *)lapic_eoi_addr) = 0;
|
|
|
|
apic_calibrate_clocks();
|
|
BOOT_VERBOSE(printf("APIC timer calibrated\n"));
|
|
|
|
return 1;
|
|
}
|
|
|
|
PRIVATE void apic_spurios_intr(void)
|
|
{
|
|
printf("WARNING spurious interrupt\n");
|
|
for(;;);
|
|
}
|
|
|
|
PRIVATE struct gate_table_s gate_table_ioapic[] = {
|
|
{ apic_hwint00, VECTOR( 0), INTR_PRIVILEGE },
|
|
{ apic_hwint01, VECTOR( 1), INTR_PRIVILEGE },
|
|
{ apic_hwint02, VECTOR( 2), INTR_PRIVILEGE },
|
|
{ apic_hwint03, VECTOR( 3), INTR_PRIVILEGE },
|
|
{ apic_hwint04, VECTOR( 4), INTR_PRIVILEGE },
|
|
{ apic_hwint05, VECTOR( 5), INTR_PRIVILEGE },
|
|
{ apic_hwint06, VECTOR( 6), INTR_PRIVILEGE },
|
|
{ apic_hwint07, VECTOR( 7), INTR_PRIVILEGE },
|
|
{ apic_hwint08, VECTOR( 8), INTR_PRIVILEGE },
|
|
{ apic_hwint09, VECTOR( 9), INTR_PRIVILEGE },
|
|
{ apic_hwint10, VECTOR(10), INTR_PRIVILEGE },
|
|
{ apic_hwint11, VECTOR(11), INTR_PRIVILEGE },
|
|
{ apic_hwint12, VECTOR(12), INTR_PRIVILEGE },
|
|
{ apic_hwint13, VECTOR(13), INTR_PRIVILEGE },
|
|
{ apic_hwint14, VECTOR(14), INTR_PRIVILEGE },
|
|
{ apic_hwint15, VECTOR(15), INTR_PRIVILEGE },
|
|
{ apic_spurios_intr, APIC_SPURIOUS_INT_VECTOR, INTR_PRIVILEGE },
|
|
{ NULL, 0, 0}
|
|
};
|
|
|
|
PRIVATE struct gate_table_s gate_table_common[] = {
|
|
{ ipc_entry, IPC_VECTOR, USER_PRIVILEGE },
|
|
{ kernel_call_entry, KERN_CALL_VECTOR, USER_PRIVILEGE },
|
|
{ NULL, 0, 0}
|
|
};
|
|
|
|
#ifdef CONFIG_APIC_DEBUG
|
|
PRIVATE void lapic_set_dummy_handlers(void)
|
|
{
|
|
char * handler;
|
|
int vect = 32;
|
|
|
|
handler = &lapic_intr_dummy_handles_start;
|
|
handler += vect * LAPIC_INTR_DUMMY_HANDLER_SIZE;
|
|
for(; handler < &lapic_intr_dummy_handles_end;
|
|
handler += LAPIC_INTR_DUMMY_HANDLER_SIZE) {
|
|
int_gate(vect++, (vir_bytes) handler,
|
|
PRESENT | INT_GATE_TYPE |
|
|
(INTR_PRIVILEGE << DPL_SHIFT));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Build descriptors for interrupt gates in IDT. */
|
|
PRIVATE void apic_idt_init(const int reset)
|
|
{
|
|
/* Set up idt tables for smp mode.
|
|
*/
|
|
vir_bytes local_timer_intr_handler;
|
|
|
|
if (reset) {
|
|
idt_copy_vectors(gate_table_pic);
|
|
idt_copy_vectors(gate_table_common);
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_APIC_DEBUG
|
|
if (cpu_is_bsp(cpuid))
|
|
printf("APIC debugging is enabled\n");
|
|
lapic_set_dummy_handlers();
|
|
#endif
|
|
|
|
/* Build descriptors for interrupt gates in IDT. */
|
|
if (ioapic_enabled)
|
|
idt_copy_vectors(gate_table_ioapic);
|
|
else
|
|
idt_copy_vectors(gate_table_pic);
|
|
|
|
idt_copy_vectors(gate_table_common);
|
|
|
|
/* configure the timer interupt handler */
|
|
if (cpu_is_bsp(cpuid)) {
|
|
local_timer_intr_handler = (vir_bytes) lapic_bsp_timer_int_handler;
|
|
BOOT_VERBOSE(printf("Initiating BSP timer handler\n"));
|
|
} else {
|
|
local_timer_intr_handler = (vir_bytes) lapic_ap_timer_int_handler;
|
|
BOOT_VERBOSE(printf("Initiating AP timer handler\n"));
|
|
}
|
|
|
|
/* register the timer interrupt handler for this CPU */
|
|
int_gate(APIC_TIMER_INT_VECTOR, (vir_bytes) local_timer_intr_handler,
|
|
PRESENT | INT_GATE_TYPE | (INTR_PRIVILEGE << DPL_SHIFT));
|
|
}
|
|
|
|
PUBLIC int apic_single_cpu_init(void)
|
|
{
|
|
if (!cpu_feature_apic_on_chip())
|
|
return 0;
|
|
|
|
lapic_addr = phys2vir(LOCAL_APIC_DEF_ADDR);
|
|
ioapic_enabled = 0;
|
|
|
|
if (!lapic_enable()) {
|
|
lapic_addr = 0x0;
|
|
return 0;
|
|
}
|
|
|
|
apic_idt_init(0); /* Not a reset ! */
|
|
idt_reload();
|
|
return 1;
|
|
}
|