[RFC][PATCH v1] parisc: Switch to clockevent based timers

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

 



Switch from a periodic timer-based configuration to a clockevent based
configuration. With this change we now have more fine grained hrtimers
and it's possible to switch to a CONFIG_NO_HZ_IDLE system.

Signed-off-by: Helge Deller <deller@xxxxxx>

diff --git a/arch/parisc/include/asm/processor.h b/arch/parisc/include/asm/processor.h
index 6e2a8176b0dd..186ba6bd1131 100644
--- a/arch/parisc/include/asm/processor.h
+++ b/arch/parisc/include/asm/processor.h
@@ -82,7 +82,6 @@ struct system_cpuinfo_parisc {

 /* Per CPU data structure - ie varies per CPU.  */
 struct cpuinfo_parisc {
-	unsigned long it_value;     /* Interval Timer at last timer Intr */
 	unsigned long irq_count;    /* number of IRQ's since boot */
 	unsigned long cpuid;        /* aka slot_number or set to NO_PROC_ID */
 	unsigned long hpa;          /* Host Physical address */
diff --git a/arch/parisc/kernel/irq.c b/arch/parisc/kernel/irq.c
index e76c86619949..50ec7b529922 100644
--- a/arch/parisc/kernel/irq.c
+++ b/arch/parisc/kernel/irq.c
@@ -562,17 +562,14 @@ void do_cpu_irq_mask(struct pt_regs *regs)

 static void claim_cpu_irqs(void)
 {
-	unsigned long flags = IRQF_TIMER | IRQF_PERCPU | IRQF_IRQPOLL;
 	int i;

 	for (i = CPU_IRQ_BASE; i <= CPU_IRQ_MAX; i++) {
 		irq_set_chip_and_handler(i, &cpu_interrupt_type,
 					 handle_percpu_irq);
 	}
-
-	irq_set_handler(TIMER_IRQ, handle_percpu_irq);
-	if (request_irq(TIMER_IRQ, timer_interrupt, flags, "timer", NULL))
-		pr_err("Failed to register timer interrupt\n");
+	irq_set_percpu_devid(TIMER_IRQ);
+	irq_set_handler(TIMER_IRQ, handle_percpu_devid_irq);
 #ifdef CONFIG_SMP
 	irq_set_handler(IPI_IRQ, handle_percpu_irq);
 	if (request_irq(IPI_IRQ, ipi_interrupt, IRQF_PERCPU, "IPI", NULL))
diff --git a/arch/parisc/kernel/time.c b/arch/parisc/kernel/time.c
index 04508158815c..60ae30cbc59e 100644
--- a/arch/parisc/kernel/time.c
+++ b/arch/parisc/kernel/time.c
@@ -22,6 +22,7 @@
 #include <linux/string.h>
 #include <linux/mm.h>
 #include <linux/interrupt.h>
+#include <linux/clockchips.h>
 #include <linux/time.h>
 #include <linux/init.h>
 #include <linux/smp.h>
@@ -40,82 +41,75 @@

 #include <linux/timex.h>

+/*
+ * The PA-RISC Interval Timer is a pair of registers; one is read-only and one
+ * is write-only; both accessed through CR16.  The read-only register is 32 or
+ * 64 bits wide, and increments by 1 every CPU clock tick.  The architecture
+ * only guarantees us a rate between 0.5 and 2, but all implementations use a
+ * rate of 1.  The write-only register is 32-bits wide.  When the lowest 32
+ * bits of the read-only register compare equal to the write-only register, it
+ * raises a maskable external interrupt.  Each processor has an Interval Timer
+ * of its own and they are not synchronised.
+ */
+
+#define cr16_hz	(100 * PAGE0->mem_10msec)	/* Hz */
 static unsigned long clocktick __ro_after_init;	/* timer cycles per tick */

+static DEFINE_PER_CPU(struct clock_event_device, hppa_clk_events);
+
 /*
- * We keep time on PA-RISC Linux by using the Interval Timer which is
- * a pair of registers; one is read-only and one is write-only; both
- * accessed through CR16.  The read-only register is 32 or 64 bits wide,
- * and increments by 1 every CPU clock tick.  The architecture only
- * guarantees us a rate between 0.5 and 2, but all implementations use a
- * rate of 1.  The write-only register is 32-bits wide.  When the lowest
- * 32 bits of the read-only register compare equal to the write-only
- * register, it raises a maskable external interrupt.  Each processor has
- * an Interval Timer of its own and they are not synchronised.
- *
- * We want to generate an interrupt every 1/HZ seconds.  So we program
- * CR16 to interrupt every @clocktick cycles.  The it_value in cpu_data
- * is programmed with the intended time of the next tick.  We can be
- * held off for an arbitrarily long period of time by interrupts being
- * disabled, so we may miss one or more ticks.
+ * Do not disable the timer irq. The clockevents get that frequently
+ * programmed, that it's unlikely the timer will wrap and trigger again. So
+ * it's not worth to disable and reenable the hardware irqs, instead store in a
+ * static per-cpu variable if the irq is expected or not.
  */
-irqreturn_t __irq_entry timer_interrupt(int irq, void *dev_id)
+static DEFINE_PER_CPU(bool, cr16_clockevent_enabled);
+
+static void cr16_set_next(unsigned long delta, bool reenable_irq)
+{
+	mtctl(mfctl(16) + delta, 16);
+
+	if (reenable_irq)
+		per_cpu(cr16_clockevent_enabled, smp_processor_id()) = true;
+}
+
+static int cr16_clockevent_shutdown(struct clock_event_device *evt)
+{
+	per_cpu(cr16_clockevent_enabled, smp_processor_id()) = false;
+	return 0;
+}
+
+static int cr16_clockevent_set_periodic(struct clock_event_device *evt)
+{
+	cr16_set_next(clocktick, true);
+	return 0;
+}
+
+static int cr16_clockevent_set_next_event(unsigned long delta,
+					struct clock_event_device *evt)
+{
+	cr16_set_next(delta, true);
+	return 0;
+}
+
+static irqreturn_t timer_interrupt(int irq, void *dev_id)
 {
-	unsigned long now;
-	unsigned long next_tick;
-	unsigned long ticks_elapsed = 0;
 	unsigned int cpu = smp_processor_id();
-	struct cpuinfo_parisc *cpuinfo = &per_cpu(cpu_data, cpu);
-
-	/* gcc can optimize for "read-only" case with a local clocktick */
-	unsigned long cpt = clocktick;
-
-	profile_tick(CPU_PROFILING);
-
-	/* Initialize next_tick to the old expected tick time. */
-	next_tick = cpuinfo->it_value;
-
-	/* Calculate how many ticks have elapsed. */
-	now = mfctl(16);
-	do {
-		++ticks_elapsed;
-		next_tick += cpt;
-	} while (next_tick - now > cpt);
-
-	/* Store (in CR16 cycles) up to when we are accounting right now. */
-	cpuinfo->it_value = next_tick;
-
-	/* Go do system house keeping. */
-	if (cpu == 0)
-		xtime_update(ticks_elapsed);
-
-	update_process_times(user_mode(get_irq_regs()));
-
-	/* Skip clockticks on purpose if we know we would miss those.
-	 * The new CR16 must be "later" than current CR16 otherwise
-	 * itimer would not fire until CR16 wrapped - e.g 4 seconds
-	 * later on a 1Ghz processor. We'll account for the missed
-	 * ticks on the next timer interrupt.
-	 * We want IT to fire modulo clocktick even if we miss/skip some.
-	 * But those interrupts don't in fact get delivered that regularly.
-	 *
-	 * "next_tick - now" will always give the difference regardless
-	 * if one or the other wrapped. If "now" is "bigger" we'll end up
-	 * with a very large unsigned number.
-	 */
-	now = mfctl(16);
-	while (next_tick - now > cpt)
-		next_tick += cpt;
-
-	/* Program the IT when to deliver the next interrupt.
-	 * Only bottom 32-bits of next_tick are writable in CR16!
-	 * Timer interrupt will be delivered at least a few hundred cycles
-	 * after the IT fires, so if we are too close (<= 8000 cycles) to the
-	 * next cycle, simply skip it.
-	 */
-	if (next_tick - now <= 8000)
-		next_tick += cpt;
-	mtctl(next_tick, 16);
+	struct clock_event_device *evt;
+	bool handle_irq;
+
+	evt = &per_cpu(hppa_clk_events, cpu);
+	handle_irq = per_cpu(cr16_clockevent_enabled, cpu);
+
+	if (clockevent_state_oneshot(evt))
+		per_cpu(cr16_clockevent_enabled, smp_processor_id()) = false;
+	else {
+		if (handle_irq)
+			cr16_set_next(clocktick, false);
+	}
+
+	if (handle_irq)
+		evt->event_handler(evt);

 	return IRQ_HANDLED;
 }
@@ -156,11 +150,29 @@ static struct clocksource clocksource_cr16 = {
 void __init start_cpu_itimer(void)
 {
 	unsigned int cpu = smp_processor_id();
-	unsigned long next_tick = mfctl(16) + clocktick;

-	mtctl(next_tick, 16);		/* kick off Interval Timer (CR16) */
+	struct clock_event_device *clk = this_cpu_ptr(&hppa_clk_events);
+
+	clk->name = "cr16";
+	clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
+			CLOCK_EVT_FEAT_PERCPU;
+	clk->set_state_shutdown = cr16_clockevent_shutdown;
+	clk->set_state_periodic = cr16_clockevent_set_periodic;
+	clk->set_state_oneshot = cr16_clockevent_shutdown;
+	clk->set_state_oneshot_stopped = cr16_clockevent_shutdown;
+	clk->set_next_event = cr16_clockevent_set_next_event;
+	clk->cpumask = cpumask_of(cpu);
+	clk->rating = 300;
+	clk->irq = TIMER_IRQ;
+	clockevents_config_and_register(clk, cr16_hz, 4000, 0xffffffff);
+
+	if (cpu == 0) {
+		int err = request_percpu_irq(TIMER_IRQ, timer_interrupt,
+					 "timer", clk);
+		BUG_ON(err);
+	}

-	per_cpu(cpu_data, cpu).it_value = next_tick;
+	enable_percpu_irq(clk->irq, IRQ_TYPE_NONE);
 }

 #if IS_ENABLED(CONFIG_RTC_DRV_GENERIC)
@@ -231,13 +243,9 @@ static u64 notrace read_cr16_sched_clock(void)

 void __init time_init(void)
 {
-	unsigned long cr16_hz;
-
-	clocktick = (100 * PAGE0->mem_10msec) / HZ;
+	clocktick = DIV_ROUND_CLOSEST(cr16_hz, HZ);
 	start_cpu_itimer();	/* get CPU 0 started */

-	cr16_hz = 100 * PAGE0->mem_10msec;  /* Hz */
-
 	/* register as sched_clock source */
 	sched_clock_register(read_cr16_sched_clock, BITS_PER_LONG, cr16_hz);
 }




[Index of Archives]     [Linux SoC]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux