patch-2.1.33 linux/arch/i386/kernel/smp.c
Next file: linux/arch/i386/kernel/traps.c
Previous file: linux/arch/i386/kernel/process.c
Back to the patch index
Back to the overall index
- Lines: 335
- Date:
Mon Apr 7 14:02:43 1997
- Orig file:
v2.1.32/linux/arch/i386/kernel/smp.c
- Orig date:
Mon Apr 7 11:35:29 1997
diff -u --recursive --new-file v2.1.32/linux/arch/i386/kernel/smp.c linux/arch/i386/kernel/smp.c
@@ -23,7 +23,8 @@
* Michel Lespinasse : Changes for 2.1 kernel map.
* Michael Chastain : Change trampoline.S to gnu as.
* Alan Cox : Dumb bug: 'B' step PPro's are fine
- *
+ * Ingo Molnar : Added APIC timers, based on code
+ * from Jose Renau
*/
#include <linux/kernel.h>
@@ -45,6 +46,7 @@
#include <asm/io.h>
extern unsigned long start_kernel, _etext;
+void setup_APIC_clock (void);
/*
* Some notes on processor bugs:
@@ -137,6 +139,10 @@
volatile unsigned long smp_spins_syscall_cur[NR_CPUS]={0};/* Count spins for the actual syscall */
volatile unsigned long smp_spins_sys_idle[NR_CPUS]={0}; /* Count spins for sys_idle */
volatile unsigned long smp_idle_count[1+NR_CPUS]={0,}; /* Count idle ticks */
+
+/* Count local APIC timer ticks */
+volatile unsigned long smp_apic_timer_ticks[1+NR_CPUS]={0,};
+
#endif
#if defined (__SMP_PROF__)
volatile unsigned long smp_idle_map=0; /* Map for idle processors */
@@ -621,6 +627,14 @@
l=apic_read(APIC_SPIV);
l|=(1<<8); /* Enable */
apic_write(APIC_SPIV,l);
+
+#ifdef __SMP_PROF__
+ /*
+ * Set up our APIC timer.
+ */
+ setup_APIC_clock ();
+#endif
+
sti();
/*
* Get our bogomips.
@@ -767,7 +781,14 @@
apic_write(APIC_SPIV,cfg);
udelay(10);
-
+
+#ifdef __SMP_PROF__
+ /*
+ * Set up our local APIC timer:
+ */
+ setup_APIC_clock ();
+#endif
+
/*
* Now scan the cpu present map and fire up the other CPUs.
*/
@@ -1305,3 +1326,274 @@
for(;;) __asm__("hlt");
for (;;) ;
}
+
+#ifdef __SMP_PROF__
+
+extern void (*do_profile)(struct pt_regs *);
+static void (*default_do_profile)(struct pt_regs *) = NULL;
+
+/*
+ * APIC timer interrupt
+ */
+void smp_apic_timer_interrupt(struct pt_regs * regs)
+{
+ int cpu = smp_processor_id();
+
+ if (!user_mode(regs)) {
+ unsigned long flags;
+
+ /*
+ * local timer interrupt is not NMI yet, so
+ * it's simple, we just aquire the global cli
+ * lock to mess around with profiling info.
+ *
+ * later, in the NMI version, we have to build
+ * our own 'current' pointer (as we could have
+ * interrupted code that just changes "current")
+ * and have to lock profiling info between NMI
+ * interrupts properly.
+ */
+ save_flags(flags);
+ cli();
+ default_do_profile(regs);
+ restore_flags(flags);
+ }
+
+ /*
+ * this is safe outside the lock.
+ */
+ smp_apic_timer_ticks[cpu]++;
+
+ apic_read (APIC_SPIV);
+ apic_write (APIC_EOI, 0);
+}
+
+/*
+ * This part sets up the APIC 32 bit clock in LVTT1, with HZ interrupts
+ * per second. We assume that the caller has already set up the local
+ * APIC at apic_addr.
+ *
+ * Later we might want to split HZ into two parts: introduce
+ * PROFILING_HZ and scheduling HZ. The APIC timer is not exactly
+ * sync with the external timer chip, it closely follows bus clocks.
+ */
+
+#define RTDSC(x) __asm__ __volatile__ ( ".byte 0x0f,0x31" \
+ :"=a" (((unsigned long*)&x)[0]), \
+ "=d" (((unsigned long*)&x)[1]))
+
+/*
+ * The timer chip is already set up at HZ interrupts per second here,
+ * but we do not accept timer interrupts yet. We only allow the BP
+ * to calibrate.
+ */
+unsigned int get_8254_timer_count (void)
+{
+ unsigned int count;
+
+ outb_p(0x00, 0x43);
+ count = inb_p(0x40);
+ count |= inb_p(0x40) << 8;
+
+ return count;
+}
+
+/*
+ * This function sets up the local APIC timer, with a timeout of
+ * 'clocks' APIC bus clock. During calibration we actually call
+ * this function twice, once with a bogus timeout value, second
+ * time for real. The other (noncalibrating) CPUs call this
+ * function only once, with the real value.
+ *
+ * We are strictly in irqs off mode here, as we do not want to
+ * get an APIC interrupt go off accidentally.
+ */
+
+#define APIC_DIVISOR 16
+
+/*
+ * We do reads before writes even if unnecessary, to get around the
+ * APIC double write 'bug'.
+ */
+
+void setup_APIC_timer (unsigned int clocks)
+{
+ unsigned long lvtt1_value;
+ unsigned int tmp_value;
+
+ /*
+ * Unfortunately the local APIC timer cannot be set up into NMI
+ * mode. With the IO APIC we can re-route the external timer
+ * interrupt and broadcast it as an NMI to all CPUs, so no pain.
+ *
+ * NOTE: this irq vector 19 and the gate in BUILD_SMP_TIMER_INTERRUPT
+ * should be the same ;)
+ */
+ tmp_value = apic_read(APIC_LVTT);
+ lvtt1_value = APIC_LVT_TIMER_PERIODIC | (0x20+19);
+ apic_write(APIC_LVTT , lvtt1_value);
+
+ /*
+ * Divide PICLK by 16
+ */
+ tmp_value = apic_read(APIC_TDCR);
+ apic_write(APIC_TDCR , (tmp_value & ~APIC_TDR_DIV_1 )
+ | APIC_TDR_DIV_16);
+
+ tmp_value = apic_read(APIC_TMICT);
+ apic_write(APIC_TMICT, clocks/APIC_DIVISOR);
+}
+
+void wait_8254_wraparound (void)
+{
+ unsigned int curr_count, prev_count=~0;
+ int delta;
+
+ curr_count = get_8254_timer_count();
+
+ do {
+ prev_count = curr_count;
+ curr_count = get_8254_timer_count();
+ delta = curr_count-prev_count;
+
+ /*
+ * This limit for delta seems arbitrary, but it isnt, it's
+ * slightly above the level of error a buggy Mercury/Neptune
+ * chipset timer can cause.
+ */
+
+ } while (delta<1000);
+}
+
+/*
+ * In this function we calibrate APIC bus clocks to the external
+ * timer here. Unfortunately we cannot use jiffies and
+ * the timer irq to calibrate, since some later bootup
+ * code depends on getting the first irq? Ugh.
+ *
+ * We want to do the calibration only once since we
+ * want to have local timer irqs syncron. CPUs connected
+ * by the same APIC bus have the very same bus frequency.
+ * And we want to have irqs off anyways, no accidental
+ * APIC irq that way.
+ */
+
+int calibrate_APIC_clock (void)
+{
+ unsigned long long t1,t2;
+ unsigned long tt1,tt2;
+ unsigned int prev_count, curr_count;
+ long calibration_result;
+
+ printk("calibrating APIC timer ... ");
+
+ /*
+ * Put whatever arbitrary (but long enough) timeout
+ * value into the APIC clock, we just want to get the
+ * counter running for calibration.
+ */
+ setup_APIC_timer(1000000000);
+
+ /*
+ * The timer chip counts down to zero. Lets wait
+ * for a wraparound to start exact measurement:
+ * (the current tick might have been already half done)
+ */
+
+ wait_8254_wraparound ();
+
+ /*
+ * We wrapped around just now, lets start:
+ */
+ RTDSC(t1);
+ tt1=apic_read(APIC_TMCCT);
+
+ /*
+ * lets wait until we get to the next wrapround:
+ */
+ wait_8254_wraparound ();
+
+ tt2=apic_read(APIC_TMCCT);
+ RTDSC(t2);
+
+ /*
+ * The APIC bus clock counter is 32 bits only, it
+ * might have overflown:
+ */
+ if (tt2<tt1) {
+ unsigned long tmp = tt2;
+ tt2=tt1;
+ tt1=tmp;
+ }
+
+ calibration_result = (tt2-tt1)*APIC_DIVISOR;
+
+ printk("\n..... %ld CPU clocks in 1 timer chip tick.\n",
+ (unsigned long)(t2-t1));
+
+ printk("..... %ld APIC bus clocks in 1 timer chip tick.\n",
+ calibration_result);
+
+
+ printk("..... CPU clock speed is %ld.%ld MHz.\n",
+ ((long)(t2-t1))/(1000000/HZ),
+ ((long)(t2-t1))%(1000000/HZ) );
+
+ printk("..... APIC bus clock speed is %ld.%ld MHz.\n",
+ calibration_result/(1000000/HZ),
+ calibration_result%(1000000/HZ) );
+
+ return calibration_result;
+}
+
+void setup_APIC_clock (void)
+{
+ unsigned long flags;
+
+ static volatile int calibration_lock;
+ static unsigned int calibration_result;
+
+ return;
+ save_flags(flags);
+ cli();
+
+ printk("setup_APIC_clock() called.\n");
+
+ /*
+ * We use a private profiling function. (This is preparation
+ * for NMI local timer interrupts.)
+ *
+ * [ setup_APIC_clock() is called from all CPUs, but we want
+ * to do this part of the setup only once ... and it fits
+ * here best ]
+ */
+ if (!set_bit(0,&calibration_lock)) {
+
+ default_do_profile = do_profile;
+ do_profile = NULL;
+
+ calibration_result=calibrate_APIC_clock();
+ /*
+ * Signal completion to the other CPU[s]:
+ */
+ calibration_lock = 3;
+
+ } else {
+ /*
+ * Other CPU is calibrating, wait for finish:
+ */
+ printk("waiting for other CPU calibrating APIC timer ... ");
+ while (calibration_lock == 1);
+ printk("done, continuing.\n");
+ }
+
+ setup_APIC_timer (calibration_result);
+
+ restore_flags(flags);
+}
+
+#undef APIC_DIVISOR
+#undef RTDSC
+
+#endif
+
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov