AMD64 Local APIC Timer
Coleman Kane
zombyfork at gmail.com
Sat Feb 26 17:39:11 GMT 2005
Hi,
The lapic timer patch seems to break something in the
timeout(9)/untimeout(9) handling. I have a mobile athlon64 laptop, and
have been using Fukuda Nobuhiko's acpi_ppc driver for the Cool'n'Quiet
operation. With your patch applied, this driver no longer scales the
CPU frequency. It seems to use timeout(9) to have the kernel call a
polling function regularly to monitor CPU usage and scales the CPU
speed to match the usage. This helps maintain bettery life.
The driver is at:
http://www.spa.is.uec.ac.jp/~nfukuda/software/dist/acpi_ppc-20050210.tgz
--
coleman kane
On Wed, 23 Feb 2005 01:30:53 +0900, Takeharu KATO
<takeharu1219 at ybb.ne.jp> wrote:
> Hi
>
> I found my bug in the patch which I sent before.
> I re-post the local-apic-timer patch for AMD64.
>
> Takeharu KATO wrote:
> > 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
> @@ -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 22 Feb 2005 16:16:33 -0000
> @@ -1,4 +1,6 @@
> /*-
> + * Copyright (c) 2005 Takeharu KATO
> + * (Add LAPIC timer support).
> * Copyright (c) 2003 John Baldwin <jhb at FreeBSD.org>
> * Copyright (c) 1996, by Steve Passe
> * All rights reserved.
> @@ -66,6 +68,9 @@
> 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
> @@ -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? */
> @@ -115,9 +123,23 @@
> 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
> @@ -181,6 +203,7 @@
> PCPU_SET(apic_id, lapic_id());
>
> /* XXX: timer/error/thermal interrupts */
> + setidt(APIC_TIMER_INT, IDTVEC(timerint), SDT_SYSIGT, SEL_KPL,0);
> }
>
> /*
> @@ -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"));
> @@ -281,9 +347,47 @@
> 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)
> @@ -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
> @@ -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.
> @@ -914,16 +914,16 @@
> 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 */
> @@ -948,6 +948,7 @@
> }
>
> 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
> diff -u -r1.1.1.1 CURRENT-MARS
> 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
> @@ -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. */
> @@ -172,7 +175,7 @@
> 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);
> @@ -203,6 +206,7 @@
> 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);
> @@ -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 22 Feb 2005 15:38:24 -0000
> @@ -64,13 +64,14 @@
> #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>
> @@ -113,6 +114,7 @@
> 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;
>
> @@ -139,7 +141,6 @@
> static void
> clkintr(struct clockframe *frame)
> {
> -
> if (timecounter->tc_get_timecount == i8254_get_timecount) {
> mtx_lock_spin(&clock_lock);
> if (i8254_ticked)
> @@ -151,10 +152,8 @@
> clkintr_pending = 0;
> mtx_unlock_spin(&clock_lock);
> }
> - hardclock(frame);
> -#ifdef SMP
> - forward_hardclock();
> -#endif
> + if (!using_lapic_timer)
> + hardclock(frame);
> }
>
> int
> @@ -221,9 +220,6 @@
> }
> if (pscnt == psdiv)
> statclock(frame);
> -#ifdef SMP
> - forward_statclock();
> -#endif
> }
> }
>
> @@ -730,7 +726,11 @@
> {
> int diag;
>
> - if (statclock_disable) {
> +#ifdef LAPIC_TIMER
> + using_lapic_timer = lapic_setup_clock();
> +#endif
> +
> + if ( statclock_disable || using_lapic_timer ) {
> /*
> * The stat interrupt mask is different without the
> * statistics clock. Also, don't set the interrupt
> @@ -756,7 +756,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);
> @@ -774,7 +774,8 @@
> void
> cpu_startprofclock(void)
> {
> -
> + if (using_lapic_timer)
> + return;
> rtc_statusa = RTCSA_DIVIDER | RTCSA_PROF;
> writertc(RTC_STATUSA, rtc_statusa);
> psdiv = pscnt = psratio;
> @@ -783,7 +784,8 @@
> void
> cpu_stopprofclock(void)
> {
> -
> + if (using_lapic_timer)
> + return;
> rtc_statusa = RTCSA_DIVIDER | RTCSA_NOPROF;
> writertc(RTC_STATUSA, rtc_statusa);
> psdiv = pscnt = 1;
>
>
> _______________________________________________
> freebsd-amd64 at freebsd.org mailing list
> http://lists.freebsd.org/mailman/listinfo/freebsd-amd64
> To unsubscribe, send any mail to "freebsd-amd64-unsubscribe at freebsd.org"
>
>
>
More information about the freebsd-amd64
mailing list