AMD64 Local APIC Timer

From: Takeharu KATO <takeharu1219_at_ybb.ne.jp>
Date: Tue, 22 Feb 2005 06:06:37 +0900
Hi

I ported the local APIC timer tick feature to AMD64.
Please take a look on this patch.

Regards,
-- 
Takeharu KATO

Index: amd64/amd64/apic_vector.S
===================================================================
RCS file: /home/kato/cvs/kato-sys/amd64/amd64/apic_vector.S,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- amd64/amd64/apic_vector.S	18 Feb 2005 14:05:55 -0000	1.1.1.1
+++ amd64/amd64/apic_vector.S	20 Feb 2005 18:15:29 -0000	1.2
_at__at_ -137,6 +137,26 @@
 	ISR_VEC(6, apic_isr6)
 	ISR_VEC(7, apic_isr7)
 
+/*
+ * Local APIC periodic timer handler.
+ */
+	.text
+	SUPERALIGN_TEXT
+IDTVEC(timerint)
+	PUSH_FRAME
+
+	movq	lapic, %rdx
+	movl	$0, LA_EOI(%rdx)	/* End Of Interrupt to APIC */
+	
+	FAKE_MCOUNT(TF_RIP(%rsp))
+
+
+	pushq	$0		/* XXX convert trapframe to clockframe */
+	call	lapic_handle_timer
+	addq	$8, %rsp	/* XXX convert clockframe to trapframe */
+	MEXITCOUNT
+	jmp	doreti
+
 #ifdef SMP
 /*
  * Global address space TLB shootdown.
Index: amd64/amd64/local_apic.c
===================================================================
RCS file: /home/kato/cvs/kato-sys/amd64/amd64/local_apic.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 local_apic.c
--- amd64/amd64/local_apic.c	18 Feb 2005 14:05:55 -0000	1.1.1.1
+++ amd64/amd64/local_apic.c	20 Feb 2005 19:43:06 -0000
_at__at_ -1,4 +1,6 _at__at_
 /*-
+ * Copyright (c) 2005 Takeharu KATO<takeharu1219_at_ybb.ne.jp>
+ *                    (Add LAPIC timer support).
  * Copyright (c) 2003 John Baldwin <jhb_at_FreeBSD.org>
  * Copyright (c) 1996, by Steve Passe
  * All rights reserved.
_at__at_ -66,6 +68,9 _at__at_
 CTASSERT(APIC_LOCAL_INTS == 240);
 CTASSERT(IPI_STOP < APIC_SPURIOUS_INT);
 
+#define	LAPIC_TIMER_STATHZ	128
+#define	LAPIC_TIMER_PROFHZ	1024
+
 /*
  * Support for local APICs.  Local APICs manage interrupts on each
  * individual processor as opposed to I/O APICs which receive interrupts
_at__at_ -90,6 +95,9 @@
 	u_int la_cluster:4;
 	u_int la_cluster_id:2;
 	u_int la_present:1;
+	u_long *la_timer_count;
+	u_long la_stat_ticks;
+	u_long la_prof_ticks;
 } static lapics[MAX_APICID];
 
 /* XXX: should thermal be an NMI? */
_at__at_ -115,9 +123,23 _at__at_
 	IDTVEC(apic_isr7),	/* 224 - 255 */
 };
 
+static u_int32_t lapic_timer_divisors[] = { 
+	APIC_TDCR_1, APIC_TDCR_2, APIC_TDCR_4, APIC_TDCR_8, APIC_TDCR_16,
+	APIC_TDCR_32, APIC_TDCR_64, APIC_TDCR_128
+};
+
+
 volatile lapic_t *lapic;
+static u_long lapic_timer_divisor, lapic_timer_period;
+static u_long *lapic_virtual_hardclock, *lapic_virtual_statclock,
+	*lapic_virtual_profclock;
 
 static void	lapic_enable(void);
+static void	lapic_timer_enable_intr(void);
+static u_long   calculate_lapic_timer_period(void);
+static void	lapic_timer_oneshot(u_int count);
+static void	lapic_timer_periodic(u_int count);
+static void	lapic_timer_set_divisor(u_int divisor);
 static uint32_t	lvt_mode(struct lapic *la, u_int pin, uint32_t value);
 
 static uint32_t
_at__at_ -181,6 +203,7 _at__at_
 	PCPU_SET(apic_id, lapic_id());
 
 	/* XXX: timer/error/thermal interrupts */
+	setidt(APIC_TIMER_INT, IDTVEC(timerint), SDT_SYSIGT, SEL_KPL,0);
 }
 
 /*
_at__at_ -244,13 +267,56 @@
 	    ("No ISR handler for IRQ %u", irq));
 	setidt(vector, ioint_handlers[vector / 32], SDT_SYSIGT, SEL_KPL,  0);
 }
+static u_long
+calculate_lapic_timer_period(void)
+{
+	u_long period,value;
+
+	/* Start off with a divisor of 2 (power on reset default). */
+	lapic_timer_divisor = 8;
+
+	/* Try to calibrate the local APIC timer. */
+	do {
+		printf("lapic timer divisor:%lu\n",lapic_timer_divisor);
+		lapic_timer_set_divisor(lapic_timer_divisor);
+		lapic_timer_oneshot(APIC_TIMER_MAX_COUNT);
+		DELAY(2000000);
+		value = APIC_TIMER_MAX_COUNT - lapic->ccr_timer;
+		printf("value:%lu(ccr:%u)\n",value,lapic->ccr_timer);
+		if (value != APIC_TIMER_MAX_COUNT)
+			break;
+		lapic_timer_divisor <<= 1;
+	} while (lapic_timer_divisor <= 128);
+	if (lapic_timer_divisor > 128)
+		panic("lapic: Divisor too big");
+	value /= 2;
+	printf("lapic: Frequency %lu hz\n", value);
 
+	/*
+	 * We will drive the timer via hz.  Require hz to be greater than
+	 * stathz, but if hz is less than the default profhz, cap profhz
+	 * at hz.
+	 */
+	stathz = LAPIC_TIMER_STATHZ;
+	if (hz < stathz) {
+		printf("lapic: Adjusting hz from %d to %d\n", hz, stathz);
+		hz = stathz;
+	}
+	period=value / hz;
+	KASSERT(period!=0, ("CPU:%d lapic%u: zero divisor",PCPU_GET(cpuid),lapic_id()));
+#if 0  /*  Please enable following lines if you want to show period/divisor */
+	printf("Setup CPU:%d period:%lu val=%lu\n",PCPU_GET(cpuid),lapic_timer_period,value);
+	printf("Setup CPU:%d div=%lu\n",PCPU_GET(cpuid),lapic_timer_divisor);
+#endif
+	return  period;
+}
 void
 lapic_setup(void)
 {
 	struct lapic *la;
 	u_int32_t value, maxlvt;
 	register_t eflags;
+	char buf[MAXCOMLEN + 1];
 
 	la = &lapics[lapic_id()];
 	KASSERT(la->la_present, ("missing APIC structure"));
_at__at_ -281,9 +347,47 _at__at_
 	lapic->lvt_lint1 = lvt_mode(la, LVT_LINT1, lapic->lvt_lint1);
 
 	/* XXX: more LVT entries */
+	/* Program timer LVT and setup handler. */
+	lapic->lvt_timer = lvt_mode(la, LVT_TIMER, lapic->lvt_timer);
+	snprintf(buf, sizeof(buf), "lapic%d: timer", lapic_id());
+	intrcnt_add(buf, &la->la_timer_count);
+	if (PCPU_GET(cpuid) != 0) {
+		lapic_timer_period=calculate_lapic_timer_period();
+		lapic_timer_set_divisor(lapic_timer_divisor);
+		lapic_timer_periodic(lapic_timer_period);
+		lapic_timer_enable_intr();
+	}
 
 	intr_restore(eflags);
 }
+/*
+ * Called by cpu_initclocks() on the BSP to setup the local APIC timer so
+ * that it can drive hardclock, statclock, and profclock.  This function
+ * returns true if it is able to use the local APIC timer to drive the
+ * clocks and false if it is not able.
+ */
+int
+lapic_setup_clock(void)
+{
+	/* Can't drive the timer without a local APIC. */
+	if (lapic == NULL)
+		return (0);
+
+	lapic_timer_period = calculate_lapic_timer_period();
+	profhz = imin(hz, LAPIC_TIMER_PROFHZ);
+	intrcnt_add("lapic: hardclock", &lapic_virtual_hardclock);
+	intrcnt_add("lapic: statclock", &lapic_virtual_statclock);
+	intrcnt_add("lapic: profclock", &lapic_virtual_profclock);
+
+	/*
+	 * Start up the timer on the BSP.  The APs will kick off their
+	 * timer during lapic_setup().
+	 */
+	lapic_timer_periodic(lapic_timer_period);
+	lapic_timer_enable_intr();
+	return (1);
+}
+
 
 void
 lapic_disable(void)
_at__at_ -515,6 +619,87 @@
 	isrc = intr_lookup_source(apic_idt_to_irq(vec));
 	intr_execute_handlers(isrc, &frame);
 }
+void
+lapic_handle_timer(struct clockframe frame)
+{
+	struct lapic *la;
+
+	la = &lapics[PCPU_GET(apic_id)];
+	(*la->la_timer_count)++;
+	critical_enter();
+
+	/* Hardclock fires on every interrupt since we interrupt at hz. */
+	if (PCPU_GET(cpuid) == 0) {
+		(*lapic_virtual_hardclock)++;
+		hardclock(&frame);
+	} else
+		hardclock_process(&frame);
+
+	/* Use a poor man's algorithm to fire statclock at stathz. */
+	la->la_stat_ticks += stathz;
+	if (la->la_stat_ticks >= hz) {
+		la->la_stat_ticks -= hz;
+		if (PCPU_GET(cpuid) == 0)
+			(*lapic_virtual_statclock)++;
+		statclock(&frame);
+	}
+
+	/* Use the same trick for profhz. */
+	la->la_prof_ticks += profhz;
+	if (la->la_prof_ticks >= hz) {
+		la->la_prof_ticks -= hz;
+		if (PCPU_GET(cpuid) == 0)
+			(*lapic_virtual_profclock)++;
+		if (profprocs != 0)
+			profclock(&frame);
+	}
+	critical_exit();
+}
+
+static void
+lapic_timer_set_divisor(u_int divisor)
+{
+
+	KASSERT(powerof2(divisor), ("lapic: invalid divisor %u", divisor));
+	KASSERT(ffs(divisor) <= sizeof(lapic_timer_divisors) /
+	    sizeof(u_int32_t), ("lapic: invalid divisor %u", divisor));
+	lapic->dcr_timer = lapic_timer_divisors[ffs(divisor) - 1];
+}
+
+static void
+lapic_timer_oneshot(u_int count)
+{
+	u_int32_t value;
+
+	value = lapic->lvt_timer;
+	value &= ~APIC_LVTT_TM;
+	value |= APIC_LVTT_TM_ONE_SHOT;
+	lapic->lvt_timer = value;
+	lapic->icr_timer = count;
+}
+
+static void
+lapic_timer_periodic(u_int count)
+{
+	u_int32_t value;
+
+	value = lapic->lvt_timer;
+	value &= ~APIC_LVTT_TM;
+	value |= APIC_LVTT_TM_PERIODIC;
+	lapic->lvt_timer = value;
+	lapic->icr_timer = count;
+}
+
+static void
+lapic_timer_enable_intr(void)
+{
+	u_int32_t value;
+
+	value = lapic->lvt_timer;
+	value &= ~APIC_LVT_M;
+	lapic->lvt_timer = value;
+}
+
 
 /* Translate between IDT vectors and IRQ vectors. */
 u_int
Index: amd64/amd64/mp_machdep.c
===================================================================
RCS file: /home/kato/cvs/kato-sys/amd64/amd64/mp_machdep.c,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- amd64/amd64/mp_machdep.c	18 Feb 2005 14:05:55 -0000	1.1.1.1
+++ amd64/amd64/mp_machdep.c	20 Feb 2005 18:15:29 -0000	1.2
_at__at_ -871,7 +871,7 @@
 		smp_targeted_tlb_shootdown(mask, IPI_INVLRNG, addr1, addr2);
 }
 
-
+#if 0
 /*
  * For statclock, we send an IPI to all CPU's to have them call this
  * function.
_at__at_ -914,16 +914,16 _at__at_
 	if (map != 0)
 		ipi_selected(map, IPI_HARDCLOCK);
 }
-
+#endif 
 void
 ipi_bitmap_handler(struct clockframe frame)
 {
 	int cpu = PCPU_GET(cpuid);
 	u_int ipi_bitmap;
-	struct thread *td;
 
-	ipi_bitmap = atomic_readandclear_int(&cpu_ipi_pending[cpu]);
 
+	ipi_bitmap = atomic_readandclear_int(&cpu_ipi_pending[cpu]);
+#if 0
 	critical_enter();
 
 	/* Nothing to do for AST */
_at__at_ -948,6 +948,7 _at_@
 	}
 
 	critical_exit();
+#endif
 }
 
 /*
Index: amd64/conf/CURRENT-MARS
===================================================================
RCS file: /home/kato/cvs/kato-sys/amd64/conf/CURRENT-MARS,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
Index: amd64/include/apicvar.h
===================================================================
RCS file: /home/kato/cvs/kato-sys/amd64/include/apicvar.h,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- amd64/include/apicvar.h	18 Feb 2005 14:05:55 -0000	1.1.1.1
+++ amd64/include/apicvar.h	20 Feb 2005 18:15:33 -0000	1.2
_at__at_ -123,9 +123,12 @@
 
 /* IPIs handled by IPI_BITMAPED_VECTOR  (XXX ups is there a better place?) */
 #define	IPI_AST		0 	/* Generate software trap. */
+#if 0
 #define	IPI_HARDCLOCK	1	/* Inter-CPU clock handling. */
 #define	IPI_STATCLOCK	2
 #define IPI_BITMAP_LAST IPI_STATCLOCK
+#endif
+#define IPI_BITMAP_LAST IPI_AST
 #define IPI_IS_BITMAPED(x) ((x) <= IPI_BITMAP_LAST)
 
 #define	IPI_STOP	(APIC_IPI_INTS + 6)	/* Stop CPU until restarted. */
_at__at_ -172,7 +175,7 _at__at_
 inthand_t
 	IDTVEC(apic_isr1), IDTVEC(apic_isr2), IDTVEC(apic_isr3),
 	IDTVEC(apic_isr4), IDTVEC(apic_isr5), IDTVEC(apic_isr6),
-	IDTVEC(apic_isr7), IDTVEC(spuriousint);
+	IDTVEC(apic_isr7), IDTVEC(spuriousint),IDTVEC(timerint);
 
 u_int	apic_irq_to_idt(u_int irq);
 u_int	apic_idt_to_irq(u_int vector);
_at__at_ -203,6 +206,7 _at__at_
 void	lapic_ipi_vectored(u_int vector, int dest);
 int	lapic_ipi_wait(int delay);
 void	lapic_handle_intr(void *cookie, struct intrframe frame);
+void	lapic_handle_timer(struct clockframe frame);
 void	lapic_set_logical_id(u_int apic_id, u_int cluster, u_int cluster_id);
 int	lapic_set_lvt_mask(u_int apic_id, u_int lvt, u_char masked);
 int	lapic_set_lvt_mode(u_int apic_id, u_int lvt, u_int32_t mode);
_at__at_ -212,6 +216,7 @@
 	    enum intr_trigger trigger);
 void	lapic_set_tpr(u_int vector);
 void	lapic_setup(void);
+int	lapic_setup_clock(void);
 
 #endif /* !LOCORE */
 #endif /* _MACHINE_APICVAR_H_ */
Index: amd64/isa/clock.c
===================================================================
RCS file: /home/kato/cvs/kato-sys/amd64/isa/clock.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 clock.c
--- amd64/isa/clock.c	18 Feb 2005 14:05:55 -0000	1.1.1.1
+++ amd64/isa/clock.c	20 Feb 2005 18:25:50 -0000
_at__at_ -64,13 +64,14 _at__at_
 #include <sys/sysctl.h>
 #include <sys/cons.h>
 #include <sys/power.h>
-
+#define LAPIC_TIMER
 #include <machine/clock.h>
 #include <machine/frame.h>
 #include <machine/intr_machdep.h>
 #include <machine/md_var.h>
 #include <machine/psl.h>
-#ifdef SMP
+#ifdef LAPIC_TIMER
+#include <machine/apicvar.h>
 #include <machine/smp.h>
 #endif
 #include <machine/specialreg.h>
_at__at_ -113,6 +114,7 _at__at_
 static	u_int32_t i8254_offset;
 static	int	(*i8254_pending)(struct intsrc *);
 static	int	i8254_ticked;
+static	int	using_lapic_timer;
 static	u_char	rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
 static	u_char	rtc_statusb = RTCSB_24HR | RTCSB_PINTR;
 
_at__at_ -139,7 +141,6 _at__at_
 static void
 clkintr(struct clockframe *frame)
 {
-
 	if (timecounter->tc_get_timecount == i8254_get_timecount) {
 		mtx_lock_spin(&clock_lock);
 		if (i8254_ticked)
_at__at_ -151,10 +152,8 _at__at_
 		clkintr_pending = 0;
 		mtx_unlock_spin(&clock_lock);
 	}
-	hardclock(frame);
-#ifdef SMP
-	forward_hardclock();
-#endif
+	if (!using_lapic_timer)	
+		hardclock(frame);
 }
 
 int
_at__at_ -221,9 +220,6 @@
 		}
 		if (pscnt == psdiv)
 			statclock(frame);
-#ifdef SMP
-		forward_statclock();
-#endif
 	}
 }
 
_at__at_ -731,6 +727,9 _at__at_
 	int diag;
 
 	if (statclock_disable) {
+#ifdef LAPIC_TIMER
+	using_lapic_timer = lapic_setup_clock();
+#endif
 		/*
 		 * The stat interrupt mask is different without the
 		 * statistics clock.  Also, don't set the interrupt
_at__at_ -756,7 +755,7 @@
 	writertc(RTC_STATUSB, RTCSB_24HR);
 
 	/* Don't bother enabling the statistics clock. */
-	if (!statclock_disable) {
+	if (!statclock_disable && !using_lapic_timer) {
 		diag = rtcin(RTC_DIAG);
 		if (diag != 0)
 			printf("RTC BIOS diagnostic error %b\n", diag, RTCDG_BITS);
_at__at_ -774,7 +773,8 _at__at_
 void
 cpu_startprofclock(void)
 {
-
+	if (using_lapic_timer)
+		return;
 	rtc_statusa = RTCSA_DIVIDER | RTCSA_PROF;
 	writertc(RTC_STATUSA, rtc_statusa);
 	psdiv = pscnt = psratio;
_at_@ -783,7 +783,8 @@
 void
 cpu_stopprofclock(void)
 {
-
+	if (using_lapic_timer)
+		return;
 	rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
 	writertc(RTC_STATUSA, rtc_statusa);
 	psdiv = pscnt = 1;
Received on Mon Feb 21 2005 - 20:06:41 UTC

This archive was generated by hypermail 2.4.0 : Wed May 19 2021 - 11:38:28 UTC