Re: [RFC Implement clockevents/clocksource for R4000-style cp0 timer [take #3]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

Sergei Shtylyov wrote:
Franck Bui-Huu wrote:
   - Rename CP0_HPT_TIMER into CP0_CLOCKS but I'm still not sure. If

   Why not just CP0_TIMER? Hm, CP0_HPT_TIMER was even redundant... :-)


I already gave up CP0_HPT_TIMER, as you said it was redundant. Now I use
CPO_CLOCKS but I'm not really happy with it.

Ok for CP0_TIMER...

     someone can come with a better idea that would be nice. BTW
     hpt-cp0.c file should be renamed into clock-cp0.c too.

   I suggest timer-cp0.c ot cp0-timer.c


Ok for timer-cp0.c

 arch/mips/Kconfig          |    9 +
 arch/mips/kernel/Makefile  |    2 +
 arch/mips/kernel/hpt-cp0.c |  259 +++++++++++++++++++++++++++
 arch/mips/kernel/process.c |    3 +
 arch/mips/kernel/smp.c     |    2 +
 arch/mips/kernel/time.c    |  416
++++----------------------------------------
 include/asm-mips/hpt.h     |   36 ++++

   That HPT just doesn't want to die. :-)

yes I wanted to be sure about the name before getting rid of
all these 'hpt'. Done now.


[...]
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 7bcf38d..a994af1 100644
[...]
@@ -1741,6 +1749,7 @@ config HZ
     default 1000 if HZ_1000
     default 1024 if HZ_1024

+source "kernel/time/Kconfig"
 source "kernel/Kconfig.preempt"

   I would have put this part into a separate patch, along with tickless
mode
hooks in arch/mips/kernel/process.c...


Fair enough, that was my plan too but I wanted this single rfc patch
to be functional.

+static int disable_clockevent __initdata;
+static int disable_clocksource __initdata;
+
+static int __init cp0_clock_setup(char *arg)
+{
+    if (arg == NULL)
+        return -EINVAL;
+
+    if (!strcmp(arg, "disable_event"))
+        disable_clockevent = 1;
+    else if (!strcmp(arg, "disable_source"))
+        disable_clocksource = 1;
+    else if (!strcmp(arg, "disable_both")) {
+        disable_clocksource = 1;
+        disable_clockevent = 1;

   How about "noevent", "nosource", and "none"?


No objection.

+    }
+    return 0;
+}
+early_param("cp0_clock", cp0_clock_setup);
+
+/*
+ * cp0 count/compare operations.
+ */
+static void cp0_count_ack(void)
+{
+    write_c0_compare(read_c0_compare());
+}
+
+static cycle_t cp0_count_read(void)
+{
+        return read_c0_count();

   "Entab" spaces here please.


I forgot to enable git hooks in my new repo...  sorry, it shouldn't
happen anymore.

+}
+
+/*
+ * Clocksource device. Its rating should not depend on its frequency:
+ * stability is a feature more valuable for a clock source.
+ */
+struct clocksource cp0_clocksource = {
+    .name        = CP0_CLOCK_NAME,
+    .rating        = 200,

   Perhaps we even need to rise the rating to 300 or 400 -- according to
what
<linux/clocksource.h> says...


I dunno. I kept the previous value (without the timer freq depedency).
Do you think cp0 counter is stable enough to rise this rating ?

+    .mask        = CLOCKSOURCE_MASK(32),
+    .flags        = CLOCK_SOURCE_IS_CONTINUOUS,
+    .read        = cp0_count_read,
+};
+
+static void __init setup_cp0_clocksource(void)
+{
+    int cpu = smp_processor_id();
+    unsigned freq = get_freq(cpu);

   Might fold these 2 lines into:


OK

unsigned freq = get_freq(smp_processor_id());

+    unsigned shift = 0;

   Unneeded initializer.


OK

+    u64 mult;
+
+    for (shift = 32; shift > 0; shift--) {
+        mult = (u64)NSEC_PER_SEC << shift;
+        do_div(mult, freq);
+        if ((mult >> 32) == 0)
+            break;
+    }
+
+    cp0_clocksource.shift = shift;
+    cp0_clocksource.mult = mult;
+
+    clocksource_register(&cp0_clocksource);
+}
+
+/*
+ * High precision timer functions
+ */
+static int cp0_set_next_event(unsigned long delta,
+                   struct clock_event_device *evt)
+{
+    unsigned int cnt;
+
+    BUG_ON(evt->mode != CLOCK_EVT_MODE_ONESHOT);
+
+    /* interrupt ack is done by setting up the next event */

   We acknowledge interrupt independently from this code anyway, so the
comment seems misplaced.


Old comment. Removed.

+    cnt = read_c0_count();
+    cnt += delta;

  Could be merge into one statement...

+    write_c0_compare(cnt);
+
+    return ((long)(read_c0_count() - cnt) > 0L) ? -ETIME : 0;
+}
+
+static void cp0_set_mode(enum clock_event_mode mode,
+                 struct clock_event_device *evt)
+{
+    switch (mode) {
+    case CLOCK_EVT_MODE_UNUSED:
+    case CLOCK_EVT_MODE_SHUTDOWN:
+        /*
+         * For now, we don't disable cp0 hpt interrupts. So we

   A reference to hpt needs to be killed.

Done


+         * leave them enabled, and ignore them in this mode.
+         * Therefore we will get one useless but also harmless
+         * interrupt every 2^32 cycles...
+         */
+        cp0_count_ack();
+        break;
+    case CLOCK_EVT_MODE_ONESHOT:
+        /* nothing to do */
+        break;
+    case CLOCK_EVT_MODE_PERIODIC:
+        BUG();
+    };
+}
+
+static struct clock_event_device cp0_clockevent __initdata = {
+    .name        = CP0_CLOCK_NAME,
+    .mode        = CLOCK_EVT_MODE_UNUSED,

   Needless intialization, that constant is 0 anyway, so this field will
just get zeroed implicitly.


Well you can state this because you took a look to the clockevent
implementation internals. What's happening if they change this value ?
I know it won't happen but it's cleaner anyway. I prefer leave it
alone if you don't mind.


+    .features    = CLOCK_EVT_FEAT_ONESHOT,
+    .shift        = 32,
+    .set_mode    = cp0_set_mode,
+    .set_next_event    = cp0_set_next_event,
+    .irq        = -1,
+};
+
+static DEFINE_PER_CPU(struct clock_event_device, cp0_clock_events);
+
+void __init setup_cp0_clockevent(void)
+{
+    struct clock_event_device *evt;
+    int cpu = smp_processor_id();
+    unsigned freq;
+
+    if (disable_clockevent)
+        return;
+
+    evt = &__get_cpu_var(cp0_clock_events);

   Could use per_cpu() here, as we already have "sampled"
smp_processor_id().


OK.

+
+    memcpy(evt, &cp0_clockevent, sizeof(*evt));
+
+    freq = get_freq(cpu);
+
+    evt->rating = 200 + freq/10000000;
+    evt->mult = div_sc(freq, NSEC_PER_SEC, evt->shift);
+    evt->cpumask = cpumask_of_cpu(cpu);
+
+    evt->max_delta_ns = clockevent_delta2ns(0x7fffffff, evt);
+    evt->min_delta_ns = clockevent_delta2ns(0x10, evt);
+
+    clockevents_register_device(evt);
+
+    printk("Using %u.%03u MHz CP0 high precision timer on CPU #%d.\n",

   The "high precision " part should disapper.

Killed.


+           ((freq + 500) / 1000) / 1000,
+           ((freq + 500) / 1000) % 1000,
+        cpu);
+}
+
+static irqreturn_t cp0_clockevent_interrupt(int irq, void *dev_id)
+{
+    const int r2 = cpu_has_mips_r2;
+    struct clock_event_device *evt;
+
[...]
+    /*
+     * The same applies to performance counter interrupts.  But with the
+     * above we now know that the reason we got here must be a timer
+     * interrupt.  Being the paranoiacs we are we check anyway.
+     */
+    if (!r2 || (read_c0_cause() & (1 << 30))) {
+        evt = &__get_cpu_var(cp0_clock_events);

   I'd think 'evt' should be declared in this block too, not at the
function level.

OK.


+
+        /*
+         * We can get interrupts whereas the hpt clock event

   A reference to hpt needs to be killed there too...

Destroyed.


+         * device has been disabled since we can't shut it
+         * down. So always ack the timer.
+         */
+        cp0_count_ack();
+
+        switch (evt->mode) {
+        case CLOCK_EVT_MODE_ONESHOT:
+            evt->event_handler(evt);
+            break;
+        case CLOCK_EVT_MODE_UNUSED:
+        case CLOCK_EVT_MODE_SHUTDOWN:
+        case CLOCK_EVT_MODE_PERIODIC:
+            /* nothing */;
+        }

   Isn't this over-engineered? Why use *switch*' where the simple *if*
would suffice?

I just wanted to be sure that I won't miss any new mode, being
paranoid I guess. I'll change it.


+    }
+out:
+    return IRQ_HANDLED;
+}
+
+static struct irqaction cp0_clockevent_irqaction = {
+    .handler    = cp0_clockevent_interrupt,
+    .flags        = IRQF_DISABLED | IRQF_PERCPU,
+    .name        = CP0_CLOCK_NAME,
+};
+
+
+/*
+ * This function is used by platforms which use the hpt as clock
+ * source and timer.
+ */
+int __init setup_cp0_clocks(struct cp0_clock_info *info)
+{

   Erm, do we need this function at all after we have separate setups
for clock source/event?  Just move all the override checks there.


Well I would say no. setup_cp0_clockevent() is normally called only
once by board init code. Whereas setup_cp0_clockevent() can be called
several times in SMP. Thus we can have several cp0 counters with
different frequencies. It was Ralf's requierement.

Maybe I should rename setup_cp0_clockevent() into
setup_cp0_clockevent_per_cpu() ?

+    if (!cpu_has_counter)
+        goto disable_all;
+    if (info->get_freq == NULL)
+        goto disable_all;
+
+    get_freq = info->get_freq;
+    perf_handler = info->perf_handler;

   I seriously don't understand why are you expecting this to be passed
by the platform code... :-/  Currently this is not so -- since it's the
Oprofile code that handles the performance interrupt.


You're right, I didn't give a close look to this. I'll remove it and
try to implement something else.

+
+    if (info->irq == 0)
+        disable_clockevent = 1;
+
+    if (!disable_clocksource)
+        setup_cp0_clocksource();
+    if (!disable_clockevent) {
+        setup_cp0_clockevent();
+        setup_irq(info->irq, &cp0_clockevent_irqaction);

   Erm... why not include setup_irq() into setup_cp0_clockevent()? This
way, it'll be autonomous and callable by the platfrom code on its own...

See my previous answer about setup_cp0_clockevent() which can be
called several times in SMP.

but there would remain perf_irq and get_freq problems. Wouldn't it be
better to declare get_cp0_timer_freq as a pointer ot function and let
the platform just fill it before calling setup?


see structure comment below...

-
-static unsigned int __init calibrate_hpt(void)
+unsigned __init calibrate_timer(cycle_t (*x_read)(void),
+                int (*y_state)(void))

   Hm, with those x/y it looks a bit like joystick code. :-)


Yeah. Actually it was said that this function should be rewritten in
order to be called by any platform code. Until this, I added 2
parameters to this function and killed 2 global function pointers.

Actually it should be part of a separate patch but until it got
written I leave this joystick code.

diff --git a/include/asm-mips/hpt.h b/include/asm-mips/hpt.h
new file mode 100644
index 0000000..f0acab3
--- /dev/null
+++ b/include/asm-mips/hpt.h
@@ -0,0 +1,36 @@
+#ifndef _ASM_HPT_H
+#define _ASM_HPT_H
+
+#ifdef CONFIG_CP0_CLOCKS
+
+struct cp0_clock_info {
+    /*
+     * This is the irq num of the cp0 count/compare timer.
+     */
+    int irq;
+
+    /*
+     * This mandartory hook is called to get the frequency of
+     * the running processor.
+     */
+    unsigned (*get_freq)(int cpu);

   Creating a global variable looks like a better idea...

+
+    /*
+     * The performance counter overflow irq may be shared with the
+     * hpt interrupt. In that case this handler will be called
+     * during a hpt interrupt.
+     */
+    irqreturn_t (*perf_handler)(int irq, void *dev_id);

   The only issue is that the platform code may have no idea what it
should be...  I think we need to stick to the old approach here.

+};

   ... this would leave us with only IRQ field and eliminate the need
for this structure.

I think this structure allow us to gather all information needed by
cp0 timer 'driver' in one place. They're all one place and thus easy
for a board to see what they need to setup in order to use cp0 timer.


+
+extern int setup_cp0_clocks(struct cp0_clock_info *info);
+extern void setup_cp0_clockevent(void);
+
+#else
+
+static inline void setup_cp0_clockevent(void) {}
+
+#endif    /* CONFIG_CP0_CLOCKS */
+
+#endif    /* _ASM_HPT_H */

   The remaining declaration might be joined to time.h, thus making this
whole header unneeded... well, that's up to you.


OK.

I'll cook up a new patch.

Thanks a lot for your comments.
---
               Franck


[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux