X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/fe8ab488e9161c46dd9885d58fc52996dc0249ff..bca245acd4c03fd752d1a45f011ad495e60fe53d:/bsd/kern/kern_kpc.c diff --git a/bsd/kern/kern_kpc.c b/bsd/kern/kern_kpc.c index dde93bbce..5e23e548e 100644 --- a/bsd/kern/kern_kpc.c +++ b/bsd/kern/kern_kpc.c @@ -2,7 +2,7 @@ * Copyright (c) 2012 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 @@ -11,10 +11,10 @@ * 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, @@ -22,7 +22,7 @@ * 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@ */ @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -53,35 +54,31 @@ #define REQ_PERIOD (10) #define REQ_ACTIONID (11) #define REQ_SW_INC (14) +#define REQ_PMU_VERSION (15) /* Type-munging casts */ typedef int (*getint_t)(void); typedef int (*setint_t)(int); -/* safety */ static int kpc_initted = 0; -/* locking and buffer for large data requests */ -#define SYSCTL_BUFFER_SIZE (33 * sizeof(uint64_t)) -static lck_grp_attr_t *sysctl_buffer_lckgrp_attr = NULL; -static lck_grp_t *sysctl_buffer_lckgrp = NULL; -static lck_mtx_t sysctl_buffer_lock; -static void *sysctl_buffer = NULL; +static lck_grp_attr_t *sysctl_lckgrp_attr = NULL; +static lck_grp_t *sysctl_lckgrp = NULL; +static lck_mtx_t sysctl_lock; -typedef int (*setget_func_t)(int); +/* + * Another element is needed to hold the CPU number when getting counter values. + */ +#define KPC_MAX_BUF_LEN (KPC_MAX_COUNTERS_COPIED + 1) -/* init our stuff */ -extern void kpc_arch_init(void); -extern void kpc_common_init(void); -extern void kpc_thread_init(void); /* osfmk/kern/kpc_thread.c */ +typedef int (*setget_func_t)(int); void kpc_init(void) { - sysctl_buffer_lckgrp_attr = lck_grp_attr_alloc_init(); - sysctl_buffer_lckgrp = lck_grp_alloc_init("kpc", - sysctl_buffer_lckgrp_attr); - lck_mtx_init(&sysctl_buffer_lock, sysctl_buffer_lckgrp, LCK_ATTR_NULL); + sysctl_lckgrp_attr = lck_grp_attr_alloc_init(); + sysctl_lckgrp = lck_grp_alloc_init("kpc", sysctl_lckgrp_attr); + lck_mtx_init(&sysctl_lock, sysctl_lckgrp, LCK_ATTR_NULL); kpc_arch_init(); kpc_common_init(); @@ -90,16 +87,39 @@ kpc_init(void) kpc_initted = 1; } +static uint64_t * +kpc_get_bigarray(uint32_t *size_out) +{ + static uint64_t *bigarray = NULL; + + LCK_MTX_ASSERT(&sysctl_lock, LCK_MTX_ASSERT_OWNED); + + uint32_t size = kpc_get_counterbuf_size() + sizeof(uint64_t); + *size_out = size; + + if (bigarray) { + return bigarray; + } + + /* + * Another element is needed to hold the CPU number when getting counter + * values. + */ + bigarray = kalloc_tag(size, VM_KERN_MEMORY_DIAG); + assert(bigarray != NULL); + return bigarray; +} + /* abstract sysctl handlers */ static int sysctl_get_int( struct sysctl_oid *oidp, struct sysctl_req *req, - uint32_t value ) + uint32_t value ) { int error = 0; - + /* copy out the old value */ error = sysctl_handle_int(oidp, &value, 0, req); - + return error; } @@ -108,11 +128,12 @@ sysctl_set_int( struct sysctl_req *req, int (*set_func)(int)) { int error = 0; int value = 0; - - error = SYSCTL_IN( req, &value, sizeof(value) ); - if( error ) + + error = SYSCTL_IN( req, &value, sizeof(value)); + if (error) { return error; - + } + error = set_func( value ); return error; @@ -120,18 +141,19 @@ sysctl_set_int( struct sysctl_req *req, int (*set_func)(int)) static int sysctl_getset_int( struct sysctl_oid *oidp, struct sysctl_req *req, - int (*get_func)(void), int (*set_func)(int) ) + int (*get_func)(void), int (*set_func)(int)) { int error = 0; uint32_t value = 0; - + /* get the old value and process it */ value = get_func(); /* copy out the old value, get the new value */ error = sysctl_handle_int(oidp, &value, 0, req); - if (error || !req->newptr) - return (error); + if (error || !req->newptr) { + return error; + } /* if that worked, and we're writing... */ error = set_func( value ); @@ -142,101 +164,86 @@ sysctl_getset_int( struct sysctl_oid *oidp, struct sysctl_req *req, static int sysctl_setget_int( struct sysctl_req *req, - int (*setget_func)(int) ) + int (*setget_func)(int)) { int error = 0; int value = 0; - - error = SYSCTL_IN( req, &value, sizeof(value) ); - if( error ) + + error = SYSCTL_IN( req, &value, sizeof(value)); + if (error) { return error; - + } + value = setget_func(value); - error = SYSCTL_OUT( req, &value, sizeof(value) ); + error = SYSCTL_OUT( req, &value, sizeof(value)); return error; } static int -kpc_sysctl_acquire_buffer(void) -{ - if( sysctl_buffer == NULL ) - { - sysctl_buffer = kalloc(SYSCTL_BUFFER_SIZE); - if( sysctl_buffer ) - { - bzero( sysctl_buffer, SYSCTL_BUFFER_SIZE ); - } - } - - if( !sysctl_buffer ) - { - return ENOMEM; - } - - return 0; -} - -static int sysctl_kpc_get_counters(uint32_t counters, - uint32_t *size, void *buf) + uint32_t *size, void *buf) { uint64_t *ctr_buf = (uint64_t*)buf; int curcpu; uint32_t count; count = kpc_get_cpu_counters(counters & KPC_ALL_CPUS, - counters, - &curcpu, &ctr_buf[1]); - if (!count) + counters, + &curcpu, &ctr_buf[1]); + if (!count) { return EINVAL; + } ctr_buf[0] = curcpu; - *size = (count+1) * sizeof(uint64_t); + *size = (count + 1) * sizeof(uint64_t); return 0; } -static int +static int sysctl_kpc_get_shadow_counters(uint32_t counters, - uint32_t *size, void *buf) + uint32_t *size, void *buf) { uint64_t *ctr_buf = (uint64_t*)buf; int curcpu; uint32_t count; count = kpc_get_shadow_counters(counters & KPC_ALL_CPUS, - counters, - &curcpu, &ctr_buf[1]); + counters, + &curcpu, &ctr_buf[1]); - if (!count) + if (!count) { return EINVAL; + } ctr_buf[0] = curcpu; - *size = (count+1) * sizeof(uint64_t); + *size = (count + 1) * sizeof(uint64_t); return 0; } -static int +static int sysctl_kpc_get_thread_counters(uint32_t tid, - uint32_t *size, void *buf) + uint32_t *size, void *buf) { uint32_t count = *size / sizeof(uint64_t); int r; - if( tid != 0 ) + if (tid != 0) { return EINVAL; + } r = kpc_get_curthread_counters(&count, buf); - if( !r ) + if (!r) { *size = count * sizeof(uint64_t); + } return r; -} +} static int sysctl_kpc_get_config(uint32_t classes, void* buf) @@ -247,6 +254,10 @@ sysctl_kpc_get_config(uint32_t classes, void* buf) static int sysctl_kpc_set_config(uint32_t classes, void* buf) { + /* userspace cannot reconfigure the power class */ + if (classes & KPC_CLASS_POWER_MASK) { + return EPERM; + } return kpc_set_config( classes, buf); } @@ -259,6 +270,10 @@ sysctl_kpc_get_period(uint32_t classes, void* buf) static int sysctl_kpc_set_period(uint32_t classes, void* buf) { + /* userspace cannot reconfigure the power class */ + if (classes & KPC_CLASS_POWER_MASK) { + return EPERM; + } return kpc_set_period( classes, buf); } @@ -276,35 +291,23 @@ sysctl_kpc_set_actionid(uint32_t classes, void* buf) static int -sysctl_get_bigarray( struct sysctl_req *req, - int (*get_fn)(uint32_t, uint32_t*, void*) ) +sysctl_get_bigarray(struct sysctl_req *req, + int (*get_fn)(uint32_t, uint32_t*, void*)) { - int error = 0; - uint32_t bufsize = SYSCTL_BUFFER_SIZE; + uint32_t bufsize = 0; + uint64_t *buf = kpc_get_bigarray(&bufsize); uint32_t arg = 0; /* get the argument */ - error = SYSCTL_IN( req, &arg, sizeof(arg) ); - if(error) - { - printf( "kpc: no arg?\n" ); + int error = SYSCTL_IN(req, &arg, sizeof(arg)); + if (error) { return error; } - /* get the wired buffer */ - error = kpc_sysctl_acquire_buffer(); - if (error) - return error; - - /* atomically get the array into the wired buffer. We have a double - * copy, but this is better than page faulting / interrupting during - * a copy. - */ - error = get_fn( arg, &bufsize, sysctl_buffer ); - - /* do the copy out */ - if( !error ) - error = SYSCTL_OUT( req, sysctl_buffer, bufsize ); + error = get_fn(arg, &bufsize, buf); + if (!error) { + error = SYSCTL_OUT(req, buf, bufsize); + } return error; } @@ -329,79 +332,54 @@ sysctl_actionid_size( uint32_t classes ) } static int -sysctl_getset_bigarray( struct sysctl_req *req, - int (*size_fn)(uint32_t arg), - int (*get_fn)(uint32_t, void*), - int (*set_fn)(uint32_t, void*) ) +sysctl_getset_bigarray(struct sysctl_req *req, int (*size_fn)(uint32_t arg), + int (*get_fn)(uint32_t, void*), int (*set_fn)(uint32_t, void*)) { int error = 0; - uint32_t bufsize = SYSCTL_BUFFER_SIZE; - uint32_t regsize = 0; uint64_t arg; + uint32_t bufsize = 0; + uint64_t *buf = kpc_get_bigarray(&bufsize); + /* get the config word */ - error = SYSCTL_IN( req, &arg, sizeof(arg) ); - if(error) - { - printf( "kpc: no arg?\n" ); + error = SYSCTL_IN(req, &arg, sizeof(arg)); + if (error) { return error; } - /* Work out size of registers */ - regsize = size_fn((uint32_t)arg); - - /* Ignore NULL requests */ - if(regsize == 0) - return EINVAL; - - /* ensure not too big */ - if( regsize > bufsize ) + /* Determine the size of registers to modify. */ + uint32_t regsize = size_fn((uint32_t)arg); + if (regsize == 0 || regsize > bufsize) { return EINVAL; + } - /* get the wired buffer */ - error = kpc_sysctl_acquire_buffer(); - if (error) - return error; - - // if writing... - if(req->newptr) - { - // copy in the rest in -- sysctl remembers we did one already - error = SYSCTL_IN( req, sysctl_buffer, - regsize ); - - // if SYSCTL_IN fails it means we are only doing a read - if(!error) { - // set it - error = set_fn( (uint32_t)arg, sysctl_buffer ); - if( error ) - goto fail; + /* if writing */ + if (req->newptr) { + /* copy the rest -- SYSCTL_IN knows the copyin should be shifted */ + error = SYSCTL_IN(req, buf, regsize); + + /* SYSCTL_IN failure means only need to read */ + if (!error) { + error = set_fn((uint32_t)arg, buf); + if (error) { + return error; + } } } - // if reading - if(req->oldptr) - { - // read it - error = get_fn( (uint32_t)arg, sysctl_buffer ); - if( error ) - goto fail; + /* if reading */ + if (req->oldptr) { + error = get_fn((uint32_t)arg, buf); + if (error) { + return error; + } - // copy out the full set - error = SYSCTL_OUT( req, sysctl_buffer, regsize ); + error = SYSCTL_OUT(req, buf, regsize); } - -fail: + return error; } - - -/* - * #define SYSCTL_HANDLER_ARGS (struct sysctl_oid *oidp, \ - * void *arg1, int arg2, \ - * struct sysctl_req *req ) - */ static int kpc_sysctl SYSCTL_HANDLER_ARGS { @@ -409,12 +387,19 @@ kpc_sysctl SYSCTL_HANDLER_ARGS // __unused struct sysctl_oid *unused_oidp = oidp; (void)arg2; - - if( !kpc_initted ) + + if (!kpc_initted) { panic("kpc_init not called"); + } + + if (!kpc_supported) { + return ENOTSUP; + } + + ktrace_lock(); // Most sysctls require an access check, but a few are public. - switch( (uintptr_t) arg1 ) { + switch ((uintptr_t) arg1) { case REQ_CLASSES: case REQ_CONFIG_COUNT: case REQ_COUNTER_COUNT: @@ -424,41 +409,42 @@ kpc_sysctl SYSCTL_HANDLER_ARGS default: // Require kperf access to read or write anything else. // This is either root or the blessed pid. - ret = kperf_access_check(); - if (ret) { + if ((ret = ktrace_read_check())) { + ktrace_unlock(); return ret; } break; } - lck_mtx_lock(&sysctl_buffer_lock); + ktrace_unlock(); + + lck_mtx_lock(&sysctl_lock); /* which request */ - switch( (uintptr_t) arg1 ) - { + switch ((uintptr_t) arg1) { case REQ_CLASSES: ret = sysctl_get_int( oidp, req, - kpc_get_classes() ); + kpc_get_classes()); break; case REQ_COUNTING: ret = sysctl_getset_int( oidp, req, - (getint_t)kpc_get_running, - (setint_t)kpc_set_running ); + (getint_t)kpc_get_running, + (setint_t)kpc_set_running ); break; case REQ_THREAD_COUNTING: ret = sysctl_getset_int( oidp, req, - (getint_t)kpc_get_thread_counting, - (setint_t)kpc_set_thread_counting ); + (getint_t)kpc_get_thread_counting, + (setint_t)kpc_set_thread_counting ); break; case REQ_CONFIG_COUNT: ret = sysctl_setget_int( req, - (setget_func_t)kpc_get_config_count ); + (setget_func_t)kpc_get_config_count ); break; case REQ_COUNTER_COUNT: ret = sysctl_setget_int( req, - (setget_func_t)kpc_get_counter_count ); + (setget_func_t)kpc_get_counter_count ); break; @@ -476,37 +462,41 @@ kpc_sysctl SYSCTL_HANDLER_ARGS case REQ_CONFIG: ret = sysctl_getset_bigarray( req, - sysctl_config_size, - sysctl_kpc_get_config, - sysctl_kpc_set_config ); + sysctl_config_size, + sysctl_kpc_get_config, + sysctl_kpc_set_config ); break; case REQ_PERIOD: ret = sysctl_getset_bigarray( req, - sysctl_counter_size, - sysctl_kpc_get_period, - sysctl_kpc_set_period ); + sysctl_counter_size, + sysctl_kpc_get_period, + sysctl_kpc_set_period ); break; case REQ_ACTIONID: ret = sysctl_getset_bigarray( req, - sysctl_actionid_size, - sysctl_kpc_get_actionid, - sysctl_kpc_set_actionid ); + sysctl_actionid_size, + sysctl_kpc_get_actionid, + sysctl_kpc_set_actionid ); break; case REQ_SW_INC: ret = sysctl_set_int( req, (setget_func_t)kpc_set_sw_inc ); - break; + break; + + case REQ_PMU_VERSION: + ret = sysctl_get_int(oidp, req, kpc_get_pmu_version()); + break; default: ret = ENOENT; break; } - lck_mtx_unlock(&sysctl_buffer_lock); - + lck_mtx_unlock(&sysctl_lock); + return ret; } @@ -514,76 +504,81 @@ kpc_sysctl SYSCTL_HANDLER_ARGS /*** sysctl definitions ***/ /* root kperf node */ -SYSCTL_NODE(, OID_AUTO, kpc, CTLFLAG_RW|CTLFLAG_LOCKED, 0, - "kpc"); +SYSCTL_NODE(, OID_AUTO, kpc, CTLFLAG_RW | CTLFLAG_LOCKED, 0, + "kpc"); /* values */ SYSCTL_PROC(_kpc, OID_AUTO, classes, - CTLTYPE_INT|CTLFLAG_RD|CTLFLAG_ANYBODY, - (void*)REQ_CLASSES, - sizeof(int), kpc_sysctl, "I", "Available classes"); + CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_CLASSES, + sizeof(int), kpc_sysctl, "I", "Available classes"); SYSCTL_PROC(_kpc, OID_AUTO, counting, - CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY, - (void*)REQ_COUNTING, - sizeof(int), kpc_sysctl, "I", "PMCs counting"); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_COUNTING, + sizeof(int), kpc_sysctl, "I", "PMCs counting"); SYSCTL_PROC(_kpc, OID_AUTO, thread_counting, - CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY, - (void*)REQ_THREAD_COUNTING, - sizeof(int), kpc_sysctl, "I", "Thread accumulation"); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_THREAD_COUNTING, + sizeof(int), kpc_sysctl, "I", "Thread accumulation"); + +SYSCTL_PROC(_kpc, OID_AUTO, pmu_version, + CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void *)REQ_PMU_VERSION, + sizeof(int), kpc_sysctl, "I", "PMU version for hardware"); /* faux values */ SYSCTL_PROC(_kpc, OID_AUTO, config_count, - CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY, - (void*)REQ_CONFIG_COUNT, - sizeof(int), kpc_sysctl, "S", "Config count"); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_CONFIG_COUNT, + sizeof(int), kpc_sysctl, "S", "Config count"); SYSCTL_PROC(_kpc, OID_AUTO, counter_count, - CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY, - (void*)REQ_COUNTER_COUNT, - sizeof(int), kpc_sysctl, "S", "Counter count"); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_COUNTER_COUNT, + sizeof(int), kpc_sysctl, "S", "Counter count"); SYSCTL_PROC(_kpc, OID_AUTO, sw_inc, - CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_ANYBODY, - (void*)REQ_SW_INC, - sizeof(int), kpc_sysctl, "S", "Software increment"); + CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_SW_INC, + sizeof(int), kpc_sysctl, "S", "Software increment"); /* arrays */ SYSCTL_PROC(_kpc, OID_AUTO, thread_counters, - CTLFLAG_RD|CTLFLAG_WR|CTLFLAG_ANYBODY, - (void*)REQ_THREAD_COUNTERS, - sizeof(uint64_t), kpc_sysctl, - "QU", "Current thread counters"); + CTLFLAG_RD | CTLFLAG_WR | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_THREAD_COUNTERS, + sizeof(uint64_t), kpc_sysctl, + "QU", "Current thread counters"); SYSCTL_PROC(_kpc, OID_AUTO, counters, - CTLFLAG_RD|CTLFLAG_WR|CTLFLAG_ANYBODY, - (void*)REQ_COUNTERS, - sizeof(uint64_t), kpc_sysctl, - "QU", "Current counters"); + CTLFLAG_RD | CTLFLAG_WR | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_COUNTERS, + sizeof(uint64_t), kpc_sysctl, + "QU", "Current counters"); SYSCTL_PROC(_kpc, OID_AUTO, shadow_counters, - CTLFLAG_RD|CTLFLAG_WR|CTLFLAG_ANYBODY, - (void*)REQ_SHADOW_COUNTERS, - sizeof(uint64_t), kpc_sysctl, - "QU", "Current shadow counters"); + CTLFLAG_RD | CTLFLAG_WR | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_SHADOW_COUNTERS, + sizeof(uint64_t), kpc_sysctl, + "QU", "Current shadow counters"); SYSCTL_PROC(_kpc, OID_AUTO, config, - CTLFLAG_RD|CTLFLAG_WR|CTLFLAG_ANYBODY, - (void*)REQ_CONFIG, - sizeof(uint64_t), kpc_sysctl, - "QU", "Set counter configs"); + CTLFLAG_RD | CTLFLAG_WR | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_CONFIG, + sizeof(uint64_t), kpc_sysctl, + "QU", "Set counter configs"); SYSCTL_PROC(_kpc, OID_AUTO, period, - CTLFLAG_RD|CTLFLAG_WR|CTLFLAG_ANYBODY, - (void*)REQ_PERIOD, - sizeof(uint64_t), kpc_sysctl, - "QU", "Set counter periods"); + CTLFLAG_RD | CTLFLAG_WR | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_PERIOD, + sizeof(uint64_t), kpc_sysctl, + "QU", "Set counter periods"); SYSCTL_PROC(_kpc, OID_AUTO, actionid, - CTLFLAG_RD|CTLFLAG_WR|CTLFLAG_ANYBODY, - (void*)REQ_ACTIONID, - sizeof(uint32_t), kpc_sysctl, - "QU", "Set counter actionids"); + CTLFLAG_RD | CTLFLAG_WR | CTLFLAG_ANYBODY | CTLFLAG_MASKED | CTLFLAG_LOCKED, + (void*)REQ_ACTIONID, + sizeof(uint32_t), kpc_sysctl, + "QU", "Set counter actionids");