[Skiboot] [PATCH 3/3] slw/timer: SBE based timer support

Benjamin Herrenschmidt benh at au1.ibm.com
Thu Sep 10 10:09:26 AEST 2015


Recent HostBoot & SBE firmware provide a HW timer facility that can
be used to implement OPAL timers and thus limit the reliance on the
Linux heartbeat.

This implements support for it. The side effect is that i2c from Centaurs
is now usable.

Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
---

Tested on Firestone with latest FW

 core/interrupts.c |   7 ++-
 core/timer.c      |  69 +++++++++++++-------
 hw/occ.c          |   7 ++-
 hw/slw.c          | 183 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 include/skiboot.h |   6 ++
 5 files changed, 229 insertions(+), 43 deletions(-)

diff --git a/core/interrupts.c b/core/interrupts.c
index b91508f..32f43ef 100644
--- a/core/interrupts.c
+++ b/core/interrupts.c
@@ -296,9 +296,6 @@ static int64_t opal_handle_interrupt(uint32_t isn, uint64_t *outstanding_event_m
 	struct irq_source *is = irq_find_source(isn);
 	int64_t rc = OPAL_SUCCESS;
 
-	/* We run the timers first */
-	check_timers(true);
-
 	/* No source ? return */
 	if (!is || !is->ops->interrupt) {
 		rc = OPAL_PARAMETER;
@@ -308,6 +305,10 @@ static int64_t opal_handle_interrupt(uint32_t isn, uint64_t *outstanding_event_m
 	/* Run it */
 	is->ops->interrupt(is->data, isn);
 
+	/* Check timers if SLW timer isn't working */
+	if (!slw_timer_ok())
+		check_timers(true);
+
 	/* Update output events */
  bail:
 	if (outstanding_event_mask)
diff --git a/core/timer.c b/core/timer.c
index d5a3477..e97dc07 100644
--- a/core/timer.c
+++ b/core/timer.c
@@ -12,6 +12,9 @@
 #include <cpu.h>
 #endif
 
+/* Heartbeat requested from Linux */
+#define HEARTBEAT_DEFAULT_MS	2000
+
 static struct lock timer_lock = LOCK_UNLOCKED;
 static LIST_HEAD(timer_list);
 static LIST_HEAD(timer_poll_list);
@@ -72,28 +75,44 @@ void cancel_timer_async(struct timer *t)
 	unlock(&timer_lock);
 }
 
-void schedule_timer_at(struct timer *t, uint64_t when)
+static void __schedule_timer_at(struct timer *t, uint64_t when)
 {
 	struct timer *lt;
 
-	lock(&timer_lock);
+	/* If the timer is already scheduled, take it out */
 	if (t->link.next)
 		__remove_timer(t);
+
+	/* Update target */
 	t->target = when;
+
 	if (when == TIMER_POLL) {
+		/* It's a poller, add it to the poller list */
 		t->gen = timer_poll_gen;
 		list_add_tail(&timer_poll_list, &t->link);
 	} else {
+		/* It's a real timer, add it in the right spot in the
+		 * ordered timer list
+		 */
 		list_for_each(&timer_list, lt, link) {
-			if (when < lt->target) {
-				list_add_before(&timer_list, &t->link,
-						&lt->link);
-				unlock(&timer_lock);
-				return;
-			}
+			if (when >= lt->target)
+				continue;
+			list_add_before(&timer_list, &t->link, &lt->link);
+			goto bail;
 		}
 		list_add_tail(&timer_list, &t->link);
 	}
+ bail:
+	/* Pick up the next timer and upddate the SBE HW timer */
+	lt = list_top(&timer_list, struct timer, link);
+	if (lt)
+		slw_update_timer_expiry(lt->target);
+}
+
+void schedule_timer_at(struct timer *t, uint64_t when)
+{
+	lock(&timer_lock);
+	__schedule_timer_at(t, when);
 	unlock(&timer_lock);
 }
 
@@ -134,18 +153,19 @@ static void __check_poll_timers(uint64_t now)
 		t = list_top(&timer_poll_list, struct timer, link);
 
 		/* Top timer has a different generation than current ? Must
-		 * be older
+		 * be older, we are done.
 		 */
 		if (!t || t->gen == timer_poll_gen)
 			break;
 
-		/* Top of list still running, we have to delay handling
-		 * it. For now just skip until the next poll, when we have
-		 * SLW interrupts, we'll probably want to trip another one
-		 * ASAP
+		/* Top of list still running, we have to delay handling it,
+		 * let's reprogram the SLW with a small delay. We chose
+		 * arbitrarily 1us.
 		 */
-		if (t->running)
+		if (t->running) {
+			slw_update_timer_expiry(now + usecs_to_tb(1));
 			break;
+		}
 
 		/* Allright, first remove it and mark it running */
 		__remove_timer(t);
@@ -222,24 +242,25 @@ void check_timers(bool from_interrupt)
 }
 
 #ifndef __TEST__
+
 void late_init_timers(void)
 {
 	/* Add a property requesting the OS to call opal_poll_event() at
 	 * a specified interval in order for us to run our background
-	 * low priority poller.
+	 * low priority pollers.
 	 *
-	 * When we have a working SLW based HW timer, we'll be able to
-	 * reduce this or even remove it, for now however, we want to be
-	 * called at least every couple of seconds on FSP based machines
-	 * and a bit faster on BMC based machines where the LPC and i2c
-	 * interrupts might not be functional.
+	 * If we have an SLW timer facility, we run this 10 times slower,
+	 * we could possibly completely get rid of it.
 	 *
 	 * We use a value in milliseconds, we don't want this to ever be
 	 * faster than that.
 	 */
-	if (fsp_present())
-		dt_add_property_cells(opal_node, "ibm,heartbeat-ms", 2000);
-	else
-		dt_add_property_cells(opal_node, "ibm,heartbeat-ms", 250);
+	if (slw_timer_ok() || fsp_present()) {
+		dt_add_property_cells(opal_node, "ibm,heartbeat-ms",
+				      HEARTBEAT_DEFAULT_MS);
+	} else {
+		dt_add_property_cells(opal_node, "ibm,heartbeat-ms",
+				      HEARTBEAT_DEFAULT_MS / 10);
+	}
 }
 #endif
diff --git a/hw/occ.c b/hw/occ.c
index 4b74b99..68b7032 100644
--- a/hw/occ.c
+++ b/hw/occ.c
@@ -26,6 +26,7 @@
 #include <errorlog.h>
 #include <opal-api.h>
 #include <opal-msg.h>
+#include <timer.h>
 
 /* OCC Communication Area for PStates */
 
@@ -715,9 +716,11 @@ static struct fsp_client fsp_occ_client = {
 #define OCB_OCI_OCCMISC_OR	0x6a022
 #define OCB_OCI_OCIMISC_IRQ		PPC_BIT(0)
 #define OCB_OCI_OCIMISC_IRQ_TMGT	PPC_BIT(1)
+#define OCB_OCI_OCIMISC_IRQ_SLW_TMR	PPC_BIT(14)
 #define OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY	PPC_BIT(15)
 #define OCB_OCI_OCIMISC_MASK		(OCB_OCI_OCIMISC_IRQ_TMGT | \
-					 OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY )
+					 OCB_OCI_OCIMISC_IRQ_OPAL_DUMMY | \
+					 OCB_OCI_OCIMISC_IRQ_SLW_TMR)
 
 void occ_send_dummy_interrupt(void)
 {
@@ -765,6 +768,8 @@ void occ_interrupt(uint32_t chip_id)
 	/* Dispatch */
 	if (ireg & OCB_OCI_OCIMISC_IRQ_TMGT)
 		prd_tmgt_interrupt(chip_id);
+	if (ireg & OCB_OCI_OCIMISC_IRQ_SLW_TMR)
+		check_timers(true);
 
 	/* We may have masked-out OCB_OCI_OCIMISC_IRQ in the previous
 	 * OCCMISC_AND write. Check if there are any new source bits set,
diff --git a/hw/slw.c b/hw/slw.c
index 9aec41c..a009090 100644
--- a/hw/slw.c
+++ b/hw/slw.c
@@ -40,6 +40,14 @@ static uint32_t slw_saved_reset[MAX_RESET_PATCH_SIZE];
 static bool slw_current_le = false;
 #endif /* __HAVE_LIBPORE__ */
 
+/* SLW timer related stuff */
+static bool slw_has_timer;
+static uint64_t slw_timer_inc;
+static uint64_t slw_timer_target;
+static uint32_t slw_timer_chip;
+static uint64_t slw_last_gen;
+static uint64_t slw_last_gen_stamp;
+
 /* Assembly in head.S */
 extern void enter_rvwinkle(void);
 
@@ -897,7 +905,7 @@ static void slw_patch_regs(struct proc_chip *chip)
 
 static void slw_init_chip(struct proc_chip *chip)
 {
-	int rc __unused;
+	int64_t rc;
 	struct cpu_thread *c;
 
 	prlog(PR_DEBUG, "SLW: Init chip 0x%x\n", chip->id);
@@ -913,7 +921,7 @@ static void slw_init_chip(struct proc_chip *chip)
 				&chip->slw_image_size);
 	if (rc != 0) {
 		log_simple_error(&e_info(OPAL_RC_SLW_INIT),
-			"SLW: Error %d reading SLW image size\n", rc);
+			"SLW: Error %lld reading SLW image size\n", rc);
 		/* XXX Panic ? */
 		chip->slw_base = 0;
 		chip->slw_bar_size = 0;
@@ -939,19 +947,6 @@ static void slw_init_chip(struct proc_chip *chip)
 	}
 }
 
-void slw_init(void)
-{
-	struct proc_chip *chip;
-
-	if (proc_gen != proc_gen_p8)
-		return;
-
-	for_each_chip(chip)
-		slw_init_chip(chip);
-
-	add_cpu_idle_state_properties();
-}
-
 /* Workarounds while entering fast-sleep */
 
 static void fast_sleep_enter(void)
@@ -1085,3 +1080,161 @@ static int64_t opal_slw_set_reg(uint64_t cpu_pir, uint64_t sprn, uint64_t val)
 
 opal_call(OPAL_SLW_SET_REG, opal_slw_set_reg, 3);
 #endif /* __HAVE_LIBPORE__ */
+
+static void slw_dump_timer_ffdc(void)
+{
+	uint64_t i, val;
+	int64_t rc;
+
+	static const uint32_t dump_regs[] = {
+		0xe0000, 0xe0001, 0xe0002, 0xe0003,
+		0xe0004, 0xe0005, 0xe0006, 0xe0007,
+		0xe0008, 0xe0009, 0xe000a, 0xe000b,
+		0xe000c, 0xe000d, 0xe000e, 0xe000f,
+		0xe0010, 0xe0011, 0xe0012, 0xe0013,
+		0xe0014, 0xe0015, 0xe0016, 0xe0017,
+		0xe0018, 0xe0019,
+		0x5001c,
+		0x50038, 0x50039, 0x5003a, 0x5003b
+	};
+
+	prlog(PR_ERR, "SLW: Register state:\n");
+
+	for (i = 0; i < ARRAY_SIZE(dump_regs); i++) {
+		uint32_t reg = dump_regs[i];
+		rc = xscom_read(slw_timer_chip, reg, &val);
+		if (rc) {
+			prlog(PR_ERR, "SLW: XSCOM error %lld reading"
+			      " reg 0x%x\n", rc, reg);
+			break;
+		}
+		prlog(PR_ERR, "SLW:  %5x = %016llx\n", reg, val);
+	}
+}
+
+/* This is called with the timer lock held, so there is no
+ * issue with re-entrancy or concurrence
+ */
+void slw_update_timer_expiry(uint64_t new_target)
+{
+	uint64_t count, gen, gen2, req, now = mftb();
+	int64_t rc;
+
+	if (!slw_has_timer || new_target == slw_timer_target)
+		return;
+
+	slw_timer_target = new_target;
+
+	/* Calculate how many increments from now, rounded up */
+	if (now < new_target)
+		count = (new_target - now + slw_timer_inc - 1) / slw_timer_inc;
+	else
+		count = 1;
+
+	/* Max counter is 24-bit */
+	if (count > 0xffffff)
+		count = 0xffffff;
+	/* Fabricate update request */
+	req = (1ull << 63) | (count << 32);
+
+	prlog(PR_TRACE, "SLW: TMR expiry: 0x%llx, req: %016llx\n", count, req);
+
+	do {
+		/* Grab generation and spin if odd */
+		for (;;) {
+			rc = xscom_read(slw_timer_chip, 0xE0006, &gen);
+			if (rc) {
+				prerror("SLW: Error %lld reading tmr gen "
+					" count\n", rc);
+				return;
+			}
+			if (!(gen & 1))
+				break;
+			if (tb_compare(now + msecs_to_tb(1), mftb()) == TB_ABEFOREB) {
+				prerror("SLW: Stuck with odd generation !\n");
+				slw_has_timer = false;
+				slw_dump_timer_ffdc();
+				return;
+			}
+		}
+
+		rc = xscom_write(slw_timer_chip, 0x5003A, req);
+		if (rc) {
+			prerror("SLW: Error %lld writing tmr request\n", rc);
+			return;
+		}
+
+		/* Re-check gen count */
+		rc = xscom_read(slw_timer_chip, 0xE0006, &gen2);
+		if (rc) {
+			prerror("SLW: Error %lld re-reading tmr gen "
+				" count\n", rc);
+			return;
+		}
+	} while(gen != gen2);
+
+	/* Check if the timer is working. If at least 1ms has elapsed
+	 * since the last call to this function, check that the gen
+	 * count has changed
+	 */
+	if (tb_compare(slw_last_gen_stamp + msecs_to_tb(1), now)
+	    == TB_ABEFOREB) {
+		if (slw_last_gen == gen) {
+			prlog(PR_ERR,
+			      "SLW: Timer appears to not be running !\n");
+			slw_has_timer = false;
+			slw_dump_timer_ffdc();
+		}
+		slw_last_gen = gen;
+		slw_last_gen_stamp = mftb();
+	}
+
+	prlog(PR_TRACE, "SLW: gen: %llx\n", gen);
+}
+
+bool slw_timer_ok(void)
+{
+	return slw_has_timer;
+}
+
+static void slw_init_timer(void)
+{
+	struct dt_node *np;
+	int64_t rc;
+	uint32_t tick_us;
+
+	np = dt_find_compatible_node(dt_root, NULL, "ibm,power8-sbe-timer");
+	if (!np)
+		return;
+
+	slw_timer_chip = dt_get_chip_id(np);
+	tick_us = dt_prop_get_u32(np, "tick-time-us");
+	slw_timer_inc = usecs_to_tb(tick_us);
+	slw_timer_target = ~0ull;
+
+	rc = xscom_read(slw_timer_chip, 0xE0006, &slw_last_gen);
+	if (rc) {
+		prerror("SLW: Error %lld reading tmr gen count\n", rc);
+		return;
+	}
+	slw_last_gen_stamp = mftb();
+
+	prlog(PR_INFO, "SLW: Timer facility on chip %d, resolution %dus\n",
+	      slw_timer_chip, tick_us);
+	slw_has_timer = true;
+}
+
+void slw_init(void)
+{
+	struct proc_chip *chip;
+
+	if (proc_gen != proc_gen_p8)
+		return;
+
+	for_each_chip(chip)
+		slw_init_chip(chip);
+
+	add_cpu_idle_state_properties();
+
+	slw_init_timer();
+}
diff --git a/include/skiboot.h b/include/skiboot.h
index 2ec3e7a..30a94d4 100644
--- a/include/skiboot.h
+++ b/include/skiboot.h
@@ -263,6 +263,12 @@ extern void *create_dtb(const struct dt_node *root);
 /* SLW reinit function for switching core settings */
 extern int64_t slw_reinit(uint64_t flags);
 
+/* SLW update timer function */
+extern void slw_update_timer_expiry(uint64_t new_target);
+
+/* Is SLW timer available ? */
+extern bool slw_timer_ok(void);
+
 /* Fallback fake RTC */
 extern void fake_rtc_init(void);
 




More information about the Skiboot mailing list