[patch] cycle-counter synchronization on SMP, 2.2.0-final

MOLNAR Ingo (mingo@chiara.csoma.elte.hu)
Fri, 22 Jan 1999 17:44:52 +0100 (CET)


On Fri, 22 Jan 1999, Mark-Andre Hopf wrote:

> My first assumption, my large difference in the TSC registers was wrong,
> so I guess this bug should be visible on other Intel SMP systems too,
> maybe even UP systems. [...]

(btw. i have noticed timestamp-counter anomalies on overclocked CPUs.
Everything was OK, but the timestamp-counter got corrupted sometimes)

if it's asynchron cycle-counters on SMP, then could you try the attached
patch? I didnt want to deal with this because most systems have a synchron
cycle counter, but it seems to be more widespread than suspected ...

with the patch installed my box synchronizes all 4 CPUs on bootup:

[... bootlog ...]
checking TSC synchronization across CPUs: passed.
[...]

what does it output on your system?

-- mingo

--- linux/arch/i386/kernel/smp.c.orig Fri Jan 22 13:01:53 1999
+++ linux/arch/i386/kernel/smp.c Fri Jan 22 17:34:04 1999
@@ -168,6 +168,17 @@
#endif

/*
+ * Reads and clears the Pentium Timestamp-Counter
+ */
+#define READ_TSC(x) __asm__ __volatile__ ( "rdtsc" \
+ :"=a" (((unsigned long*)&(x))[0]), \
+ "=d" (((unsigned long*)&(x))[1]))
+
+#define CLEAR_TSC \
+ __asm__ __volatile__ ("\t.byte 0x0f, 0x30;\n"::\
+ "a"(0x00001000), "d"(0x00001000), "c"(0x10):"memory")
+
+/*
* Setup routine for controlling SMP activation
*
* Command-line option of "nosmp" or "maxcpus=0" will disable SMP
@@ -713,6 +724,153 @@
return memory_start;
}

+#ifdef CONFIG_X86_TSC
+/*
+ * TSC synchronization.
+ *
+ * We first check wether all CPUs have their TSC's synchronized,
+ * then we resync if not.
+ */
+
+static atomic_t tsc_counter = ATOMIC_INIT(0);
+static atomic_t tsc_flag = ATOMIC_INIT(0);
+
+static atomic_t tsc_value_flag = ATOMIC_INIT(0);
+static unsigned long long tsc_values[NR_CPUS] = { 0, };
+static atomic_t tsc_value_count = ATOMIC_INIT(0);
+
+#define NR_LOOPS 5
+
+extern unsigned long fast_gettimeoffset_quotient;
+
+/*
+ * accurate 64-bit division, expanded to 32-bit divisions. Not terribly
+ * optimized but we need it at boot time only anyway.
+ *
+ * result == a / b
+ * == (a1 + a2*(2^32)) / b
+ * == a1/b + a2*(2^32/b)
+ * == a1/b + a2*((2^32-1)/b) + a2/b + (a2*((2^32-1) % b))/b
+ * ^---- (this multiplication can overflow)
+ */
+
+unsigned long long div64 (unsigned long long a, unsigned long long b)
+{
+ unsigned int a1, a2, b0;
+ unsigned long long res;
+
+ if (b > 0x00000000ffffffffULL)
+ return 0;
+ if (!b)
+ panic("huh?\n");
+
+ b0 = (unsigned int) b;
+ a1 = ((unsigned int*)&a)[0];
+ a2 = ((unsigned int*)&a)[1];
+
+ res = a1/b0 +
+ (unsigned long long)a2 * (unsigned long long)(0xffffffff/b0) +
+ a2 / b0 +
+ (a2 * (0xffffffff % b0)) / b0;
+
+ return res;
+}
+
+
+static void __init synchronize_tsc_bp (void)
+{
+ int i;
+ unsigned long long t0;
+ unsigned long long sum, avg;
+ long long delta;
+ unsigned long one_usec;
+ int buggy = 0;
+
+ printk("checking TSC synchronization across CPUs: ");
+
+ one_usec = ((1<<30)/fast_gettimeoffset_quotient)*(1<<2);
+
+ while (atomic_read(&tsc_counter) != smp_num_cpus-1) mb();
+ atomic_inc(&tsc_flag);
+
+ for (i = 0; i < NR_LOOPS; i++) {
+ atomic_set(&tsc_value_count, 0);
+ atomic_inc(&tsc_value_flag);
+
+ READ_TSC(tsc_values[smp_processor_id()]);
+
+ while (atomic_read(&tsc_value_count) != smp_num_cpus-1) mb();
+
+ atomic_dec(&tsc_value_flag);
+ if (i == NR_LOOPS-1)
+ CLEAR_TSC;
+ }
+ READ_TSC(t0);
+ atomic_dec(&tsc_flag);
+
+ sum = 0;
+ for (i = 0; i < NR_CPUS; i++) {
+ if (!(cpu_online_map & (1 << i)))
+ continue;
+
+ t0 = tsc_values[i];
+ sum += t0;
+ }
+ avg = div64(sum, smp_num_cpus);
+
+ sum = 0;
+ for (i = 0; i < NR_CPUS; i++) {
+ if (!(cpu_online_map & (1 << i)))
+ continue;
+
+ delta = tsc_values[i] - avg;
+ if (delta < 0)
+ delta = -delta;
+ /*
+ * We report bigger than 2 microseconds clock differences.
+ */
+ if (delta > 2*one_usec) {
+ if (!buggy) {
+ buggy = 1;
+ printk("\n");
+ }
+ printk("BIOS BUG: CPU#%d inproperly initialized, has %ld usecs TSC scew! FIXED.\n",
+ smp_processor_id(),
+ (unsigned long)div64(delta, one_usec));
+ }
+
+ sum += delta;
+ }
+ if (!buggy)
+ printk("passed.\n");
+}
+
+static void __init synchronize_tsc_ap (void)
+{
+ int i;
+ unsigned long long t0;
+
+ atomic_inc(&tsc_counter);
+ while (!atomic_read(&tsc_flag)) mb();
+
+ for (i = 0; i < NR_LOOPS; i++) {
+ while (!atomic_read(&tsc_value_flag)) mb();
+
+ READ_TSC(tsc_values[smp_processor_id()]);
+ atomic_inc(&tsc_value_count);
+ while (atomic_read(&tsc_value_flag)) mb();
+ if (i == NR_LOOPS-1)
+ CLEAR_TSC;
+ }
+ READ_TSC(t0);
+
+ atomic_dec(&tsc_counter);
+ while (atomic_read(&tsc_counter)) mb();
+}
+#undef NR_LOOPS
+
+#endif
+
extern void calibrate_delay(void);

void __init smp_callin(void)
@@ -790,6 +948,13 @@
* Allow the master to continue.
*/
set_bit(cpuid, (unsigned long *)&cpu_callin_map[0]);
+
+#ifdef CONFIG_X86_TSC
+ /*
+ * Synchronize the TSC with the BP
+ */
+ synchronize_tsc_ap ();
+#endif
}

int cpucount = 0;
@@ -1310,6 +1475,14 @@
setup_IO_APIC();
smp_done:
#endif
+#ifdef CONFIG_X86_TSC
+ /*
+ * Synchronize the TSC with the AP
+ */
+ if (cpucount)
+ synchronize_tsc_bp();
+#endif
+
}


@@ -1747,10 +1920,6 @@
* closely follows bus clocks.
*/

-#define RDTSC(x) __asm__ __volatile__ ( "rdtsc" \
- :"=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
@@ -1869,7 +2038,7 @@
/*
* We wrapped around just now. Let's start:
*/
- RDTSC(t1);
+ READ_TSC(t1);
tt1=apic_read(APIC_TMCCT);

#define LOOPS (HZ/10)
@@ -1880,7 +2049,7 @@
wait_8254_wraparound ();

tt2=apic_read(APIC_TMCCT);
- RDTSC(t2);
+ READ_TSC(t2);

/*
* The APIC bus clock counter is 32 bits only, it
--- linux/arch/i386/kernel/time.c.orig Fri Jan 22 15:01:48 1999
+++ linux/arch/i386/kernel/time.c Fri Jan 22 15:14:13 1999
@@ -86,7 +86,7 @@
* Equal to 2^32 * (1 / (clocks per usec) ).
* Initialized in time_init.
*/
-static unsigned long fast_gettimeoffset_quotient=0;
+unsigned long fast_gettimeoffset_quotient=0;

extern rwlock_t xtime_lock;

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/