⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 apic_32.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 3 页
字号:
/* *	Local APIC handling, local APIC timers * *	(c) 1999, 2000 Ingo Molnar <mingo@redhat.com> * *	Fixes *	Maciej W. Rozycki	:	Bits for genuine 82489DX APICs; *					thanks to Eric Gilmore *					and Rolf G. Tews *					for testing these extensively. *	Maciej W. Rozycki	:	Various updates and fixes. *	Mikael Pettersson	:	Power Management for UP-APIC. *	Pavel Machek and *	Mikael Pettersson	:	PM converted to driver model. */#include <linux/init.h>#include <linux/mm.h>#include <linux/delay.h>#include <linux/bootmem.h>#include <linux/interrupt.h>#include <linux/mc146818rtc.h>#include <linux/kernel_stat.h>#include <linux/sysdev.h>#include <linux/cpu.h>#include <linux/clockchips.h>#include <linux/acpi_pmtmr.h>#include <linux/module.h>#include <linux/dmi.h>#include <asm/atomic.h>#include <asm/smp.h>#include <asm/mtrr.h>#include <asm/mpspec.h>#include <asm/desc.h>#include <asm/arch_hooks.h>#include <asm/hpet.h>#include <asm/i8253.h>#include <asm/nmi.h>#include <mach_apic.h>#include <mach_apicdef.h>#include <mach_ipi.h>#include "io_ports.h"/* * Sanity check */#if (SPURIOUS_APIC_VECTOR & 0x0F) != 0x0F# error SPURIOUS_APIC_VECTOR definition error#endif/* * Knob to control our willingness to enable the local APIC. * * -1=force-disable, +1=force-enable */static int enable_local_apic __initdata = 0;/* Local APIC timer verification ok */static int local_apic_timer_verify_ok;/* Disable local APIC timer from the kernel commandline or via dmi quirk   or using CPU MSR check */int local_apic_timer_disabled;/* Local APIC timer works in C2 */int local_apic_timer_c2_ok;EXPORT_SYMBOL_GPL(local_apic_timer_c2_ok);/* * Debug level, exported for io_apic.c */int apic_verbosity;static unsigned int calibration_result;static int lapic_next_event(unsigned long delta,			    struct clock_event_device *evt);static void lapic_timer_setup(enum clock_event_mode mode,			      struct clock_event_device *evt);static void lapic_timer_broadcast(cpumask_t mask);static void apic_pm_activate(void);/* * The local apic timer can be used for any function which is CPU local. */static struct clock_event_device lapic_clockevent = {	.name		= "lapic",	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT			| CLOCK_EVT_FEAT_C3STOP | CLOCK_EVT_FEAT_DUMMY,	.shift		= 32,	.set_mode	= lapic_timer_setup,	.set_next_event	= lapic_next_event,	.broadcast	= lapic_timer_broadcast,	.rating		= 100,	.irq		= -1,};static DEFINE_PER_CPU(struct clock_event_device, lapic_events);/* Local APIC was disabled by the BIOS and enabled by the kernel */static int enabled_via_apicbase;/* * Get the LAPIC version */static inline int lapic_get_version(void){	return GET_APIC_VERSION(apic_read(APIC_LVR));}/* * Check, if the APIC is integrated or a seperate chip */static inline int lapic_is_integrated(void){	return APIC_INTEGRATED(lapic_get_version());}/* * Check, whether this is a modern or a first generation APIC */static int modern_apic(void){	/* AMD systems use old APIC versions, so check the CPU */	if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&	    boot_cpu_data.x86 >= 0xf)		return 1;	return lapic_get_version() >= 0x14;}void apic_wait_icr_idle(void){	while (apic_read(APIC_ICR) & APIC_ICR_BUSY)		cpu_relax();}unsigned long safe_apic_wait_icr_idle(void){	unsigned long send_status;	int timeout;	timeout = 0;	do {		send_status = apic_read(APIC_ICR) & APIC_ICR_BUSY;		if (!send_status)			break;		udelay(100);	} while (timeout++ < 1000);	return send_status;}/** * enable_NMI_through_LVT0 - enable NMI through local vector table 0 */void enable_NMI_through_LVT0 (void * dummy){	unsigned int v = APIC_DM_NMI;	/* Level triggered for 82489DX */	if (!lapic_is_integrated())		v |= APIC_LVT_LEVEL_TRIGGER;	apic_write_around(APIC_LVT0, v);}/** * get_physical_broadcast - Get number of physical broadcast IDs */int get_physical_broadcast(void){	return modern_apic() ? 0xff : 0xf;}/** * lapic_get_maxlvt - get the maximum number of local vector table entries */int lapic_get_maxlvt(void){	unsigned int v = apic_read(APIC_LVR);	/* 82489DXs do not report # of LVT entries. */	return APIC_INTEGRATED(GET_APIC_VERSION(v)) ? GET_APIC_MAXLVT(v) : 2;}/* * Local APIC timer *//* Clock divisor is set to 16 */#define APIC_DIVISOR 16/* * This function sets up the local APIC timer, with a timeout of * 'clocks' APIC bus clock. During calibration we actually call * this function twice on the boot CPU, once with a bogus timeout * value, second time for real. The other (noncalibrating) CPUs * call this function only once, with the real, calibrated value. * * We do reads before writes even if unnecessary, to get around the * P5 APIC double write bug. */static void __setup_APIC_LVTT(unsigned int clocks, int oneshot, int irqen){	unsigned int lvtt_value, tmp_value;	lvtt_value = LOCAL_TIMER_VECTOR;	if (!oneshot)		lvtt_value |= APIC_LVT_TIMER_PERIODIC;	if (!lapic_is_integrated())		lvtt_value |= SET_APIC_TIMER_BASE(APIC_TIMER_BASE_DIV);	if (!irqen)		lvtt_value |= APIC_LVT_MASKED;	apic_write_around(APIC_LVTT, lvtt_value);	/*	 * Divide PICLK by 16	 */	tmp_value = apic_read(APIC_TDCR);	apic_write_around(APIC_TDCR, (tmp_value				& ~(APIC_TDR_DIV_1 | APIC_TDR_DIV_TMBASE))				| APIC_TDR_DIV_16);	if (!oneshot)		apic_write_around(APIC_TMICT, clocks/APIC_DIVISOR);}/* * Program the next event, relative to now */static int lapic_next_event(unsigned long delta,			    struct clock_event_device *evt){	apic_write_around(APIC_TMICT, delta);	return 0;}/* * Setup the lapic timer in periodic or oneshot mode */static void lapic_timer_setup(enum clock_event_mode mode,			      struct clock_event_device *evt){	unsigned long flags;	unsigned int v;	/* Lapic used for broadcast ? */	if (!local_apic_timer_verify_ok)		return;	local_irq_save(flags);	switch (mode) {	case CLOCK_EVT_MODE_PERIODIC:	case CLOCK_EVT_MODE_ONESHOT:		__setup_APIC_LVTT(calibration_result,				  mode != CLOCK_EVT_MODE_PERIODIC, 1);		break;	case CLOCK_EVT_MODE_UNUSED:	case CLOCK_EVT_MODE_SHUTDOWN:		v = apic_read(APIC_LVTT);		v |= (APIC_LVT_MASKED | LOCAL_TIMER_VECTOR);		apic_write_around(APIC_LVTT, v);		break;	case CLOCK_EVT_MODE_RESUME:		/* Nothing to do here */		break;	}	local_irq_restore(flags);}/* * Local APIC timer broadcast function */static void lapic_timer_broadcast(cpumask_t mask){#ifdef CONFIG_SMP	send_IPI_mask(mask, LOCAL_TIMER_VECTOR);#endif}/* * Setup the local APIC timer for this CPU. Copy the initilized values * of the boot CPU and register the clock event in the framework. */static void __devinit setup_APIC_timer(void){	struct clock_event_device *levt = &__get_cpu_var(lapic_events);	memcpy(levt, &lapic_clockevent, sizeof(*levt));	levt->cpumask = cpumask_of_cpu(smp_processor_id());	clockevents_register_device(levt);}/* * In this functions we calibrate APIC bus clocks to the external timer. * * We want to do the calibration only once since we want to have local timer * irqs syncron. CPUs connected by the same APIC bus have the very same bus * frequency. * * This was previously done by reading the PIT/HPET and waiting for a wrap * around to find out, that a tick has elapsed. I have a box, where the PIT * readout is broken, so it never gets out of the wait loop again. This was * also reported by others. * * Monitoring the jiffies value is inaccurate and the clockevents * infrastructure allows us to do a simple substitution of the interrupt * handler. * * The calibration routine also uses the pm_timer when possible, as the PIT * happens to run way too slow (factor 2.3 on my VAIO CoreDuo, which goes * back to normal later in the boot process). */#define LAPIC_CAL_LOOPS		(HZ/10)static __initdata int lapic_cal_loops = -1;static __initdata long lapic_cal_t1, lapic_cal_t2;static __initdata unsigned long long lapic_cal_tsc1, lapic_cal_tsc2;static __initdata unsigned long lapic_cal_pm1, lapic_cal_pm2;static __initdata unsigned long lapic_cal_j1, lapic_cal_j2;/* * Temporary interrupt handler. */static void __init lapic_cal_handler(struct clock_event_device *dev){	unsigned long long tsc = 0;	long tapic = apic_read(APIC_TMCCT);	unsigned long pm = acpi_pm_read_early();	if (cpu_has_tsc)		rdtscll(tsc);	switch (lapic_cal_loops++) {	case 0:		lapic_cal_t1 = tapic;		lapic_cal_tsc1 = tsc;		lapic_cal_pm1 = pm;		lapic_cal_j1 = jiffies;		break;	case LAPIC_CAL_LOOPS:		lapic_cal_t2 = tapic;		lapic_cal_tsc2 = tsc;		if (pm < lapic_cal_pm1)			pm += ACPI_PM_OVRRUN;		lapic_cal_pm2 = pm;		lapic_cal_j2 = jiffies;		break;	}}/* * Setup the boot APIC * * Calibrate and verify the result. */void __init setup_boot_APIC_clock(void){	struct clock_event_device *levt = &__get_cpu_var(lapic_events);	const long pm_100ms = PMTMR_TICKS_PER_SEC/10;	const long pm_thresh = pm_100ms/100;	void (*real_handler)(struct clock_event_device *dev);	unsigned long deltaj;	long delta, deltapm;	int pm_referenced = 0;	/*	 * The local apic timer can be disabled via the kernel	 * commandline or from the CPU detection code. Register the lapic	 * timer as a dummy clock event source on SMP systems, so the	 * broadcast mechanism is used. On UP systems simply ignore it.	 */	if (local_apic_timer_disabled) {		/* No broadcast on UP ! */		if (num_possible_cpus() > 1)			setup_APIC_timer();		return;	}	apic_printk(APIC_VERBOSE, "Using local APIC timer interrupts.\n"		    "calibrating APIC timer ...\n");	local_irq_disable();	/* Replace the global interrupt handler */	real_handler = global_clock_event->event_handler;	global_clock_event->event_handler = lapic_cal_handler;	/*	 * Setup the APIC counter to 1e9. There is no way the lapic	 * can underflow in the 100ms detection time frame	 */	__setup_APIC_LVTT(1000000000, 0, 0);	/* Let the interrupts run */	local_irq_enable();	while (lapic_cal_loops <= LAPIC_CAL_LOOPS)		cpu_relax();	local_irq_disable();	/* Restore the real event handler */	global_clock_event->event_handler = real_handler;	/* Build delta t1-t2 as apic timer counts down */	delta = lapic_cal_t1 - lapic_cal_t2;	apic_printk(APIC_VERBOSE, "... lapic delta = %ld\n", delta);	/* Check, if the PM timer is available */	deltapm = lapic_cal_pm2 - lapic_cal_pm1;	apic_printk(APIC_VERBOSE, "... PM timer delta = %ld\n", deltapm);	if (deltapm) {		unsigned long mult;		u64 res;		mult = clocksource_hz2mult(PMTMR_TICKS_PER_SEC, 22);		if (deltapm > (pm_100ms - pm_thresh) &&		    deltapm < (pm_100ms + pm_thresh)) {			apic_printk(APIC_VERBOSE, "... PM timer result ok\n");		} else {			res = (((u64) deltapm) *  mult) >> 22;			do_div(res, 1000000);			printk(KERN_WARNING "APIC calibration not consistent "			       "with PM Timer: %ldms instead of 100ms\n",			       (long)res);			/* Correct the lapic counter value */			res = (((u64) delta ) * pm_100ms);			do_div(res, deltapm);			printk(KERN_INFO "APIC delta adjusted to PM-Timer: "			       "%lu (%ld)\n", (unsigned long) res, delta);			delta = (long) res;		}		pm_referenced = 1;	}	/* Calculate the scaled math multiplication factor */	lapic_clockevent.mult = div_sc(delta, TICK_NSEC * LAPIC_CAL_LOOPS, 32);	lapic_clockevent.max_delta_ns =		clockevent_delta2ns(0x7FFFFF, &lapic_clockevent);	lapic_clockevent.min_delta_ns =		clockevent_delta2ns(0xF, &lapic_clockevent);	calibration_result = (delta * APIC_DIVISOR) / LAPIC_CAL_LOOPS;	apic_printk(APIC_VERBOSE, "..... delta %ld\n", delta);	apic_printk(APIC_VERBOSE, "..... mult: %ld\n", lapic_clockevent.mult);	apic_printk(APIC_VERBOSE, "..... calibration result: %u\n",		    calibration_result);	if (cpu_has_tsc) {		delta = (long)(lapic_cal_tsc2 - lapic_cal_tsc1);		apic_printk(APIC_VERBOSE, "..... CPU clock speed is "			    "%ld.%04ld MHz.\n",			    (delta / LAPIC_CAL_LOOPS) / (1000000 / HZ),			    (delta / LAPIC_CAL_LOOPS) % (1000000 / HZ));	}	apic_printk(APIC_VERBOSE, "..... host bus clock speed is "		    "%u.%04u MHz.\n",		    calibration_result / (1000000 / HZ),		    calibration_result % (1000000 / HZ));	local_apic_timer_verify_ok = 1;	/* We trust the pm timer based calibration */	if (!pm_referenced) {		apic_printk(APIC_VERBOSE, "... verify APIC timer\n");		/*		 * Setup the apic timer manually		 */		levt->event_handler = lapic_cal_handler;		lapic_timer_setup(CLOCK_EVT_MODE_PERIODIC, levt);		lapic_cal_loops = -1;		/* Let the interrupts run */		local_irq_enable();		while (lapic_cal_loops <= LAPIC_CAL_LOOPS)			cpu_relax();		local_irq_disable();		/* Stop the lapic timer */		lapic_timer_setup(CLOCK_EVT_MODE_SHUTDOWN, levt);		local_irq_enable();		/* Jiffies delta */		deltaj = lapic_cal_j2 - lapic_cal_j1;		apic_printk(APIC_VERBOSE, "... jiffies delta = %lu\n", deltaj);		/* Check, if the jiffies result is consistent */		if (deltaj >= LAPIC_CAL_LOOPS-2 && deltaj <= LAPIC_CAL_LOOPS+2)			apic_printk(APIC_VERBOSE, "... jiffies result ok\n");		else			local_apic_timer_verify_ok = 0;	} else		local_irq_enable();	if (!local_apic_timer_verify_ok) {		printk(KERN_WARNING		       "APIC timer disabled due to verification failure.\n");		/* No broadcast on UP ! */		if (num_possible_cpus() == 1)			return;	} else {		/*		 * If nmi_watchdog is set to IO_APIC, we need the		 * PIT/HPET going.  Otherwise register lapic as a dummy		 * device.		 */		if (nmi_watchdog != NMI_IO_APIC)

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -