diff mbox

[RFC] hw/fake-rtc: Add support for emulated rtc clock

Message ID 1441616660-6355-1-git-send-email-vaibhav@linux.vnet.ibm.com
State Superseded
Headers show

Commit Message

Vaibhav Jain Sept. 7, 2015, 9:04 a.m. UTC
This patch adds support for an emulated rtc clock over existing fake rtc
implementation for generic platform (e.g BML). Presently a fake rtc
clock is initialized when reserved region named 'ibm,fake-rtc' is
present in the boot device tree. This mem-region points to the initial
value of bcd coded date-time values. However as this region is in system
memory hence its not updated with time. This results in an error from
hwclock tool which tries to detect a change in system clock and then
complains "Timed out waiting for time change."

The patch overcomes this issue by emulating an rtc clock that's driven
by a periodically scheduled timer. The initial epoch is set from the
values at "ibm,fake-rtc" memory region. It then creates the periodic
timer and on each tick the rtc counter is incremented.

Signed-off-by: Vaibhav Jain <vaibhav@linux.vnet.ibm.com>
---
 hw/fake-rtc.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 75 insertions(+), 9 deletions(-)

Comments

Benjamin Herrenschmidt Sept. 7, 2015, 8:56 p.m. UTC | #1
On Mon, 2015-09-07 at 14:34 +0530, Vaibhav Jain wrote:
> This patch adds support for an emulated rtc clock over existing fake
> rtc
> implementation for generic platform (e.g BML). Presently a fake rtc
> clock is initialized when reserved region named 'ibm,fake-rtc' is
> present in the boot device tree. This mem-region points to the
> initial
> value of bcd coded date-time values. However as this region is in
> system
> memory hence its not updated with time. This results in an error from
> hwclock tool which tries to detect a change in system clock and then
> complains "Timed out waiting for time change."
> 
> The patch overcomes this issue by emulating an rtc clock that's
> driven
> by a periodically scheduled timer. The initial epoch is set from the
> values at "ibm,fake-rtc" memory region. It then creates the periodic
> timer and on each tick the rtc counter is incremented.

Why ? Why not deduce the RTC clock from the timebase ? We already have
code to do that, don't we ?

Cheers,
Ben.
Vaibhav Jain Sept. 8, 2015, 4:30 a.m. UTC | #2
On Tue, 2015-09-08 at 06:56 +1000, Benjamin Herrenschmidt wrote:
> On Mon, 2015-09-07 at 14:34 +0530, Vaibhav Jain wrote:
> > The patch overcomes this issue by emulating an rtc clock that's
> > driven
> > by a periodically scheduled timer. The initial epoch is set from the
> > values at "ibm,fake-rtc" memory region. It then creates the periodic
> > timer and on each tick the rtc counter is incremented.
> 
> Why ? Why not deduce the RTC clock from the timebase ? We already have
> code to do that, don't we ?
Agreed Benh. I will send v2 with the changes. Original patch was an
effort to reduce the overhead of rtc_read. However just realized that it
was pointless.

~ Vaibhav
diff mbox

Patch

diff --git a/hw/fake-rtc.c b/hw/fake-rtc.c
index 1b7c473..7b447fc 100644
--- a/hw/fake-rtc.c
+++ b/hw/fake-rtc.c
@@ -17,14 +17,30 @@ 
 #include <skiboot.h>
 #include <opal.h>
 #include <mem_region.h>
+#include <device.h>
+#include <timebase.h>
+#include <timer.h>
+#include <time-utils.h>
+#include <lock.h>
 
-static uint32_t *fake_ymd;
-static uint64_t *fake_hmsm;
+/* use it to store and hold rtc value */
+static struct timer emulation_timer;
+
+/* timebase when last synchronization happened */
+static unsigned long tb_synctime;
+
+/* current rtc value */
+struct tm tm_offset;
+
+/* protects tm_offset & tb_synctime */
+struct lock emulation_lock;
 
 static int64_t fake_rtc_write(uint32_t ymd, uint64_t hmsm)
 {
-	*fake_ymd = ymd;
-	*fake_hmsm = hmsm;
+	lock(&emulation_lock);
+	datetime_to_tm(ymd, hmsm, &tm_offset);
+	tb_synctime = mftb();
+	unlock(&emulation_lock);
 
 	return OPAL_SUCCESS;
 }
@@ -34,16 +50,51 @@  static int64_t fake_rtc_read(uint32_t *ymd, uint64_t *hmsm)
 	if (!ymd || !hmsm)
 		return OPAL_PARAMETER;
 
-	*ymd = *fake_ymd;
-	*hmsm = *fake_hmsm;
+	/* Read emulated clock */
+	lock(&emulation_lock);
+	tm_to_datetime(&tm_offset, ymd, hmsm);
+	unlock(&emulation_lock);
 
 	return OPAL_SUCCESS;
 }
 
+/* update the emulated rtc. In case more than 60 seconds have passed
+ * since last sync then recompute the tm_offset.
+ */
+static void __emulated_rtc_update(struct timer *tm __unused,
+			       void *data __unused)
+{
+	time_t sec;
+
+	if (try_lock(&emulation_lock)) {
+
+		sec = tb_to_secs(mftb() - tb_synctime);
+		tb_synctime = mftb();
+
+		if ((sec + tm_offset.tm_sec) >= 60) {
+			sec += mktime(&tm_offset);
+			gmtime_r(&sec, &tm_offset);
+		} else {
+			tm_offset.tm_sec += sec;
+		}
+
+		unlock(&emulation_lock);
+	}
+	/* reschedule the timer */
+	schedule_timer(&emulation_timer, secs_to_tb(1));
+}
+
 void fake_rtc_init(void)
 {
 	struct mem_region *rtc_region = NULL;
-	uint32_t *rtc = NULL;
+	uint32_t *rtc = NULL, *fake_ymd;
+	uint64_t *fake_hmsm;
+	struct dt_node *np;
+	const time_t inittime = 0;
+
+	/* Init the timer to 0 epoch */
+	gmtime_r(&inittime, &tm_offset);
+	tb_synctime = mftb();
 
 	/* Read initial values from reserved memory */
 	rtc_region = find_mem_region("ibm,fake-rtc");
@@ -54,14 +105,29 @@  void fake_rtc_init(void)
 		return;
 	}
 
+	/* Fetch the initial rtc values */
 	rtc = (uint32_t *) rtc_region->start;
 
 	fake_ymd = rtc;
 	fake_hmsm = ((uint64_t *) &rtc[1]);
 
-	prlog(PR_TRACE, "Init fake RTC to 0x%x 0x%llx\n",
-	      *fake_ymd, *fake_hmsm);
+	datetime_to_tm(*fake_ymd, *fake_hmsm, &tm_offset);
 
+	init_lock(&emulation_lock);
+
+	/* Schedule Tick Timer */
+	init_timer(&emulation_timer, __emulated_rtc_update, NULL);
+	schedule_timer(&emulation_timer, secs_to_tb(1));
+
+	/* Register opal calls */
 	opal_register(OPAL_RTC_READ, fake_rtc_read, 2);
 	opal_register(OPAL_RTC_WRITE, fake_rtc_write, 2);
+
+	/* add the fake rtc dt node */
+	np = dt_new(opal_node, "rtc");
+	dt_add_property_strings(np, "compatible", "ibm,opal-rtc");
+
+	prlog(PR_TRACE, "Init fake RTC to Date:%d-%d-%d Time:%d-%d-%d\n",
+	      tm_offset.tm_mon, tm_offset.tm_mday, tm_offset.tm_year,
+	      tm_offset.tm_hour, tm_offset.tm_min, tm_offset.tm_sec);
 }