apic.c

来自「LINUX 2.6.17.4的源码」· C语言 代码 · 共 1,188 行 · 第 1/2 页

C
1,188
字号
	error = sysdev_class_register(&lapic_sysclass);	if (!error)		error = sysdev_register(&device_lapic);	return error;}device_initcall(init_lapic_sysfs);#else	/* CONFIG_PM */static void apic_pm_activate(void) { }#endif	/* CONFIG_PM */static int __init apic_set_verbosity(char *str){	if (strcmp("debug", str) == 0)		apic_verbosity = APIC_DEBUG;	else if (strcmp("verbose", str) == 0)		apic_verbosity = APIC_VERBOSE;	else		printk(KERN_WARNING "APIC Verbosity level %s not recognised"				" use apic=verbose or apic=debug", str);	return 1;}__setup("apic=", apic_set_verbosity);/* * Detect and enable local APICs on non-SMP boards. * Original code written by Keir Fraser. * On AMD64 we trust the BIOS - if it says no APIC it is likely * not correctly set up (usually the APIC timer won't work etc.)  */static int __init detect_init_APIC (void){	if (!cpu_has_apic) {		printk(KERN_INFO "No local APIC present\n");		return -1;	}	mp_lapic_addr = APIC_DEFAULT_PHYS_BASE;	boot_cpu_id = 0;	return 0;}void __init init_apic_mappings(void){	unsigned long apic_phys;	/*	 * If no local APIC can be found then set up a fake all	 * zeroes page to simulate the local APIC and another	 * one for the IO-APIC.	 */	if (!smp_found_config && detect_init_APIC()) {		apic_phys = (unsigned long) alloc_bootmem_pages(PAGE_SIZE);		apic_phys = __pa(apic_phys);	} else		apic_phys = mp_lapic_addr;	set_fixmap_nocache(FIX_APIC_BASE, apic_phys);	apic_printk(APIC_VERBOSE,"mapped APIC to %16lx (%16lx)\n", APIC_BASE, apic_phys);	/*	 * Fetch the APIC ID of the BSP in case we have a	 * default configuration (or the MP table is broken).	 */	boot_cpu_id = GET_APIC_ID(apic_read(APIC_ID));#ifdef CONFIG_X86_IO_APIC	{		unsigned long ioapic_phys, idx = FIX_IO_APIC_BASE_0;		int i;		for (i = 0; i < nr_ioapics; i++) {			if (smp_found_config) {				ioapic_phys = mp_ioapics[i].mpc_apicaddr;			} else {				ioapic_phys = (unsigned long) alloc_bootmem_pages(PAGE_SIZE);				ioapic_phys = __pa(ioapic_phys);			}			set_fixmap_nocache(idx, ioapic_phys);			apic_printk(APIC_VERBOSE,"mapped IOAPIC to %016lx (%016lx)\n",					__fix_to_virt(idx), ioapic_phys);			idx++;		}	}#endif}/* * 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. */#define APIC_DIVISOR 16static void __setup_APIC_LVTT(unsigned int clocks){	unsigned int lvtt_value, tmp_value, ver;	int cpu = smp_processor_id();	ver = GET_APIC_VERSION(apic_read(APIC_LVR));	lvtt_value = APIC_LVT_TIMER_PERIODIC | LOCAL_TIMER_VECTOR;	if (cpu_isset(cpu, timer_interrupt_broadcast_ipi_mask))		lvtt_value |= APIC_LVT_MASKED;	apic_write(APIC_LVTT, lvtt_value);	/*	 * Divide PICLK by 16	 */	tmp_value = apic_read(APIC_TDCR);	apic_write(APIC_TDCR, (tmp_value				& ~(APIC_TDR_DIV_1 | APIC_TDR_DIV_TMBASE))				| APIC_TDR_DIV_16);	apic_write(APIC_TMICT, clocks/APIC_DIVISOR);}static void setup_APIC_timer(unsigned int clocks){	unsigned long flags;	local_irq_save(flags);	/* wait for irq slice */ 	if (vxtime.hpet_address && hpet_use_timer) { 		int trigger = hpet_readl(HPET_T0_CMP); 		while (hpet_readl(HPET_COUNTER) >= trigger) 			/* do nothing */ ; 		while (hpet_readl(HPET_COUNTER) <  trigger) 			/* do nothing */ ; 	} else {		int c1, c2;		outb_p(0x00, 0x43);		c2 = inb_p(0x40);		c2 |= inb_p(0x40) << 8;		do {			c1 = c2;			outb_p(0x00, 0x43);			c2 = inb_p(0x40);			c2 |= inb_p(0x40) << 8;		} while (c2 - c1 < 300);	}	__setup_APIC_LVTT(clocks);	/* Turn off PIT interrupt if we use APIC timer as main timer.	   Only works with the PM timer right now	   TBD fix it for HPET too. */	if (vxtime.mode == VXTIME_PMTMR &&		smp_processor_id() == boot_cpu_id &&		apic_runs_main_timer == 1 &&		!cpu_isset(boot_cpu_id, timer_interrupt_broadcast_ipi_mask)) {		stop_timer_interrupt();		apic_runs_main_timer++;	}	local_irq_restore(flags);}/* * In this function we calibrate APIC bus clocks to the external * timer. Unfortunately we cannot use jiffies and the timer irq * to calibrate, since some later bootup code depends on getting * the first irq? Ugh. * * 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. * And we want to have irqs off anyways, no accidental * APIC irq that way. */#define TICK_COUNT 100000000static int __init calibrate_APIC_clock(void){	int apic, apic_start, tsc, tsc_start;	int result;	/*	 * Put whatever arbitrary (but long enough) timeout	 * value into the APIC clock, we just want to get the	 * counter running for calibration.	 */	__setup_APIC_LVTT(1000000000);	apic_start = apic_read(APIC_TMCCT);#ifdef CONFIG_X86_PM_TIMER	if (apic_calibrate_pmtmr && pmtmr_ioport) {		pmtimer_wait(5000);  /* 5ms wait */		apic = apic_read(APIC_TMCCT);		result = (apic_start - apic) * 1000L / 5;	} else#endif	{		rdtscl(tsc_start);		do {			apic = apic_read(APIC_TMCCT);			rdtscl(tsc);		} while ((tsc - tsc_start) < TICK_COUNT &&				(apic - apic_start) < TICK_COUNT);		result = (apic_start - apic) * 1000L * cpu_khz /					(tsc - tsc_start);	}	printk("result %d\n", result);	printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n",		result / 1000 / 1000, result / 1000 % 1000);	return result * APIC_DIVISOR / HZ;}static unsigned int calibration_result;void __init setup_boot_APIC_clock (void){	if (disable_apic_timer) { 		printk(KERN_INFO "Disabling APIC timer\n"); 		return; 	} 	printk(KERN_INFO "Using local APIC timer interrupts.\n");	using_apic_timer = 1;	local_irq_disable();	calibration_result = calibrate_APIC_clock();	/*	 * Now set up the timer for real.	 */	setup_APIC_timer(calibration_result);	local_irq_enable();}void __cpuinit setup_secondary_APIC_clock(void){	local_irq_disable(); /* FIXME: Do we need this? --RR */	setup_APIC_timer(calibration_result);	local_irq_enable();}void disable_APIC_timer(void){	if (using_apic_timer) {		unsigned long v;		v = apic_read(APIC_LVTT);		apic_write(APIC_LVTT, v | APIC_LVT_MASKED);	}}void enable_APIC_timer(void){	int cpu = smp_processor_id();	if (using_apic_timer &&	    !cpu_isset(cpu, timer_interrupt_broadcast_ipi_mask)) {		unsigned long v;		v = apic_read(APIC_LVTT);		apic_write(APIC_LVTT, v & ~APIC_LVT_MASKED);	}}void switch_APIC_timer_to_ipi(void *cpumask){	cpumask_t mask = *(cpumask_t *)cpumask;	int cpu = smp_processor_id();	if (cpu_isset(cpu, mask) &&	    !cpu_isset(cpu, timer_interrupt_broadcast_ipi_mask)) {		disable_APIC_timer();		cpu_set(cpu, timer_interrupt_broadcast_ipi_mask);	}}EXPORT_SYMBOL(switch_APIC_timer_to_ipi);void smp_send_timer_broadcast_ipi(void){	cpumask_t mask;	cpus_and(mask, cpu_online_map, timer_interrupt_broadcast_ipi_mask);	if (!cpus_empty(mask)) {		send_IPI_mask(mask, LOCAL_TIMER_VECTOR);	}}void switch_ipi_to_APIC_timer(void *cpumask){	cpumask_t mask = *(cpumask_t *)cpumask;	int cpu = smp_processor_id();	if (cpu_isset(cpu, mask) &&	    cpu_isset(cpu, timer_interrupt_broadcast_ipi_mask)) {		cpu_clear(cpu, timer_interrupt_broadcast_ipi_mask);		enable_APIC_timer();	}}EXPORT_SYMBOL(switch_ipi_to_APIC_timer);int setup_profiling_timer(unsigned int multiplier){	return -EINVAL;}#ifdef CONFIG_X86_MCE_AMDvoid setup_threshold_lvt(unsigned long lvt_off){	unsigned int v = 0;	unsigned long reg = (lvt_off << 4) + 0x500;	v |= THRESHOLD_APIC_VECTOR;	apic_write(reg, v);}#endif /* CONFIG_X86_MCE_AMD */#undef APIC_DIVISOR/* * Local timer interrupt handler. It does both profiling and * process statistics/rescheduling. * * We do profiling in every local tick, statistics/rescheduling * happen only every 'profiling multiplier' ticks. The default * multiplier is 1 and it can be changed by writing the new multiplier * value into /proc/profile. */void smp_local_timer_interrupt(struct pt_regs *regs){	profile_tick(CPU_PROFILING, regs);#ifdef CONFIG_SMP	update_process_times(user_mode(regs));#endif	if (apic_runs_main_timer > 1 && smp_processor_id() == boot_cpu_id)		main_timer_handler(regs);	/*	 * We take the 'long' return path, and there every subsystem	 * grabs the appropriate locks (kernel lock/ irq lock).	 *	 * we might want to decouple profiling from the 'long path',	 * and do the profiling totally in assembly.	 *	 * Currently this isn't too much of an issue (performance wise),	 * we can take more than 100K local irqs per second on a 100 MHz P5.	 */}/* * Local APIC timer interrupt. This is the most natural way for doing * local interrupts, but local timer interrupts can be emulated by * broadcast interrupts too. [in case the hw doesn't support APIC timers] * * [ if a single-CPU system runs an SMP kernel then we call the local *   interrupt as well. Thus we cannot inline the local irq ... ] */void smp_apic_timer_interrupt(struct pt_regs *regs){	/*	 * the NMI deadlock-detector uses this.	 */	add_pda(apic_timer_irqs, 1);	/*	 * NOTE! We'd better ACK the irq immediately,	 * because timer handling can be slow.	 */	ack_APIC_irq();	/*	 * update_process_times() expects us to have done irq_enter().	 * Besides, if we don't timer interrupts ignore the global	 * interrupt lock, which is the WrongThing (tm) to do.	 */	exit_idle();	irq_enter();	smp_local_timer_interrupt(regs);	irq_exit();}/* * oem_force_hpet_timer -- force HPET mode for some boxes. * * Thus far, the major user of this is IBM's Summit2 series: * * Clustered boxes may have unsynced TSC problems if they are * multi-chassis. Use available data to take a good guess. * If in doubt, go HPET. */__cpuinit int oem_force_hpet_timer(void){	int i, clusters, zeros;	unsigned id;	DECLARE_BITMAP(clustermap, NUM_APIC_CLUSTERS);	bitmap_zero(clustermap, NUM_APIC_CLUSTERS);	for (i = 0; i < NR_CPUS; i++) {		id = bios_cpu_apicid[i];		if (id != BAD_APICID)			__set_bit(APIC_CLUSTERID(id), clustermap);	}	/* Problem:  Partially populated chassis may not have CPUs in some of	 * the APIC clusters they have been allocated.  Only present CPUs have	 * bios_cpu_apicid entries, thus causing zeroes in the bitmap.  Since	 * clusters are allocated sequentially, count zeros only if they are	 * bounded by ones.	 */	clusters = 0;	zeros = 0;	for (i = 0; i < NUM_APIC_CLUSTERS; i++) {		if (test_bit(i, clustermap)) {			clusters += 1 + zeros;			zeros = 0;		} else			++zeros;	}	/*	 * If clusters > 2, then should be multi-chassis.  Return 1 for HPET.	 * Else return 0 to use TSC.	 * May have to revisit this when multi-core + hyperthreaded CPUs come	 * out, but AFAIK this will work even for them.	 */	return (clusters > 2);}/* * This interrupt should _never_ happen with our APIC/SMP architecture */asmlinkage void smp_spurious_interrupt(void){	unsigned int v;	exit_idle();	irq_enter();	/*	 * Check if this really is a spurious interrupt and ACK it	 * if it is a vectored one.  Just in case...	 * Spurious interrupts should not be ACKed.	 */	v = apic_read(APIC_ISR + ((SPURIOUS_APIC_VECTOR & ~0x1f) >> 1));	if (v & (1 << (SPURIOUS_APIC_VECTOR & 0x1f)))		ack_APIC_irq();#if 0	static unsigned long last_warning; 	static unsigned long skipped; 	/* see sw-dev-man vol 3, chapter 7.4.13.5 */	if (time_before(last_warning+30*HZ,jiffies)) { 		printk(KERN_INFO "spurious APIC interrupt on CPU#%d, %ld skipped.\n",		       smp_processor_id(), skipped);		last_warning = jiffies; 		skipped = 0;	} else { 		skipped++; 	} #endif 	irq_exit();}/* * This interrupt should never happen with our APIC/SMP architecture */asmlinkage void smp_error_interrupt(void){	unsigned int v, v1;	exit_idle();	irq_enter();	/* First tickle the hardware, only then report what went on. -- REW */	v = apic_read(APIC_ESR);	apic_write(APIC_ESR, 0);	v1 = apic_read(APIC_ESR);	ack_APIC_irq();	atomic_inc(&irq_err_count);	/* Here is what the APIC error bits mean:	   0: Send CS error	   1: Receive CS error	   2: Send accept error	   3: Receive accept error	   4: Reserved	   5: Send illegal vector	   6: Received illegal vector	   7: Illegal register address	*/	printk (KERN_DEBUG "APIC error on CPU%d: %02x(%02x)\n",	        smp_processor_id(), v , v1);	irq_exit();}int disable_apic; /* * This initializes the IO-APIC and APIC hardware if this is * a UP kernel. */int __init APIC_init_uniprocessor (void){	if (disable_apic) { 		printk(KERN_INFO "Apic disabled\n");		return -1; 	}	if (!cpu_has_apic) { 		disable_apic = 1;		printk(KERN_INFO "Apic disabled by BIOS\n");		return -1;	}	verify_local_APIC();	connect_bsp_APIC();	phys_cpu_present_map = physid_mask_of_physid(boot_cpu_id);	apic_write(APIC_ID, SET_APIC_ID(boot_cpu_id));	setup_local_APIC();#ifdef CONFIG_X86_IO_APIC	if (smp_found_config && !skip_ioapic_setup && nr_ioapics)			setup_IO_APIC();	else		nr_ioapics = 0;#endif	setup_boot_APIC_clock();	check_nmi_watchdog();	return 0;}static __init int setup_disableapic(char *str) { 	disable_apic = 1;	return 1;} static __init int setup_nolapic(char *str) { 	disable_apic = 1;	return 1;} static __init int setup_noapictimer(char *str) { 	if (str[0] != ' ' && str[0] != 0)		return 0;	disable_apic_timer = 1;	return 1;} static __init int setup_apicmaintimer(char *str){	apic_runs_main_timer = 1;	nohpet = 1;	return 1;}__setup("apicmaintimer", setup_apicmaintimer);static __init int setup_noapicmaintimer(char *str){	apic_runs_main_timer = -1;	return 1;}__setup("noapicmaintimer", setup_noapicmaintimer);static __init int setup_apicpmtimer(char *s){	apic_calibrate_pmtmr = 1;	notsc_setup(NULL);	return setup_apicmaintimer(NULL);}__setup("apicpmtimer", setup_apicpmtimer);/* dummy parsing: see setup.c */__setup("disableapic", setup_disableapic); __setup("nolapic", setup_nolapic);  /* same as disableapic, for compatibility */__setup("noapictimer", setup_noapictimer); /* no "lapic" flag - we only use the lapic when the BIOS tells us so. */

⌨️ 快捷键说明

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