/*
 * Copyright (c) 2007 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/*
 * @OSF_COPYRIGHT@
 */
/*
 * @APPLE_FREE_COPYRIGHT@
 */
/*
 * File: arm/rtclock.c
 * Purpose: Routines for handling the machine dependent
 *   real-time clock.
 */

#include <mach/mach_types.h>

#include <kern/clock.h>
#include <kern/thread.h>
#include <kern/macro_help.h>
#include <kern/spl.h>
#include <kern/timer_queue.h>

#include <kern/host_notify.h>

#include <machine/commpage.h>
#include <machine/machine_routines.h>
#include <arm/exception.h>
#include <arm/cpu_data_internal.h>
#if __arm64__
#include <arm64/proc_reg.h>
#elif __arm__
#include <arm/proc_reg.h>
#else
#error Unsupported arch
#endif
#include <arm/rtclock.h>

#include <IOKit/IOPlatformExpert.h>
#include <libkern/OSAtomic.h>

#include <sys/kdebug.h>

#define MAX_TIMEBASE_TRIES 10

int rtclock_init(void);

static int
deadline_to_decrementer(uint64_t deadline,
                        uint64_t now);
static void
timebase_callback(struct timebase_freq_t * freq);

#if DEVELOPMENT || DEBUG
uint32_t absolute_time_validation = 1;
#endif

/*
 * Configure the real-time clock device at boot
 */
void
rtclock_early_init(void)
{
	PE_register_timebase_callback(timebase_callback);
#if DEVELOPMENT || DEBUG
	uint32_t tmp_mv = 1;
	if (kern_feature_override(KF_MATV_OVRD)) {
		absolute_time_validation = 0;
	}
	if (PE_parse_boot_argn("timebase_validation", &tmp_mv, sizeof(tmp_mv))) {
		if (tmp_mv == 0) {
			absolute_time_validation = 0;
		}
	}
#endif
}

static void
timebase_callback(struct timebase_freq_t * freq)
{
	unsigned long numer, denom;
	uint64_t      t64_1, t64_2;
	uint32_t      divisor;

	if (freq->timebase_den < 1 || freq->timebase_den > 4 ||
	    freq->timebase_num < freq->timebase_den)
		panic("rtclock timebase_callback: invalid constant %ld / %ld",
		      freq->timebase_num, freq->timebase_den);

	denom = freq->timebase_num;
	numer = freq->timebase_den * NSEC_PER_SEC;
	// reduce by the greatest common denominator to minimize overflow
	if (numer > denom) {
		t64_1 = numer;
		t64_2 = denom;
	} else {
		t64_1 = denom;
		t64_2 = numer;
	}
	while (t64_2 != 0) {
		uint64_t temp = t64_2;
		t64_2 = t64_1 % t64_2;
		t64_1 = temp;
	}
	numer /= t64_1;
	denom /= t64_1;

	rtclock_timebase_const.numer = (uint32_t)numer;
	rtclock_timebase_const.denom = (uint32_t)denom;
	divisor = (uint32_t)(freq->timebase_num / freq->timebase_den);

	rtclock_sec_divisor = divisor;
	rtclock_usec_divisor = divisor / USEC_PER_SEC;
}

/*
 * Initialize the system clock device for the current cpu
 */
int
rtclock_init(void)
{
	uint64_t     abstime;
	cpu_data_t * cdp;

	clock_timebase_init();
	ml_init_lock_timeout();

	cdp = getCpuDatap();

	abstime = mach_absolute_time();
	cdp->rtcPop = EndOfAllTime;					/* Init Pop time */
	timer_resync_deadlines();					/* Start the timers going */

	return (1);
}

uint64_t
mach_absolute_time(void)
{
#if DEVELOPMENT || DEBUG
	if (__improbable(absolute_time_validation == 1)) {
		static volatile uint64_t s_last_absolute_time = 0;
		uint64_t                 new_absolute_time, old_absolute_time;
		int                      attempts = 0;

		/* ARM 64: We need a dsb here to ensure that the load of s_last_absolute_time
		 * completes before the timebase read. Were the load to complete after the
		 * timebase read, there would be a window for another CPU to update
		 * s_last_absolute_time and leave us in an inconsistent state. Consider the
		 * following interleaving:
		 *
		 *   Let s_last_absolute_time = t0
		 *   CPU0: Read timebase at t1
		 *   CPU1: Read timebase at t2
		 *   CPU1: Update s_last_absolute_time to t2
		 *   CPU0: Load completes
		 *   CPU0: Update s_last_absolute_time to t1
		 *
		 * This would cause the assertion to fail even though time did not go
		 * backwards. Thus, we use a dsb to guarantee completion of the load before
		 * the timebase read.
		 */
		do {
			attempts++;
			old_absolute_time = s_last_absolute_time;

#if __arm64__
			__asm__ volatile("dsb ld" ::: "memory");
#else
			OSSynchronizeIO(); // See osfmk/arm64/rtclock.c
#endif

			new_absolute_time = ml_get_timebase();
		} while (attempts < MAX_TIMEBASE_TRIES && !OSCompareAndSwap64(old_absolute_time, new_absolute_time, &s_last_absolute_time));

		if (attempts < MAX_TIMEBASE_TRIES && old_absolute_time > new_absolute_time) {
			panic("mach_absolute_time returning non-monotonically increasing value 0x%llx (old value 0x%llx\n)\n",
			    new_absolute_time, old_absolute_time);
		}
		return new_absolute_time;
	} else {
		return ml_get_timebase();
	}
#else
	return ml_get_timebase();
#endif
}

uint64_t
mach_approximate_time(void)
{
#if __ARM_TIME__ || __ARM_TIME_TIMEBASE_ONLY__ || __arm64__
	/* Hardware supports a fast timestamp, so grab it without asserting monotonicity */
	return ml_get_timebase();
#else
	processor_t processor;
	uint64_t    approx_time;

	disable_preemption();
	processor = current_processor();
	approx_time = processor->last_dispatch;
	enable_preemption();

	return approx_time;
#endif
}

void
clock_get_system_microtime(clock_sec_t *  secs,
                           clock_usec_t * microsecs)
{
	absolutetime_to_microtime(mach_absolute_time(), secs, microsecs);
}

void
clock_get_system_nanotime(clock_sec_t *  secs,
                          clock_nsec_t * nanosecs)
{
	uint64_t abstime;
	uint64_t t64;

	abstime = mach_absolute_time();
	*secs = (t64 = abstime / rtclock_sec_divisor);
	abstime -= (t64 * rtclock_sec_divisor);

	*nanosecs = (clock_nsec_t)((abstime * NSEC_PER_SEC) / rtclock_sec_divisor);
}

void
clock_gettimeofday_set_commpage(uint64_t abstime,
                                uint64_t sec,
                                uint64_t frac,
                                uint64_t scale,
                                uint64_t tick_per_sec)
{
	commpage_set_timestamp(abstime, sec, frac, scale, tick_per_sec);
}

void
clock_timebase_info(mach_timebase_info_t info)
{
	*info = rtclock_timebase_const;
}

/*
 * Real-time clock device interrupt.
 */
void
rtclock_intr(__unused unsigned int is_user_context)
{
	uint64_t                 abstime;
	cpu_data_t *             cdp;
	struct arm_saved_state * regs;
	unsigned int             user_mode;
	uintptr_t                pc;

	cdp = getCpuDatap();

	cdp->cpu_stat.timer_cnt++;
	cdp->cpu_stat.timer_cnt_wake++;
	SCHED_STATS_TIMER_POP(current_processor());

	assert(!ml_get_interrupts_enabled());

	abstime = mach_absolute_time();

	if (cdp->cpu_idle_pop != 0x0ULL) {
		if (( cdp->rtcPop-abstime) < cdp->cpu_idle_latency) {
			cdp->cpu_idle_pop = 0x0ULL;
			while (abstime < cdp->rtcPop)
				abstime = mach_absolute_time();
		} else {
			ClearIdlePop(FALSE);
		}
	}

	if ((regs = cdp->cpu_int_state)) {
		pc = get_saved_state_pc(regs);

#if __arm64__
		user_mode = PSR64_IS_USER(get_saved_state_cpsr(regs));
#else
		user_mode = (regs->cpsr & PSR_MODE_MASK) == PSR_USER_MODE;
#endif
	} else {
		pc = 0;
		user_mode = 0;
	}
	if (abstime >= cdp->rtcPop) {
		/* Log the interrupt service latency (-ve value expected by tool) */
		KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
		                          MACHDBG_CODE(DBG_MACH_EXCP_DECI, 0) | DBG_FUNC_NONE,
		                          -(abstime - cdp->rtcPop),
		                          user_mode ? pc : VM_KERNEL_UNSLIDE(pc), user_mode, 0, 0);
	}

	/* call the generic etimer */
	timer_intr(user_mode, pc);
}

static int
deadline_to_decrementer(uint64_t deadline,
                        uint64_t now)
{
	uint64_t delt;

	if (deadline <= now)
		return DECREMENTER_MIN;
	else {
		delt = deadline - now;

		return (delt >= (DECREMENTER_MAX + 1)) ? DECREMENTER_MAX : ((delt >= (DECREMENTER_MIN + 1)) ? (int)delt : DECREMENTER_MIN);
	}
}

/*
 *	Request a decrementer pop
 */
int
setPop(uint64_t time)
{
	int          delay_time;
	uint64_t     current_time;
	cpu_data_t * cdp;

	cdp = getCpuDatap();
	current_time = mach_absolute_time();

	delay_time = deadline_to_decrementer(time, current_time);
	cdp->rtcPop = delay_time + current_time;

	ml_set_decrementer((uint32_t) delay_time);

	return (delay_time);
}

/*
 *	Request decrementer Idle Pop. Return true if set
 */
boolean_t
SetIdlePop(void)
{
	int          delay_time;
	uint64_t     time;
	uint64_t     current_time;
	cpu_data_t * cdp;

	cdp = getCpuDatap();
	current_time = mach_absolute_time();

	if (((cdp->rtcPop < current_time) ||
	    (cdp->rtcPop - current_time) < cdp->cpu_idle_latency))
		return FALSE;

	time = cdp->rtcPop - cdp->cpu_idle_latency;

	delay_time = deadline_to_decrementer(time, current_time);
	cdp->cpu_idle_pop = delay_time + current_time;
	ml_set_decrementer((uint32_t) delay_time);

	return TRUE;
}

/*
 *	Clear decrementer Idle Pop
 */
void
ClearIdlePop(
             boolean_t wfi)
{
#if !__arm64__
#pragma unused(wfi)
#endif
	cpu_data_t * cdp;

	cdp = getCpuDatap();
	cdp->cpu_idle_pop = 0x0ULL;

#if __arm64__
	/*
	 * Don't update the HW timer if there's a pending
	 * interrupt (we can lose interrupt assertion);
	 * we want to take the interrupt right now and update
	 * the deadline from the handler).
	 *
	 * ARM64_TODO: consider this more carefully.
	 */
	if (!(wfi && ml_get_timer_pending()))
#endif
	{
		setPop(cdp->rtcPop);
	}
}

void
absolutetime_to_microtime(uint64_t       abstime,
                          clock_sec_t *  secs,
                          clock_usec_t * microsecs)
{
	uint64_t t64;

	*secs = t64 = abstime / rtclock_sec_divisor;
	abstime -= (t64 * rtclock_sec_divisor);

	*microsecs = (uint32_t)(abstime / rtclock_usec_divisor);
}

void
absolutetime_to_nanoseconds(uint64_t   abstime,
                            uint64_t * result)
{
	uint64_t        t64;

	*result = (t64 = abstime / rtclock_sec_divisor) * NSEC_PER_SEC;
	abstime -= (t64 * rtclock_sec_divisor);
	*result += (abstime * NSEC_PER_SEC) / rtclock_sec_divisor;
}

void
nanoseconds_to_absolutetime(uint64_t   nanosecs,
                            uint64_t * result)
{
	uint64_t        t64;

	*result = (t64 = nanosecs / NSEC_PER_SEC) * rtclock_sec_divisor;
	nanosecs -= (t64 * NSEC_PER_SEC);
	*result += (nanosecs * rtclock_sec_divisor) / NSEC_PER_SEC;
}

void
nanotime_to_absolutetime(clock_sec_t  secs,
                         clock_nsec_t nanosecs,
                         uint64_t *   result)
{
	*result = ((uint64_t) secs * rtclock_sec_divisor) +
	((uint64_t) nanosecs * rtclock_sec_divisor) / NSEC_PER_SEC;
}

void
clock_interval_to_absolutetime_interval(uint32_t   interval,
                                        uint32_t   scale_factor,
                                        uint64_t * result)
{
	uint64_t nanosecs = (uint64_t) interval * scale_factor;
	uint64_t t64;

	*result = (t64 = nanosecs / NSEC_PER_SEC) * rtclock_sec_divisor;
	nanosecs -= (t64 * NSEC_PER_SEC);
	*result += (nanosecs * rtclock_sec_divisor) / NSEC_PER_SEC;
}

void
machine_delay_until(uint64_t interval,
                    uint64_t deadline)
{
#pragma unused(interval)
	uint64_t now;

	do {
#if	__ARM_ENABLE_WFE_
#if __arm64__
		if (arm64_wfe_allowed())
#endif /* __arm64__ */
		{
			__builtin_arm_wfe();
		}
#endif /* __ARM_ENABLE_WFE_ */

		now = mach_absolute_time();
	} while (now < deadline);
}
