271 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| // Copyright (C) 2005-2017 Andes Technology Corporation
 | |
| 
 | |
| #include <linux/compiler.h>
 | |
| #include <linux/hrtimer.h>
 | |
| #include <linux/time.h>
 | |
| #include <asm/io.h>
 | |
| #include <asm/barrier.h>
 | |
| #include <asm/bug.h>
 | |
| #include <asm/page.h>
 | |
| #include <asm/unistd.h>
 | |
| #include <asm/vdso_datapage.h>
 | |
| #include <asm/vdso_timer_info.h>
 | |
| #include <asm/asm-offsets.h>
 | |
| 
 | |
| #define X(x) #x
 | |
| #define Y(x) X(x)
 | |
| 
 | |
| extern struct vdso_data *__get_datapage(void);
 | |
| extern struct vdso_data *__get_timerpage(void);
 | |
| 
 | |
| static notrace unsigned int __vdso_read_begin(const struct vdso_data *vdata)
 | |
| {
 | |
| 	u32 seq;
 | |
| repeat:
 | |
| 	seq = READ_ONCE(vdata->seq_count);
 | |
| 	if (seq & 1) {
 | |
| 		cpu_relax();
 | |
| 		goto repeat;
 | |
| 	}
 | |
| 	return seq;
 | |
| }
 | |
| 
 | |
| static notrace unsigned int vdso_read_begin(const struct vdso_data *vdata)
 | |
| {
 | |
| 	unsigned int seq;
 | |
| 
 | |
| 	seq = __vdso_read_begin(vdata);
 | |
| 
 | |
| 	smp_rmb();		/* Pairs with smp_wmb in vdso_write_end */
 | |
| 	return seq;
 | |
| }
 | |
| 
 | |
| static notrace int vdso_read_retry(const struct vdso_data *vdata, u32 start)
 | |
| {
 | |
| 	smp_rmb();		/* Pairs with smp_wmb in vdso_write_begin */
 | |
| 	return vdata->seq_count != start;
 | |
| }
 | |
| 
 | |
| static notrace long clock_gettime_fallback(clockid_t _clkid,
 | |
| 					   struct timespec *_ts)
 | |
| {
 | |
| 	register struct timespec *ts asm("$r1") = _ts;
 | |
| 	register clockid_t clkid asm("$r0") = _clkid;
 | |
| 	register long ret asm("$r0");
 | |
| 
 | |
| 	asm volatile ("movi	$r15, %3\n"
 | |
| 		      "syscall 	0x0\n"
 | |
| 		      :"=r" (ret)
 | |
| 		      :"r"(clkid), "r"(ts), "i"(__NR_clock_gettime)
 | |
| 		      :"$r15", "memory");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static notrace int do_realtime_coarse(struct timespec *ts,
 | |
| 				      struct vdso_data *vdata)
 | |
| {
 | |
| 	u32 seq;
 | |
| 
 | |
| 	do {
 | |
| 		seq = vdso_read_begin(vdata);
 | |
| 
 | |
| 		ts->tv_sec = vdata->xtime_coarse_sec;
 | |
| 		ts->tv_nsec = vdata->xtime_coarse_nsec;
 | |
| 
 | |
| 	} while (vdso_read_retry(vdata, seq));
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static notrace int do_monotonic_coarse(struct timespec *ts,
 | |
| 				       struct vdso_data *vdata)
 | |
| {
 | |
| 	struct timespec tomono;
 | |
| 	u32 seq;
 | |
| 
 | |
| 	do {
 | |
| 		seq = vdso_read_begin(vdata);
 | |
| 
 | |
| 		ts->tv_sec = vdata->xtime_coarse_sec;
 | |
| 		ts->tv_nsec = vdata->xtime_coarse_nsec;
 | |
| 
 | |
| 		tomono.tv_sec = vdata->wtm_clock_sec;
 | |
| 		tomono.tv_nsec = vdata->wtm_clock_nsec;
 | |
| 
 | |
| 	} while (vdso_read_retry(vdata, seq));
 | |
| 
 | |
| 	ts->tv_sec += tomono.tv_sec;
 | |
| 	timespec_add_ns(ts, tomono.tv_nsec);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static notrace inline u64 vgetsns(struct vdso_data *vdso)
 | |
| {
 | |
| 	u32 cycle_now;
 | |
| 	u32 cycle_delta;
 | |
| 	u32 *timer_cycle_base;
 | |
| 
 | |
| 	timer_cycle_base =
 | |
| 	    (u32 *) ((char *)__get_timerpage() + vdso->cycle_count_offset);
 | |
| 	cycle_now = readl_relaxed(timer_cycle_base);
 | |
| 	if (true == vdso->cycle_count_down)
 | |
| 		cycle_now = ~(*timer_cycle_base);
 | |
| 	cycle_delta = cycle_now - (u32) vdso->cs_cycle_last;
 | |
| 	return ((u64) cycle_delta & vdso->cs_mask) * vdso->cs_mult;
 | |
| }
 | |
| 
 | |
| static notrace int do_realtime(struct timespec *ts, struct vdso_data *vdata)
 | |
| {
 | |
| 	unsigned count;
 | |
| 	u64 ns;
 | |
| 	do {
 | |
| 		count = vdso_read_begin(vdata);
 | |
| 		ts->tv_sec = vdata->xtime_clock_sec;
 | |
| 		ns = vdata->xtime_clock_nsec;
 | |
| 		ns += vgetsns(vdata);
 | |
| 		ns >>= vdata->cs_shift;
 | |
| 	} while (vdso_read_retry(vdata, count));
 | |
| 
 | |
| 	ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
 | |
| 	ts->tv_nsec = ns;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static notrace int do_monotonic(struct timespec *ts, struct vdso_data *vdata)
 | |
| {
 | |
| 	struct timespec tomono;
 | |
| 	u64 nsecs;
 | |
| 	u32 seq;
 | |
| 
 | |
| 	do {
 | |
| 		seq = vdso_read_begin(vdata);
 | |
| 
 | |
| 		ts->tv_sec = vdata->xtime_clock_sec;
 | |
| 		nsecs = vdata->xtime_clock_nsec;
 | |
| 		nsecs += vgetsns(vdata);
 | |
| 		nsecs >>= vdata->cs_shift;
 | |
| 
 | |
| 		tomono.tv_sec = vdata->wtm_clock_sec;
 | |
| 		tomono.tv_nsec = vdata->wtm_clock_nsec;
 | |
| 
 | |
| 	} while (vdso_read_retry(vdata, seq));
 | |
| 
 | |
| 	ts->tv_sec += tomono.tv_sec;
 | |
| 	ts->tv_nsec = 0;
 | |
| 	timespec_add_ns(ts, nsecs + tomono.tv_nsec);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| notrace int __vdso_clock_gettime(clockid_t clkid, struct timespec *ts)
 | |
| {
 | |
| 	struct vdso_data *vdata;
 | |
| 	int ret = -1;
 | |
| 
 | |
| 	vdata = __get_datapage();
 | |
| 	if (vdata->cycle_count_offset == EMPTY_REG_OFFSET)
 | |
| 		return clock_gettime_fallback(clkid, ts);
 | |
| 
 | |
| 	switch (clkid) {
 | |
| 	case CLOCK_REALTIME_COARSE:
 | |
| 		ret = do_realtime_coarse(ts, vdata);
 | |
| 		break;
 | |
| 	case CLOCK_MONOTONIC_COARSE:
 | |
| 		ret = do_monotonic_coarse(ts, vdata);
 | |
| 		break;
 | |
| 	case CLOCK_REALTIME:
 | |
| 		ret = do_realtime(ts, vdata);
 | |
| 		break;
 | |
| 	case CLOCK_MONOTONIC:
 | |
| 		ret = do_monotonic(ts, vdata);
 | |
| 		break;
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (ret)
 | |
| 		ret = clock_gettime_fallback(clkid, ts);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static notrace int clock_getres_fallback(clockid_t _clk_id,
 | |
| 					  struct timespec *_res)
 | |
| {
 | |
| 	register clockid_t clk_id asm("$r0") = _clk_id;
 | |
| 	register struct timespec *res asm("$r1") = _res;
 | |
| 	register int ret asm("$r0");
 | |
| 
 | |
| 	asm volatile ("movi	$r15, %3\n"
 | |
| 		      "syscall	0x0\n"
 | |
| 		      :"=r" (ret)
 | |
| 		      :"r"(clk_id), "r"(res), "i"(__NR_clock_getres)
 | |
| 		      :"$r15", "memory");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| notrace int __vdso_clock_getres(clockid_t clk_id, struct timespec *res)
 | |
| {
 | |
| 	if (res == NULL)
 | |
| 		return 0;
 | |
| 	switch (clk_id) {
 | |
| 	case CLOCK_REALTIME:
 | |
| 	case CLOCK_MONOTONIC:
 | |
| 	case CLOCK_MONOTONIC_RAW:
 | |
| 		res->tv_sec = 0;
 | |
| 		res->tv_nsec = CLOCK_REALTIME_RES;
 | |
| 		break;
 | |
| 	case CLOCK_REALTIME_COARSE:
 | |
| 	case CLOCK_MONOTONIC_COARSE:
 | |
| 		res->tv_sec = 0;
 | |
| 		res->tv_nsec = CLOCK_COARSE_RES;
 | |
| 		break;
 | |
| 	default:
 | |
| 		return clock_getres_fallback(clk_id, res);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static notrace inline int gettimeofday_fallback(struct timeval *_tv,
 | |
| 						struct timezone *_tz)
 | |
| {
 | |
| 	register struct timeval *tv asm("$r0") = _tv;
 | |
| 	register struct timezone *tz asm("$r1") = _tz;
 | |
| 	register int ret asm("$r0");
 | |
| 
 | |
| 	asm volatile ("movi	$r15, %3\n"
 | |
| 		      "syscall	0x0\n"
 | |
| 		      :"=r" (ret)
 | |
| 		      :"r"(tv), "r"(tz), "i"(__NR_gettimeofday)
 | |
| 		      :"$r15", "memory");
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| notrace int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz)
 | |
| {
 | |
| 	struct timespec ts;
 | |
| 	struct vdso_data *vdata;
 | |
| 	int ret;
 | |
| 
 | |
| 	vdata = __get_datapage();
 | |
| 
 | |
| 	if (vdata->cycle_count_offset == EMPTY_REG_OFFSET)
 | |
| 		return gettimeofday_fallback(tv, tz);
 | |
| 
 | |
| 	ret = do_realtime(&ts, vdata);
 | |
| 
 | |
| 	if (tv) {
 | |
| 		tv->tv_sec = ts.tv_sec;
 | |
| 		tv->tv_usec = ts.tv_nsec / 1000;
 | |
| 	}
 | |
| 	if (tz) {
 | |
| 		tz->tz_minuteswest = vdata->tz_minuteswest;
 | |
| 		tz->tz_dsttime = vdata->tz_dsttime;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | 
