lkml.org 
[lkml]   [2005]   [Mar]   [31]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subject[PATCH 2.6.12-rc1-mm5 3/3] perfctr: ppc64 driver core
ppc64 perfctr driver from David Gibson <david@gibson.dropbear.id.au>:
- ppc64 perfctr driver core

Signed-off-by: Mikael Pettersson <mikpe@csd.uu.se>

drivers/perfctr/ppc64.c | 743 ++++++++++++++++++++++++++++++++++++++++++
drivers/perfctr/ppc64_tests.c | 322 ++++++++++++++++++
drivers/perfctr/ppc64_tests.h | 12
include/asm-ppc64/perfctr.h | 166 +++++++++
4 files changed, 1243 insertions(+)

diff -rupN linux-2.6.12-rc1-mm4/drivers/perfctr/ppc64.c linux-2.6.12-rc1-mm4.perfctr-ppc64-driver/drivers/perfctr/ppc64.c
--- linux-2.6.12-rc1-mm4/drivers/perfctr/ppc64.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.12-rc1-mm4.perfctr-ppc64-driver/drivers/perfctr/ppc64.c 2005-03-31 23:37:37.000000000 +0200
@@ -0,0 +1,743 @@
+/*
+ * PPC64 performance-monitoring counters driver.
+ *
+ * based on Mikael Pettersson's 32 bit ppc code
+ * Copyright (C) 2004 David Gibson, IBM Corporation.
+ * Copyright (C) 2004 Mikael Pettersson
+ */
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/perfctr.h>
+#include <asm/prom.h>
+#include <asm/time.h> /* tb_ticks_per_jiffy */
+#include <asm/pmc.h>
+#include <asm/cputable.h>
+
+#include "ppc64_tests.h"
+
+extern void ppc64_enable_pmcs(void);
+
+/* Support for lazy perfctr SPR updates. */
+struct per_cpu_cache { /* roughly a subset of perfctr_cpu_state */
+ unsigned int id; /* cache owner id */
+ /* Physically indexed cache of the MMCRs. */
+ unsigned long ppc64_mmcr0, ppc64_mmcr1, ppc64_mmcra;
+};
+static DEFINE_PER_CPU(struct per_cpu_cache, per_cpu_cache);
+#define __get_cpu_cache(cpu) (&per_cpu(per_cpu_cache, cpu))
+#define get_cpu_cache() (&__get_cpu_var(per_cpu_cache))
+
+/* Structure for counter snapshots, as 32-bit values. */
+struct perfctr_low_ctrs {
+ unsigned int tsc;
+ unsigned int pmc[8];
+};
+
+static unsigned int new_id(void)
+{
+ static DEFINE_SPINLOCK(lock);
+ static unsigned int counter;
+ int id;
+
+ spin_lock(&lock);
+ id = ++counter;
+ spin_unlock(&lock);
+ return id;
+}
+
+static inline unsigned int read_pmc(unsigned int pmc)
+{
+ switch (pmc) {
+ case 0:
+ return mfspr(SPRN_PMC1);
+ break;
+ case 1:
+ return mfspr(SPRN_PMC2);
+ break;
+ case 2:
+ return mfspr(SPRN_PMC3);
+ break;
+ case 3:
+ return mfspr(SPRN_PMC4);
+ break;
+ case 4:
+ return mfspr(SPRN_PMC5);
+ break;
+ case 5:
+ return mfspr(SPRN_PMC6);
+ break;
+ case 6:
+ return mfspr(SPRN_PMC7);
+ break;
+ case 7:
+ return mfspr(SPRN_PMC8);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static inline void write_pmc(int pmc, s32 val)
+{
+ switch (pmc) {
+ case 0:
+ mtspr(SPRN_PMC1, val);
+ break;
+ case 1:
+ mtspr(SPRN_PMC2, val);
+ break;
+ case 2:
+ mtspr(SPRN_PMC3, val);
+ break;
+ case 3:
+ mtspr(SPRN_PMC4, val);
+ break;
+ case 4:
+ mtspr(SPRN_PMC5, val);
+ break;
+ case 5:
+ mtspr(SPRN_PMC6, val);
+ break;
+ case 6:
+ mtspr(SPRN_PMC7, val);
+ break;
+ case 7:
+ mtspr(SPRN_PMC8, val);
+ break;
+ }
+}
+
+#ifdef CONFIG_PERFCTR_INTERRUPT_SUPPORT
+static void perfctr_default_ihandler(unsigned long pc)
+{
+ unsigned int mmcr0 = mfspr(SPRN_MMCR0);
+
+ mmcr0 &= ~MMCR0_PMXE;
+ mtspr(SPRN_MMCR0, mmcr0);
+}
+
+static perfctr_ihandler_t perfctr_ihandler = perfctr_default_ihandler;
+
+void do_perfctr_interrupt(struct pt_regs *regs)
+{
+ unsigned long mmcr0;
+
+ /* interrupts are disabled here, so we don't need to
+ * preempt_disable() */
+
+ (*perfctr_ihandler)(instruction_pointer(regs));
+
+ /* clear PMAO so the interrupt doesn't reassert immediately */
+ mmcr0 = mfspr(SPRN_MMCR0) & ~MMCR0_PMAO;
+ mtspr(SPRN_MMCR0, mmcr0);
+}
+
+void perfctr_cpu_set_ihandler(perfctr_ihandler_t ihandler)
+{
+ perfctr_ihandler = ihandler ? ihandler : perfctr_default_ihandler;
+}
+
+#else
+#define perfctr_cstatus_has_ictrs(cstatus) 0
+#endif
+
+
+#if defined(CONFIG_SMP) && defined(CONFIG_PERFCTR_INTERRUPT_SUPPORT)
+
+static inline void
+set_isuspend_cpu(struct perfctr_cpu_state *state, int cpu)
+{
+ state->isuspend_cpu = cpu;
+}
+
+static inline int
+is_isuspend_cpu(const struct perfctr_cpu_state *state, int cpu)
+{
+ return state->isuspend_cpu == cpu;
+}
+
+static inline void clear_isuspend_cpu(struct perfctr_cpu_state *state)
+{
+ state->isuspend_cpu = NR_CPUS;
+}
+
+#else
+static inline void set_isuspend_cpu(struct perfctr_cpu_state *state, int cpu) { }
+static inline int is_isuspend_cpu(const struct perfctr_cpu_state *state, int cpu) { return 1; }
+static inline void clear_isuspend_cpu(struct perfctr_cpu_state *state) { }
+#endif
+
+
+static void ppc64_clear_counters(void)
+{
+ mtspr(SPRN_MMCR0, 0);
+ mtspr(SPRN_MMCR1, 0);
+ mtspr(SPRN_MMCRA, 0);
+
+ mtspr(SPRN_PMC1, 0);
+ mtspr(SPRN_PMC2, 0);
+ mtspr(SPRN_PMC3, 0);
+ mtspr(SPRN_PMC4, 0);
+ mtspr(SPRN_PMC5, 0);
+ mtspr(SPRN_PMC6, 0);
+
+ if (cpu_has_feature(CPU_FTR_PMC8)) {
+ mtspr(SPRN_PMC7, 0);
+ mtspr(SPRN_PMC8, 0);
+ }
+}
+
+/*
+ * Driver methods, internal and exported.
+ */
+
+static void perfctr_cpu_write_control(const struct perfctr_cpu_state *state)
+{
+ struct per_cpu_cache *cache;
+ unsigned long long value;
+
+ cache = get_cpu_cache();
+ /*
+ * Order matters here: update threshmult and event
+ * selectors before updating global control, which
+ * potentially enables PMIs.
+ *
+ * Since mtspr doesn't accept a runtime value for the
+ * SPR number, unroll the loop so each mtspr targets
+ * a constant SPR.
+ *
+ * For processors without MMCR2, we ensure that the
+ * cache and the state indicate the same value for it,
+ * preventing any actual mtspr to it. Ditto for MMCR1.
+ */
+ value = state->control.mmcra;
+ if (value != cache->ppc64_mmcra) {
+ cache->ppc64_mmcra = value;
+ mtspr(SPRN_MMCRA, value);
+ }
+ value = state->control.mmcr1;
+ if (value != cache->ppc64_mmcr1) {
+ cache->ppc64_mmcr1 = value;
+ mtspr(SPRN_MMCR1, value);
+ }
+ value = state->control.mmcr0;
+ if (perfctr_cstatus_has_ictrs(state->user.cstatus))
+ value |= MMCR0_PMXE;
+ if (value != cache->ppc64_mmcr0) {
+ cache->ppc64_mmcr0 = value;
+ mtspr(SPRN_MMCR0, value);
+ }
+ cache->id = state->id;
+}
+
+static void perfctr_cpu_read_counters(struct perfctr_cpu_state *state,
+ struct perfctr_low_ctrs *ctrs)
+{
+ unsigned int cstatus, i, pmc;
+
+ cstatus = state->user.cstatus;
+ if (perfctr_cstatus_has_tsc(cstatus))
+ ctrs->tsc = mftb() & 0xffffffff;
+
+ for (i = 0; i < perfctr_cstatus_nractrs(cstatus); ++i) {
+ pmc = state->user.pmc[i].map;
+ ctrs->pmc[i] = read_pmc(pmc);
+ }
+}
+
+#ifdef CONFIG_PERFCTR_INTERRUPT_SUPPORT
+static void perfctr_cpu_isuspend(struct perfctr_cpu_state *state)
+{
+ unsigned int cstatus, nrctrs, i;
+ int cpu;
+
+ cpu = smp_processor_id();
+ set_isuspend_cpu(state, cpu); /* early to limit cpu's live range */
+ cstatus = state->user.cstatus;
+ nrctrs = perfctr_cstatus_nrctrs(cstatus);
+ for (i = perfctr_cstatus_nractrs(cstatus); i < nrctrs; ++i) {
+ unsigned int pmc = state->user.pmc[i].map;
+ unsigned int now = read_pmc(pmc);
+
+ state->user.pmc[i].sum += now - state->user.pmc[i].start;
+ state->user.pmc[i].start = now;
+ }
+}
+
+static void perfctr_cpu_iresume(const struct perfctr_cpu_state *state)
+{
+ struct per_cpu_cache *cache;
+ unsigned int cstatus, nrctrs, i;
+ int cpu;
+
+ cpu = smp_processor_id();
+ cache = __get_cpu_cache(cpu);
+ if (cache->id == state->id) {
+ /* Clearing cache->id to force write_control()
+ to unfreeze MMCR0 would be done here, but it
+ is subsumed by resume()'s MMCR0 reload logic. */
+ if (is_isuspend_cpu(state, cpu)) {
+ return; /* skip reload of PMCs */
+ }
+ }
+ /*
+ * The CPU state wasn't ours.
+ *
+ * The counters must be frozen before being reinitialised,
+ * to prevent unexpected increments and missed overflows.
+ *
+ * All unused counters must be reset to a non-overflow state.
+ */
+ if (!(cache->ppc64_mmcr0 & MMCR0_FC)) {
+ cache->ppc64_mmcr0 |= MMCR0_FC;
+ mtspr(SPRN_MMCR0, cache->ppc64_mmcr0);
+ }
+ cstatus = state->user.cstatus;
+ nrctrs = perfctr_cstatus_nrctrs(cstatus);
+ for (i = perfctr_cstatus_nractrs(cstatus); i < nrctrs; ++i) {
+ write_pmc(state->user.pmc[i].map, state->user.pmc[i].start);
+ }
+}
+
+/* Call perfctr_cpu_ireload() just before perfctr_cpu_resume() to
+ bypass internal caching and force a reload if the I-mode PMCs. */
+void perfctr_cpu_ireload(struct perfctr_cpu_state *state)
+{
+#ifdef CONFIG_SMP
+ clear_isuspend_cpu(state);
+#else
+ get_cpu_cache()->id = 0;
+#endif
+}
+
+/* PRE: the counters have been suspended and sampled by perfctr_cpu_suspend() */
+unsigned int perfctr_cpu_identify_overflow(struct perfctr_cpu_state *state)
+{
+ unsigned int cstatus, nractrs, nrctrs, i;
+ unsigned int pmc_mask = 0;
+ int nr_pmcs = 6;
+
+ if (cpu_has_feature(CPU_FTR_PMC8))
+ nr_pmcs = 8;
+
+ cstatus = state->user.cstatus;
+ nractrs = perfctr_cstatus_nractrs(cstatus);
+ nrctrs = perfctr_cstatus_nrctrs(cstatus);
+
+ /* Ickity, ickity, ick. We don't have fine enough interrupt
+ * control to disable interrupts on all the counters we're not
+ * interested in. So, we have to deal with overflows on actrs
+ * amd unused PMCs as well as the ones we actually care
+ * about. */
+ for (i = 0; i < nractrs; ++i) {
+ int pmc = state->user.pmc[i].map;
+ unsigned int val = read_pmc(pmc);
+
+ /* For actrs, force a sample if they overflowed */
+
+ if ((int)val < 0) {
+ state->user.pmc[i].sum += val - state->user.pmc[i].start;
+ state->user.pmc[i].start = 0;
+ write_pmc(pmc, 0);
+ }
+ }
+ for (; i < nrctrs; ++i) {
+ if ((int)state->user.pmc[i].start < 0) { /* PPC64-specific */
+ int pmc = state->user.pmc[i].map;
+ /* XXX: "+=" to correct for overshots */
+ state->user.pmc[i].start = state->control.ireset[pmc];
+ pmc_mask |= (1 << i);
+ }
+ }
+
+ /* Clear any unused overflowed counters, so we don't loop on
+ * the interrupt */
+ for (i = 0; i < nr_pmcs; ++i) {
+ if (! (state->unused_pmcs & (1<<i)))
+ continue;
+
+ if ((int)read_pmc(i) < 0)
+ write_pmc(i, 0);
+ }
+
+ /* XXX: HW cleared MMCR0[ENINT]. We presumably cleared the entire
+ MMCR0, so the re-enable occurs automatically later, no? */
+ return pmc_mask;
+}
+
+static inline int check_ireset(struct perfctr_cpu_state *state)
+{
+ unsigned int nrctrs, i;
+
+ i = state->control.header.nractrs;
+ nrctrs = i + state->control.header.nrictrs;
+ for(; i < nrctrs; ++i) {
+ unsigned int pmc = state->user.pmc[i].map;
+ if ((int)state->control.ireset[pmc] < 0) /* PPC64-specific */
+ return -EINVAL;
+ state->user.pmc[i].start = state->control.ireset[pmc];
+ }
+ return 0;
+}
+
+#else /* CONFIG_PERFCTR_INTERRUPT_SUPPORT */
+static inline void perfctr_cpu_isuspend(struct perfctr_cpu_state *state) { }
+static inline void perfctr_cpu_iresume(const struct perfctr_cpu_state *state) { }
+static inline int check_ireset(struct perfctr_cpu_state *state) { return 0; }
+#endif /* CONFIG_PERFCTR_INTERRUPT_SUPPORT */
+
+static int check_control(struct perfctr_cpu_state *state)
+{
+ unsigned int i, nractrs, nrctrs, pmc_mask, pmc;
+ unsigned int nr_pmcs = 6;
+
+ if (cpu_has_feature(CPU_FTR_PMC8))
+ nr_pmcs = 8;
+
+ nractrs = state->control.header.nractrs;
+ nrctrs = nractrs + state->control.header.nrictrs;
+ if (nrctrs < nractrs || nrctrs > nr_pmcs)
+ return -EINVAL;
+
+ pmc_mask = 0;
+ for (i = 0; i < nrctrs; ++i) {
+ pmc = state->control.pmc_map[i];
+ state->user.pmc[i].map = pmc;
+ if (pmc >= nr_pmcs || (pmc_mask & (1<<pmc)))
+ return -EINVAL;
+ pmc_mask |= (1<<pmc);
+ }
+
+ /* We need to retain internal control of PMXE and PMAO. PMXE
+ * will be set when ictrs are active. We can't really handle
+ * TB interrupts, so we don't allow those either. */
+ if ( (state->control.mmcr0 & MMCR0_PMXE)
+ || (state->control.mmcr0 & MMCR0_PMAO)
+ || (state->control.mmcr0 & MMCR0_TBEE) )
+ return -EINVAL;
+
+ state->unused_pmcs = ((1 << nr_pmcs)-1) & ~pmc_mask;
+
+ state->id = new_id();
+
+ return 0;
+}
+
+int perfctr_cpu_update_control(struct perfctr_cpu_state *state, int is_global)
+{
+ int err;
+
+ clear_isuspend_cpu(state);
+ state->user.cstatus = 0;
+
+ /* disallow i-mode counters if we cannot catch the interrupts */
+ if (!(perfctr_info.cpu_features & PERFCTR_FEATURE_PCINT)
+ && state->control.header.nrictrs)
+ return -EPERM;
+
+ err = check_control(state); /* may initialise state->cstatus */
+ if (err < 0)
+ return err;
+ err = check_ireset(state);
+ if (err < 0)
+ return err;
+ state->user.cstatus |= perfctr_mk_cstatus(state->control.header.tsc_on,
+ state->control.header.nractrs,
+ state->control.header.nrictrs);
+ return 0;
+}
+
+/*
+ * get_reg_offset() maps SPR numbers to offsets into struct perfctr_cpu_control.
+ */
+static const struct {
+ unsigned int spr;
+ unsigned int offset;
+ unsigned int size;
+} reg_offsets[] = {
+ { SPRN_MMCR0, offsetof(struct perfctr_cpu_control, mmcr0), sizeof(long) },
+ { SPRN_MMCR1, offsetof(struct perfctr_cpu_control, mmcr1), sizeof(long) },
+ { SPRN_MMCRA, offsetof(struct perfctr_cpu_control, mmcra), sizeof(long) },
+ { SPRN_PMC1, offsetof(struct perfctr_cpu_control, ireset[1-1]), sizeof(int) },
+ { SPRN_PMC2, offsetof(struct perfctr_cpu_control, ireset[2-1]), sizeof(int) },
+ { SPRN_PMC3, offsetof(struct perfctr_cpu_control, ireset[3-1]), sizeof(int) },
+ { SPRN_PMC4, offsetof(struct perfctr_cpu_control, ireset[4-1]), sizeof(int) },
+ { SPRN_PMC5, offsetof(struct perfctr_cpu_control, ireset[5-1]), sizeof(int) },
+ { SPRN_PMC6, offsetof(struct perfctr_cpu_control, ireset[6-1]), sizeof(int) },
+ { SPRN_PMC7, offsetof(struct perfctr_cpu_control, ireset[7-1]), sizeof(int) },
+ { SPRN_PMC8, offsetof(struct perfctr_cpu_control, ireset[8-1]), sizeof(int) },
+};
+
+static int get_reg_offset(unsigned int spr, unsigned int *size)
+{
+ unsigned int i;
+
+ for(i = 0; i < ARRAY_SIZE(reg_offsets); ++i)
+ if (spr == reg_offsets[i].spr) {
+ *size = reg_offsets[i].size;
+ return reg_offsets[i].offset;
+ }
+ return -1;
+}
+
+static int access_regs(struct perfctr_cpu_control *control,
+ void *argp, unsigned int argbytes, int do_write)
+{
+ struct perfctr_cpu_reg *regs;
+ unsigned int i, nr_regs, size;
+ int offset;
+
+ nr_regs = argbytes / sizeof(struct perfctr_cpu_reg);
+ if (nr_regs * sizeof(struct perfctr_cpu_reg) != argbytes)
+ return -EINVAL;
+ regs = (struct perfctr_cpu_reg*)argp;
+
+ for(i = 0; i < nr_regs; ++i) {
+ offset = get_reg_offset(regs[i].nr, &size);
+ if (offset < 0)
+ return -EINVAL;
+ if (size == sizeof(long)) {
+ unsigned long *where = (unsigned long*)((char*)control + offset);
+ if (do_write)
+ *where = regs[i].value;
+ else
+ regs[i].value = *where;
+ } else {
+ unsigned int *where = (unsigned int*)((char*)control + offset);
+ if (do_write)
+ *where = regs[i].value;
+ else
+ regs[i].value = *where;
+ }
+ }
+ return argbytes;
+}
+
+int perfctr_cpu_control_write(struct perfctr_cpu_control *control, unsigned int domain,
+ const void *srcp, unsigned int srcbytes)
+{
+ if (domain != PERFCTR_DOMAIN_CPU_REGS)
+ return -EINVAL;
+ return access_regs(control, (void*)srcp, srcbytes, 1);
+}
+
+int perfctr_cpu_control_read(const struct perfctr_cpu_control *control, unsigned int domain,
+ void *dstp, unsigned int dstbytes)
+{
+ if (domain != PERFCTR_DOMAIN_CPU_REGS)
+ return -EINVAL;
+ return access_regs((struct perfctr_cpu_control*)control, dstp, dstbytes, 0);
+}
+
+void perfctr_cpu_suspend(struct perfctr_cpu_state *state)
+{
+ unsigned int i, cstatus;
+ struct perfctr_low_ctrs now;
+
+ /* quiesce the counters */
+ mtspr(SPRN_MMCR0, MMCR0_FC);
+ get_cpu_cache()->ppc64_mmcr0 = MMCR0_FC;
+
+ if (perfctr_cstatus_has_ictrs(state->user.cstatus))
+ perfctr_cpu_isuspend(state);
+
+ perfctr_cpu_read_counters(state, &now);
+ cstatus = state->user.cstatus;
+ if (perfctr_cstatus_has_tsc(cstatus))
+ state->user.tsc_sum += now.tsc - state->user.tsc_start;
+
+ for (i = 0; i < perfctr_cstatus_nractrs(cstatus); ++i)
+ state->user.pmc[i].sum += now.pmc[i] - state->user.pmc[i].start;
+}
+
+void perfctr_cpu_resume(struct perfctr_cpu_state *state)
+{
+ struct perfctr_low_ctrs now;
+ unsigned int i, cstatus;
+
+ if (perfctr_cstatus_has_ictrs(state->user.cstatus))
+ perfctr_cpu_iresume(state);
+ perfctr_cpu_write_control(state);
+
+ perfctr_cpu_read_counters(state, &now);
+ cstatus = state->user.cstatus;
+ if (perfctr_cstatus_has_tsc(cstatus))
+ state->user.tsc_start = now.tsc;
+
+ for (i = 0; i < perfctr_cstatus_nractrs(cstatus); ++i)
+ state->user.pmc[i].start = now.pmc[i];
+
+ /* XXX: if (SMP && start.tsc == now.tsc) ++now.tsc; */
+}
+
+void perfctr_cpu_sample(struct perfctr_cpu_state *state)
+{
+ unsigned int i, cstatus, nractrs;
+ struct perfctr_low_ctrs now;
+
+ perfctr_cpu_read_counters(state, &now);
+ cstatus = state->user.cstatus;
+ if (perfctr_cstatus_has_tsc(cstatus)) {
+ state->user.tsc_sum += now.tsc - state->user.tsc_start;
+ state->user.tsc_start = now.tsc;
+ }
+ nractrs = perfctr_cstatus_nractrs(cstatus);
+ for(i = 0; i < nractrs; ++i) {
+ state->user.pmc[i].sum += now.pmc[i] - state->user.pmc[i].start;
+ state->user.pmc[i].start = now.pmc[i];
+ }
+}
+
+static void perfctr_cpu_clear_counters(void)
+{
+ struct per_cpu_cache *cache;
+
+ cache = get_cpu_cache();
+ memset(cache, 0, sizeof *cache);
+ cache->id = 0;
+
+ ppc64_clear_counters();
+}
+
+/****************************************************************
+ * *
+ * Processor detection and initialisation procedures. *
+ * *
+ ****************************************************************/
+
+static void ppc64_cpu_setup(void)
+{
+ /* allow user to initialize these???? */
+
+ unsigned long long mmcr0 = mfspr(SPRN_MMCR0);
+ unsigned long long mmcra = mfspr(SPRN_MMCRA);
+
+
+ ppc64_enable_pmcs();
+
+ mmcr0 |= MMCR0_FC;
+ mtspr(SPRN_MMCR0, mmcr0);
+
+ mmcr0 |= MMCR0_FCM1|MMCR0_PMXE|MMCR0_FCECE;
+ mmcr0 |= MMCR0_PMC1CE|MMCR0_PMCjCE;
+ mtspr(SPRN_MMCR0, mmcr0);
+
+ mmcra |= MMCRA_SAMPLE_ENABLE;
+ mtspr(SPRN_MMCRA, mmcra);
+
+ printk("setup on cpu %d, mmcr0 %lx\n", smp_processor_id(),
+ mfspr(SPRN_MMCR0));
+ printk("setup on cpu %d, mmcr1 %lx\n", smp_processor_id(),
+ mfspr(SPRN_MMCR1));
+ printk("setup on cpu %d, mmcra %lx\n", smp_processor_id(),
+ mfspr(SPRN_MMCRA));
+
+/* mtmsrd(mfmsr() | MSR_PMM); */
+
+ ppc64_clear_counters();
+
+ mmcr0 = mfspr(SPRN_MMCR0);
+ mmcr0 &= ~MMCR0_PMAO;
+ mmcr0 &= ~MMCR0_FC;
+ mtspr(SPRN_MMCR0, mmcr0);
+
+ printk("start on cpu %d, mmcr0 %llx\n", smp_processor_id(), mmcr0);
+}
+
+
+static void perfctr_cpu_clear_one(void *ignore)
+{
+ /* PREEMPT note: when called via on_each_cpu(),
+ this is in IRQ context with preemption disabled. */
+ perfctr_cpu_clear_counters();
+}
+
+static void perfctr_cpu_reset(void)
+{
+ on_each_cpu(perfctr_cpu_clear_one, NULL, 1, 1);
+ perfctr_cpu_set_ihandler(NULL);
+}
+
+int __init perfctr_cpu_init(void)
+{
+ extern unsigned long ppc_proc_freq;
+ extern unsigned long ppc_tb_freq;
+
+ perfctr_info.cpu_features = PERFCTR_FEATURE_RDTSC
+ | PERFCTR_FEATURE_RDPMC | PERFCTR_FEATURE_PCINT;
+
+ perfctr_cpu_name = "PowerPC64";
+
+ perfctr_info.cpu_khz = ppc_proc_freq / 1000;
+ /* We need to round here rather than truncating, because in a
+ * few cases the raw ratio can end up being 7.9999 or
+ * suchlike */
+ perfctr_info.tsc_to_cpu_mult =
+ (ppc_proc_freq + ppc_tb_freq - 1) / ppc_tb_freq;
+
+ on_each_cpu((void *)ppc64_cpu_setup, NULL, 0, 1);
+
+ perfctr_ppc64_init_tests();
+
+ perfctr_cpu_reset();
+ return 0;
+}
+
+void __exit perfctr_cpu_exit(void)
+{
+ perfctr_cpu_reset();
+}
+
+/****************************************************************
+ * *
+ * Hardware reservation. *
+ * *
+ ****************************************************************/
+
+static spinlock_t service_mutex = SPIN_LOCK_UNLOCKED;
+static const char *current_service = NULL;
+
+const char *perfctr_cpu_reserve(const char *service)
+{
+ const char *ret;
+
+ spin_lock(&service_mutex);
+
+ ret = current_service;
+ if (ret)
+ goto out;
+
+ ret = "unknown driver (oprofile?)";
+ if (reserve_pmc_hardware(do_perfctr_interrupt) != 0)
+ goto out;
+
+ current_service = service;
+ ret = NULL;
+
+ out:
+ spin_unlock(&service_mutex);
+ return ret;
+}
+
+void perfctr_cpu_release(const char *service)
+{
+ spin_lock(&service_mutex);
+
+ if (service != current_service) {
+ printk(KERN_ERR "%s: attempt by %s to release while reserved by %s\n",
+ __FUNCTION__, service, current_service);
+ goto out;
+ }
+
+ /* power down the counters */
+ perfctr_cpu_reset();
+ current_service = NULL;
+ release_pmc_hardware();
+
+ out:
+ spin_unlock(&service_mutex);
+}
diff -rupN linux-2.6.12-rc1-mm4/drivers/perfctr/ppc64_tests.c linux-2.6.12-rc1-mm4.perfctr-ppc64-driver/drivers/perfctr/ppc64_tests.c
--- linux-2.6.12-rc1-mm4/drivers/perfctr/ppc64_tests.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.12-rc1-mm4.perfctr-ppc64-driver/drivers/perfctr/ppc64_tests.c 2005-03-31 23:37:37.000000000 +0200
@@ -0,0 +1,322 @@
+/*
+ * Performance-monitoring counters driver.
+ * Optional PPC64-specific init-time tests.
+ *
+ * Copyright (C) 2004 David Gibson, IBM Corporation.
+ * Copyright (C) 2004 Mikael Pettersson
+ */
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/perfctr.h>
+#include <asm/processor.h>
+#include <asm/time.h> /* for tb_ticks_per_jiffy */
+#include "ppc64_tests.h"
+
+#define NITER 256
+#define X2(S) S"; "S
+#define X8(S) X2(X2(X2(S)))
+
+static void __init do_read_tbl(unsigned int unused)
+{
+ unsigned int i, dummy;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mftbl %0") : "=r"(dummy));
+}
+
+static void __init do_read_pmc1(unsigned int unused)
+{
+ unsigned int i, dummy;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mfspr %0," __stringify(SPRN_PMC1)) : "=r"(dummy));
+}
+
+static void __init do_read_pmc2(unsigned int unused)
+{
+ unsigned int i, dummy;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mfspr %0," __stringify(SPRN_PMC2)) : "=r"(dummy));
+}
+
+static void __init do_read_pmc3(unsigned int unused)
+{
+ unsigned int i, dummy;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mfspr %0," __stringify(SPRN_PMC3)) : "=r"(dummy));
+}
+
+static void __init do_read_pmc4(unsigned int unused)
+{
+ unsigned int i, dummy;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mfspr %0," __stringify(SPRN_PMC4)) : "=r"(dummy));
+}
+
+static void __init do_read_mmcr0(unsigned int unused)
+{
+ unsigned int i, dummy;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mfspr %0," __stringify(SPRN_MMCR0)) : "=r"(dummy));
+}
+
+static void __init do_read_mmcr1(unsigned int unused)
+{
+ unsigned int i, dummy;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mfspr %0," __stringify(SPRN_MMCR1)) : "=r"(dummy));
+}
+
+static void __init do_write_pmc2(unsigned int arg)
+{
+ unsigned int i;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mtspr " __stringify(SPRN_PMC2) ",%0") : : "r"(arg));
+}
+
+static void __init do_write_pmc3(unsigned int arg)
+{
+ unsigned int i;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mtspr " __stringify(SPRN_PMC3) ",%0") : : "r"(arg));
+}
+
+static void __init do_write_pmc4(unsigned int arg)
+{
+ unsigned int i;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mtspr " __stringify(SPRN_PMC4) ",%0") : : "r"(arg));
+}
+
+static void __init do_write_mmcr1(unsigned int arg)
+{
+ unsigned int i;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mtspr " __stringify(SPRN_MMCR1) ",%0") : : "r"(arg));
+}
+
+static void __init do_write_mmcr0(unsigned int arg)
+{
+ unsigned int i;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__(X8("mtspr " __stringify(SPRN_MMCR0) ",%0") : : "r"(arg));
+}
+
+static void __init do_empty_loop(unsigned int unused)
+{
+ unsigned i;
+ for(i = 0; i < NITER/8; ++i)
+ __asm__ __volatile__("" : : );
+}
+
+static unsigned __init run(void (*doit)(unsigned int), unsigned int arg)
+{
+ unsigned int start, stop;
+ start = mfspr(SPRN_PMC1);
+ (*doit)(arg); /* should take < 2^32 cycles to complete */
+ stop = mfspr(SPRN_PMC1);
+ return stop - start;
+}
+
+static void __init init_tests_message(void)
+{
+#if 0
+ printk(KERN_INFO "Please email the following PERFCTR INIT lines "
+ "to mikpe@csd.uu.se\n"
+ KERN_INFO "To remove this message, rebuild the driver "
+ "with CONFIG_PERFCTR_INIT_TESTS=n\n");
+ printk(KERN_INFO "PERFCTR INIT: PVR 0x%08x, CPU clock %u kHz, TB clock %lu kHz\n",
+ pvr,
+ perfctr_info.cpu_khz,
+ tb_ticks_per_jiffy*(HZ/10)/(1000/10));
+#endif
+}
+
+static void __init clear(void)
+{
+ mtspr(SPRN_MMCR0, 0);
+ mtspr(SPRN_MMCR1, 0);
+ mtspr(SPRN_MMCRA, 0);
+ mtspr(SPRN_PMC1, 0);
+ mtspr(SPRN_PMC2, 0);
+ mtspr(SPRN_PMC3, 0);
+ mtspr(SPRN_PMC4, 0);
+ mtspr(SPRN_PMC5, 0);
+ mtspr(SPRN_PMC6, 0);
+ mtspr(SPRN_PMC7, 0);
+ mtspr(SPRN_PMC8, 0);
+}
+
+static void __init check_fcece(unsigned int pmc1ce)
+{
+ unsigned int mmcr0;
+ unsigned int pmc1;
+ int x = 0;
+
+ /* JHE check out section 1.6.6.2 of the POWER5 pdf */
+
+ /*
+ * This test checks if MMCR0[FC] is set after PMC1 overflows
+ * when MMCR0[FCECE] is set.
+ * 74xx documentation states this behaviour, while documentation
+ * for 604/750 processors doesn't mention this at all.
+ *
+ * Also output the value of PMC1 shortly after the overflow.
+ * This tells us if PMC1 really was frozen. On 604/750, it may not
+ * freeze since we don't enable PMIs. [No freeze confirmed on 750.]
+ *
+ * When pmc1ce == 0, MMCR0[PMC1CE] is zero. It's unclear whether
+ * this masks all PMC1 overflow events or just PMC1 PMIs.
+ *
+ * PMC1 counts processor cycles, with 100 to go before overflowing.
+ * FCECE is set.
+ * PMC1CE is clear if !pmc1ce, otherwise set.
+ */
+ pmc1 = mfspr(SPRN_PMC1);
+
+ mtspr(SPRN_PMC1, 0x80000000-100);
+ mmcr0 = MMCR0_FCECE | MMCR0_SHRFC;
+
+ if (pmc1ce)
+ mmcr0 |= MMCR0_PMC1CE;
+
+ mtspr(SPRN_MMCR0, mmcr0);
+
+ pmc1 = mfspr(SPRN_PMC1);
+
+ do {
+ do_empty_loop(0);
+
+ pmc1 = mfspr(SPRN_PMC1);
+ if (x++ > 20000000) {
+ break;
+ }
+ } while (!(mfspr(SPRN_PMC1) & 0x80000000));
+ do_empty_loop(0);
+
+ printk(KERN_INFO "PERFCTR INIT: %s(%u): MMCR0[FC] is %u, PMC1 is %#lx\n",
+ __FUNCTION__, pmc1ce,
+ !!(mfspr(SPRN_MMCR0) & MMCR0_FC), mfspr(SPRN_PMC1));
+ mtspr(SPRN_MMCR0, 0);
+ mtspr(SPRN_PMC1, 0);
+}
+
+static void __init check_trigger(unsigned int pmc1ce)
+{
+ unsigned int mmcr0;
+ unsigned int pmc1;
+ int x = 0;
+
+ /*
+ * This test checks if MMCR0[TRIGGER] is reset after PMC1 overflows.
+ * 74xx documentation states this behaviour, while documentation
+ * for 604/750 processors doesn't mention this at all.
+ * [No reset confirmed on 750.]
+ *
+ * Also output the values of PMC1 and PMC2 shortly after the overflow.
+ * PMC2 should be equal to PMC1-0x80000000.
+ *
+ * When pmc1ce == 0, MMCR0[PMC1CE] is zero. It's unclear whether
+ * this masks all PMC1 overflow events or just PMC1 PMIs.
+ *
+ * PMC1 counts processor cycles, with 100 to go before overflowing.
+ * PMC2 counts processor cycles, starting from 0.
+ * TRIGGER is set, so PMC2 doesn't start until PMC1 overflows.
+ * PMC1CE is clear if !pmc1ce, otherwise set.
+ */
+ mtspr(SPRN_PMC2, 0);
+ mtspr(SPRN_PMC1, 0x80000000-100);
+ mmcr0 = MMCR0_TRIGGER | MMCR0_SHRFC | MMCR0_FCHV;
+
+ if (pmc1ce)
+ mmcr0 |= MMCR0_PMC1CE;
+
+ mtspr(SPRN_MMCR0, mmcr0);
+ do {
+ do_empty_loop(0);
+ pmc1 = mfspr(SPRN_PMC1);
+ if (x++ > 20000000) {
+ break;
+ }
+
+ } while (!(mfspr(SPRN_PMC1) & 0x80000000));
+ do_empty_loop(0);
+ printk(KERN_INFO "PERFCTR INIT: %s(%u): MMCR0[TRIGGER] is %u, PMC1 is %#lx, PMC2 is %#lx\n",
+ __FUNCTION__, pmc1ce,
+ !!(mfspr(SPRN_MMCR0) & MMCR0_TRIGGER), mfspr(SPRN_PMC1), mfspr(SPRN_PMC2));
+ mtspr(SPRN_MMCR0, 0);
+ mtspr(SPRN_PMC1, 0);
+ mtspr(SPRN_PMC2, 0);
+}
+
+static void __init measure_overheads(void)
+{
+ int i;
+ unsigned int mmcr0, loop, ticks[12];
+ const char *name[12];
+
+ clear();
+
+ /* PMC1 = "processor cycles",
+ PMC2 = "completed instructions",
+ not disabled in any mode,
+ no interrupts */
+ /* mmcr0 = (0x01 << 6) | (0x02 << 0); */
+ mmcr0 = MMCR0_SHRFC | MMCR0_FCWAIT;
+ mtspr(SPRN_MMCR0, mmcr0);
+
+ name[0] = "mftbl";
+ ticks[0] = run(do_read_tbl, 0);
+ name[1] = "mfspr (pmc1)";
+ ticks[1] = run(do_read_pmc1, 0);
+ name[2] = "mfspr (pmc2)";
+ ticks[2] = run(do_read_pmc2, 0);
+ name[3] = "mfspr (pmc3)";
+ ticks[3] = run(do_read_pmc3, 0);
+ name[4] = "mfspr (pmc4)";
+ ticks[4] = run(do_read_pmc4, 0);
+ name[5] = "mfspr (mmcr0)";
+ ticks[5] = run(do_read_mmcr0, 0);
+ name[6] = "mfspr (mmcr1)";
+ ticks[6] = run(do_read_mmcr1, 0);
+ name[7] = "mtspr (pmc2)";
+ ticks[7] = run(do_write_pmc2, 0);
+ name[8] = "mtspr (pmc3)";
+ ticks[8] = run(do_write_pmc3, 0);
+ name[9] = "mtspr (pmc4)";
+ ticks[9] = run(do_write_pmc4, 0);
+ name[10] = "mtspr (mmcr1)";
+ ticks[10] = run(do_write_mmcr1, 0);
+ name[11] = "mtspr (mmcr0)";
+ ticks[11] = run(do_write_mmcr0, mmcr0);
+
+ loop = run(do_empty_loop, 0);
+
+ clear();
+
+ init_tests_message();
+ printk(KERN_INFO "PERFCTR INIT: NITER == %u\n", NITER);
+ printk(KERN_INFO "PERFCTR INIT: loop overhead is %u cycles\n", loop);
+ for(i = 0; i < ARRAY_SIZE(ticks); ++i) {
+ unsigned int x;
+ if (!ticks[i])
+ continue;
+ x = ((ticks[i] - loop) * 10) / NITER;
+ printk(KERN_INFO "PERFCTR INIT: %s cost is %u.%u cycles (%u total)\n",
+ name[i], x/10, x%10, ticks[i]);
+ }
+
+ check_fcece(0);
+#if 0
+ check_fcece(1);
+ check_trigger(0);
+ check_trigger(1);
+#endif
+}
+
+void __init perfctr_ppc64_init_tests(void)
+{
+ preempt_disable();
+ measure_overheads();
+ preempt_enable();
+}
diff -rupN linux-2.6.12-rc1-mm4/drivers/perfctr/ppc64_tests.h linux-2.6.12-rc1-mm4.perfctr-ppc64-driver/drivers/perfctr/ppc64_tests.h
--- linux-2.6.12-rc1-mm4/drivers/perfctr/ppc64_tests.h 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.12-rc1-mm4.perfctr-ppc64-driver/drivers/perfctr/ppc64_tests.h 2005-03-31 23:37:37.000000000 +0200
@@ -0,0 +1,12 @@
+/*
+ * Performance-monitoring counters driver.
+ * Optional PPC32-specific init-time tests.
+ *
+ * Copyright (C) 2004 Mikael Pettersson
+ */
+
+#ifdef CONFIG_PERFCTR_INIT_TESTS
+extern void perfctr_ppc64_init_tests(void);
+#else
+static inline void perfctr_ppc64_init_tests(void) { }
+#endif
diff -rupN linux-2.6.12-rc1-mm4/include/asm-ppc64/perfctr.h linux-2.6.12-rc1-mm4.perfctr-ppc64-driver/include/asm-ppc64/perfctr.h
--- linux-2.6.12-rc1-mm4/include/asm-ppc64/perfctr.h 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.12-rc1-mm4.perfctr-ppc64-driver/include/asm-ppc64/perfctr.h 2005-03-31 23:37:37.000000000 +0200
@@ -0,0 +1,166 @@
+/*
+ * PPC64 Performance-Monitoring Counters driver
+ *
+ * Copyright (C) 2004 David Gibson, IBM Corporation.
+ * Copyright (C) 2004 Mikael Pettersson
+ */
+#ifndef _ASM_PPC64_PERFCTR_H
+#define _ASM_PPC64_PERFCTR_H
+
+#include <asm/types.h>
+
+struct perfctr_sum_ctrs {
+ __u64 tsc;
+ __u64 pmc[8]; /* the size is not part of the user ABI */
+};
+
+struct perfctr_cpu_control_header {
+ __u32 tsc_on;
+ __u32 nractrs; /* number of accumulation-mode counters */
+ __u32 nrictrs; /* number of interrupt-mode counters */
+};
+
+struct perfctr_cpu_state_user {
+ __u32 cstatus;
+ /* The two tsc fields must be inlined. Placing them in a
+ sub-struct causes unwanted internal padding on x86-64. */
+ __u32 tsc_start;
+ __u64 tsc_sum;
+ struct {
+ __u32 map;
+ __u32 start;
+ __u64 sum;
+ } pmc[8]; /* the size is not part of the user ABI */
+};
+
+/* cstatus is a re-encoding of control.tsc_on/nractrs/nrictrs
+ which should have less overhead in most cases */
+/* XXX: ppc driver internally also uses cstatus&(1<<30) */
+
+static inline
+unsigned int perfctr_mk_cstatus(unsigned int tsc_on, unsigned int nractrs,
+ unsigned int nrictrs)
+{
+ return (tsc_on<<31) | (nrictrs<<16) | ((nractrs+nrictrs)<<8) | nractrs;
+}
+
+static inline unsigned int perfctr_cstatus_enabled(unsigned int cstatus)
+{
+ return cstatus;
+}
+
+static inline int perfctr_cstatus_has_tsc(unsigned int cstatus)
+{
+ return (int)cstatus < 0; /* test and jump on sign */
+}
+
+static inline unsigned int perfctr_cstatus_nractrs(unsigned int cstatus)
+{
+ return cstatus & 0x7F; /* and with imm8 */
+}
+
+static inline unsigned int perfctr_cstatus_nrctrs(unsigned int cstatus)
+{
+ return (cstatus >> 8) & 0x7F;
+}
+
+static inline unsigned int perfctr_cstatus_has_ictrs(unsigned int cstatus)
+{
+ return cstatus & (0x7F << 16);
+}
+
+/*
+ * 'struct siginfo' support for perfctr overflow signals.
+ * In unbuffered mode, si_code is set to SI_PMC_OVF and a bitmask
+ * describing which perfctrs overflowed is put in si_pmc_ovf_mask.
+ * A bitmask is used since more than one perfctr can have overflowed
+ * by the time the interrupt handler runs.
+ */
+#define SI_PMC_OVF -8
+#define si_pmc_ovf_mask _sifields._pad[0] /* XXX: use an unsigned field later */
+
+#ifdef __KERNEL__
+
+#if defined(CONFIG_PERFCTR)
+
+struct perfctr_cpu_control {
+ struct perfctr_cpu_control_header header;
+ u64 mmcr0;
+ u64 mmcr1;
+ u64 mmcra;
+ unsigned int ireset[8]; /* [0,0x7fffffff], for i-mode counters, physical indices */
+ unsigned int pmc_map[8]; /* virtual to physical index map */
+};
+
+struct perfctr_cpu_state {
+ /* Don't change field order here without first considering the number
+ of cache lines touched during sampling and context switching. */
+ unsigned int id;
+ int isuspend_cpu;
+ struct perfctr_cpu_state_user user;
+ unsigned int unused_pmcs;
+ struct perfctr_cpu_control control;
+};
+
+/* Driver init/exit. */
+extern int perfctr_cpu_init(void);
+extern void perfctr_cpu_exit(void);
+
+/* CPU type name. */
+extern char *perfctr_cpu_name;
+
+/* Hardware reservation. */
+extern const char *perfctr_cpu_reserve(const char *service);
+extern void perfctr_cpu_release(const char *service);
+
+/* PRE: state has no running interrupt-mode counters.
+ Check that the new control data is valid.
+ Update the driver's private control data.
+ Returns a negative error code if the control data is invalid. */
+extern int perfctr_cpu_update_control(struct perfctr_cpu_state *state, int is_global);
+
+/* Parse and update control for the given domain. */
+extern int perfctr_cpu_control_write(struct perfctr_cpu_control *control,
+ unsigned int domain,
+ const void *srcp, unsigned int srcbytes);
+
+/* Retrieve and format control for the given domain.
+ Returns number of bytes written. */
+extern int perfctr_cpu_control_read(const struct perfctr_cpu_control *control,
+ unsigned int domain,
+ void *dstp, unsigned int dstbytes);
+
+/* Read a-mode counters. Subtract from start and accumulate into sums.
+ Must be called with preemption disabled. */
+extern void perfctr_cpu_suspend(struct perfctr_cpu_state *state);
+
+/* Write control registers. Read a-mode counters into start.
+ Must be called with preemption disabled. */
+extern void perfctr_cpu_resume(struct perfctr_cpu_state *state);
+
+/* Perform an efficient combined suspend/resume operation.
+ Must be called with preemption disabled. */
+extern void perfctr_cpu_sample(struct perfctr_cpu_state *state);
+
+/* The type of a perfctr overflow interrupt handler.
+ It will be called in IRQ context, with preemption disabled. */
+typedef void (*perfctr_ihandler_t)(unsigned long pc);
+
+/* Operations related to overflow interrupt handling. */
+#ifdef CONFIG_PERFCTR_INTERRUPT_SUPPORT
+extern void perfctr_cpu_set_ihandler(perfctr_ihandler_t);
+extern void perfctr_cpu_ireload(struct perfctr_cpu_state*);
+extern unsigned int perfctr_cpu_identify_overflow(struct perfctr_cpu_state*);
+#else
+static inline void perfctr_cpu_set_ihandler(perfctr_ihandler_t x) { }
+#endif
+static inline int perfctr_cpu_has_pending_interrupt(const struct perfctr_cpu_state *state)
+{
+ return 0;
+}
+
+#endif /* CONFIG_PERFCTR */
+
+#endif /* __KERNEL__ */
+
+#endif /* _ASM_PPC64_PERFCTR_H */
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
\
 
 \ /
  Last update: 2005-04-06 13:31    [W:0.078 / U:0.200 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site