📄 time.c
字号:
/****************************************************************************** * arch/x86/time.c * * Per-CPU time calibration and management. * * Copyright (c) 2002-2005, K A Fraser * * Portions from Linux are: * Copyright (c) 1991, 1992, 1995 Linus Torvalds */#include <xen/config.h>#include <xen/errno.h>#include <xen/event.h>#include <xen/sched.h>#include <xen/lib.h>#include <xen/config.h>#include <xen/init.h>#include <xen/time.h>#include <xen/timer.h>#include <xen/smp.h>#include <xen/irq.h>#include <xen/softirq.h>#include <asm/io.h>#include <asm/msr.h>#include <asm/mpspec.h>#include <asm/processor.h>#include <asm/fixmap.h>#include <asm/mc146818rtc.h>#include <asm/div64.h>#include <asm/hpet.h>#include <io_ports.h>/* opt_clocksource: Force clocksource to one of: pit, hpet, cyclone, acpi. */static char opt_clocksource[10];string_param("clocksource", opt_clocksource);#define EPOCH MILLISECS(1000)unsigned long cpu_khz; /* CPU clock frequency in kHz. */unsigned long hpet_address;DEFINE_SPINLOCK(rtc_lock);volatile unsigned long jiffies;static u32 wc_sec, wc_nsec; /* UTC time at last 'time update'. */static DEFINE_SPINLOCK(wc_lock);struct time_scale { int shift; u32 mul_frac;};struct cpu_time { u64 local_tsc_stamp; s_time_t stime_local_stamp; s_time_t stime_master_stamp; struct time_scale tsc_scale; struct timer calibration_timer;};struct platform_timesource { char *name; u64 frequency; u32 (*read_counter)(void); int counter_bits;};static DEFINE_PER_CPU(struct cpu_time, cpu_time);/* * Protected by platform_timer_lock, which must be acquired with interrupts * disabled because plt_overflow() is called from PIT ch0 interrupt context. */static s_time_t stime_platform_stamp;static u64 platform_timer_stamp;static DEFINE_SPINLOCK(platform_timer_lock);/* * Folding platform timer into 64-bit software counter is a really critical * operation! We therefore do it directly in PIT ch0 interrupt handler. */static u32 plt_overflow_jiffies;static void plt_overflow(void);/* * 32-bit division of integer dividend and integer divisor yielding * 32-bit fractional quotient. */static inline u32 div_frac(u32 dividend, u32 divisor){ u32 quotient, remainder; ASSERT(dividend < divisor); asm ( "divl %4" : "=a" (quotient), "=d" (remainder) : "0" (0), "1" (dividend), "r" (divisor) ); return quotient;}/* * 32-bit multiplication of multiplicand and fractional multiplier * yielding 32-bit product (radix point at same position as in multiplicand). */static inline u32 mul_frac(u32 multiplicand, u32 multiplier){ u32 product_int, product_frac; asm ( "mul %3" : "=a" (product_frac), "=d" (product_int) : "0" (multiplicand), "r" (multiplier) ); return product_int;}/* * Scale a 64-bit delta by scaling and multiplying by a 32-bit fraction, * yielding a 64-bit result. */static inline u64 scale_delta(u64 delta, struct time_scale *scale){ u64 product;#ifdef CONFIG_X86_32 u32 tmp1, tmp2;#endif if ( scale->shift < 0 ) delta >>= -scale->shift; else delta <<= scale->shift;#ifdef CONFIG_X86_32 asm ( "mul %5 ; " "mov %4,%%eax ; " "mov %%edx,%4 ; " "mul %5 ; " "xor %5,%5 ; " "add %4,%%eax ; " "adc %5,%%edx ; " : "=A" (product), "=r" (tmp1), "=r" (tmp2) : "a" ((u32)delta), "1" ((u32)(delta >> 32)), "2" (scale->mul_frac) );#else asm ( "mul %%rdx ; shrd $32,%%rdx,%%rax" : "=a" (product) : "0" (delta), "d" ((u64)scale->mul_frac) );#endif return product;}void timer_interrupt(int irq, void *dev_id, struct cpu_user_regs *regs){ ASSERT(local_irq_is_enabled()); /* Update jiffies counter. */ (*(volatile unsigned long *)&jiffies)++; /* Rough hack to allow accurate timers to sort-of-work with no APIC. */ if ( !cpu_has_apic ) raise_softirq(TIMER_SOFTIRQ); if ( --plt_overflow_jiffies == 0 ) plt_overflow();}static struct irqaction irq0 = { timer_interrupt, "timer", NULL};/* ------ Calibrate the TSC ------- * Return processor ticks per second / CALIBRATE_FRAC. */#define CLOCK_TICK_RATE 1193180 /* system crystal frequency (Hz) */#define CALIBRATE_FRAC 20 /* calibrate over 50ms */#define CALIBRATE_LATCH ((CLOCK_TICK_RATE+(CALIBRATE_FRAC/2))/CALIBRATE_FRAC)static u64 init_pit_and_calibrate_tsc(void){ u64 start, end; unsigned long count; /* Set PIT channel 0 to HZ Hz. */#define LATCH (((CLOCK_TICK_RATE)+(HZ/2))/HZ) outb_p(0x34, PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff, PIT_CH0); /* LSB */ outb(LATCH >> 8, PIT_CH0); /* MSB */ /* Set the Gate high, disable speaker */ outb((inb(0x61) & ~0x02) | 0x01, 0x61); /* * Now let's take care of CTC channel 2 * * Set the Gate high, program CTC channel 2 for mode 0, (interrupt on * terminal count mode), binary count, load 5 * LATCH count, (LSB and MSB) * to begin countdown. */ outb(0xb0, PIT_MODE); /* binary, mode 0, LSB/MSB, Ch 2 */ outb(CALIBRATE_LATCH & 0xff, PIT_CH2); /* LSB of count */ outb(CALIBRATE_LATCH >> 8, PIT_CH2); /* MSB of count */ rdtscll(start); for ( count = 0; (inb(0x61) & 0x20) == 0; count++ ) continue; rdtscll(end); /* Error if the CTC doesn't behave itself. */ if ( count == 0 ) return 0; return ((end - start) * (u64)CALIBRATE_FRAC);}static void set_time_scale(struct time_scale *ts, u64 ticks_per_sec){ u64 tps64 = ticks_per_sec; u32 tps32; int shift = 0; ASSERT(tps64 != 0); while ( tps64 > (MILLISECS(1000)*2) ) { tps64 >>= 1; shift--; } tps32 = (u32)tps64; while ( tps32 <= (u32)MILLISECS(1000) ) { tps32 <<= 1; shift++; } ts->mul_frac = div_frac(MILLISECS(1000), tps32); ts->shift = shift;}static atomic_t tsc_calibrate_gang = ATOMIC_INIT(0);static unsigned int tsc_calibrate_status = 0;void calibrate_tsc_bp(void){ while ( atomic_read(&tsc_calibrate_gang) != (num_booting_cpus() - 1) ) mb(); outb(CALIBRATE_LATCH & 0xff, PIT_CH2); outb(CALIBRATE_LATCH >> 8, PIT_CH2); tsc_calibrate_status = 1; wmb(); while ( (inb(0x61) & 0x20) == 0 ) continue; tsc_calibrate_status = 2; wmb(); while ( atomic_read(&tsc_calibrate_gang) != 0 ) mb();}void calibrate_tsc_ap(void){ u64 t1, t2, ticks_per_sec; atomic_inc(&tsc_calibrate_gang); while ( tsc_calibrate_status < 1 ) mb(); rdtscll(t1); while ( tsc_calibrate_status < 2 ) mb(); rdtscll(t2); ticks_per_sec = (t2 - t1) * (u64)CALIBRATE_FRAC; set_time_scale(&this_cpu(cpu_time).tsc_scale, ticks_per_sec); atomic_dec(&tsc_calibrate_gang);}static char *freq_string(u64 freq){ static char s[20]; unsigned int x, y; y = (unsigned int)do_div(freq, 1000000) / 1000; x = (unsigned int)freq; snprintf(s, sizeof(s), "%u.%03uMHz", x, y); return s;}/************************************************************ * PLATFORM TIMER 1: PROGRAMMABLE INTERVAL TIMER (LEGACY PIT) */static u32 read_pit_count(void){ u16 count; ASSERT(spin_is_locked(&platform_timer_lock)); outb(0x80, PIT_MODE); count = inb(PIT_CH2); count |= inb(PIT_CH2) << 8; return ~count;}static void init_pit(struct platform_timesource *pts){ pts->name = "PIT"; pts->frequency = CLOCK_TICK_RATE; pts->read_counter = read_pit_count; pts->counter_bits = 16;}/************************************************************ * PLATFORM TIMER 2: HIGH PRECISION EVENT TIMER (HPET) */static u32 read_hpet_count(void){ return hpet_read32(HPET_COUNTER);}static int init_hpet(struct platform_timesource *pts){ u64 hpet_rate; u32 hpet_id, hpet_period, cfg; int i; if ( hpet_address == 0 ) return 0; set_fixmap_nocache(FIX_HPET_BASE, hpet_address); hpet_id = hpet_read32(HPET_ID); if ( hpet_id == 0 ) { printk("BAD HPET vendor id.\n"); return 0; } /* Check for sane period (100ps <= period <= 100ns). */ hpet_period = hpet_read32(HPET_PERIOD); if ( (hpet_period > 100000000) || (hpet_period < 100000) ) { printk("BAD HPET period %u.\n", hpet_period); return 0; } cfg = hpet_read32(HPET_CFG); cfg &= ~(HPET_CFG_ENABLE | HPET_CFG_LEGACY); hpet_write32(cfg, HPET_CFG); for ( i = 0; i <= ((hpet_id >> 8) & 31); i++ ) { cfg = hpet_read32(HPET_T0_CFG + i*0x20); cfg &= ~HPET_TN_ENABLE; hpet_write32(cfg & ~HPET_TN_ENABLE, HPET_T0_CFG); } cfg = hpet_read32(HPET_CFG); cfg |= HPET_CFG_ENABLE; hpet_write32(cfg, HPET_CFG); hpet_rate = 1000000000000000ULL; /* 10^15 */ (void)do_div(hpet_rate, hpet_period); pts->name = "HPET"; pts->frequency = hpet_rate; pts->read_counter = read_hpet_count; pts->counter_bits = 32; return 1;}/************************************************************ * PLATFORM TIMER 3: IBM 'CYCLONE' TIMER */int use_cyclone;/* * Although the counter is read via a 64-bit register, I believe it is actually * a 40-bit counter. Since this will wrap, I read only the low 32 bits and * periodically fold into a 64-bit software counter, just as for PIT and HPET. */#define CYCLONE_CBAR_ADDR 0xFEB00CD0#define CYCLONE_PMCC_OFFSET 0x51A0#define CYCLONE_MPMC_OFFSET 0x51D0#define CYCLONE_MPCS_OFFSET 0x51A8#define CYCLONE_TIMER_FREQ 100000000/* Cyclone MPMC0 register. */static volatile u32 *cyclone_timer;static u32 read_cyclone_count(void){ return *cyclone_timer;}static volatile u32 *map_cyclone_reg(unsigned long regaddr){ unsigned long pageaddr = regaddr & PAGE_MASK; unsigned long offset = regaddr & ~PAGE_MASK; set_fixmap_nocache(FIX_CYCLONE_TIMER, pageaddr); return (volatile u32 *)(fix_to_virt(FIX_CYCLONE_TIMER) + offset);}static int init_cyclone(struct platform_timesource *pts){ u32 base; if ( !use_cyclone ) return 0; /* Find base address. */ base = *(map_cyclone_reg(CYCLONE_CBAR_ADDR)); if ( base == 0 ) { printk(KERN_ERR "Cyclone: Could not find valid CBAR value.\n"); return 0; } /* Enable timer and map the counter register. */ *(map_cyclone_reg(base + CYCLONE_PMCC_OFFSET)) = 1; *(map_cyclone_reg(base + CYCLONE_MPCS_OFFSET)) = 1; cyclone_timer = map_cyclone_reg(base + CYCLONE_MPMC_OFFSET); pts->name = "IBM Cyclone"; pts->frequency = CYCLONE_TIMER_FREQ; pts->read_counter = read_cyclone_count; pts->counter_bits = 32; return 1;}/************************************************************ * PLATFORM TIMER 4: ACPI PM TIMER */u32 pmtmr_ioport;/* ACPI PM timer ticks at 3.579545 MHz. */#define ACPI_PM_FREQUENCY 3579545static u32 read_pmtimer_count(void){ return inl(pmtmr_ioport);}static int init_pmtimer(struct platform_timesource *pts){ if ( pmtmr_ioport == 0 ) return 0; pts->name = "ACPI PM Timer"; pts->frequency = ACPI_PM_FREQUENCY; pts->read_counter = read_pmtimer_count; pts->counter_bits = 24; return 1;}/************************************************************ * GENERIC PLATFORM TIMER INFRASTRUCTURE */static struct platform_timesource plt_src; /* details of chosen timesource */static u32 plt_mask; /* hardware-width mask */static u32 plt_overflow_period; /* jiffies between calls to plt_overflow() */static struct time_scale plt_scale; /* scale: platform counter -> nanosecs *//* Protected by platform_timer_lock. */static u64 plt_count64; /* 64-bit platform counter stamp */static u32 plt_count; /* hardware-width platform counter stamp */static void plt_overflow(void){ u32 count; unsigned long flags; spin_lock_irqsave(&platform_timer_lock, flags); count = plt_src.read_counter(); plt_count64 += (count - plt_count) & plt_mask; plt_count = count; plt_overflow_jiffies = plt_overflow_period; spin_unlock_irqrestore(&platform_timer_lock, flags);}static s_time_t __read_platform_stime(u64 platform_time){ u64 diff = platform_time - platform_timer_stamp; ASSERT(spin_is_locked(&platform_timer_lock)); return (stime_platform_stamp + scale_delta(diff, &plt_scale));}static s_time_t read_platform_stime(void){ u64 count; s_time_t stime; unsigned long flags; spin_lock_irqsave(&platform_timer_lock, flags); count = plt_count64 + ((plt_src.read_counter() - plt_count) & plt_mask); stime = __read_platform_stime(count); spin_unlock_irqrestore(&platform_timer_lock, flags); return stime;}static void platform_time_calibration(void){ u64 count; s_time_t stamp; unsigned long flags; spin_lock_irqsave(&platform_timer_lock, flags); count = plt_count64 + ((plt_src.read_counter() - plt_count) & plt_mask); stamp = __read_platform_stime(count); stime_platform_stamp = stamp; platform_timer_stamp = count; spin_unlock_irqrestore(&platform_timer_lock, flags);}static void resume_platform_timer(void){ /* No change in platform_stime across suspend/resume. */ platform_timer_stamp = plt_count64; plt_count = plt_src.read_counter();}static void init_platform_timer(void){ struct platform_timesource *pts = &plt_src; u64 overflow_period; int rc = -1; if ( opt_clocksource[0] != '\0' ) { if ( !strcmp(opt_clocksource, "pit") ) rc = (init_pit(pts), 1); else if ( !strcmp(opt_clocksource, "hpet") ) rc = init_hpet(pts); else if ( !strcmp(opt_clocksource, "cyclone") ) rc = init_cyclone(pts); else if ( !strcmp(opt_clocksource, "acpi") )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -