📄 apic.c
字号:
return 0; /* XXX: remove suspend/resume procs if !apic_pm_state.active? */ 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 0;}__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; ver = GET_APIC_VERSION(apic_read(APIC_LVR)); lvtt_value = APIC_LVT_TIMER_PERIODIC | LOCAL_TIMER_VECTOR; 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); apic_write_around(APIC_TMICT, clocks/APIC_DIVISOR);}static void setup_APIC_timer(unsigned int clocks){ unsigned long flags; local_irq_save(flags); /* For some reasons this doesn't work on Simics, so fake it for now */ if (!strstr(boot_cpu_data.x86_model_id, "Screwdriver")) { __setup_APIC_LVTT(clocks); return; } /* wait for irq slice */ if (vxtime.hpet_address) { 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); 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); 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(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 __cpuinit disable_APIC_timer(void){ if (using_apic_timer) { unsigned long v; v = apic_read(APIC_LVTT); apic_write_around(APIC_LVTT, v | APIC_LVT_MASKED); }}void enable_APIC_timer(void){ if (using_apic_timer) { unsigned long v; v = apic_read(APIC_LVTT); apic_write_around(APIC_LVTT, v & ~APIC_LVT_MASKED); }}/* * the frequency of the profiling timer can be changed * by writing a multiplier value into /proc/profile. */int setup_profiling_timer(unsigned int multiplier){ int i; /* * Sanity check. [at least 500 APIC cycles should be * between APIC interrupts as a rule of thumb, to avoid * irqs flooding us] */ if ( (!multiplier) || (calibration_result/multiplier < 500)) return -EINVAL; /* * Set the new multiplier for each CPU. CPUs don't start using the * new values until the next timer interrupt in which they do process * accounting. At that time they also adjust their APIC timers * accordingly. */ for (i = 0; i < NR_CPUS; ++i) per_cpu(prof_multiplier, i) = multiplier; return 0;}#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){ int cpu = smp_processor_id(); profile_tick(CPU_PROFILING, regs); if (--per_cpu(prof_counter, cpu) <= 0) { /* * The multiplier may have changed since the last time we got * to this point as a result of the user writing to * /proc/profile. In this case we need to adjust the APIC * timer accordingly. * * Interrupts are already masked off at this point. */ per_cpu(prof_counter, cpu) = per_cpu(prof_multiplier, cpu); if (per_cpu(prof_counter, cpu) != per_cpu(prof_old_multiplier, cpu)) { __setup_APIC_LVTT(calibration_result/ per_cpu(prof_counter, cpu)); per_cpu(prof_old_multiplier, cpu) = per_cpu(prof_counter, cpu); }#ifdef CONFIG_SMP update_process_times(user_mode(regs));#endif } /* * 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. */ 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. */__init 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; 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; 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_around(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 0;} static __init int setup_nolapic(char *str) { disable_apic = 1; return 0;} static __init int setup_noapictimer(char *str) { disable_apic_timer = 1; return 0;} /* 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 + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -