/*
 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This 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 OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * @OSF_COPYRIGHT@
 */
/* 
 * Mach Operating System
 * Copyright (c) 1991,1990 Carnegie Mellon University
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 * 
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * Carnegie Mellon requests users of this software to return to
 * 
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 * 
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */
/*
 */

/*
 * Clock interrupt.
 */
#include <cpus.h>
#include <time_stamp.h>
#include <mach_kdb.h>
#include <kern/cpu_number.h>
#include <kern/cpu_data.h>
#include <kern/kern_types.h>
#include <platforms.h>
#include <mp_v1_1.h>
#include <mach_kprof.h>
#include <mach_mp_debug.h>
#include <mach/std_types.h>

#include <mach/clock_types.h>
#include <mach/boolean.h>
#include <i386/thread.h>
#include <i386/eflags.h>
#include <kern/assert.h>
#include <kern/misc_protos.h>
#include <i386/misc_protos.h>
#include <kern/time_out.h>

#include <i386/ipl.h>

#include <i386/hardclock_entries.h>
#include <i386/rtclock_entries.h>

#if	MACH_MP_DEBUG
#include <i386/mach_param.h>	/* for HZ */
#endif	/* MACH_MP_DEBUG */

extern	char	return_to_iret[];

#if	TIME_STAMP && NCPUS > 1
extern	unsigned time_stamp;
unsigned old_time_stamp, time_stamp_cum, nstamps;

/*
 *	If H/W provides a counter, record number of ticks and cumulated
 *	time stamps to know timestamps rate.
 *	This should go away when ALARMCLOCKS installed
 */
#define time_stamp_stat()					\
	if (my_cpu == 0)					\
	if (!old_time_stamp) {					\
		old_time_stamp = time_stamp;			\
		nstamps = 0;					\
	} else {						\
		nstamps++;					\
		time_stamp_cum = (time_stamp - old_time_stamp);	\
	}
#else	/* TIME_STAMP && AT386 && NCPUS > 1 */
#define time_stamp_stat()
#endif	/* TIME_STAMP && AT386 && NCPUS > 1 */

#if	MACH_KPROF
int	masked_pc[NCPUS];
int	missed_clock[NCPUS];
int	detect_lost_tick = 0;
#endif	/* MACH_KPROF */

#if	MACH_MP_DEBUG
int	masked_state_cnt[NCPUS];
int	masked_state_max = 10*HZ;
#endif	/* MACH_MP_DEBUG */

/*
 * In the interest of a fast clock interrupt service path,
 * this routine should be folded into assembly language with
 * a direct interrupt vector on the i386. The "pit" interrupt
 * should always call the rtclock_intr() routine on the master
 * processor. The return value of the rtclock_intr() routine
 * indicates whether HZ rate clock processing should be
 * performed. (On the Sequent, all slave processors will
 * run at HZ rate). For now, we'll leave this routine in C
 * (with TIME_STAMP, MACH_MP_DEBUG and MACH_KPROF code this
 * routine is way too large for assembler anyway).
 */

#ifdef	PARANOID_KDB
int paranoid_debugger = TRUE;
int paranoid_count = 1000;
int paranoid_current = 0;
int paranoid_cpu = 0;
#endif	/* PARANOID_KDB */

void
hardclock(struct i386_interrupt_state	*regs) /* saved registers */
{
	int mycpu;
	register unsigned pc;
	register boolean_t usermode;

	mp_disable_preemption();
	mycpu = cpu_number();

#ifdef	PARANOID_KDB
	if (paranoid_cpu == mycpu &&
	    paranoid_current++ >= paranoid_count) {
		paranoid_current = 0;
		if (paranoid_debugger)
		    Debugger("hardclock");
	}
#endif	/* PARANOID_KDB */

#if 0
#if	MACH_MP_DEBUG
	/*
	 * Increments counter of clock ticks handled under a masked state.
	 * Debugger() is called if masked state is kept during 1 sec.
	 * The counter is reset by splx() when ipl mask is set back to SPL0,
	 * and by spl0().
	 */
	if (SPL_CMP_GT((old_ipl & 0xFF), SPL0)) {
		if (masked_state_cnt[mycpu]++ >= masked_state_max) {
			int max_save = masked_state_max;

			masked_state_cnt[mycpu] = 0;
			masked_state_max = 0x7fffffff;

			if (ret_addr == return_to_iret) {
				usermode = (regs->efl & EFL_VM) ||
						((regs->cs & 0x03) != 0);
				pc = (unsigned)regs->eip;
			} else {
				usermode = FALSE;
				pc = (unsigned)
				((struct i386_interrupt_state *)&old_ipl)->eip;
			}
			printf("looping at high IPL, usermode=%d pc=0x%x\n",
					usermode, pc);
			Debugger("");

			masked_state_cnt[mycpu] = 0;
			masked_state_max = max_save;
		}
	} else
		masked_state_cnt[mycpu] = 0;
#endif	/* MACH_MP_DEBUG */
#endif

#if	MACH_KPROF
	/*
	 * If we were masked against the clock skip call
	 * to rtclock_intr(). When MACH_KPROF is set, the
	 * clock frequency of the master-cpu is confined
	 * to the HZ rate.
	 */
	if (SPL_CMP_LT(old_ipl & 0xFF, SPL7))
#endif	/* MACH_KPROF */
	/*
	 * The master processor executes the rtclock_intr() routine
	 * on every clock tick. The rtclock_intr() routine returns
	 * a zero value on a HZ tick boundary.
	 */
	if (mycpu == master_cpu) {
		if (rtclock_intr() != 0) {
			mp_enable_preemption();
			return;
		}
	}

	/*
	 * The following code is executed at HZ rate by all processors
	 * in the system. This implies that the clock rate on slave
	 * processors must be HZ rate.
	 */

	time_stamp_stat();

#if 0
	if (ret_addr == return_to_iret) {
		/*
		 * A kernel-loaded task executing within itself will look like
		 * "kernel mode", here.  This is correct with syscalls
		 * implemented using migrating threads, because it means that  
		 * the time spent in the server by a client thread will be
		 * treated as "system" time for the client thread (and nothing
		 * for the server).  This conforms to the CPU reporting for an
		 * integrated kernel.
		 */
#endif
		usermode = (regs->efl & EFL_VM) || ((regs->cs & 0x03) != 0);
		pc = (unsigned)regs->eip;
#if 0
	} else {
		usermode = FALSE;
		pc = (unsigned)((struct i386_interrupt_state *)&old_ipl)->eip;
	}
#endif

#if	MACH_KPROF
	/*
	 * If we were masked against the clock, just memorize pc
	 * and the fact that the clock interrupt is delayed
	 */
	if (SPL_CMP_GE((old_ipl & 0xFF), SPL7)) {
		assert(!usermode);
		if (missed_clock[mycpu]++ && detect_lost_tick > 1)
			Debugger("Mach_KPROF");
		masked_pc[mycpu] = pc;
	} else
#endif	/* MACH_KPROF */

	hertz_tick(usermode, pc);

#if	NCPUS >1 
	/*
	 * Instead of having the master processor interrupt
	 * all active processors, each processor in turn interrupts
	 * the next active one. This avoids all slave processors
	 * accessing the same R/W data simultaneously.
	 */
	slave_clock();
#endif	/* NCPUS >1 && AT386 */

	mp_enable_preemption();
}

#if	MACH_KPROF
void
delayed_clock(void)
{
	int	i;
	int	my_cpu;

	mp_disable_preemption();
	my_cpu = cpu_number();

	if (missed_clock[my_cpu] > 1 && detect_lost_tick)
		printf("hardclock: missed %d clock interrupt(s) at %x\n",
		       missed_clock[my_cpu]-1, masked_pc[my_cpu]);
	if (my_cpu == master_cpu) {
		i = rtclock_intr();
		assert(i == 0);
	}
	hertz_tick(0, masked_pc[my_cpu]);
	missed_clock[my_cpu] = 0;

	mp_enable_preemption();
}
#endif	/* MACH_KPROF */
