@@ -1,6 +1,6 @@# List of files in the vdso, has to be asm only for now-obj-vdso64=sigtramp.ogettimeofday.odatapage.ocacheflush.onote.ogetcpu.o+obj-vdso64=sigtramp.ogettimeofday.odatapage.ocacheflush.onote.ogetcpu.ogettime.o# Build rules
@@ -0,0 +1,162 @@+/*+*Userlandimplementationofgettimeofday()for64bitsprocessesina+*ppc64kernelforuseinthevDSO+*+*Copyright(C)2017SantoshSivaraj(santosh@fossix.org),IBM.+*+*Originallyimplementedinassemblyby:+*BenjaminHerrenschmuidt(benh@kernel.crashing.org),+*IBMCorp.+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsoftheGNUGeneralPublicLicense+*aspublishedbytheFreeSoftwareFoundation;eitherversion+*2oftheLicense,or(atyouroption)anylaterversion.+*/++#include<linux/compiler.h>+#include<linux/types.h>+#include<asm/ppc_asm.h>+#include<asm/vdso.h>+#include<asm/vdso_datapage.h>+#include<asm/time.h>++staticnotracevoidkernel_get_tspec(structtimespec*tp,+structvdso_data*vdata,u32*wtom_sec,+u32*wtom_nsec)+{+u64tb;+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++/* Get TB, offset it and scale result */+tb=mulhdu((get_tb()-vdata->tb_orig_stamp)<<12,+vdata->tb_to_xs)+vdata->stamp_sec_fraction;+tp->tv_sec=vdata->stamp_xtime.tv_sec;+if(wtom_sec)+*wtom_sec=vdata->wtom_clock_sec;+if(wtom_nsec)+*wtom_nsec=vdata->wtom_clock_nsec;+}while(update_count!=vdata->tb_update_count);++tp->tv_nsec=((u64)mulhwu(tb,NSEC_PER_SEC)<<32)>>32;+tp->tv_sec+=(tb>>32);+}++staticnotraceintclock_get_realtime(structtimespec*tp,+structvdso_data*vdata)+{+kernel_get_tspec(tp,vdata,NULL,NULL);++return0;+}++staticnotraceintclock_get_monotonic(structtimespec*tp,+structvdso_data*vdata)+{+__s32wtom_sec,wtom_nsec;+u64nsec;++kernel_get_tspec(tp,vdata,&wtom_sec,&wtom_nsec);++tp->tv_sec+=wtom_sec;++nsec=tp->tv_nsec;+tp->tv_nsec=0;+timespec_add_ns(tp,nsec+wtom_nsec);++return0;+}++staticnotraceintclock_realtime_coarse(structtimespec*tp,+structvdso_data*vdata)+{+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++tp->tv_sec=vdata->stamp_xtime.tv_sec;+tp->tv_nsec=vdata->stamp_xtime.tv_nsec;+}while(update_count!=vdata->tb_update_count);++return0;+}++staticnotraceintclock_monotonic_coarse(structtimespec*tp,+structvdso_data*vdata)+{+__s32wtom_sec,wtom_nsec;+u64nsec;+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++tp->tv_sec=vdata->stamp_xtime.tv_sec;+tp->tv_nsec=vdata->stamp_xtime.tv_nsec;+wtom_sec=vdata->wtom_clock_sec;+wtom_nsec=vdata->wtom_clock_nsec;+}while(update_count!=vdata->tb_update_count);++tp->tv_sec+=wtom_sec;+nsec=tp->tv_nsec;+tp->tv_nsec=0;+timespec_add_ns(tp,nsec+wtom_nsec);++return0;+}++staticnotraceintgettime_syscall_fallback(clockid_tclk_id,+structtimespec*tp)+{+registerclockid_tidasm("r3")=clk_id;+registerstructtimespec*tasm("r4")=tp;+registerintnrasm("r0")=__NR_clock_gettime;+registerintretasm("r3");++asmvolatile("sc"+:"=r"(ret)+:"r"(nr),"r"(id),"r"(t)+:"memory");++returnret;+}++notraceintkernel_clock_gettime(clockid_tclk_id,structtimespec*tp)+{+intret;+structvdso_data*vdata=__get_datapage();++if(!tp||!vdata)+return-EBADR;++switch(clk_id){+caseCLOCK_REALTIME:+ret=clock_get_realtime(tp,vdata);+break;+caseCLOCK_MONOTONIC:+ret=clock_get_monotonic(tp,vdata);+break;+caseCLOCK_REALTIME_COARSE:+ret=clock_realtime_coarse(tp,vdata);+break;+caseCLOCK_MONOTONIC_COARSE:+ret=clock_monotonic_coarse(tp,vdata);+break;+default:+/* fallback to syscall */+ret=-1;+break;+}++if(ret)+ret=gettime_syscall_fallback(clk_id,tp);++returnret;+}
From: Michael Ellerman <mpe@ellerman.id.au> Date: 2017-07-20 13:18:27
Santosh Sivaraj [off-list ref] writes:
Current vDSO64 implementation does not have support for coarse
clocks (CLOCK_MONOTONIC_COARSE, CLOCK_REALTIME_COARSE), for which it falls
back to system call. Below is a benchmark of the difference in execution
time with and without vDSO support.
Hi Santosh,
Great patch! Always good to see asm replaced with C.
Clearing CR0[SO] says that the syscall always succeeded.
What happens if you call this with a completely bogus clock id?
I think the solution is probably to do the syscall fallback in asm, and
everything else in C.
cheers
From: Benjamin Herrenschmidt <benh@kernel.crashing.org> Date: 2017-07-20 21:17:59
On Thu, 2017-07-20 at 23:18 +1000, Michael Ellerman wrote:
Santosh Sivaraj [off-list ref] writes:
quoted
Current vDSO64 implementation does not have support for coarse
clocks (CLOCK_MONOTONIC_COARSE, CLOCK_REALTIME_COARSE), for which it falls
back to system call. Below is a benchmark of the difference in execution
time with and without vDSO support.
Hi Santosh,
Great patch! Always good to see asm replaced with C.
Yeah ewll ... when C becomes some kind of weird glorifed asm like
below, I don't see much of a point ;-)
Clearing CR0[SO] says that the syscall always succeeded.
What happens if you call this with a completely bogus clock id?
I think the solution is probably to do the syscall fallback in asm, and
everything else in C.
cheers
+static notrace int gettime_syscall_fallback(clockid_t clk_id,
+ struct timespec *tp)
+{
+ register clockid_t id asm("r3") = clk_id;
+ register struct timespec *t asm("r4") = tp;
+ register int nr asm("r0") = __NR_clock_gettime;
+ register int ret asm("r3");
I guess this works. I've always been a bit nervous about register
variables TBH.
Does it really work ? That really makes me nervous too, I woudn't do
this without a strong ack from a toolchain person... Segher ?
Local register variables work perfectly well, but only for one thing:
those variables are guaranteed to be in those registers, _as arguments
to an asm_.
Not sure we need the memory clobber?
It can clobber more registers than that though.
It needs the memory clobber (if the system call accessess any of "your"
memory). You need more register clobbers: also for most CR fields,
CTR, etc.
One trick that often works is doing the system call from within an
assembler function that uses the C ABI, since that has almost the same
calling conventions.
Something as simple as
.globl syscall42
syscall42:
li 0,42
sc
blr
(and yeah handle the CR bit 3 thing somehow)
and declare it as
int syscall42(some_type r3_arg, another_type r4_arg);
Inline asm is good when you want the asm code inlined into the callers,
potentially with arguments optimised etc. The only overhead making the
syscall a function has is that single blr; the only optimisation you
miss is you could potentially load GPR0 a bit earlier (and you can get
a tiny bit more scheduling flexibility).
Segher
From: Benjamin Herrenschmidt <benh@kernel.crashing.org> Date: 2017-07-21 06:06:00
On Fri, 2017-07-21 at 10:10 +0530, Santosh Sivaraj wrote:
In case of a bogus clock id, the default case sets 'ret' to -1, which forces it
to fallback to the syscall.
quoted
I think the solution is probably to do the syscall fallback in asm, and
everything else in C.
Ok. That's what Sergey also sugested. I will send a v2.
Beware that the vDSO has no TOC. You need to be extremely careful that
the generated code by the compiler doesn't try to access/use a TOC.
I would rather continue doing things in asm at this stage.
Ben.
@@ -1,6 +1,6 @@# List of files in the vdso, has to be asm only for now-obj-vdso64=sigtramp.ogettimeofday.odatapage.ocacheflush.onote.ogetcpu.o+obj-vdso64=sigtramp.ogettimeofday.odatapage.ocacheflush.onote.ogetcpu.ogettime.o# Build rules
@@ -0,0 +1,143 @@+/*+*Userlandimplementationofgettimeofday()for64bitsprocessesina+*ppc64kernelforuseinthevDSO+*+*Copyright(C)2017SantoshSivaraj(santosh@fossix.org),IBM.+*+*Originallyimplementedinassemblyby:+*BenjaminHerrenschmuidt(benh@kernel.crashing.org),+*IBMCorp.+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsoftheGNUGeneralPublicLicense+*aspublishedbytheFreeSoftwareFoundation;eitherversion+*2oftheLicense,or(atyouroption)anylaterversion.+*/++#include<linux/compiler.h>+#include<linux/types.h>+#include<asm/ppc_asm.h>+#include<asm/vdso.h>+#include<asm/vdso_datapage.h>+#include<asm/time.h>++staticnotracevoidkernel_get_tspec(structtimespec*tp,+structvdso_data*vdata,u32*wtom_sec,+u32*wtom_nsec)+{+u64tb;+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++/* Get TB, offset it and scale result */+tb=mulhdu((get_tb()-vdata->tb_orig_stamp)<<12,+vdata->tb_to_xs)+vdata->stamp_sec_fraction;+tp->tv_sec=vdata->stamp_xtime.tv_sec;+if(wtom_sec)+*wtom_sec=vdata->wtom_clock_sec;+if(wtom_nsec)+*wtom_nsec=vdata->wtom_clock_nsec;+}while(update_count!=vdata->tb_update_count);++tp->tv_nsec=((u64)mulhwu(tb,NSEC_PER_SEC)<<32)>>32;+tp->tv_sec+=(tb>>32);+}++staticnotraceintclock_get_realtime(structtimespec*tp,+structvdso_data*vdata)+{+kernel_get_tspec(tp,vdata,NULL,NULL);++return0;+}++staticnotraceintclock_get_monotonic(structtimespec*tp,+structvdso_data*vdata)+{+__s32wtom_sec,wtom_nsec;+u64nsec;++kernel_get_tspec(tp,vdata,&wtom_sec,&wtom_nsec);++tp->tv_sec+=wtom_sec;++nsec=tp->tv_nsec;+tp->tv_nsec=0;+timespec_add_ns(tp,nsec+wtom_nsec);++return0;+}++staticnotraceintclock_realtime_coarse(structtimespec*tp,+structvdso_data*vdata)+{+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++tp->tv_sec=vdata->stamp_xtime.tv_sec;+tp->tv_nsec=vdata->stamp_xtime.tv_nsec;+}while(update_count!=vdata->tb_update_count);++return0;+}++staticnotraceintclock_monotonic_coarse(structtimespec*tp,+structvdso_data*vdata)+{+__s32wtom_sec,wtom_nsec;+u64nsec;+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++tp->tv_sec=vdata->stamp_xtime.tv_sec;+tp->tv_nsec=vdata->stamp_xtime.tv_nsec;+wtom_sec=vdata->wtom_clock_sec;+wtom_nsec=vdata->wtom_clock_nsec;+}while(update_count!=vdata->tb_update_count);++tp->tv_sec+=wtom_sec;+nsec=tp->tv_nsec;+tp->tv_nsec=0;+timespec_add_ns(tp,nsec+wtom_nsec);++return0;+}++notraceintkernel_clock_gettime(clockid_tclk_id,structtimespec*tp)+{+intret;+structvdso_data*vdata=__get_datapage();++if(!tp||!vdata)+return-EBADR;++switch(clk_id){+caseCLOCK_REALTIME:+ret=clock_get_realtime(tp,vdata);+break;+caseCLOCK_MONOTONIC:+ret=clock_get_monotonic(tp,vdata);+break;+caseCLOCK_REALTIME_COARSE:+ret=clock_realtime_coarse(tp,vdata);+break;+caseCLOCK_MONOTONIC_COARSE:+ret=clock_monotonic_coarse(tp,vdata);+break;+default:+/* fallback to syscall */+ret=-1;+break;+}++returnret;+}
gettime.c:(.text+0xac): undefined reference to `.__get_datapage'
collect2: error: ld returned 1 exit status
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
@@ -1,6 +1,6 @@# List of files in the vdso, has to be asm only for now-obj-vdso64=sigtramp.ogettimeofday.odatapage.ocacheflush.onote.ogetcpu.o+obj-vdso64=sigtramp.ogettimeofday.odatapage.ocacheflush.onote.ogetcpu.ogettime.o# Build rules
@@ -22,12 +22,6 @@ __kernel_datapage_offset:V_FUNCTION_BEGIN(__get_datapage).cfi_startproc-/*Wedon't want that exposed or overridable as we want other objects-*tobeabletobldirectlytohere-*/-.protected__get_datapage-.hidden__get_datapage-mflrr0.cfi_registerlr,r0
@@ -0,0 +1,143 @@+/*+*Userlandimplementationofgettimeofday()for64bitsprocessesina+*ppc64kernelforuseinthevDSO+*+*Copyright(C)2017SantoshSivaraj(santosh@fossix.org),IBM.+*+*Originallyimplementedinassemblyby:+*BenjaminHerrenschmuidt(benh@kernel.crashing.org),+*IBMCorp.+*+*Thisprogramisfreesoftware;youcanredistributeitand/or+*modifyitunderthetermsoftheGNUGeneralPublicLicense+*aspublishedbytheFreeSoftwareFoundation;eitherversion+*2oftheLicense,or(atyouroption)anylaterversion.+*/++#include<linux/compiler.h>+#include<linux/types.h>+#include<asm/ppc_asm.h>+#include<asm/vdso.h>+#include<asm/vdso_datapage.h>+#include<asm/time.h>++staticnotracevoidkernel_get_tspec(structtimespec*tp,+structvdso_data*vdata,u32*wtom_sec,+u32*wtom_nsec)+{+u64tb;+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++/* Get TB, offset it and scale result */+tb=mulhdu((get_tb()-vdata->tb_orig_stamp)<<12,+vdata->tb_to_xs)+vdata->stamp_sec_fraction;+tp->tv_sec=vdata->stamp_xtime.tv_sec;+if(wtom_sec)+*wtom_sec=vdata->wtom_clock_sec;+if(wtom_nsec)+*wtom_nsec=vdata->wtom_clock_nsec;+}while(update_count!=vdata->tb_update_count);++tp->tv_nsec=((u64)mulhwu(tb,NSEC_PER_SEC)<<32)>>32;+tp->tv_sec+=(tb>>32);+}++staticnotraceintclock_get_realtime(structtimespec*tp,+structvdso_data*vdata)+{+kernel_get_tspec(tp,vdata,NULL,NULL);++return0;+}++staticnotraceintclock_get_monotonic(structtimespec*tp,+structvdso_data*vdata)+{+__s32wtom_sec,wtom_nsec;+u64nsec;++kernel_get_tspec(tp,vdata,&wtom_sec,&wtom_nsec);++tp->tv_sec+=wtom_sec;++nsec=tp->tv_nsec;+tp->tv_nsec=0;+timespec_add_ns(tp,nsec+wtom_nsec);++return0;+}++staticnotraceintclock_realtime_coarse(structtimespec*tp,+structvdso_data*vdata)+{+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++tp->tv_sec=vdata->stamp_xtime.tv_sec;+tp->tv_nsec=vdata->stamp_xtime.tv_nsec;+}while(update_count!=vdata->tb_update_count);++return0;+}++staticnotraceintclock_monotonic_coarse(structtimespec*tp,+structvdso_data*vdata)+{+__s32wtom_sec,wtom_nsec;+u64nsec;+u32update_count;++do{+/* check for update count & load values */+update_count=vdata->tb_update_count;++tp->tv_sec=vdata->stamp_xtime.tv_sec;+tp->tv_nsec=vdata->stamp_xtime.tv_nsec;+wtom_sec=vdata->wtom_clock_sec;+wtom_nsec=vdata->wtom_clock_nsec;+}while(update_count!=vdata->tb_update_count);++tp->tv_sec+=wtom_sec;+nsec=tp->tv_nsec;+tp->tv_nsec=0;+timespec_add_ns(tp,nsec+wtom_nsec);++return0;+}++notraceintkernel_clock_gettime(clockid_tclk_id,structtimespec*tp)+{+intret;+structvdso_data*vdata=__get_datapage();++if(!tp||!vdata)+return-EBADR;++switch(clk_id){+caseCLOCK_REALTIME:+ret=clock_get_realtime(tp,vdata);+break;+caseCLOCK_MONOTONIC:+ret=clock_get_monotonic(tp,vdata);+break;+caseCLOCK_REALTIME_COARSE:+ret=clock_realtime_coarse(tp,vdata);+break;+caseCLOCK_MONOTONIC_COARSE:+ret=clock_monotonic_coarse(tp,vdata);+break;+default:+/* fallback to syscall */+ret=-1;+break;+}++returnret;+}
+ do {
+ /* check for update count & load values */
+ update_count = vdata->tb_update_count;
+
+ /* Get TB, offset it and scale result */
+ tb = mulhdu((get_tb() - vdata->tb_orig_stamp) << 12,
+ vdata->tb_to_xs) + vdata->stamp_sec_fraction;
+ tp->tv_sec = vdata->stamp_xtime.tv_sec;
+ if (wtom_sec)
+ *wtom_sec = vdata->wtom_clock_sec;
+ if (wtom_nsec)
+ *wtom_nsec = vdata->wtom_clock_nsec;
+ } while (update_count != vdata->tb_update_count);
The assembly code is carefuly crafted to create a chain of data
dependencies in order to enforce the ordering in this function,
you completely broke it.
IE. the pointer used to access tb_orig_stamp etc... depends on the
initial update count, and the final read of the update count depends
on all the previously read values (or should), thus ordering those
loads. Withtout that you'll need more expensive lwsync's.
Additionally, you broke another semantic of the seqlock which is
to spin on the first update count if it has an odd value.
The same problem exist in all your other implementations.
I am really NOT a fan of that attempt at converting to C. The code is
hand crafted assembly for a number of reasons, including the above ones
and maximum performance.
As it is, it's deeply broken.
+ do {
+ /* check for update count & load values */
+ update_count = vdata->tb_update_count;
+
+ /* Get TB, offset it and scale result */
+ tb = mulhdu((get_tb() - vdata->tb_orig_stamp) << 12,
+ vdata->tb_to_xs) + vdata->stamp_sec_fraction;
+ tp->tv_sec = vdata->stamp_xtime.tv_sec;
+ if (wtom_sec)
+ *wtom_sec = vdata->wtom_clock_sec;
+ if (wtom_nsec)
+ *wtom_nsec = vdata->wtom_clock_nsec;
+ } while (update_count != vdata->tb_update_count);
The assembly code is carefuly crafted to create a chain of data
dependencies in order to enforce the ordering in this function,
you completely broke it.
IE. the pointer used to access tb_orig_stamp etc... depends on the
initial update count, and the final read of the update count depends
on all the previously read values (or should), thus ordering those
loads. Withtout that you'll need more expensive lwsync's.
Additionally, you broke another semantic of the seqlock which is
to spin on the first update count if it has an odd value.
The same problem exist in all your other implementations.
I am really NOT a fan of that attempt at converting to C. The code is
hand crafted assembly for a number of reasons, including the above ones
and maximum performance.
As it is, it's deeply broken.
I get the point. I looked at the generated assembly a bit closer, the update
count is optimized out. Will send the alternative asm only patch.
Thanks,
Santosh
From: Benjamin Herrenschmidt <benh@kernel.crashing.org> Date: 2017-07-26 02:03:07
On Tue, 2017-07-25 at 19:17 +0530, Santosh Sivaraj wrote:
I get the point. I looked at the generated assembly a bit closer, the update
count is optimized out. Will send the alternative asm only patch.
We could do it in C the way x86 does it, using some helpers for
begin/end and have either an lwsync (but that would be slower than
the data dependency I think) or carefully crafting the helpers to
create one (make them return the pointer).
If you go down that path though, you need to make sure we do not
generate any TOC reference as the vDSO doesn't have a TOC.
Cheers,
Ben.
Current vDSO64 implementation does not have support for coarse clocks
(CLOCK_MONOTONIC_COARSE, CLOCK_REALTIME_COARSE), for which it falls back
to system call, increasing the response time, vDSO implementation reduces
the cycle time. Below is a benchmark of the difference in execution time
with and without vDSO support.
(Non-coarse clocks are also included just for completion)
Without vDSO support:
--------------------
clock-gettime-realtime: syscall: 172 nsec/call
clock-gettime-realtime: libc: 26 nsec/call
clock-gettime-realtime: vdso: 21 nsec/call
clock-gettime-monotonic: syscall: 170 nsec/call
clock-gettime-monotonic: libc: 30 nsec/call
clock-gettime-monotonic: vdso: 24 nsec/call
clock-gettime-realtime-coarse: syscall: 153 nsec/call
clock-gettime-realtime-coarse: libc: 15 nsec/call
clock-gettime-realtime-coarse: vdso: 9 nsec/call
clock-gettime-monotonic-coarse: syscall: 167 nsec/call
clock-gettime-monotonic-coarse: libc: 15 nsec/call
clock-gettime-monotonic-coarse: vdso: 11 nsec/call
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Santosh Sivaraj <redacted>
---
arch/powerpc/kernel/asm-offsets.c | 2 +
arch/powerpc/kernel/vdso64/gettimeofday.S | 73 ++++++++++++++++++++++++++++---
2 files changed, 68 insertions(+), 7 deletions(-)
@@ -396,6 +396,8 @@ int main(void)/* Other bits used by the vdso */DEFINE(CLOCK_REALTIME,CLOCK_REALTIME);DEFINE(CLOCK_MONOTONIC,CLOCK_MONOTONIC);+DEFINE(CLOCK_REALTIME_COARSE,CLOCK_REALTIME_COARSE);+DEFINE(CLOCK_MONOTONIC_COARSE,CLOCK_MONOTONIC_COARSE);DEFINE(NSEC_PER_SEC,NSEC_PER_SEC);DEFINE(CLOCK_REALTIME_RES,MONOTONIC_RES_NSEC);
@@ -110,6 +117,58 @@ V_FUNCTION_BEGIN(__kernel_clock_gettime)1:bgecr1,80faddir4,r4,-1addr5,r5,r7+b80f++/*+*Forcoarseclockswegetdatadirectlyfromthevdsodatapage,so+*wedon't need to call __do_get_tspec, but we still need to do the+*countertrick.+*/+65:blV_LOCAL_FUNC(__get_datapage)/*getdatapage*/+70:ldr8,CFG_TB_UPDATE_COUNT(r3)+andi.r0,r8,1/*pendingupdate?loop*/+bne-70b+xorr0,r8,r8/*createdependency*/+addr3,r3,r0++/*+*CLOCK_REALTIME_COARSE,belowvaluesareneededforMONOTONIC_COARSE+*too+*/+ldr4,STAMP_XTIME+TSPC64_TV_SEC(r3)+ldr5,STAMP_XTIME+TSPC64_TV_NSEC(r3)+bnecr1,78f++/*CLOCK_MONOTONIC_COARSE*/+lwar6,WTOM_CLOCK_SEC(r3)+lwar9,WTOM_CLOCK_NSEC(r3)++/*checkifcounterhasupdated*/+78:orr0,r6,r9+xorr0,r0,r0+addr3,r3,r0+ldr0,CFG_TB_UPDATE_COUNT(r3)+cmpldcr0,r0,r8/*checkifupdated*/+bne-70b++/*Counterhasnotupdated,socontinuecalculatingpropervaluesfor+*secandnsecifmonotoniccoarse,orjustreturnwiththeproper+*valuesforrealtime.+*/+bnecr1,80f++/*Addwall->monotonicoffsetandcheckforoverfloworunderflow.+*/+addr4,r4,r6+addr5,r5,r9+cmpdcr0,r5,r7+cmpdicr1,r5,0+blt79f+subfr5,r7,r5+addir4,r4,1+79:bgecr1,80f+addir4,r4,-1+addr5,r5,r780:stdr4,TSPC64_TV_SEC(r11)stdr5,TSPC64_TV_NSEC(r11)