#include <sys/param.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/kauth.h>
#include <libkern/libkern.h>
#include <kern/debug.h>
#include <pexpert/pexpert.h>
#include <kperf/context.h>
#include <kperf/action.h>
#include <kperf/timetrigger.h>
#include <kperf/pet.h>
#include <kperf/kperfbsd.h>
#include <kperf/kperf.h>
static pid_t blessed_pid = -1;
static boolean_t blessed_preempt = FALSE;
#define REQ_SAMPLING (1)
#define REQ_ACTION_COUNT (2)
#define REQ_ACTION_SAMPLERS (3)
#define REQ_TIMER_COUNT (4)
#define REQ_TIMER_PERIOD (5)
#define REQ_TIMER_PET (6)
#define REQ_TIMER_ACTION (7)
#define REQ_BLESS (8)
#define REQ_ACTION_USERDATA (9)
#define REQ_ACTION_FILTER_BY_TASK (10)
#define REQ_ACTION_FILTER_BY_PID (11)
#define REQ_KDBG_CALLSTACKS (12)
#define REQ_PET_IDLE_RATE (13)
#define REQ_BLESS_PREEMPT (14)
int kperf_debug_level = 0;
static lck_grp_attr_t *kperf_cfg_lckgrp_attr = NULL;
static lck_grp_t *kperf_cfg_lckgrp = NULL;
static lck_mtx_t kperf_cfg_lock;
static boolean_t kperf_cfg_initted = FALSE;
void
kperf_bootstrap(void)
{
kperf_cfg_lckgrp_attr = lck_grp_attr_alloc_init();
kperf_cfg_lckgrp = lck_grp_alloc_init("kperf cfg",
kperf_cfg_lckgrp_attr);
lck_mtx_init(&kperf_cfg_lock, kperf_cfg_lckgrp, LCK_ATTR_NULL);
kperf_cfg_initted = TRUE;
}
static int
sysctl_timer_period( __unused struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
uint64_t inputs[2], retval;
unsigned timer, set = 0;
error = SYSCTL_IN( req, inputs, 2*sizeof(inputs[0]) );
if(error)
return (error);
timer = (unsigned) inputs[0];
if( inputs[1] != ~0ULL )
set = 1;
if( set )
{
error = kperf_timer_set_period( timer, inputs[1] );
if( error )
return error;
}
error = kperf_timer_get_period(timer, &retval);
if(error)
return (error);
inputs[1] = retval;
if( error == 0 )
error = SYSCTL_OUT( req, inputs, 2*sizeof(inputs[0]) );
return error;
}
static int
sysctl_timer_action( __unused struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
uint64_t inputs[2];
uint32_t retval;
unsigned timer, set = 0;
error = SYSCTL_IN( req, inputs, 2*sizeof(inputs[0]) );
if(error)
return (error);
timer = (unsigned) inputs[0];
if( inputs[1] != ~0ULL )
set = 1;
if( set )
{
error = kperf_timer_set_action( timer, inputs[1] );
if( error )
return error;
}
error = kperf_timer_get_action(timer, &retval);
if(error)
return (error);
inputs[1] = retval;
if( error == 0 )
error = SYSCTL_OUT( req, inputs, 2*sizeof(inputs[0]) );
return error;
}
static int
sysctl_action_samplers( __unused struct sysctl_oid *oidp,
struct sysctl_req *req )
{
int error = 0;
uint64_t inputs[3];
uint32_t retval;
unsigned actionid, set = 0;
error = SYSCTL_IN( req, inputs, 3*sizeof(inputs[0]) );
if(error)
return (error);
set = (unsigned) inputs[0];
actionid = (unsigned) inputs[1];
if( set )
{
error = kperf_action_set_samplers( actionid, inputs[2] );
if( error )
return error;
}
error = kperf_action_get_samplers(actionid, &retval);
if(error)
return (error);
inputs[2] = retval;
if( error == 0 )
error = SYSCTL_OUT( req, inputs, 3*sizeof(inputs[0]) );
return error;
}
static int
sysctl_action_userdata( __unused struct sysctl_oid *oidp,
struct sysctl_req *req )
{
int error = 0;
uint64_t inputs[3];
uint32_t retval;
unsigned actionid, set = 0;
error = SYSCTL_IN( req, inputs, 3*sizeof(inputs[0]) );
if(error)
return (error);
set = (unsigned) inputs[0];
actionid = (unsigned) inputs[1];
if( set )
{
error = kperf_action_set_userdata( actionid, inputs[2] );
if( error )
return error;
}
error = kperf_action_get_userdata(actionid, &retval);
if(error)
return (error);
inputs[2] = retval;
if( error == 0 )
error = SYSCTL_OUT( req, inputs, 3*sizeof(inputs[0]) );
return error;
}
static int
sysctl_action_filter( __unused struct sysctl_oid *oidp,
struct sysctl_req *req, int is_task_t )
{
int error = 0;
uint64_t inputs[3];
int retval;
unsigned actionid, set = 0;
mach_port_name_t portname;
int pid;
error = SYSCTL_IN( req, inputs, 3*sizeof(inputs[0]) );
if(error)
return (error);
set = (unsigned) inputs[0];
actionid = (unsigned) inputs[1];
if( set )
{
if( is_task_t )
{
portname = (mach_port_name_t) inputs[2];
pid = kperf_port_to_pid(portname);
}
else
pid = (int) inputs[2];
error = kperf_action_set_filter( actionid, pid );
if( error )
return error;
}
error = kperf_action_get_filter(actionid, &retval);
if(error)
return (error);
inputs[2] = retval;
if( error == 0 )
error = SYSCTL_OUT( req, inputs, 3*sizeof(inputs[0]) );
return error;
}
static int
sysctl_sampling( struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
uint32_t value = 0;
value = kperf_sampling_status();
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || !req->newptr)
return (error);
if( value )
error = kperf_sampling_enable();
else
error = kperf_sampling_disable();
return error;
}
static int
sysctl_action_count( struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
uint32_t value = 0;
value = kperf_action_get_count();
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || !req->newptr)
return (error);
return kperf_action_set_count(value);
}
static int
sysctl_timer_count( struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
uint32_t value = 0;
value = kperf_timer_get_count();
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || !req->newptr)
return (error);
return kperf_timer_set_count(value);
}
static int
sysctl_timer_pet( struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
uint32_t value = 0;
value = kperf_timer_get_petid();
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || !req->newptr)
return (error);
return kperf_timer_set_petid(value);
}
static int
sysctl_bless( struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
int value = 0;
value = blessed_pid;
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || !req->newptr)
return (error);
error = kperf_bless_pid(value);
return error;
}
static int
sysctl_bless_preempt( struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
int value = 0;
value = blessed_preempt;
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || !req->newptr)
return (error);
blessed_preempt = value ? TRUE : FALSE;
return 0;
}
static int
sysctl_kdbg_callstacks( struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
int value = 0;
value = kperf_kdbg_get_stacks();
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || !req->newptr)
return (error);
error = kperf_kdbg_set_stacks(value);
return error;
}
static int
sysctl_pet_idle_rate( struct sysctl_oid *oidp, struct sysctl_req *req )
{
int error = 0;
int value = 0;
value = kperf_get_pet_idle_rate();
error = sysctl_handle_int(oidp, &value, 0, req);
if (error || !req->newptr)
return (error);
kperf_set_pet_idle_rate(value);
return error;
}
static int
kperf_sysctl SYSCTL_HANDLER_ARGS
{
int ret;
(void)arg2;
if ( !kperf_cfg_initted )
panic("kperf_bootstrap not called");
ret = kperf_access_check();
if (ret) {
return ret;
}
lck_mtx_lock(&kperf_cfg_lock);
switch( (uintptr_t) arg1 )
{
case REQ_ACTION_COUNT:
ret = sysctl_action_count( oidp, req );
break;
case REQ_ACTION_SAMPLERS:
ret = sysctl_action_samplers( oidp, req );
break;
case REQ_ACTION_USERDATA:
ret = sysctl_action_userdata( oidp, req );
break;
case REQ_TIMER_COUNT:
ret = sysctl_timer_count( oidp, req );
break;
case REQ_TIMER_PERIOD:
ret = sysctl_timer_period( oidp, req );
break;
case REQ_TIMER_PET:
ret = sysctl_timer_pet( oidp, req );
break;
case REQ_TIMER_ACTION:
ret = sysctl_timer_action( oidp, req );
break;
case REQ_SAMPLING:
ret = sysctl_sampling( oidp, req );
break;
case REQ_KDBG_CALLSTACKS:
ret = sysctl_kdbg_callstacks( oidp, req );
break;
case REQ_ACTION_FILTER_BY_TASK:
ret = sysctl_action_filter( oidp, req, 1 );
break;
case REQ_ACTION_FILTER_BY_PID:
ret = sysctl_action_filter( oidp, req, 0 );
break;
case REQ_PET_IDLE_RATE:
ret = sysctl_pet_idle_rate( oidp, req );
break;
case REQ_BLESS_PREEMPT:
ret = sysctl_bless_preempt( oidp, req );
break;
default:
ret = ENOENT;
break;
}
lck_mtx_unlock(&kperf_cfg_lock);
return ret;
}
static int
kperf_sysctl_bless_handler SYSCTL_HANDLER_ARGS
{
int ret;
(void)arg2;
if ( !kperf_cfg_initted )
panic("kperf_bootstrap not called");
lck_mtx_lock(&kperf_cfg_lock);
if ( (uintptr_t) arg1 == REQ_BLESS )
ret = sysctl_bless( oidp, req );
else
ret = ENOENT;
lck_mtx_unlock(&kperf_cfg_lock);
return ret;
}
int
kperf_access_check(void)
{
proc_t p = current_proc();
proc_t blessed_p;
int ret = 0;
boolean_t pid_gone = FALSE;
blessed_p = proc_find(blessed_pid);
if ( blessed_p != NULL )
proc_rele(blessed_p);
else
pid_gone = TRUE;
if ( blessed_pid == -1 || pid_gone ) {
ret = suser(kauth_cred_get(), &p->p_acflag);
if( !ret )
return ret;
}
if( p->p_pid != blessed_pid )
return EACCES;
return 0;
}
int
kperf_bless_pid(pid_t newpid)
{
proc_t p = NULL;
pid_t current_pid;
p = current_proc();
current_pid = p->p_pid;
if ( (newpid != -1) && (blessed_pid != -1) &&
(blessed_pid != current_pid) && !blessed_preempt ) {
p = proc_find(blessed_pid);
if ( p != NULL ) {
proc_rele(p);
return EACCES;
}
}
if ( newpid != -1 ) {
p = proc_find(newpid);
if ( p == NULL )
return EINVAL;
proc_rele(p);
}
blessed_pid = newpid;
blessed_preempt = FALSE;
return 0;
}
SYSCTL_NODE(, OID_AUTO, kperf, CTLFLAG_RW|CTLFLAG_LOCKED, 0,
"kperf");
SYSCTL_NODE(_kperf, OID_AUTO, action, CTLFLAG_RW|CTLFLAG_LOCKED, 0,
"action");
SYSCTL_PROC(_kperf_action, OID_AUTO, count,
CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_ACTION_COUNT,
sizeof(int), kperf_sysctl, "I", "Number of actions");
SYSCTL_PROC(_kperf_action, OID_AUTO, samplers,
CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_ACTION_SAMPLERS,
3*sizeof(uint64_t), kperf_sysctl, "UQ",
"What to sample what a trigger fires an action");
SYSCTL_PROC(_kperf_action, OID_AUTO, userdata,
CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_ACTION_USERDATA,
3*sizeof(uint64_t), kperf_sysctl, "UQ",
"User data to attribute to action");
SYSCTL_PROC(_kperf_action, OID_AUTO, filter_by_task,
CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_ACTION_FILTER_BY_TASK,
3*sizeof(uint64_t), kperf_sysctl, "UQ",
"Apply a task filter to the action");
SYSCTL_PROC(_kperf_action, OID_AUTO, filter_by_pid,
CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_ACTION_FILTER_BY_PID,
3*sizeof(uint64_t), kperf_sysctl, "UQ",
"Apply a pid filter to the action");
SYSCTL_NODE(_kperf, OID_AUTO, timer, CTLFLAG_RW|CTLFLAG_LOCKED, 0,
"timer");
SYSCTL_PROC(_kperf_timer, OID_AUTO, count,
CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_TIMER_COUNT,
sizeof(int), kperf_sysctl, "I", "Number of time triggers");
SYSCTL_PROC(_kperf_timer, OID_AUTO, period,
CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_TIMER_PERIOD,
2*sizeof(uint64_t), kperf_sysctl, "UQ", "Timer number and period");
SYSCTL_PROC(_kperf_timer, OID_AUTO, action,
CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_TIMER_ACTION,
2*sizeof(uint64_t), kperf_sysctl, "UQ", "Timer number and actionid");
SYSCTL_PROC(_kperf_timer, OID_AUTO, pet_timer,
CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_TIMER_PET,
sizeof(int), kperf_sysctl, "I", "Which timer ID does PET");
SYSCTL_PROC(_kperf, OID_AUTO, sampling,
CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_SAMPLING,
sizeof(int), kperf_sysctl, "I", "Sampling running");
SYSCTL_PROC(_kperf, OID_AUTO, blessed_pid,
CTLTYPE_INT|CTLFLAG_RW,
(void*)REQ_BLESS,
sizeof(int), kperf_sysctl_bless_handler, "I", "Blessed pid");
SYSCTL_PROC(_kperf, OID_AUTO, blessed_preempt,
CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_BLESS_PREEMPT,
sizeof(int), kperf_sysctl, "I", "Blessed preemption");
SYSCTL_PROC(_kperf, OID_AUTO, kdbg_callstacks,
CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_KDBG_CALLSTACKS,
sizeof(int), kperf_sysctl, "I", "Generate kdbg callstacks");
SYSCTL_INT(_kperf, OID_AUTO, kdbg_cswitch,
CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
&kperf_cswitch_hook, 0, "Generate context switch info");
SYSCTL_PROC(_kperf, OID_AUTO, pet_idle_rate,
CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY,
(void*)REQ_PET_IDLE_RATE,
sizeof(int), kperf_sysctl, "I", "Rate at which unscheduled threads are forced to be sampled in PET mode");
SYSCTL_INT(_kperf, OID_AUTO, debug_level, CTLFLAG_RW,
&kperf_debug_level, 0, "debug level");