Re: AMD64 Local APIC Timer

From: Coleman Kane <zombyfork_at_gmail.com>
Date: Sat, 26 Feb 2005 12:39:09 -0500
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
> _at__at_ -137,6 +137,26 _at__at_
>         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
> _at__at_ -1,4 +1,6 _at__at_
>  /*-
> + * 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.
> _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 _at__at_
>         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 _at__at_
>             ("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 _at__at_
>         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 _at__at_
>                 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__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
> 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
> _at__at_ -123,9 +123,12 _at__at_
> 
>  /* 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 _at__at_
>             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
> _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 _at__at_
>                 }
>                 if (pscnt == psdiv)
>                         statclock(frame);
> -#ifdef SMP
> -               forward_statclock();
> -#endif
>         }
>  }
> 
> _at__at_ -730,7 +726,11 _at__at_
>  {
>         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
> _at__at_ -756,7 +756,7 _at__at_
>         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 +774,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__at_ -783,7 +784,8 _at__at_
>  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"
> 
> 
>
Received on Sat Feb 26 2005 - 16:39:11 UTC

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