[v5,5/5] SBE: Add timer support

Message ID 20170723113050.8815-6-hegdevasant@linux.vnet.ibm.com
State New
Headers show

Commit Message

Vasant Hegde July 23, 2017, 11:30 a.m.
SBE on P9 provides one shot programmable timer facility. We can use this
to implement OPAL timers and hence limit the reliance on the Linux
heartbeat (similar to HW timer facility provided by SLW on P8).

Design:
  - We will continue to run Linux heartbeat.
  - Each chip has SBE. This patch always schedules timer on SBE on master chip.
  - Timer MBOX has two options (start/stop timer). We cannot modify inflight
    timer message. Hence we reschedule timer if new timer value is greater than
    current time and less than scheduled timer value.
  - SBE expects timeout value in milliseconds. We track timeout value in TB.
    Hence we convert tb to milliseconds before sending request to SBE.
  - We are not requesting ack/response from SBE for timer message. This will
    reduce unnecessary interrupts (as SBE send interrupt for ack/response).
  - Disabling SBE timer
    We expect SBE to send timer expiry interrupt whenever timer expires. We
    wait for few more millisecond (presently SBE_TIMER_DEFAULT_MS) before
    disabling timer.
    In future we can consider below alternative approaches:
      - Presently SBE timer disable is permanent (until we reboot system).
        SBE sends "I'm back" interrupt after reset. We can consider restarting
        timer after SBE reset.
      - Reset SBE and start timer again.
      - Each chip has SBE. On multi chip system we can try to schedule timer
        on different chip.

Signed-off-by: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---
 core/interrupts.c |   3 +-
 core/timer.c      |  16 ++++--
 hw/sbe-p9.c       | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 include/sbe-p9.h  |   6 +++
 4 files changed, 164 insertions(+), 4 deletions(-)

Comments

Benjamin Herrenschmidt July 23, 2017, 10:35 p.m. | #1
On Sun, 2017-07-23 at 17:00 +0530, Vasant Hegde wrote:
> SBE on P9 provides one shot programmable timer facility. We can use this
> to implement OPAL timers and hence limit the reliance on the Linux
> heartbeat (similar to HW timer facility provided by SLW on P8).
> 
> Design:
>   - We will continue to run Linux heartbeat.
>   - Each chip has SBE. This patch always schedules timer on SBE on master chip.
>   - Timer MBOX has two options (start/stop timer). We cannot modify inflight
>     timer message. Hence we reschedule timer if new timer value is greater than
>     current time and less than scheduled timer value.

Can't they fix their interface to match what we need instead ?

>   - SBE expects timeout value in milliseconds. We track timeout value in TB.
>     Hence we convert tb to milliseconds before sending request to SBE.

This is WAYYYYYY to coarse resolution. We want microseconds. They need
to fix their stuff.

>   - We are not requesting ack/response from SBE for timer message. This will
>     reduce unnecessary interrupts (as SBE send interrupt for ack/response).
>   - Disabling SBE timer
>     We expect SBE to send timer expiry interrupt whenever timer expires. We
>     wait for few more millisecond (presently SBE_TIMER_DEFAULT_MS) before
>     disabling timer.
>     In future we can consider below alternative approaches:
>       - Presently SBE timer disable is permanent (until we reboot system).
>         SBE sends "I'm back" interrupt after reset. We can consider restarting
>         timer after SBE reset.
>       - Reset SBE and start timer again.
>       - Each chip has SBE. On multi chip system we can try to schedule timer
>         on different chip.
> 
> Signed-off-by: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
> CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> ---
>  core/interrupts.c |   3 +-
>  core/timer.c      |  16 ++++--
>  hw/sbe-p9.c       | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  include/sbe-p9.h  |   6 +++
>  4 files changed, 164 insertions(+), 4 deletions(-)
> 
> diff --git a/core/interrupts.c b/core/interrupts.c
> index 439e1fb..73833d2 100644
> --- a/core/interrupts.c
> +++ b/core/interrupts.c
> @@ -26,6 +26,7 @@
>  #include <ccan/str/str.h>
>  #include <timer.h>
>  #include <sbe-p8.h>
> +#include <sbe-p9.h>
>  
>  /* ICP registers */
>  #define ICP_XIRR		0x4	/* 32-bit access */
> @@ -470,7 +471,7 @@ static int64_t opal_handle_interrupt(uint32_t isn, __be64 *outstanding_event_mas
>  	is->ops->interrupt(is, isn);
>  
>  	/* Check timers if SBE timer isn't working */
> -	if (!sbe_p8_timer_ok())
> +	if (!sbe_p8_timer_ok() && !sbe_timer_ok())
>  		check_timers(true);
>  
>  	/* Update output events */
> diff --git a/core/timer.c b/core/timer.c
> index cb4d07b..46b1894 100644
> --- a/core/timer.c
> +++ b/core/timer.c
> @@ -5,6 +5,7 @@
>  #include <device.h>
>  #include <opal.h>
>  #include <sbe-p8.h>
> +#include <sbe-p9.h>
>  
>  #ifdef __TEST__
>  #define this_cpu()	((void *)-1)
> @@ -109,8 +110,12 @@ static void __schedule_timer_at(struct timer *t, uint64_t when)
>   bail:
>  	/* Pick up the next timer and upddate the SBE HW timer */
>  	lt = list_top(&timer_list, struct timer, link);
> -	if (lt)
> -		sbe_p8_update_timer_expiry(lt->target);
> +	if (lt) {
> +		if (proc_gen < proc_gen_p9)
> +			sbe_p8_update_timer_expiry(lt->target);
> +		else
> +			sbe_update_timer_expiry(lt->target);
> +	}
>  }
>  
>  void schedule_timer_at(struct timer *t, uint64_t when)
> @@ -167,7 +172,10 @@ static void __check_poll_timers(uint64_t now)
>  		 * arbitrarily 1us.
>  		 */
>  		if (t->running) {
> -			sbe_p8_update_timer_expiry(now + usecs_to_tb(1));
> +			if (proc_gen < proc_gen_p9)
> +				sbe_p8_update_timer_expiry(now + usecs_to_tb(1));
> +			else
> +				sbe_update_timer_expiry(now + usecs_to_tb(1));
>  			break;
>  		}
>  
> @@ -265,6 +273,8 @@ void late_init_timers(void)
>  	 */
>  	if (platform.heartbeat_time) {
>  		heartbeat = platform.heartbeat_time();
> +	} else if (sbe_timer_ok()) {
> +		heartbeat = HEARTBEAT_DEFAULT_MS * 10;
>  	} else if (sbe_p8_timer_ok() || fsp_present()) {
>  		heartbeat = HEARTBEAT_DEFAULT_MS * 10;
>  	}
> diff --git a/hw/sbe-p9.c b/hw/sbe-p9.c
> index 3af1094..4ae4d7b 100644
> --- a/hw/sbe-p9.c
> +++ b/hw/sbe-p9.c
> @@ -23,6 +23,7 @@
>  #include <sbe-p9.h>
>  #include <skiboot.h>
>  #include <timebase.h>
> +#include <timer.h>
>  #include <trace.h>
>  #include <xscom.h>
>  
> @@ -48,6 +49,21 @@ struct sbe {
>  /* Default SBE chip ID */
>  static int sbe_default_chip_id = -1;
>  
> +/* Is SBE timer running? */
> +static bool sbe_has_timer = false;
> +static bool sbe_timer_in_progress = false;
> +
> +/* Inflight and next timer in TB */
> +static uint64_t sbe_last_gen_stamp;
> +static uint64_t sbe_timer_target;
> +
> +/* Default timeout value */
> +#define SBE_TIMER_DEFAULT_MS	10
> +static uint64_t sbe_timer_def_tb;
> +
> +/* Timer control message */
> +static struct sbe_msg *timer_ctrl_msg;
> +
>  #define SBE_STATUS_PRI_SHIFT	0x30
>  #define SBE_STATUS_SEC_SHIFT	0x20
>  #define SBE_FFDC_PRESENT	PPC_BIT(1)
> @@ -529,6 +545,12 @@ void sbe_interrupt(uint32_t chip_id)
>  		prd_sbe_passthrough(chip_id);
>  	}
>  
> +	/* Timer expired */
> +	if (chip_id == sbe_default_chip_id && (data & SBE_HOST_TIMER_EXPIRY)) {
> +		sbe_timer_in_progress = false;
> +		check_timers(true);
> +	}
> +
>  	/* Handle SBE response */
>  	if (msg_complete) {
>  		lock(&sbe->lock);
> @@ -594,6 +616,124 @@ static void sbe_timeout_poll(void *user_data __unused)
>  		sbe_poke_queue(sbe);
>  		unlock(&sbe->lock);
>  	}
> +
> +	/*
> +	 * Check if the timer is working. If at least SBE_TIMER_DEFAULT_MS
> +	 * milliseconds elapsed since last scheduled timer expiry.
> +	 */
> +	if (sbe_has_timer && sbe_timer_in_progress) {
> +		if (tb_compare(mftb(), sbe_last_gen_stamp + sbe_timer_def_tb)
> +		    != TB_AAFTERB)
> +			return;
> +
> +		/*
> +		 * In some cases there will be a delay in calling OPAL interrupt
> +		 * handler routine (opal_handle_interrupt). In such cases its
> +		 * possible that SBE has responded, but OPAL didn't act on that.
> +		 * Hence check for SBE response before disabling timer.
> +		 */
> +		sbe_interrupt(sbe_default_chip_id);
> +
> +		if (!sbe_timer_in_progress)
> +			return;
> +
> +		if (tb_compare(mftb(), sbe_last_gen_stamp + sbe_timer_def_tb)
> +		    != TB_AAFTERB)
> +			return;
> +
> +		prlog(PR_ERR, "Timer stuck, falling back to OPAL pollers.\n");
> +		prlog(PR_ERR, "You will likely have slower I2C and may have "
> +		      "experienced increased jitter.\n");
> +		sbe_reg_dump(sbe_default_chip_id);
> +		sbe_has_timer = false;
> +	}
> +}
> +
> +static void sbe_timer_start(void)
> +{
> +	int rc;
> +	u32 ms = SBE_TIMER_DEFAULT_MS;
> +	u64 tb_cnt, now = mftb();
> +
> +	if (!sbe_has_timer)
> +		return;
> +
> +	if (sbe_timer_in_progress) {
> +		if (sbe_timer_target < now ||
> +		    sbe_timer_target > sbe_last_gen_stamp)
> +			return;
> +
> +		/* Update timer control message */
> +		timer_ctrl_msg->reg[0] &= 0xffff;
> +		timer_ctrl_msg->reg[0] |= ((u64)CONTROL_TIMER_STOP << 32);
> +
> +		rc = sbe_sync_msg(sbe_default_chip_id, timer_ctrl_msg, false);
> +		if (rc != OPAL_SUCCESS) {
> +			/*
> +			 * Lets hope SBE will respond whenever original
> +			 * timer expires.
> +			 */
> +			prlog(PR_ERR, "Failed to stop timer [chip id = %x]\n",
> +			      sbe_default_chip_id);
> +			return;
> +		}
> +	}
> +
> +	now = mftb();
> +	if (now < sbe_timer_target) {
> +		/* Calculate how many ms from now, rounded up */
> +		tb_cnt = sbe_timer_target - now + msecs_to_tb(1) - 1;
> +		ms = tb_to_msecs(tb_cnt);
> +	}
> +
> +	/* Update timer control message */
> +	timer_ctrl_msg->reg[0] &= 0xffff;
> +	timer_ctrl_msg->reg[0] |= ((u64)CONTROL_TIMER_START << 32);
> +	timer_ctrl_msg->reg[1] = ms;
> +
> +	rc = sbe_sync_msg(sbe_default_chip_id, timer_ctrl_msg, false);
> +	if (rc != OPAL_SUCCESS) {
> +		prlog(PR_ERR, "Failed to start timer [chip id = %x]\n",
> +		      sbe_default_chip_id);
> +		return;
> +	}
> +
> +	/* Update last scheduled timer value */
> +	sbe_last_gen_stamp = mftb() + msecs_to_tb(ms);
> +	sbe_timer_in_progress = true;
> +}
> +
> +/*
> + * This is called with the timer lock held, so there is no
> + * issue with re-entrancy or concurrence
> + */
> +void sbe_update_timer_expiry(uint64_t new_target)
> +{
> +	if (!sbe_has_timer || new_target == sbe_timer_target)
> +		return;
> +
> +	sbe_timer_target = new_target;
> +	sbe_timer_start();
> +}
> +
> +/* Initialize SBE timer */
> +static void sbe_timer_init(void)
> +{
> +	/* Do not request ack/response for timer message */
> +	timer_ctrl_msg = sbe_mkmsg(SBE_CMD_CONTROL_TIMER,
> +				   CONTROL_TIMER_START, 0, 0, 0);
> +	assert(timer_ctrl_msg);
> +
> +	sbe_has_timer = true;
> +	sbe_timer_target = mftb();
> +	sbe_last_gen_stamp = ~0ull;
> +	sbe_timer_def_tb = msecs_to_tb(SBE_TIMER_DEFAULT_MS);
> +	prlog(PR_INFO, "Timer facility on chip %x\n", sbe_default_chip_id);
> +}
> +
> +bool sbe_timer_ok(void)
> +{
> +	return sbe_has_timer;
>  }
>  
>  void sbe_init(void)
> @@ -630,6 +770,9 @@ void sbe_init(void)
>  		return;
>  	}
>  
> +	/* Initiate SBE timer */
> +	sbe_timer_init();
> +
>  	/* Initiate SBE timeout poller */
>  	opal_add_poller(sbe_timeout_poll, NULL);
>  }
> diff --git a/include/sbe-p9.h b/include/sbe-p9.h
> index c5616ba..6ba2759 100644
> --- a/include/sbe-p9.h
> +++ b/include/sbe-p9.h
> @@ -227,4 +227,10 @@ extern void sbe_init(void);
>  /* SBE interrupt */
>  extern void sbe_interrupt(uint32_t chip_id);
>  
> +/* Is SBE timer available ? */
> +extern bool sbe_timer_ok(void);
> +
> +/* Update SBE timer expiry */
> +extern void sbe_update_timer_expiry(uint64_t new_target);
> +
>  #endif	/* __SBE_P9_H */
Vasant Hegde July 24, 2017, 1:51 p.m. | #2
On 07/24/2017 04:05 AM, Benjamin Herrenschmidt wrote:
> On Sun, 2017-07-23 at 17:00 +0530, Vasant Hegde wrote:
>> SBE on P9 provides one shot programmable timer facility. We can use this
>> to implement OPAL timers and hence limit the reliance on the Linux
>> heartbeat (similar to HW timer facility provided by SLW on P8).
>>
>> Design:
>>   - We will continue to run Linux heartbeat.
>>   - Each chip has SBE. This patch always schedules timer on SBE on master chip.
>>   - Timer MBOX has two options (start/stop timer). We cannot modify inflight
>>     timer message. Hence we reschedule timer if new timer value is greater than
>>     current time and less than scheduled timer value.
>
> Can't they fix their interface to match what we need instead ?

We had asked them .. but they told they are not supporting multiple timer.

So if new timer request is < inflight timer then we have to cancel and schedule 
new timer.

>
>>   - SBE expects timeout value in milliseconds. We track timeout value in TB.
>>     Hence we convert tb to milliseconds before sending request to SBE.
>
> This is WAYYYYYY to coarse resolution. We want microseconds. They need
> to fix their stuff.

We will check with FW folks on this.

-Vasant
Benjamin Herrenschmidt July 24, 2017, 9 p.m. | #3
On Mon, 2017-07-24 at 19:21 +0530, Vasant Hegde wrote:
> 
> > Can't they fix their interface to match what we need instead ?
> 
> We had asked them .. but they told they are not supporting multiple timer.
> 
> So if new timer request is < inflight timer then we have to cancel and schedule 
> new timer.

Why can't they have a "modify timer" request instead of requiring us to
send 2 requests ? That sounds brain dead. We should push, it's a
requirement from us. After all, afaik, we are the only user of that
facility.
> 
> > 
> > >   - SBE expects timeout value in milliseconds. We track timeout value in TB.
> > >     Hence we convert tb to milliseconds before sending request to SBE.
> > 
> > This is WAYYYYYY to coarse resolution. We want microseconds. They need
> > to fix their stuff.
> 
> We will check with FW folks on this.

Ben.

Patch

diff --git a/core/interrupts.c b/core/interrupts.c
index 439e1fb..73833d2 100644
--- a/core/interrupts.c
+++ b/core/interrupts.c
@@ -26,6 +26,7 @@ 
 #include <ccan/str/str.h>
 #include <timer.h>
 #include <sbe-p8.h>
+#include <sbe-p9.h>
 
 /* ICP registers */
 #define ICP_XIRR		0x4	/* 32-bit access */
@@ -470,7 +471,7 @@  static int64_t opal_handle_interrupt(uint32_t isn, __be64 *outstanding_event_mas
 	is->ops->interrupt(is, isn);
 
 	/* Check timers if SBE timer isn't working */
-	if (!sbe_p8_timer_ok())
+	if (!sbe_p8_timer_ok() && !sbe_timer_ok())
 		check_timers(true);
 
 	/* Update output events */
diff --git a/core/timer.c b/core/timer.c
index cb4d07b..46b1894 100644
--- a/core/timer.c
+++ b/core/timer.c
@@ -5,6 +5,7 @@ 
 #include <device.h>
 #include <opal.h>
 #include <sbe-p8.h>
+#include <sbe-p9.h>
 
 #ifdef __TEST__
 #define this_cpu()	((void *)-1)
@@ -109,8 +110,12 @@  static void __schedule_timer_at(struct timer *t, uint64_t when)
  bail:
 	/* Pick up the next timer and upddate the SBE HW timer */
 	lt = list_top(&timer_list, struct timer, link);
-	if (lt)
-		sbe_p8_update_timer_expiry(lt->target);
+	if (lt) {
+		if (proc_gen < proc_gen_p9)
+			sbe_p8_update_timer_expiry(lt->target);
+		else
+			sbe_update_timer_expiry(lt->target);
+	}
 }
 
 void schedule_timer_at(struct timer *t, uint64_t when)
@@ -167,7 +172,10 @@  static void __check_poll_timers(uint64_t now)
 		 * arbitrarily 1us.
 		 */
 		if (t->running) {
-			sbe_p8_update_timer_expiry(now + usecs_to_tb(1));
+			if (proc_gen < proc_gen_p9)
+				sbe_p8_update_timer_expiry(now + usecs_to_tb(1));
+			else
+				sbe_update_timer_expiry(now + usecs_to_tb(1));
 			break;
 		}
 
@@ -265,6 +273,8 @@  void late_init_timers(void)
 	 */
 	if (platform.heartbeat_time) {
 		heartbeat = platform.heartbeat_time();
+	} else if (sbe_timer_ok()) {
+		heartbeat = HEARTBEAT_DEFAULT_MS * 10;
 	} else if (sbe_p8_timer_ok() || fsp_present()) {
 		heartbeat = HEARTBEAT_DEFAULT_MS * 10;
 	}
diff --git a/hw/sbe-p9.c b/hw/sbe-p9.c
index 3af1094..4ae4d7b 100644
--- a/hw/sbe-p9.c
+++ b/hw/sbe-p9.c
@@ -23,6 +23,7 @@ 
 #include <sbe-p9.h>
 #include <skiboot.h>
 #include <timebase.h>
+#include <timer.h>
 #include <trace.h>
 #include <xscom.h>
 
@@ -48,6 +49,21 @@  struct sbe {
 /* Default SBE chip ID */
 static int sbe_default_chip_id = -1;
 
+/* Is SBE timer running? */
+static bool sbe_has_timer = false;
+static bool sbe_timer_in_progress = false;
+
+/* Inflight and next timer in TB */
+static uint64_t sbe_last_gen_stamp;
+static uint64_t sbe_timer_target;
+
+/* Default timeout value */
+#define SBE_TIMER_DEFAULT_MS	10
+static uint64_t sbe_timer_def_tb;
+
+/* Timer control message */
+static struct sbe_msg *timer_ctrl_msg;
+
 #define SBE_STATUS_PRI_SHIFT	0x30
 #define SBE_STATUS_SEC_SHIFT	0x20
 #define SBE_FFDC_PRESENT	PPC_BIT(1)
@@ -529,6 +545,12 @@  void sbe_interrupt(uint32_t chip_id)
 		prd_sbe_passthrough(chip_id);
 	}
 
+	/* Timer expired */
+	if (chip_id == sbe_default_chip_id && (data & SBE_HOST_TIMER_EXPIRY)) {
+		sbe_timer_in_progress = false;
+		check_timers(true);
+	}
+
 	/* Handle SBE response */
 	if (msg_complete) {
 		lock(&sbe->lock);
@@ -594,6 +616,124 @@  static void sbe_timeout_poll(void *user_data __unused)
 		sbe_poke_queue(sbe);
 		unlock(&sbe->lock);
 	}
+
+	/*
+	 * Check if the timer is working. If at least SBE_TIMER_DEFAULT_MS
+	 * milliseconds elapsed since last scheduled timer expiry.
+	 */
+	if (sbe_has_timer && sbe_timer_in_progress) {
+		if (tb_compare(mftb(), sbe_last_gen_stamp + sbe_timer_def_tb)
+		    != TB_AAFTERB)
+			return;
+
+		/*
+		 * In some cases there will be a delay in calling OPAL interrupt
+		 * handler routine (opal_handle_interrupt). In such cases its
+		 * possible that SBE has responded, but OPAL didn't act on that.
+		 * Hence check for SBE response before disabling timer.
+		 */
+		sbe_interrupt(sbe_default_chip_id);
+
+		if (!sbe_timer_in_progress)
+			return;
+
+		if (tb_compare(mftb(), sbe_last_gen_stamp + sbe_timer_def_tb)
+		    != TB_AAFTERB)
+			return;
+
+		prlog(PR_ERR, "Timer stuck, falling back to OPAL pollers.\n");
+		prlog(PR_ERR, "You will likely have slower I2C and may have "
+		      "experienced increased jitter.\n");
+		sbe_reg_dump(sbe_default_chip_id);
+		sbe_has_timer = false;
+	}
+}
+
+static void sbe_timer_start(void)
+{
+	int rc;
+	u32 ms = SBE_TIMER_DEFAULT_MS;
+	u64 tb_cnt, now = mftb();
+
+	if (!sbe_has_timer)
+		return;
+
+	if (sbe_timer_in_progress) {
+		if (sbe_timer_target < now ||
+		    sbe_timer_target > sbe_last_gen_stamp)
+			return;
+
+		/* Update timer control message */
+		timer_ctrl_msg->reg[0] &= 0xffff;
+		timer_ctrl_msg->reg[0] |= ((u64)CONTROL_TIMER_STOP << 32);
+
+		rc = sbe_sync_msg(sbe_default_chip_id, timer_ctrl_msg, false);
+		if (rc != OPAL_SUCCESS) {
+			/*
+			 * Lets hope SBE will respond whenever original
+			 * timer expires.
+			 */
+			prlog(PR_ERR, "Failed to stop timer [chip id = %x]\n",
+			      sbe_default_chip_id);
+			return;
+		}
+	}
+
+	now = mftb();
+	if (now < sbe_timer_target) {
+		/* Calculate how many ms from now, rounded up */
+		tb_cnt = sbe_timer_target - now + msecs_to_tb(1) - 1;
+		ms = tb_to_msecs(tb_cnt);
+	}
+
+	/* Update timer control message */
+	timer_ctrl_msg->reg[0] &= 0xffff;
+	timer_ctrl_msg->reg[0] |= ((u64)CONTROL_TIMER_START << 32);
+	timer_ctrl_msg->reg[1] = ms;
+
+	rc = sbe_sync_msg(sbe_default_chip_id, timer_ctrl_msg, false);
+	if (rc != OPAL_SUCCESS) {
+		prlog(PR_ERR, "Failed to start timer [chip id = %x]\n",
+		      sbe_default_chip_id);
+		return;
+	}
+
+	/* Update last scheduled timer value */
+	sbe_last_gen_stamp = mftb() + msecs_to_tb(ms);
+	sbe_timer_in_progress = true;
+}
+
+/*
+ * This is called with the timer lock held, so there is no
+ * issue with re-entrancy or concurrence
+ */
+void sbe_update_timer_expiry(uint64_t new_target)
+{
+	if (!sbe_has_timer || new_target == sbe_timer_target)
+		return;
+
+	sbe_timer_target = new_target;
+	sbe_timer_start();
+}
+
+/* Initialize SBE timer */
+static void sbe_timer_init(void)
+{
+	/* Do not request ack/response for timer message */
+	timer_ctrl_msg = sbe_mkmsg(SBE_CMD_CONTROL_TIMER,
+				   CONTROL_TIMER_START, 0, 0, 0);
+	assert(timer_ctrl_msg);
+
+	sbe_has_timer = true;
+	sbe_timer_target = mftb();
+	sbe_last_gen_stamp = ~0ull;
+	sbe_timer_def_tb = msecs_to_tb(SBE_TIMER_DEFAULT_MS);
+	prlog(PR_INFO, "Timer facility on chip %x\n", sbe_default_chip_id);
+}
+
+bool sbe_timer_ok(void)
+{
+	return sbe_has_timer;
 }
 
 void sbe_init(void)
@@ -630,6 +770,9 @@  void sbe_init(void)
 		return;
 	}
 
+	/* Initiate SBE timer */
+	sbe_timer_init();
+
 	/* Initiate SBE timeout poller */
 	opal_add_poller(sbe_timeout_poll, NULL);
 }
diff --git a/include/sbe-p9.h b/include/sbe-p9.h
index c5616ba..6ba2759 100644
--- a/include/sbe-p9.h
+++ b/include/sbe-p9.h
@@ -227,4 +227,10 @@  extern void sbe_init(void);
 /* SBE interrupt */
 extern void sbe_interrupt(uint32_t chip_id);
 
+/* Is SBE timer available ? */
+extern bool sbe_timer_ok(void);
+
+/* Update SBE timer expiry */
+extern void sbe_update_timer_expiry(uint64_t new_target);
+
 #endif	/* __SBE_P9_H */