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

📄 time.c

📁 ARM 嵌入式 系统 设计与实例开发 实验教材 二源码
💻 C
📖 第 1 页 / 共 2 页
字号:
		}		CMOS_WRITE(real_seconds,RTC_SECONDS);		CMOS_WRITE(real_minutes,RTC_MINUTES);	} else {		printk(KERN_WARNING		       "set_rtc_mmss: can't update from %d to %d\n",		       cmos_minutes, real_minutes);		retval = -1;	}	/* The following flags have to be released exactly in this order,	 * otherwise the DS12887 (popular MC146818A clone with integrated	 * battery and quartz) will not reset the oscillator and will not	 * update precisely 500 ms later. You won't find this mentioned in	 * the Dallas Semiconductor data sheets, but who believes data	 * sheets anyway ...                           -- Markus Kuhn	 */	CMOS_WRITE(save_control, RTC_CONTROL);	CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);	spin_unlock(&rtc_lock);	return retval;}/* last time the cmos clock got updated */static long last_rtc_update;int timer_ack;/* * timer_interrupt() needs to keep up the real-time clock, * as well as call the "do_timer()" routine every clocktick */static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs){#ifdef CONFIG_X86_IO_APIC	if (timer_ack) {		/*		 * Subtle, when I/O APICs are used we have to ack timer IRQ		 * manually to reset the IRR bit for do_slow_gettimeoffset().		 * This will also deassert NMI lines for the watchdog if run		 * on an 82489DX-based system.		 */		spin_lock(&i8259A_lock);		outb(0x0c, 0x20);		/* Ack the IRQ; AEOI will end it automatically. */		inb(0x20);		spin_unlock(&i8259A_lock);	}#endif#ifdef CONFIG_VISWS	/* Clear the interrupt */	co_cpu_write(CO_CPU_STAT,co_cpu_read(CO_CPU_STAT) & ~CO_STAT_TIMEINTR);#endif	do_timer(regs);/* * In the SMP case we use the local APIC timer interrupt to do the * profiling, except when we simulate SMP mode on a uniprocessor * system, in that case we have to call the local interrupt handler. */#ifndef CONFIG_X86_LOCAL_APIC	if (!user_mode(regs))		x86_do_profile(regs->eip);#else	if (!using_apic_timer)		smp_local_timer_interrupt(regs);#endif	/*	 * If we have an externally synchronized Linux clock, then update	 * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be	 * called as close as possible to 500 ms before the new second starts.	 */	if ((time_status & STA_UNSYNC) == 0 &&	    xtime.tv_sec > last_rtc_update + 660 &&	    xtime.tv_usec >= 500000 - ((unsigned) tick) / 2 &&	    xtime.tv_usec <= 500000 + ((unsigned) tick) / 2) {		if (set_rtc_mmss(xtime.tv_sec) == 0)			last_rtc_update = xtime.tv_sec;		else			last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */	}	    #ifdef CONFIG_MCA	if( MCA_bus ) {		/* The PS/2 uses level-triggered interrupts.  You can't		turn them off, nor would you want to (any attempt to		enable edge-triggered interrupts usually gets intercepted by a		special hardware circuit).  Hence we have to acknowledge		the timer interrupt.  Through some incredibly stupid		design idea, the reset for IRQ 0 is done by setting the		high bit of the PPI port B (0x61).  Note that some PS/2s,		notably the 55SX, work fine if this is removed.  */		irq = inb_p( 0x61 );	/* read the current state */		outb_p( irq|0x80, 0x61 );	/* reset the IRQ */	}#endif}static int use_tsc;/* * This is the same as the above, except we _also_ save the current * Time Stamp Counter value at the time of the timer interrupt, so that * we later on can estimate the time of day more exactly. */static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs){	int count;	/*	 * Here we are in the timer irq handler. We just have irqs locally	 * disabled but we don't know if the timer_bh is running on the other	 * CPU. We need to avoid to SMP race with it. NOTE: we don' t need	 * the irq version of write_lock because as just said we have irq	 * locally disabled. -arca	 */	write_lock(&xtime_lock);	if (use_tsc)	{		/*		 * It is important that these two operations happen almost at		 * the same time. We do the RDTSC stuff first, since it's		 * faster. To avoid any inconsistencies, we need interrupts		 * disabled locally.		 */		/*		 * Interrupts are just disabled locally since the timer irq		 * has the SA_INTERRUPT flag set. -arca		 */			/* read Pentium cycle counter */		rdtscl(last_tsc_low);		spin_lock(&i8253_lock);		outb_p(0x00, 0x43);     /* latch the count ASAP */		count = inb_p(0x40);    /* read the latched count */		count |= inb(0x40) << 8;		spin_unlock(&i8253_lock);		count = ((LATCH-1) - count) * TICK_SIZE;		delay_at_last_interrupt = (count + LATCH/2) / LATCH;	} 	do_timer_interrupt(irq, NULL, regs);	write_unlock(&xtime_lock);}/* not static: needed by APM */unsigned long get_cmos_time(void){	unsigned int year, mon, day, hour, min, sec;	int i;	spin_lock(&rtc_lock);	/* The Linux interpretation of the CMOS clock register contents:	 * When the Update-In-Progress (UIP) flag goes from 1 to 0, the	 * RTC registers show the second which has precisely just started.	 * Let's hope other operating systems interpret the RTC the same way.	 */	/* read RTC exactly on falling edge of update flag */	for (i = 0 ; i < 1000000 ; i++)	/* may take up to 1 second... */		if (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP)			break;	for (i = 0 ; i < 1000000 ; i++)	/* must try at least 2.228 ms */		if (!(CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP))			break;	do { /* Isn't this overkill ? UIP above should guarantee consistency */		sec = CMOS_READ(RTC_SECONDS);		min = CMOS_READ(RTC_MINUTES);		hour = CMOS_READ(RTC_HOURS);		day = CMOS_READ(RTC_DAY_OF_MONTH);		mon = CMOS_READ(RTC_MONTH);		year = CMOS_READ(RTC_YEAR);	} while (sec != CMOS_READ(RTC_SECONDS));	if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || RTC_ALWAYS_BCD)	  {	    BCD_TO_BIN(sec);	    BCD_TO_BIN(min);	    BCD_TO_BIN(hour);	    BCD_TO_BIN(day);	    BCD_TO_BIN(mon);	    BCD_TO_BIN(year);	  }	spin_unlock(&rtc_lock);	if ((year += 1900) < 1970)		year += 100;	return mktime(year, mon, day, hour, min, sec);}static struct irqaction irq0  = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};/* ------ Calibrate the TSC -------  * Return 2^32 * (1 / (TSC clocks per usec)) for do_fast_gettimeoffset(). * Too much 64-bit arithmetic here to do this cleanly in C, and for * accuracy's sake we want to keep the overhead on the CTC speaker (channel 2) * output busy loop as low as possible. We avoid reading the CTC registers * directly because of the awkward 8-bit access mechanism of the 82C54 * device. */#define CALIBRATE_LATCH	(5 * LATCH)#define CALIBRATE_TIME	(5 * 1000020/HZ)static unsigned long __init calibrate_tsc(void){       /* 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, 0x43);			/* binary, mode 0, LSB/MSB, Ch 2 */	outb(CALIBRATE_LATCH & 0xff, 0x42);	/* LSB of count */	outb(CALIBRATE_LATCH >> 8, 0x42);	/* MSB of count */	{		unsigned long startlow, starthigh;		unsigned long endlow, endhigh;		unsigned long count;		rdtsc(startlow,starthigh);		count = 0;		do {			count++;		} while ((inb(0x61) & 0x20) == 0);		rdtsc(endlow,endhigh);		last_tsc_low = endlow;		/* Error: ECTCNEVERSET */		if (count <= 1)			goto bad_ctc;		/* 64-bit subtract - gcc just messes up with long longs */		__asm__("subl %2,%0\n\t"			"sbbl %3,%1"			:"=a" (endlow), "=d" (endhigh)			:"g" (startlow), "g" (starthigh),			 "0" (endlow), "1" (endhigh));		/* Error: ECPUTOOFAST */		if (endhigh)			goto bad_ctc;		/* Error: ECPUTOOSLOW */		if (endlow <= CALIBRATE_TIME)			goto bad_ctc;		__asm__("divl %2"			:"=a" (endlow), "=d" (endhigh)			:"r" (endlow), "0" (0), "1" (CALIBRATE_TIME));		return endlow;	}	/*	 * The CTC wasn't reliable: we got a hit on the very first read,	 * or the CPU was so fast/slow that the quotient wouldn't fit in	 * 32 bits..	 */bad_ctc:	return 0;}void __init time_init(void){	extern int x86_udelay_tsc;		xtime.tv_sec = get_cmos_time();	xtime.tv_usec = 0;/* * If we have APM enabled or the CPU clock speed is variable * (CPU stops clock on HLT or slows clock to save power) * then the TSC timestamps may diverge by up to 1 jiffy from * 'real time' but nothing will break. * The most frequent case is that the CPU is "woken" from a halt * state by the timer interrupt itself, so we get 0 error. In the * rare cases where a driver would "wake" the CPU and request a * timestamp, the maximum error is < 1 jiffy. But timestamps are * still perfectly ordered. * Note that the TSC counter will be reset if APM suspends * to disk; this won't break the kernel, though, 'cuz we're * smart.  See arch/i386/kernel/apm.c. */ 	/* 	 *	Firstly we have to do a CPU check for chips with 	 * 	a potentially buggy TSC. At this point we haven't run 	 *	the ident/bugs checks so we must run this hook as it 	 *	may turn off the TSC flag. 	 * 	 *	NOTE: this doesnt yet handle SMP 486 machines where only 	 *	some CPU's have a TSC. Thats never worked and nobody has 	 *	moaned if you have the only one in the world - you fix it! 	 */  	dodgy_tsc(); 		if (cpu_has_tsc) {		unsigned long tsc_quotient = calibrate_tsc();		if (tsc_quotient) {			fast_gettimeoffset_quotient = tsc_quotient;			use_tsc = 1;			/*			 *	We could be more selective here I suspect			 *	and just enable this for the next intel chips ?			 */			x86_udelay_tsc = 1;#ifndef do_gettimeoffset			do_gettimeoffset = do_fast_gettimeoffset;#endif			/* report CPU clock rate in Hz.			 * The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =			 * clock/second. Our precision is about 100 ppm.			 */			{	unsigned long eax=0, edx=1000;				__asm__("divl %2"		       		:"=a" (cpu_khz), "=d" (edx)        	       		:"r" (tsc_quotient),	                	"0" (eax), "1" (edx));				printk("Detected %lu.%03lu MHz processor.\n", cpu_khz / 1000, cpu_khz % 1000);			}		}	}#ifdef CONFIG_VISWS	printk("Starting Cobalt Timer system clock\n");	/* Set the countdown value */	co_cpu_write(CO_CPU_TIMEVAL, CO_TIME_HZ/HZ);	/* Start the timer */	co_cpu_write(CO_CPU_CTRL, co_cpu_read(CO_CPU_CTRL) | CO_CTRL_TIMERUN);	/* Enable (unmask) the timer interrupt */	co_cpu_write(CO_CPU_CTRL, co_cpu_read(CO_CPU_CTRL) & ~CO_CTRL_TIMEMASK);	/* Wire cpu IDT entry to s/w handler (and Cobalt APIC to IDT) */	setup_irq(CO_IRQ_TIMER, &irq0);#else	setup_irq(0, &irq0);#endif}

⌨️ 快捷键说明

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