#include <mach/mach_types.h>
#include <kern/cpu_data.h>
#include <kern/kalloc.h>
#include <sys/errno.h>
#include <machine/machine_routines.h>
#include <chud/chud_xnu.h>
#include <kperf/kperf.h>
#include <kperf/buffer.h>
#include <kperf/context.h>
#include <kperf/action.h>
#include <kperf/timetrigger.h>
#include <kperf/kperf_arch.h>
#include <kperf/pet.h>
#include <kperf/sample.h>
void kperf_signal_handler(void);
struct time_trigger
{
struct timer_call tcall;
uint64_t period;
unsigned actionid;
volatile unsigned active;
#ifdef USE_SIMPLE_SIGNALS
uint64_t fire_count;
uint64_t last_cpu_fire[MAX_CPUS];
#endif
};
static unsigned timerc = 0;
static struct time_trigger *timerv;
static unsigned pet_timer = 999;
#define TIMER_MAX 16
#define MIN_TIMER_NS (10000)
#define MIN_PET_TIMER_NS (2000000)
static void
kperf_timer_schedule( struct time_trigger *trigger, uint64_t now )
{
uint64_t deadline;
BUF_INFO1(PERF_TM_SCHED, trigger->period);
if( !trigger->period )
return;
deadline = now + trigger->period;
timer_call_enter( &trigger->tcall, deadline, TIMER_CALL_SYS_CRITICAL);
}
static void
kperf_ipi_handler( void *param )
{
int r;
int ncpu;
struct kperf_sample *intbuf = NULL;
struct kperf_context ctx;
struct time_trigger *trigger = param;
task_t task = NULL;
BUF_DATA1(PERF_TM_HNDLR | DBG_FUNC_START, 0);
intbuf = kperf_intr_sample_buffer();
ctx.cur_pid = 0;
ctx.cur_thread = current_thread();
task = chudxnu_task_for_thread(ctx.cur_thread);
if (task)
ctx.cur_pid = chudxnu_pid_for_task(task);
ctx.trigger_type = TRIGGER_TYPE_TIMER;
ctx.trigger_id = (unsigned)(trigger-timerv);
ncpu = chudxnu_cpu_number();
if (ctx.trigger_id == pet_timer && ncpu < machine_info.logical_cpu_max)
kperf_thread_on_cpus[ncpu] = ctx.cur_thread;
if( kperf_sampling_status() == KPERF_SAMPLING_OFF ) {
BUF_INFO1(PERF_TM_HNDLR | DBG_FUNC_END, SAMPLE_OFF);
return;
} else if( kperf_sampling_status() == KPERF_SAMPLING_SHUTDOWN ) {
BUF_INFO1(PERF_TM_HNDLR | DBG_FUNC_END, SAMPLE_SHUTDOWN);
return;
}
r = kperf_sample( intbuf, &ctx, trigger->actionid, SAMPLE_FLAG_PEND_USER );
BUF_INFO1(PERF_TM_HNDLR | DBG_FUNC_END, r);
}
#ifdef USE_SIMPLE_SIGNALS
void
kperf_signal_handler(void)
{
int i, cpu;
struct time_trigger *tr = NULL;
OSMemoryBarrier();
cpu = chudxnu_cpu_number();
for( i = 0; i < (int) timerc; i++ )
{
tr = &timerv[i];
if( tr->fire_count <= tr->last_cpu_fire[cpu] )
continue;
tr->last_cpu_fire[cpu] = tr->fire_count;
kperf_ipi_handler( tr );
}
}
#else
void
kperf_signal_handler(void)
{
}
#endif
static void
kperf_timer_handler( void *param0, __unused void *param1 )
{
struct time_trigger *trigger = param0;
unsigned ntimer = (unsigned)(trigger - timerv);
unsigned ncpus = machine_info.logical_cpu_max;
trigger->active = 1;
if( kperf_sampling_status() == KPERF_SAMPLING_SHUTDOWN )
goto deactivate;
bzero(kperf_thread_on_cpus, ncpus * sizeof(*kperf_thread_on_cpus));
#ifndef USE_SIMPLE_SIGNALS
kperf_mp_broadcast( kperf_ipi_handler, trigger );
#else
trigger->fire_count++;
OSMemoryBarrier();
kperf_mp_signal();
#endif
if( ntimer == pet_timer )
{
kperf_pet_thread_go();
}
else
{
uint64_t now = mach_absolute_time();
kperf_timer_schedule( trigger, now );
}
deactivate:
trigger->active = 0;
}
int
kperf_timer_pet_set( unsigned timer, uint64_t elapsed_ticks )
{
static uint64_t pet_min_ticks = 0;
uint64_t now;
struct time_trigger *trigger = NULL;
uint64_t period = 0;
uint64_t deadline;
if( pet_min_ticks == 0 )
nanoseconds_to_absolutetime(MIN_PET_TIMER_NS, &pet_min_ticks);
if( timer != pet_timer )
panic( "PET setting with bogus ID\n" );
if( timer >= timerc )
return EINVAL;
if( kperf_sampling_status() == KPERF_SAMPLING_OFF ) {
BUF_INFO1(PERF_PET_END, SAMPLE_OFF);
return 0;
}
if( kperf_sampling_status() == KPERF_SAMPLING_SHUTDOWN ) {
BUF_INFO1(PERF_PET_END, SAMPLE_SHUTDOWN);
return 0;
}
now = mach_absolute_time();
trigger = &timerv[timer];
if( !trigger->period )
return 0;
if ( trigger->period > elapsed_ticks )
period = trigger->period - elapsed_ticks;
if ( period < pet_min_ticks )
period = pet_min_ticks;
deadline = now + period;
BUF_INFO(PERF_PET_SCHED, trigger->period, period, elapsed_ticks, deadline);
timer_call_enter( &trigger->tcall, deadline, TIMER_CALL_SYS_CRITICAL);
return 0;
}
extern int
kperf_timer_go(void)
{
unsigned i;
uint64_t now = mach_absolute_time();
for( i = 0; i < timerc; i++ )
{
if( timerv[i].period == 0 )
continue;
kperf_timer_schedule( &timerv[i], now );
}
return 0;
}
extern int
kperf_timer_stop(void)
{
unsigned i;
for( i = 0; i < timerc; i++ )
{
if( timerv[i].period == 0 )
continue;
while (timerv[i].active)
;
timer_call_cancel( &timerv[i].tcall );
}
kperf_pet_thread_wait();
return 0;
}
unsigned
kperf_timer_get_petid(void)
{
return pet_timer;
}
int
kperf_timer_set_petid(unsigned timerid)
{
struct time_trigger *trigger = NULL;
pet_timer = timerid;
if( pet_timer >= timerc )
{
kperf_pet_timer_config( 0, 0 );
return 0;
}
trigger = &timerv[pet_timer];
kperf_pet_timer_config( pet_timer, trigger->actionid );
return 0;
}
int
kperf_timer_get_period( unsigned timer, uint64_t *period )
{
if( timer >= timerc )
return EINVAL;
*period = timerv[timer].period;
return 0;
}
int
kperf_timer_set_period( unsigned timer, uint64_t period )
{
static uint64_t min_timer_ticks = 0;
if( timer >= timerc )
return EINVAL;
if( min_timer_ticks == 0 )
nanoseconds_to_absolutetime(MIN_TIMER_NS, &min_timer_ticks);
if( period && (period < min_timer_ticks) )
period = min_timer_ticks;
timerv[timer].period = period;
return 0;
}
int
kperf_timer_get_action( unsigned timer, uint32_t *action )
{
if( timer >= timerc )
return EINVAL;
*action = timerv[timer].actionid;
return 0;
}
int
kperf_timer_set_action( unsigned timer, uint32_t action )
{
if( timer >= timerc )
return EINVAL;
timerv[timer].actionid = action;
return 0;
}
unsigned
kperf_timer_get_count(void)
{
return timerc;
}
static void
setup_timer_call( struct time_trigger *trigger )
{
timer_call_setup( &trigger->tcall, kperf_timer_handler, trigger );
}
extern int
kperf_timer_set_count(unsigned count)
{
struct time_trigger *new_timerv = NULL, *old_timerv = NULL;
unsigned old_count, i;
if( count == timerc )
return 0;
if( count < timerc )
return EINVAL;
if( count > TIMER_MAX )
return EINVAL;
if( timerc == 0 )
{
int r;
r = kperf_init();
if( r )
return r;
r = kperf_pet_init();
if( r )
return r;
}
if( kperf_timer_stop() )
return EBUSY;
new_timerv = kalloc( count * sizeof(*new_timerv) );
if( new_timerv == NULL )
return ENOMEM;
old_timerv = timerv;
old_count = timerc;
if( old_timerv != NULL )
bcopy( timerv, new_timerv, timerc * sizeof(*timerv) );
bzero( &new_timerv[timerc], (count - old_count) * sizeof(*new_timerv) );
for( i = 0; i < count; i++ )
setup_timer_call( &new_timerv[i] );
timerv = new_timerv;
timerc = count;
if( old_timerv != NULL )
kfree( old_timerv, old_count * sizeof(*timerv) );
return 0;
}