root/sys/platform/pc64/apic/lapic.c
/*
 * Copyright (c) 1996, by Steve Passe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the developer may NOT be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD: src/sys/i386/i386/mpapic.c,v 1.37.2.7 2003/01/25 02:31:47 peter Exp $
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/ktr.h>
#include <sys/bus.h>
#include <sys/machintr.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <machine/globaldata.h>
#include <machine/clock.h>
#include <machine/limits.h>
#include <machine/smp.h>
#include <machine/md_var.h>
#include <machine/pmap.h>
#include <machine/specialreg.h>
#include <machine_base/apic/lapic.h>
#include <machine_base/apic/ioapic.h>
#include <machine_base/apic/ioapic_abi.h>
#include <machine_base/apic/apicvar.h>
#include <machine_base/icu/icu_var.h>
#include <machine/segments.h>
#include <sys/spinlock2.h>

#include <machine/cputypes.h>
#include <machine/intr_machdep.h>

#if !defined(KTR_LAPIC)
#define KTR_LAPIC       KTR_ALL
#endif
KTR_INFO_MASTER(lapic);
KTR_INFO(KTR_LAPIC, lapic, mem_eoi, 0, "mem_eoi");
KTR_INFO(KTR_LAPIC, lapic, msr_eoi, 0, "msr_eoi");
#define log_lapic(name)     KTR_LOG(lapic_ ## name)

extern int naps;

volatile lapic_t *lapic_mem;

static void     lapic_timer_calibrate(void);
static void     lapic_timer_set_divisor(int);
static void     lapic_timer_fixup_handler(void *);
static void     lapic_timer_restart_handler(void *);

static int      lapic_timer_c1e_test = -1;      /* auto-detect */
TUNABLE_INT("hw.lapic_timer_c1e_test", &lapic_timer_c1e_test);

static int      lapic_timer_enable = 1;
TUNABLE_INT("hw.lapic_timer_enable", &lapic_timer_enable);

static int      lapic_timer_tscdeadline = 1;
TUNABLE_INT("hw.lapic_timer_tscdeadline", &lapic_timer_tscdeadline);

static int      lapic_calibrate_test = 0;
TUNABLE_INT("hw.lapic_calibrate_test", &lapic_calibrate_test);

static int      lapic_calibrate_fast = 1;
TUNABLE_INT("hw.lapic_calibrate_fast", &lapic_calibrate_fast);

static void     lapic_timer_tscdlt_reload(struct cputimer_intr *, sysclock_t);
static void     lapic_mem_timer_intr_reload(struct cputimer_intr *, sysclock_t);
static void     lapic_msr_timer_intr_reload(struct cputimer_intr *, sysclock_t);
static void     lapic_timer_intr_enable(struct cputimer_intr *);
static void     lapic_timer_intr_restart(struct cputimer_intr *);
static void     lapic_timer_intr_pmfixup(struct cputimer_intr *);

static struct cputimer_intr lapic_cputimer_intr = {
        .freq = 0,
        .reload = lapic_mem_timer_intr_reload,
        .enable = lapic_timer_intr_enable,
        .config = cputimer_intr_default_config,
        .restart = lapic_timer_intr_restart,
        .pmfixup = lapic_timer_intr_pmfixup,
        .initclock = cputimer_intr_default_initclock,
        .pcpuhand = NULL,
        .next = SLIST_ENTRY_INITIALIZER,
        .name = "lapic",
        .type = CPUTIMER_INTR_LAPIC,
        .prio = CPUTIMER_INTR_PRIO_LAPIC,
        .caps = CPUTIMER_INTR_CAP_NONE,
        .priv = NULL
};

static int              lapic_timer_divisor_idx = -1;
static const uint32_t   lapic_timer_divisors[] = {
        APIC_TDCR_2,    APIC_TDCR_4,    APIC_TDCR_8,    APIC_TDCR_16,
        APIC_TDCR_32,   APIC_TDCR_64,   APIC_TDCR_128,  APIC_TDCR_1
};
#define APIC_TIMER_NDIVISORS (int)(NELEM(lapic_timer_divisors))

static int      lapic_use_tscdeadline = 0;

/*
 * APIC ID <-> CPU ID mapping structures.
 */
int     cpu_id_to_apic_id[NAPICID];
int     apic_id_to_cpu_id[NAPICID];
int     lapic_enable = 1;
int     lapic_usable = 0;
int     x2apic_enable = 1;

SYSCTL_INT(_hw, OID_AUTO, x2apic_enable, CTLFLAG_RD, &x2apic_enable, 0, "");

/* Separate cachelines for each cpu's info. */
struct deadlines {
        uint64_t timestamp;
        uint64_t downcount_time;
        uint64_t padding[6];
};
static struct deadlines *tsc_deadlines = NULL;

static void     lapic_mem_eoi(void);
static int      lapic_mem_ipi(int dest_type, int vector, int delivery_mode);
static void     lapic_mem_single_ipi(int cpu, int vector, int delivery_mode);

static void     lapic_msr_eoi(void);
static int      lapic_msr_ipi(int dest_type, int vector, int delivery_mode);
static void     lapic_msr_single_ipi(int cpu, int vector, int delivery_mode);

void            (*lapic_eoi)(void);
int             (*apic_ipi)(int dest_type, int vector, int delivery_mode);
void            (*single_apic_ipi)(int cpu, int vector, int delivery_mode);

static __inline void
lapic_mem_icr_set(uint32_t apic_id, uint32_t icr_lo_val)
{
        uint32_t icr_lo, icr_hi;

        icr_hi = (LAPIC_MEM_READ(icr_hi) & ~APIC_ID_MASK) |
            (apic_id << APIC_ID_SHIFT);
        icr_lo = (LAPIC_MEM_READ(icr_lo) & APIC_ICRLO_RESV_MASK) | icr_lo_val;

        LAPIC_MEM_WRITE(icr_hi, icr_hi);
        LAPIC_MEM_WRITE(icr_lo, icr_lo);
}

static __inline void
lapic_msr_icr_set(uint32_t apic_id, uint32_t icr_lo_val)
{
        LAPIC_MSR_WRITE(MSR_X2APIC_ICR,
            ((uint64_t)apic_id << 32) | ((uint64_t)icr_lo_val));
}

/*
 * Enable LAPIC, configure interrupts.
 */
void
lapic_init(boolean_t bsp)
{
        uint32_t timer;
        u_int   temp;

        if (bsp) {
                /* Decide whether we want to use TSC Deadline mode. */
                if (lapic_timer_tscdeadline != 0 &&
                    (cpu_feature2 & CPUID2_TSCDLT) &&
                    tsc_invariant && tsc_frequency != 0) {
                        lapic_use_tscdeadline = 1;
                        tsc_deadlines =
                                kmalloc(sizeof(struct deadlines) * (naps + 1),
                                        M_DEVBUF,
                                        M_WAITOK | M_ZERO | M_CACHEALIGN);
                }
        }

        /*
         * Install vectors
         *
         * Since IDT is shared between BSP and APs, these vectors
         * only need to be installed once; we do it on BSP.
         */
        if (bsp) {
                if (cpu_vendor_id == CPU_VENDOR_AMD &&
                    CPUID_TO_FAMILY(cpu_id) >= 0x0f &&
                    CPUID_TO_FAMILY(cpu_id) < 0x17) {   /* XXX */
                        uint32_t tcr;

                        /*
                         * Set the LINTEN bit in the HyperTransport
                         * Transaction Control Register.
                         *
                         * This will cause EXTINT and NMI interrupts
                         * routed over the hypertransport bus to be
                         * fed into the LAPIC LINT0/LINT1.  If the bit
                         * isn't set, the interrupts will go to the
                         * general cpu INTR/NMI pins.  On a dual-core
                         * cpu the interrupt winds up going to BOTH cpus.
                         * The first cpu that does the interrupt ack
                         * cycle will get the correct interrupt.  The
                         * second cpu that does it will get a spurious
                         * interrupt vector (typically IRQ 7).
                         */
                        outl(0x0cf8,
                            (1 << 31) | /* enable */
                            (0 << 16) | /* bus */
                            (0x18 << 11) | /* dev (cpu + 0x18) */
                            (0 << 8) |  /* func */
                            0x68        /* reg */
                            );
                        tcr = inl(0xcfc);
                        if ((tcr & 0x00010000) == 0) {
                                kprintf("LAPIC: AMD LINTEN on\n");
                                outl(0xcfc, tcr|0x00010000);
                        }
                        outl(0x0cf8, 0);
                }

                /* Install a 'Spurious INTerrupt' vector */
                setidt_global(XSPURIOUSINT_OFFSET, Xspuriousint,
                    SDT_SYSIGT, SEL_KPL, 0);

                /* Install a timer vector */
                setidt_global(XTIMER_OFFSET, Xtimer,
                    SDT_SYSIGT, SEL_KPL, 0);

                /* Install an inter-CPU IPI for TLB invalidation */
                setidt_global(XINVLTLB_OFFSET, Xinvltlb,
                    SDT_SYSIGT, SEL_KPL, 0);

                /* Install an inter-CPU IPI for IPIQ messaging */
                setidt_global(XIPIQ_OFFSET, Xipiq,
                    SDT_SYSIGT, SEL_KPL, 0);

                /* Install an inter-CPU IPI for CPU stop/restart */
                setidt_global(XCPUSTOP_OFFSET, Xcpustop,
                    SDT_SYSIGT, SEL_KPL, 0);

                /* Install an inter-CPU IPI for TLB invalidation */
                setidt_global(XSNIFF_OFFSET, Xsniff,
                    SDT_SYSIGT, SEL_KPL, 0);
        }

        /*
         * Setup LINT0 as ExtINT on the BSP.  This is theoretically an
         * aggregate interrupt input from the 8259.  The INTA cycle
         * will be routed to the external controller (the 8259) which
         * is expected to supply the vector.
         *
         * Must be setup edge triggered, active high.
         *
         * Disable LINT0 on BSP, if I/O APIC is enabled.
         *
         * Disable LINT0 on the APs.  It doesn't matter what delivery
         * mode we use because we leave it masked.
         */
        temp = LAPIC_READ(lvt_lint0);
        temp &= ~(APIC_LVT_MASKED | APIC_LVT_TRIG_MASK |
                  APIC_LVT_POLARITY_MASK | APIC_LVT_DM_MASK);
        if (bsp) {
                temp |= APIC_LVT_DM_EXTINT;
                if (ioapic_enable)
                        temp |= APIC_LVT_MASKED;
        } else {
                temp |= APIC_LVT_DM_FIXED | APIC_LVT_MASKED;
        }
        LAPIC_WRITE(lvt_lint0, temp);

        /*
         * Setup LINT1 as NMI.
         *
         * Must be setup edge trigger, active high.
         *
         * Enable LINT1 on BSP, if I/O APIC is enabled.
         *
         * Disable LINT1 on the APs.
         */
        temp = LAPIC_READ(lvt_lint1);
        temp &= ~(APIC_LVT_MASKED | APIC_LVT_TRIG_MASK |
                  APIC_LVT_POLARITY_MASK | APIC_LVT_DM_MASK);
        temp |= APIC_LVT_MASKED | APIC_LVT_DM_NMI;
        if (bsp && ioapic_enable)
                temp &= ~APIC_LVT_MASKED;
        LAPIC_WRITE(lvt_lint1, temp);

        /*
         * Mask the LAPIC error interrupt, LAPIC performance counter
         * interrupt.
         */
        LAPIC_WRITE(lvt_error, LAPIC_READ(lvt_error) | APIC_LVT_MASKED);
        LAPIC_WRITE(lvt_pcint, LAPIC_READ(lvt_pcint) | APIC_LVT_MASKED);

        /*
         * Set LAPIC timer vector and mask the LAPIC timer interrupt.
         */
        timer = LAPIC_READ(lvt_timer);
        timer &= ~APIC_LVTT_VECTOR;
        timer |= XTIMER_OFFSET;
        timer |= APIC_LVTT_MASKED;
        LAPIC_WRITE(lvt_timer, timer);

        /*
         * Set the Task Priority Register as needed.   At the moment allow
         * interrupts on all cpus (the APs will remain CLId until they are
         * ready to deal).
         */
        temp = LAPIC_READ(tpr);
        temp &= ~APIC_TPR_PRIO;         /* clear priority field */
        LAPIC_WRITE(tpr, temp);

        /*
         * AMD specific setup
         */
        if (cpu_vendor_id == CPU_VENDOR_AMD && lapic_mem != NULL &&
            (LAPIC_MEM_READ(version) & APIC_VER_AMD_EXT_SPACE)) {
                uint32_t ext_feat;
                uint32_t count;
                uint32_t max_count;
                uint32_t lvt;
                uint32_t i;

                ext_feat = LAPIC_MEM_READ(ext_feat);
                count = (ext_feat & APIC_EXTFEAT_MASK) >> APIC_EXTFEAT_SHIFT;
                max_count = sizeof(lapic_mem->ext_lvt) /
                    sizeof(lapic_mem->ext_lvt[0]);
                if (count > max_count)
                        count = max_count;
                for (i = 0; i < count; ++i) {
                        lvt = LAPIC_MEM_READ(ext_lvt[i].lvt);

                        lvt &= ~(APIC_LVT_POLARITY_MASK | APIC_LVT_TRIG_MASK |
                                 APIC_LVT_DM_MASK | APIC_LVT_MASKED);
                        lvt |= APIC_LVT_MASKED | APIC_LVT_DM_FIXED;

                        switch(i) {
                        case APIC_EXTLVT_IBS:
                                break;
                        case APIC_EXTLVT_MCA:
                                break;
                        case APIC_EXTLVT_DEI:
                                break;
                        case APIC_EXTLVT_SBI:
                                break;
                        default:
                                break;
                        }
                        if (bsp) {
                                kprintf("   LAPIC AMD elvt%d: 0x%08x",
                                        i, LAPIC_MEM_READ(ext_lvt[i].lvt));
                                if (LAPIC_MEM_READ(ext_lvt[i].lvt) != lvt)
                                        kprintf(" -> 0x%08x", lvt);
                                kprintf("\n");
                        }
                        LAPIC_MEM_WRITE(ext_lvt[i].lvt, lvt);
                }
        }

        /*
         * Enable the LAPIC
         */
        temp = LAPIC_READ(svr);
        temp |= APIC_SVR_ENABLE;        /* enable the LAPIC */
        temp &= ~APIC_SVR_FOCUS_DISABLE; /* enable lopri focus processor */

        if (LAPIC_READ(version) & APIC_VER_EOI_SUPP) {
                if (temp & APIC_SVR_EOI_SUPP) {
                        temp &= ~APIC_SVR_EOI_SUPP;
                        if (bsp)
                                kprintf("    LAPIC disabling EOI supp\n");
                }
                /* (future, on KVM auto-EOI must be disabled) */
                if (vmm_guest == VMM_GUEST_KVM)
                        temp &= ~APIC_SVR_EOI_SUPP;
        }

        /*
         * Set the spurious interrupt vector.  The low 4 bits of the vector
         * must be 1111.
         */
        if ((XSPURIOUSINT_OFFSET & 0x0F) != 0x0F)
                panic("bad XSPURIOUSINT_OFFSET: 0x%08x", XSPURIOUSINT_OFFSET);
        temp &= ~APIC_SVR_VECTOR;
        temp |= XSPURIOUSINT_OFFSET;

        LAPIC_WRITE(svr, temp);

        /*
         * Pump out a few EOIs to clean out interrupts that got through
         * before we were able to set the TPR.
         */
        LAPIC_WRITE(eoi, 0);
        LAPIC_WRITE(eoi, 0);
        LAPIC_WRITE(eoi, 0);

        if (bsp) {
                lapic_timer_calibrate();
                if (lapic_timer_enable) {
                        if (cpu_thermal_feature & CPUID_THERMAL_ARAT) {
                                /*
                                 * Local APIC timer will not stop
                                 * in deep C-state.
                                 */
                                lapic_cputimer_intr.caps |=
                                    CPUTIMER_INTR_CAP_PS;
                        }
                        if (lapic_use_tscdeadline) {
                                lapic_cputimer_intr.reload =
                                    lapic_timer_tscdlt_reload;
                        }
                        cputimer_intr_register(&lapic_cputimer_intr);
                        cputimer_intr_select(&lapic_cputimer_intr, 0);
                }
        } else if (!lapic_use_tscdeadline) {
                lapic_timer_set_divisor(lapic_timer_divisor_idx);
        }

        if (bootverbose)
                apic_dump("apic_initialize()");
}

static void
lapic_timer_set_divisor(int divisor_idx)
{
        KKASSERT(divisor_idx >= 0 && divisor_idx < APIC_TIMER_NDIVISORS);
        LAPIC_WRITE(dcr_timer, lapic_timer_divisors[divisor_idx]);
}

static void
lapic_timer_oneshot(u_int count)
{
        uint32_t value;

        value = LAPIC_READ(lvt_timer);
        value &= ~(APIC_LVTT_PERIODIC | APIC_LVTT_TSCDLT);
        LAPIC_WRITE(lvt_timer, value);
        LAPIC_WRITE(icr_timer, count);
}

static void
lapic_timer_oneshot_quick(u_int count)
{
        LAPIC_WRITE(icr_timer, count);
}

static void
lapic_timer_tscdeadline_quick(uint64_t diff)
{
        uint64_t val = rdtsc() + diff;

        wrmsr(MSR_TSC_DEADLINE, val);
        tsc_deadlines[mycpuid].timestamp = val;
}

static uint64_t
lapic_scale_to_tsc(unsigned value, unsigned scale)
{
        uint64_t val;

        val = value;
        val *= tsc_frequency;
        val += (scale - 1);
        val /= scale;
        return val;
}

#define MAX_MEASURE_RETRIES     100

static u_int64_t
do_tsc_calibration(u_int us, u_int64_t apic_delay_tsc)
{
        u_int64_t old_tsc1, old_tsc2, new_tsc1, new_tsc2;
        u_int64_t diff, count;
        u_int64_t a;
        u_int32_t start, end;
        int retries1 = 0, retries2 = 0;

retry1:
        lapic_timer_oneshot_quick(APIC_TIMER_MAX_COUNT);
        old_tsc1 = rdtsc_ordered();
        start = LAPIC_READ(ccr_timer);
        old_tsc2 = rdtsc_ordered();
        if (apic_delay_tsc > 0 && retries1 < MAX_MEASURE_RETRIES &&
            old_tsc2 - old_tsc1 > 2 * apic_delay_tsc) {
                retries1++;
                goto retry1;
        }
        DELAY(us);
retry2:
        new_tsc1 = rdtsc_ordered();
        end = LAPIC_READ(ccr_timer);
        new_tsc2 = rdtsc_ordered();
        if (apic_delay_tsc > 0 && retries2 < MAX_MEASURE_RETRIES &&
            new_tsc2 - new_tsc1 > 2 * apic_delay_tsc) {
                retries2++;
                goto retry2;
        }
        if (end == 0)
                return 0;

        count = start - end;

        /* Make sure the lapic can count for up to 2s */
        a = (unsigned)APIC_TIMER_MAX_COUNT;
        if (us < 2000000 && (u_int64_t)count * 2000000 >= a * us)
                return 0;

        if (lapic_calibrate_test > 0 && (retries1 > 0 || retries2 > 0)) {
                kprintf("%s: retries1=%d retries2=%d\n",
                    __func__, retries1, retries2);
        }

        diff = (new_tsc1 - old_tsc1) + (new_tsc2 - old_tsc2);
        /* XXX First estimate if the total TSC diff value makes sense */
        /* This will almost overflow, but only almost :) */
        count = (2 * count * tsc_frequency) / diff;

        return count;
}

static uint64_t
do_cputimer_calibration(u_int us)
{
        sysclock_t value;
        sysclock_t start, end;
        uint32_t beginning, finish;

        lapic_timer_oneshot(APIC_TIMER_MAX_COUNT);
        beginning = LAPIC_READ(ccr_timer);
        start = sys_cputimer->count();
        DELAY(us);
        end = sys_cputimer->count();
        finish = LAPIC_READ(ccr_timer);
        if (finish == 0)
                return 0;
        /* value is the LAPIC timer difference. */
        value = (uint32_t)(beginning - finish);
        /* end is the sys_cputimer difference. */
        end -= start;
        if (end == 0)
                return 0;
        value = muldivu64(value, sys_cputimer->freq, end);

        return value;
}

static void
lapic_timer_calibrate(void)
{
        sysclock_t value;
        u_int64_t apic_delay_tsc = 0;
        int use_tsc_calibration = 0;

        /* No need to calibrate lapic_timer, if we will use TSC Deadline mode */
        if (lapic_use_tscdeadline) {
                lapic_cputimer_intr.freq = tsc_frequency;
                kprintf(
                    "lapic: TSC Deadline Mode: frequency %lu Hz\n",
                    lapic_cputimer_intr.freq);
                return;
        }

        /*
         * On real hardware, tsc_invariant == 0 wouldn't be an issue, but in
         * a virtual machine the frequency may get changed by the host.
         */
        if (tsc_frequency != 0 && tsc_invariant && lapic_calibrate_fast)
                use_tsc_calibration = 1;

        if (use_tsc_calibration) {
                u_int64_t min_apic_tsc = 0, max_apic_tsc = 0;
                u_int64_t old_tsc, new_tsc;
                uint32_t val;
                int i;

                /* warm up */
                lapic_timer_oneshot(APIC_TIMER_MAX_COUNT);
                for (i = 0; i < 10; i++)
                        val = LAPIC_READ(ccr_timer);

                for (i = 0; i < 100; i++) {
                        old_tsc = rdtsc_ordered();
                        val = LAPIC_READ(ccr_timer);
                        new_tsc = rdtsc_ordered();
                        new_tsc -= old_tsc;
                        apic_delay_tsc += new_tsc;
                        if (min_apic_tsc == 0 ||
                            min_apic_tsc > new_tsc) {
                                min_apic_tsc = new_tsc;
                        }
                        if (max_apic_tsc < new_tsc)
                                max_apic_tsc = new_tsc;
                }
                apic_delay_tsc /= 100;
                kprintf(
                    "LAPIC latency (in TSC ticks): %lu min: %lu max: %lu\n",
                    apic_delay_tsc, min_apic_tsc, max_apic_tsc);
                apic_delay_tsc = min_apic_tsc;
        }

        if (!use_tsc_calibration) {
                int i;

                /*
                 * Do some exercising of the lapic timer access. This improves
                 * precision of the subsequent calibration run in at least some
                 * virtualization cases.
                 */
                lapic_timer_set_divisor(0);
                for (i = 0; i < 10; i++)
                        (void)do_cputimer_calibration(100);
        }
        /* Try to calibrate the local APIC timer. */
        for (lapic_timer_divisor_idx = 0;
             lapic_timer_divisor_idx < APIC_TIMER_NDIVISORS;
             lapic_timer_divisor_idx++) {
                lapic_timer_set_divisor(lapic_timer_divisor_idx);
                if (use_tsc_calibration) {
                        value = do_tsc_calibration(200*1000, apic_delay_tsc);
                } else {
                        value = do_cputimer_calibration(2*1000*1000);
                }
                if (value != 0)
                        break;
        }
        if (lapic_timer_divisor_idx >= APIC_TIMER_NDIVISORS)
                panic("lapic: no proper timer divisor?!");
        lapic_cputimer_intr.freq = value;

        kprintf("lapic: divisor index %d, frequency %lu Hz\n",
                lapic_timer_divisor_idx, lapic_cputimer_intr.freq);

        if (lapic_calibrate_test > 0) {
                uint64_t freq;
                int i;

                for (i = 1; i <= 20; i++) {
                        if (use_tsc_calibration) {
                                freq = do_tsc_calibration(i*100*1000,
                                                          apic_delay_tsc);
                        } else {
                                freq = do_cputimer_calibration(i*100*1000);
                        }
                        if (freq != 0)
                                kprintf("%ums: %lu\n", i * 100, freq);
                }
        }
}

static void
lapic_timer_tscdlt_reload(struct cputimer_intr *cti, sysclock_t reload)
{
        struct globaldata *gd = mycpu;
        uint64_t diff, now, val;

        /*
         * Set maximum deadline to 60 seconds
         */
        if (reload > sys_cputimer->freq * 60)
                reload = sys_cputimer->freq * 60;
        diff = muldivu64(reload, tsc_frequency, sys_cputimer->freq);
        if (diff < 4)
                diff = 4;
        if (cpu_vendor_id == CPU_VENDOR_INTEL)
                cpu_lfence();
        else
                cpu_mfence();
        now = rdtsc();
        val = now + diff;
        if (gd->gd_timer_running) {
                uint64_t deadline = tsc_deadlines[mycpuid].timestamp;
                if (deadline == 0 || now > deadline || val < deadline) {
                        wrmsr(MSR_TSC_DEADLINE, val);
                        tsc_deadlines[mycpuid].timestamp = val;
                }
        } else {
                gd->gd_timer_running = 1;
                wrmsr(MSR_TSC_DEADLINE, val);
                tsc_deadlines[mycpuid].timestamp = val;
        }
}

static void
lapic_mem_timer_intr_reload(struct cputimer_intr *cti, sysclock_t reload)
{
        struct globaldata *gd = mycpu;

        if ((ssysclock_t)reload < 0)
                reload = 1;
        reload = muldivu64(reload, cti->freq, sys_cputimer->freq);
        if (reload < 2)
                reload = 2;
        if (reload > 0xFFFFFFFF)
                reload = 0xFFFFFFFF;

        if (gd->gd_timer_running) {
                if (reload < LAPIC_MEM_READ(ccr_timer))
                        LAPIC_MEM_WRITE(icr_timer, (uint32_t)reload);
        } else {
                gd->gd_timer_running = 1;
                LAPIC_MEM_WRITE(icr_timer, (uint32_t)reload);
        }
}

static void
lapic_msr_timer_intr_reload(struct cputimer_intr *cti, sysclock_t reload)
{
        struct globaldata *gd = mycpu;

        if ((ssysclock_t)reload < 0)
                reload = 1;
        reload = muldivu64(reload, cti->freq, sys_cputimer->freq);
        if (reload < 2)
                reload = 2;
        if (reload > 0xFFFFFFFF)
                reload = 0xFFFFFFFF;

        if (gd->gd_timer_running) {
                if (reload < LAPIC_MSR_READ(MSR_X2APIC_CCR_TIMER))
                        LAPIC_MSR_WRITE(MSR_X2APIC_ICR_TIMER, (uint32_t)reload);
        } else {
                gd->gd_timer_running = 1;
                LAPIC_MSR_WRITE(MSR_X2APIC_ICR_TIMER, (uint32_t)reload);
        }
}

static void
lapic_timer_intr_enable(struct cputimer_intr *cti __unused)
{
        uint32_t timer;

        timer = LAPIC_READ(lvt_timer);
        timer &= ~(APIC_LVTT_MASKED | APIC_LVTT_PERIODIC | APIC_LVTT_TSCDLT);
        if (lapic_use_tscdeadline)
                timer |= APIC_LVTT_TSCDLT;
        LAPIC_WRITE(lvt_timer, timer);
        if (lapic_use_tscdeadline)
                cpu_mfence();

        lapic_timer_fixup_handler(NULL);
}

static void
lapic_timer_fixup_handler(void *arg)
{
        int *started = arg;

        if (started != NULL)
                *started = 0;

        if (cpu_vendor_id == CPU_VENDOR_AMD) {
                int c1e_test = lapic_timer_c1e_test;

                if (c1e_test < 0) {
                        if (vmm_guest == VMM_GUEST_NONE) {
                                c1e_test = 1;
                        } else {
                                /*
                                 * Don't do this C1E testing and adjustment
                                 * on virtual machines, the best case for
                                 * accessing this MSR is a NOOP; the worst
                                 * cases could be pretty nasty, e.g. crash.
                                 */
                                c1e_test = 0;
                        }
                }

                /*
                 * Detect the presence of C1E capability mostly on latest
                 * dual-cores (or future) k8 family.  This feature renders
                 * the local APIC timer dead, so we disable it by reading
                 * the Interrupt Pending Message register and clearing both
                 * C1eOnCmpHalt (bit 28) and SmiOnCmpHalt (bit 27).
                 *
                 * Reference:
                 *   "BIOS and Kernel Developer's Guide for AMD NPT
                 *    Family 0Fh Processors"
                 *   #32559 revision 3.00
                 */
                if ((cpu_id & 0x00000f00) == 0x00000f00 &&
                    (cpu_id & 0x0fff0000) >= 0x00040000 &&
                    c1e_test) {
                        uint64_t msr;

                        msr = rdmsr(0xc0010055);
                        if (msr & 0x18000000) {
                                struct globaldata *gd = mycpu;

                                kprintf("cpu%d: AMD C1E detected\n",
                                        gd->gd_cpuid);
                                wrmsr(0xc0010055, msr & ~0x18000000ULL);

                                /*
                                 * We are kinda stalled;
                                 * kick start again.
                                 */
                                gd->gd_timer_running = 1;
                                if (lapic_use_tscdeadline) {
                                        /* Maybe reached in Virtual Machines? */
                                        lapic_timer_tscdeadline_quick(5000);
                                } else {
                                        lapic_timer_oneshot_quick(2);
                                }

                                if (started != NULL)
                                        *started = 1;
                        }
                }
        }
}

static void
lapic_timer_restart_handler(void *dummy __unused)
{
        int started;

        lapic_timer_fixup_handler(&started);
        if (!started) {
                struct globaldata *gd = mycpu;

                gd->gd_timer_running = 1;
                if (lapic_use_tscdeadline) {
                        /* Maybe reached in Virtual Machines? */
                        lapic_timer_tscdeadline_quick(5000);
                } else {
                        lapic_timer_oneshot_quick(2);
                }
        }
}

/*
 * This function is called only by ACPICA code currently:
 * - AMD C1E fixup.  AMD C1E only seems to happen after ACPI
 *   module controls PM.  So once ACPICA is attached, we try
 *   to apply the fixup to prevent LAPIC timer from hanging.
 */
static void
lapic_timer_intr_pmfixup(struct cputimer_intr *cti __unused)
{
        lwkt_send_ipiq_mask(smp_active_mask,
                            lapic_timer_fixup_handler, NULL);
}

static void
lapic_timer_intr_restart(struct cputimer_intr *cti __unused)
{
        lwkt_send_ipiq_mask(smp_active_mask, lapic_timer_restart_handler, NULL);
}


/*
 * dump contents of local APIC registers
 */
void
apic_dump(char* str)
{
        kprintf("SMP: CPU%d %s:\n", mycpu->gd_cpuid, str);
        kprintf("     lint0: 0x%08x lint1: 0x%08x TPR: 0x%08x SVR: 0x%08x\n",
                LAPIC_READ(lvt_lint0), LAPIC_READ(lvt_lint1), LAPIC_READ(tpr),
                LAPIC_READ(svr));
}

/*
 * Inter Processor Interrupt functions.
 */

static __inline void
lapic_mem_icr_unpend(const char *func)
{
        if (LAPIC_MEM_READ(icr_lo) & APIC_DELSTAT_PEND) {
                int64_t tsc;
                int loops = 1;

                tsc = rdtsc();
                while (LAPIC_MEM_READ(icr_lo) & APIC_DELSTAT_PEND) {
                        cpu_pause();
                        if ((tsc_sclock_t)(rdtsc() -
                                           (tsc + tsc_frequency)) > 0) {
                                tsc = rdtsc();
                                if (++loops > 30) {
                                        panic("%s: cpu%d apic stalled",
                                            func, mycpuid);
                                } else {
                                        kprintf("%s: cpu%d apic stalled\n",
                                            func, mycpuid);
                                }
                        }
                }
        }
}

/*
 * Send APIC IPI 'vector' to 'destType' via 'deliveryMode'.
 *
 *  destType is 1 of: APIC_DEST_SELF, APIC_DEST_ALLISELF, APIC_DEST_ALLESELF
 *  vector is any valid SYSTEM INT vector
 *  delivery_mode is 1 of: APIC_DELMODE_FIXED, APIC_DELMODE_LOWPRIO
 *
 * WARNINGS!
 *
 * We now implement a per-cpu interlock (gd->gd_npoll) to prevent more than
 * one IPI from being sent to any given cpu at a time.  Thus we no longer
 * have to process incoming IPIs while waiting for the status to clear.
 * No deadlock should be possible.
 *
 * We now physically disable interrupts for the lapic ICR operation.  If
 * we do not do this then it looks like an EOI sent to the lapic (which
 * occurs even with a critical section) can interfere with the command
 * register ready status and cause an IPI to be lost.
 *
 * e.g. an interrupt can occur, issue the EOI, IRET, and cause the command
 * register to busy just before we write to icr_lo, resulting in a lost
 * issuance.  This only appears to occur on Intel cpus and is not
 * documented.  It could simply be that cpus are so fast these days that
 * it was always an issue, but is only now rearing its ugly head.  This
 * is conjecture.
 */
static int
lapic_mem_ipi(int dest_type, int vector, int delivery_mode)
{
        lapic_mem_icr_unpend(__func__);
        lapic_mem_icr_set(0,
            dest_type | APIC_LEVEL_ASSERT | delivery_mode | vector);
        return 0;
}

static int
lapic_msr_ipi(int dest_type, int vector, int delivery_mode)
{
        lapic_msr_icr_set(0,
            dest_type | APIC_LEVEL_ASSERT | delivery_mode | vector);
        return 0;
}

/*
 * Interrupts must be hard-disabled by caller
 */
static void
lapic_mem_single_ipi(int cpu, int vector, int delivery_mode)
{
        lapic_mem_icr_unpend(__func__);
        lapic_mem_icr_set(CPUID_TO_APICID(cpu),
            APIC_DEST_DESTFLD | APIC_LEVEL_ASSERT | delivery_mode | vector);
}

static void
lapic_msr_single_ipi(int cpu, int vector, int delivery_mode)
{
        lapic_msr_icr_set(CPUID_TO_APICID(cpu),
            APIC_DEST_DESTFLD | APIC_LEVEL_ASSERT | delivery_mode | vector);
}

/*
 * Send APIC IPI 'vector' to 'target's via 'delivery_mode'.
 *
 * target is a bitmask of destination cpus.  Vector is any
 * valid system INT vector.  Delivery mode may be either
 * APIC_DELMODE_FIXED or APIC_DELMODE_LOWPRIO.
 *
 * Interrupts must be hard-disabled by caller
 */
void
selected_apic_ipi(cpumask_t target, int vector, int delivery_mode)
{
        while (CPUMASK_TESTNZERO(target)) {
                int n = BSFCPUMASK(target);
                CPUMASK_NANDBIT(target, n);
                single_apic_ipi(n, vector, delivery_mode);
        }
}

/*
 * Load a 'downcount time' in uSeconds.
 */
void
set_apic_timer(int us)
{
        u_int count;

        if (lapic_use_tscdeadline) {
                uint64_t val;

                val = lapic_scale_to_tsc(us, 1000000);
                val += rdtsc();
                /* No need to arm the lapic here, just track the timeout. */
                tsc_deadlines[mycpuid].downcount_time = val;
                return;
        }

        /*
         * When we reach here, lapic timer's frequency
         * must have been calculated as well as the
         * divisor (lapic->dcr_timer is setup during the
         * divisor calculation).
         */
        KKASSERT(lapic_cputimer_intr.freq != 0 &&
                 lapic_timer_divisor_idx >= 0);

        count = ((us * (int64_t)lapic_cputimer_intr.freq) + 999999) / 1000000;
        lapic_timer_oneshot(count);
}


/*
 * Read remaining time in timer, in microseconds (rounded up).
 */
int
read_apic_timer(void)
{
        uint64_t val;

        if (lapic_use_tscdeadline) {
                uint64_t now;

                val = tsc_deadlines[mycpuid].downcount_time;
                now = rdtsc();
                if (val == 0 || now > val) {
                        return 0;
                } else {
                        val -= now;
                        val *= 1000000;
                        val += (tsc_frequency - 1);
                        val /= tsc_frequency;
                        if (val > INT_MAX)
                                val = INT_MAX;
                        return val;
                }
        }

        val = LAPIC_READ(ccr_timer);
        if (val == 0)
                return 0;

        KKASSERT(lapic_cputimer_intr.freq > 0);
        val *= 1000000;
        val += (lapic_cputimer_intr.freq - 1);
        val /= lapic_cputimer_intr.freq;
        if (val > INT_MAX)
                val = INT_MAX;
        return val;
}


/*
 * Spin-style delay, set delay time in uS, spin till it drains.
 */
void
u_sleep(int count)
{
        set_apic_timer(count);
        while (read_apic_timer())
                 /* spin */ ;
}

int
lapic_unused_apic_id(int start)
{
        int i;

        for (i = start; i < APICID_MAX; ++i) {
                if (APICID_TO_CPUID(i) == -1)
                        return i;
        }
        return NAPICID;
}

void
lapic_map(vm_paddr_t lapic_addr)
{
        lapic_mem = pmap_mapdev_uncacheable(lapic_addr, sizeof(struct LAPIC));
}

void
lapic_x2apic_enter(boolean_t bsp)
{
        uint64_t apic_base;

        KASSERT(x2apic_enable, ("X2APIC mode is not enabled"));

        /*
         * X2APIC mode is requested, if it has not been enabled by the BIOS,
         * enable it now.
         */
        apic_base = rdmsr(MSR_APICBASE);
        if ((apic_base & APICBASE_X2APIC) == 0) {
                wrmsr(MSR_APICBASE,
                    apic_base | APICBASE_X2APIC | APICBASE_ENABLED);
        }
        if (bsp) {
                lapic_eoi = lapic_msr_eoi;
                apic_ipi = lapic_msr_ipi;
                single_apic_ipi = lapic_msr_single_ipi;
                lapic_cputimer_intr.reload = lapic_msr_timer_intr_reload;
        }
}

static TAILQ_HEAD(, lapic_enumerator) lapic_enumerators =
        TAILQ_HEAD_INITIALIZER(lapic_enumerators);

int
lapic_config(void)
{
        struct lapic_enumerator *e;
        uint64_t apic_base;
        int error, i, ap_max;

        KKASSERT(lapic_enable);

        lapic_eoi = lapic_mem_eoi;
        apic_ipi = lapic_mem_ipi;
        single_apic_ipi = lapic_mem_single_ipi;

        TUNABLE_INT_FETCH("hw.x2apic_enable", &x2apic_enable);
        if (x2apic_enable < 0)
                x2apic_enable = 1;
        if ((cpu_feature2 & CPUID2_X2APIC) == 0) {
                /* X2APIC is not supported. */
                x2apic_enable = 0;
        } else {
                /*
                 * If the BIOS enabled the X2APIC mode, then we would stick
                 * with the X2APIC mode.
                 */
                apic_base = rdmsr(MSR_APICBASE);
                if (apic_base & APICBASE_X2APIC) {
                        if (x2apic_enable == 0)
                                kprintf("LAPIC: BIOS enabled X2APIC mode, force on\n");
                        else
                                kprintf("LAPIC: BIOS enabled X2APIC mode\n");
                        x2apic_enable = 1;
                }
        }
        if (cpu_feature2 & CPUID2_X2APIC) {
                apic_base = rdmsr(MSR_APICBASE);
                if (apic_base & APICBASE_X2APIC)
                        kprintf("LAPIC: BIOS already enabled X2APIC mode\n");
        }

        if (x2apic_enable) {
                /*
                 * Enter X2APIC mode.
                 */
                kprintf("LAPIC: enter X2APIC mode\n");
                lapic_x2apic_enter(TRUE);
        }

        for (i = 0; i < NAPICID; ++i)
                APICID_TO_CPUID(i) = -1;

        TAILQ_FOREACH(e, &lapic_enumerators, lapic_link) {
                error = e->lapic_probe(e);
                if (!error)
                        break;
        }
        if (e == NULL) {
                kprintf("LAPIC: Can't find LAPIC\n");
                return ENXIO;
        }

        error = e->lapic_enumerate(e);
        if (error) {
                kprintf("LAPIC: enumeration failed\n");
                return ENXIO;
        }

        /* LAPIC is usable now. */
        lapic_usable = 1;

        ap_max = MAXCPU - 1;
        TUNABLE_INT_FETCH("hw.ap_max", &ap_max);
        if (ap_max > MAXCPU - 1)
                ap_max = MAXCPU - 1;

        if (naps > ap_max) {
                kprintf("LAPIC: Warning use only %d out of %d "
                        "available APs\n",
                        ap_max, naps);
                naps = ap_max;
        }

        return 0;
}

void
lapic_enumerator_register(struct lapic_enumerator *ne)
{
        struct lapic_enumerator *e;

        TAILQ_FOREACH(e, &lapic_enumerators, lapic_link) {
                if (e->lapic_prio < ne->lapic_prio) {
                        TAILQ_INSERT_BEFORE(e, ne, lapic_link);
                        return;
                }
        }
        TAILQ_INSERT_TAIL(&lapic_enumerators, ne, lapic_link);
}

void
lapic_set_cpuid(int cpu_id, int apic_id)
{
        CPUID_TO_APICID(cpu_id) = apic_id;
        APICID_TO_CPUID(apic_id) = cpu_id;
}

void
lapic_fixup_noioapic(void)
{
        u_int   temp;

        /* Only allowed on BSP */
        KKASSERT(mycpuid == 0);
        KKASSERT(!ioapic_enable);

        temp = LAPIC_READ(lvt_lint0);
        temp &= ~APIC_LVT_MASKED;
        LAPIC_WRITE(lvt_lint0, temp);

        temp = LAPIC_READ(lvt_lint1);
        temp |= APIC_LVT_MASKED;
        LAPIC_WRITE(lvt_lint1, temp);
}

static void
lapic_mem_eoi(void)
{
        log_lapic(mem_eoi);
        LAPIC_MEM_WRITE(eoi, 0);
}

static void
lapic_msr_eoi(void)
{
        log_lapic(msr_eoi);
        LAPIC_MSR_WRITE(MSR_X2APIC_EOI, 0);
}

static void
lapic_mem_seticr_sync(uint32_t apic_id, uint32_t icr_lo_val)
{
        lapic_mem_icr_set(apic_id, icr_lo_val);
        while (LAPIC_MEM_READ(icr_lo) & APIC_DELSTAT_PEND)
                /* spin */;
}

void
lapic_seticr_sync(uint32_t apic_id, uint32_t icr_lo_val)
{
        if (x2apic_enable)
                lapic_msr_icr_set(apic_id, icr_lo_val);
        else
                lapic_mem_seticr_sync(apic_id, icr_lo_val);
}

static void
lapic_sysinit(void *dummy __unused)
{
        if (lapic_enable) {
                int error;

                error = lapic_config();
                if (error)
                        lapic_enable = 0;
        }
        if (!lapic_enable)
                x2apic_enable = 0;

        if (lapic_enable) {
                /* Initialize BSP's local APIC */
                lapic_init(TRUE);
        } else if (ioapic_enable) {
                kprintf("IOAPIC disabled - lapic was not enabled\n");
                ioapic_enable = 0;
                icu_reinit_noioapic();
        }
}
SYSINIT(lapic, SI_BOOT2_LAPIC, SI_ORDER_FIRST, lapic_sysinit, NULL);