This is xnu-11215.1.10. See this file in:
/*
* Copyright (c) 2012-2013 Apple Computer, 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@
*/
#define IOKIT_ENABLE_SHARED_PTR
#include <libkern/c++/OSSharedPtr.h>
#include <IOKit/IOKernelReportStructs.h>
#include <IOKit/IOKernelReporters.h>
#include "IOReporterDefs.h"
#define super IOReporter
OSDefineMetaClassAndStructors(IOStateReporter, IOReporter);
/* static */
OSSharedPtr<IOStateReporter>
IOStateReporter::with(IOService *reportingService,
IOReportCategories categories,
int nstates,
IOReportUnit unit /* = kIOReportUnitHWTicks*/)
{
OSSharedPtr<IOStateReporter> reporter;
if (nstates > INT16_MAX) {
return nullptr;
}
reporter = OSMakeShared<IOStateReporter>();
if (!reporter) {
return nullptr;
}
if (!reporter->initWith(reportingService, categories, (int16_t) nstates, unit)) {
return nullptr;
}
return reporter;
}
bool
IOStateReporter::initWith(IOService *reportingService,
IOReportCategories categories,
int16_t nstates,
IOReportUnit unit)
{
bool success = false;
IOReportChannelType channelType = {
.categories = categories,
.report_format = kIOReportFormatState,
.nelements = static_cast<uint16_t>(nstates),
.element_idx = 0
};
if (super::init(reportingService, channelType, unit) != true) {
IORLOG("ERROR super::initWith failed");
success = false;
goto finish;
}
_currentStates = NULL;
_lastUpdateTimes = NULL;
success = true;
finish:
return success;
}
void
IOStateReporter::free(void)
{
if (_currentStates) {
PREFL_MEMOP_PANIC(_nChannels, int);
IOFreeData(_currentStates, (size_t)_nChannels * sizeof(int));
}
if (_lastUpdateTimes) {
PREFL_MEMOP_PANIC(_nChannels, uint64_t);
IOFreeData(_lastUpdateTimes, (size_t)_nChannels * sizeof(uint64_t));
}
super::free();
}
IOReturn
IOStateReporter::handleSwapPrepare(int newNChannels)
{
IOReturn res = kIOReturnError;
size_t newCurStatesSize, newTSSize;
//IORLOG("handleSwapPrepare (state) _nChannels before = %u", _nChannels);
IOREPORTER_CHECK_CONFIG_LOCK();
if (_swapCurrentStates || _swapLastUpdateTimes) {
panic("IOStateReporter::_swap* already in use");
}
// new currentStates buffer
PREFL_MEMOP_FAIL(newNChannels, int);
newCurStatesSize = (size_t)newNChannels * sizeof(int);
_swapCurrentStates = (int*)IOMallocData(newCurStatesSize);
if (_swapCurrentStates == NULL) {
res = kIOReturnNoMemory; goto finish;
}
memset(_swapCurrentStates, -1, newCurStatesSize); // init w/"no state"
// new timestamps buffer
PREFL_MEMOP_FAIL(newNChannels, uint64_t);
newTSSize = (size_t)newNChannels * sizeof(uint64_t);
_swapLastUpdateTimes = (uint64_t *)IOMallocZeroData(newTSSize);
if (_swapLastUpdateTimes == NULL) {
res = kIOReturnNoMemory; goto finish;
}
res = super::handleSwapPrepare(newNChannels);
finish:
if (res) {
if (_swapCurrentStates) {
IOFreeData(_swapCurrentStates, newCurStatesSize);
_swapCurrentStates = NULL;
}
if (_swapLastUpdateTimes) {
IOFreeData(_swapLastUpdateTimes, newTSSize);
_swapLastUpdateTimes = NULL;
}
}
return res;
}
IOReturn
IOStateReporter::handleAddChannelSwap(uint64_t channelID,
const OSSymbol *symChannelName)
{
IOReturn res = kIOReturnError;
int cnt;
int *tmpCurStates;
uint64_t *tmpTimestamps;
bool swapComplete = false;
//IORLOG("IOStateReporter::handleSwap");
if (!_swapCurrentStates || !_swapLastUpdateTimes) {
IORLOG("IOReporter::handleSwap ERROR swap variables uninitialized!");
goto finish;
}
IOREPORTER_CHECK_CONFIG_LOCK();
IOREPORTER_CHECK_LOCK();
// Copy any existing buffers
if (_currentStates) {
PREFL_MEMOP_FAIL(_nChannels, int);
memcpy(_swapCurrentStates, _currentStates,
(size_t)_nChannels * sizeof(int));
if (!_lastUpdateTimes) {
panic("IOStateReporter::handleAddChannelSwap _lastUpdateTimes unset despite non-NULL _currentStates");
}
PREFL_MEMOP_FAIL(_nChannels, uint64_t);
memcpy(_swapLastUpdateTimes, _lastUpdateTimes,
(size_t)_nChannels * sizeof(uint64_t));
}
// Update principal instance variables, keep old values in _swap* for cleanup
tmpCurStates = _currentStates;
_currentStates = _swapCurrentStates;
_swapCurrentStates = tmpCurStates;
tmpTimestamps = _lastUpdateTimes;
_lastUpdateTimes = _swapLastUpdateTimes;
_swapLastUpdateTimes = tmpTimestamps;
swapComplete = true;
// subclass success
// invoke superclass(es): base class updates _nChannels & _nElements
res = super::handleAddChannelSwap(channelID, symChannelName);
if (res) {
IORLOG("handleSwap(state) ERROR super::handleSwap failed!");
goto finish;
}
// Channel added successfully, initialize the new channel's state_ids to 0..nStates-1
for (cnt = 0; cnt < _channelDimension; cnt++) {
handleSetStateID(channelID, cnt, (uint64_t)cnt);
}
finish:
if (res && swapComplete) {
// unswap so the unused buffers get cleaned up
tmpCurStates = _currentStates;
_currentStates = _swapCurrentStates;
_swapCurrentStates = tmpCurStates;
tmpTimestamps = _lastUpdateTimes;
_lastUpdateTimes = _swapLastUpdateTimes;
_swapLastUpdateTimes = tmpTimestamps;
}
return res;
}
void
IOStateReporter::handleSwapCleanup(int swapNChannels)
{
IOREPORTER_CHECK_CONFIG_LOCK();
super::handleSwapCleanup(swapNChannels);
if (_swapCurrentStates) {
PREFL_MEMOP_PANIC(swapNChannels, int);
IOFreeData(_swapCurrentStates, (size_t)swapNChannels * sizeof(int));
_swapCurrentStates = NULL;
}
if (_swapLastUpdateTimes) {
PREFL_MEMOP_PANIC(swapNChannels, uint64_t);
IOFreeData(_swapLastUpdateTimes, (size_t)swapNChannels * sizeof(uint64_t));
_swapLastUpdateTimes = NULL;
}
}
IOReturn
IOStateReporter::_getStateIndices(uint64_t channel_id,
uint64_t state_id,
int *channel_index,
int *state_index)
{
IOReturn res = kIOReturnError;
int cnt;
IOStateReportValues *values;
int element_index = 0;
IOREPORTER_CHECK_LOCK();
if (getChannelIndices(channel_id,
channel_index,
&element_index) != kIOReturnSuccess) {
res = kIOReturnBadArgument;
goto finish;
}
for (cnt = 0; cnt < _channelDimension; cnt++) {
values = (IOStateReportValues *)getElementValues(element_index + cnt);
if (values == NULL) {
res = kIOReturnError;
goto finish;
}
if (values->state_id == state_id) {
*state_index = cnt;
res = kIOReturnSuccess;
goto finish;
}
}
res = kIOReturnBadArgument;
finish:
return res;
}
IOReturn
IOStateReporter::setChannelState(uint64_t channel_id,
uint64_t new_state_id)
{
IOReturn res = kIOReturnError;
int channel_index, new_state_index;
uint64_t last_intransition = 0;
uint64_t prev_state_residency = 0;
lockReporter();
if (_getStateIndices(channel_id, new_state_id, &channel_index, &new_state_index) == kIOReturnSuccess) {
res = handleSetStateByIndices(channel_index, new_state_index,
last_intransition,
prev_state_residency);
goto finish;
}
res = kIOReturnBadArgument;
finish:
unlockReporter();
return res;
}
IOReturn
IOStateReporter::setChannelState(uint64_t channel_id,
uint64_t new_state_id,
uint64_t last_intransition,
uint64_t prev_state_residency)
{
return setChannelState(channel_id, new_state_id);
}
IOReturn
IOStateReporter::overrideChannelState(uint64_t channel_id,
uint64_t state_id,
uint64_t time_in_state,
uint64_t intransitions,
uint64_t last_intransition /*=0*/)
{
IOReturn res = kIOReturnError;
int channel_index, state_index;
lockReporter();
if (_getStateIndices(channel_id, state_id, &channel_index, &state_index) == kIOReturnSuccess) {
if (_lastUpdateTimes[channel_index]) {
panic("overrideChannelState() cannot be used after setChannelState()!");
}
res = handleOverrideChannelStateByIndices(channel_index, state_index,
time_in_state, intransitions,
last_intransition);
goto finish;
}
res = kIOReturnBadArgument;
finish:
unlockReporter();
return res;
}
IOReturn
IOStateReporter::handleOverrideChannelStateByIndices(int channel_index,
int state_index,
uint64_t time_in_state,
uint64_t intransitions,
uint64_t last_intransition /*=0*/)
{
IOReturn kerr, result = kIOReturnError;
IOStateReportValues state_values;
int element_index;
if (channel_index < 0 || channel_index >= _nChannels) {
result = kIOReturnBadArgument; goto finish;
}
if (channel_index < 0 || channel_index > (_nElements - state_index)
/ _channelDimension) {
result = kIOReturnOverrun; goto finish;
}
element_index = channel_index * _channelDimension + state_index;
kerr = copyElementValues(element_index, (IOReportElementValues*)&state_values);
if (kerr) {
result = kerr; goto finish;
}
// last_intransition = 0 -> no current state ("residency summary only")
state_values.last_intransition = last_intransition;
state_values.intransitions = intransitions;
state_values.upticks = time_in_state;
// determines current time for metadata
kerr = setElementValues(element_index, (IOReportElementValues *)&state_values);
if (kerr) {
result = kerr; goto finish;
}
// success
result = kIOReturnSuccess;
finish:
return result;
}
IOReturn
IOStateReporter::incrementChannelState(uint64_t channel_id,
uint64_t state_id,
uint64_t time_in_state,
uint64_t intransitions,
uint64_t last_intransition /*=0*/)
{
IOReturn res = kIOReturnError;
int channel_index, state_index;
lockReporter();
if (_getStateIndices(channel_id, state_id, &channel_index, &state_index) == kIOReturnSuccess) {
if (_lastUpdateTimes[channel_index]) {
panic("incrementChannelState() cannot be used after setChannelState()!");
}
res = handleIncrementChannelStateByIndices(channel_index, state_index,
time_in_state, intransitions,
last_intransition);
goto finish;
}
res = kIOReturnBadArgument;
finish:
unlockReporter();
return res;
}
IOReturn
IOStateReporter::handleIncrementChannelStateByIndices(int channel_index,
int state_index,
uint64_t time_in_state,
uint64_t intransitions,
uint64_t last_intransition /*=0*/)
{
IOReturn kerr, result = kIOReturnError;
IOStateReportValues state_values;
int element_index;
if (channel_index < 0 || channel_index >= _nChannels) {
result = kIOReturnBadArgument; goto finish;
}
if (channel_index < 0 || channel_index > (_nElements - state_index)
/ _channelDimension) {
result = kIOReturnOverrun; goto finish;
}
element_index = channel_index * _channelDimension + state_index;
kerr = copyElementValues(element_index, (IOReportElementValues*)&state_values);
if (kerr) {
result = kerr;
goto finish;
}
state_values.last_intransition = last_intransition;
state_values.intransitions += intransitions;
state_values.upticks += time_in_state;
// determines current time for metadata
kerr = setElementValues(element_index, (IOReportElementValues *)&state_values);
if (kerr) {
result = kerr;
goto finish;
}
// success
result = kIOReturnSuccess;
finish:
return result;
}
IOReturn
IOStateReporter::setState(uint64_t new_state_id)
{
uint64_t last_intransition = 0;
uint64_t prev_state_residency = 0;
IOReturn res = kIOReturnError;
IOStateReportValues *values;
int channel_index = 0, element_index = 0, new_state_index = 0;
int cnt;
lockReporter();
if (_nChannels == 1) {
for (cnt = 0; cnt < _channelDimension; cnt++) {
new_state_index = element_index + cnt;
values = (IOStateReportValues *)getElementValues(new_state_index);
if (values == NULL) {
res = kIOReturnError;
goto finish;
}
if (values->state_id == new_state_id) {
res = handleSetStateByIndices(channel_index, new_state_index,
last_intransition,
prev_state_residency);
goto finish;
}
}
}
res = kIOReturnBadArgument;
finish:
unlockReporter();
return res;
}
IOReturn
IOStateReporter::setState(uint64_t new_state_id,
uint64_t last_intransition,
uint64_t prev_state_residency)
{
return setState(new_state_id);
}
IOReturn
IOStateReporter::setStateID(uint64_t channel_id,
int state_index,
uint64_t state_id)
{
IOReturn res = kIOReturnError;
lockReporter();
res = handleSetStateID(channel_id, state_index, state_id);
unlockReporter();
return res;
}
IOReturn
IOStateReporter::handleSetStateID(uint64_t channel_id,
int state_index,
uint64_t state_id)
{
IOReturn res = kIOReturnError;
IOStateReportValues state_values;
int element_index = 0;
IOREPORTER_CHECK_LOCK();
if (getFirstElementIndex(channel_id, &element_index) == kIOReturnSuccess) {
if (state_index >= _channelDimension) {
res = kIOReturnBadArgument; goto finish;
}
if (_nElements - state_index <= element_index) {
res = kIOReturnOverrun; goto finish;
}
element_index += state_index;
if (copyElementValues(element_index, (IOReportElementValues *)&state_values) != kIOReturnSuccess) {
res = kIOReturnBadArgument;
goto finish;
}
state_values.state_id = state_id;
res = setElementValues(element_index, (IOReportElementValues *)&state_values);
}
// FIXME: set a bit somewhere (reporter-wide?) that state_ids can no longer be
// assumed to be contiguous
finish:
return res;
}
IOReturn
IOStateReporter::setStateByIndices(int channel_index,
int new_state_index)
{
IOReturn res = kIOReturnError;
uint64_t last_intransition = 0;
uint64_t prev_state_residency = 0;
lockReporter();
res = handleSetStateByIndices(channel_index, new_state_index,
last_intransition, prev_state_residency);
unlockReporter();
return res;
}
IOReturn
IOStateReporter::setStateByIndices(int channel_index,
int new_state_index,
uint64_t last_intransition,
uint64_t prev_state_residency)
{
return setStateByIndices(channel_index, new_state_index);
}
IOReturn
IOStateReporter::handleSetStateByIndices(int channel_index,
int new_state_index,
uint64_t last_intransition,
uint64_t prev_state_residency)
{
IOReturn res = kIOReturnError;
IOStateReportValues curr_state_values, new_state_values;
int curr_state_index = 0;
int curr_element_index, new_element_index;
uint64_t last_ch_update_time = 0;
uint64_t recordTime = mach_absolute_time();
IOREPORTER_CHECK_LOCK();
if (channel_index < 0 || channel_index >= _nChannels) {
res = kIOReturnBadArgument; goto finish;
}
// if no timestamp provided, last_intransition = time of recording (now)
if (last_intransition == 0) {
last_intransition = recordTime;
}
// First update target state if different than the current state
// _currentStates[] initialized to -1 to detect first state transition
curr_state_index = _currentStates[channel_index];
if (new_state_index != curr_state_index) {
// fetch element data
if (channel_index < 0 || channel_index > (_nElements - new_state_index)
/ _channelDimension) {
res = kIOReturnOverrun; goto finish;
}
new_element_index = channel_index * _channelDimension + new_state_index;
if (copyElementValues(new_element_index,
(IOReportElementValues *)&new_state_values)) {
res = kIOReturnBadArgument;
goto finish;
}
// Update new state's transition info
new_state_values.intransitions += 1;
new_state_values.last_intransition = last_intransition;
// and store the values
res = setElementValues(new_element_index,
(IOReportElementValues *)&new_state_values,
recordTime);
if (res != kIOReturnSuccess) {
goto finish;
}
_currentStates[channel_index] = new_state_index;
}
/* Now update time spent in any previous state
* If new_state_index = curr_state_index, this updates time in the
* current state. If this is the channel's first state transition,
* the last update time will be zero.
*
* Note: While setState() should never be called on a channel being
* updated with increment/overrideChannelState(), that's another way
* that the last update time might not exist. Regardless, if there
* is no basis for determining time spent in previous state, there's
* nothing to update!
*/
last_ch_update_time = _lastUpdateTimes[channel_index];
if (last_ch_update_time != 0) {
if (channel_index < 0 || channel_index > (_nElements - curr_state_index)
/ _channelDimension) {
res = kIOReturnOverrun; goto finish;
}
curr_element_index = channel_index * _channelDimension + curr_state_index;
if (copyElementValues(curr_element_index,
(IOReportElementValues *)&curr_state_values)) {
res = kIOReturnBadArgument;
goto finish;
}
// compute the time spent in previous state, unless provided
if (prev_state_residency == 0) {
prev_state_residency = last_intransition - last_ch_update_time;
}
curr_state_values.upticks += prev_state_residency;
res = setElementValues(curr_element_index,
(IOReportElementValues*)&curr_state_values,
recordTime);
if (res != kIOReturnSuccess) {
goto finish;
}
}
// record basis for next "time in prior state" calculation
// (also arms a panic in override/incrementChannelState())
_lastUpdateTimes[channel_index] = last_intransition;
finish:
return res;
}
// blocks might make this slightly easier?
uint64_t
IOStateReporter::getStateInTransitions(uint64_t channel_id,
uint64_t state_id)
{
return _getStateValue(channel_id, state_id, kInTransitions);
}
uint64_t
IOStateReporter::getStateResidencyTime(uint64_t channel_id,
uint64_t state_id)
{
return _getStateValue(channel_id, state_id, kResidencyTime);
}
uint64_t
IOStateReporter::getStateLastTransitionTime(uint64_t channel_id,
uint64_t state_id)
{
return _getStateValue(channel_id, state_id, kLastTransitionTime);
}
uint64_t
IOStateReporter::_getStateValue(uint64_t channel_id,
uint64_t state_id,
enum valueSelector value)
{
int channel_index = 0, element_index = 0, cnt;
IOStateReportValues *values = NULL;
uint64_t result = kIOReportInvalidValue;
lockReporter();
if (getChannelIndices(channel_id, &channel_index, &element_index) == kIOReturnSuccess) {
if (updateChannelValues(channel_index) == kIOReturnSuccess) {
for (cnt = 0; cnt < _channelDimension; cnt++) {
values = (IOStateReportValues *)getElementValues(element_index);
if (state_id == values->state_id) {
switch (value) {
case kInTransitions:
result = values->intransitions;
break;
case kResidencyTime:
result = values->upticks;
break;
case kLastTransitionTime:
result = values->last_intransition;
break;
default:
break;
}
break;
}
element_index++;
}
}
}
unlockReporter();
return result;
}
uint64_t
IOStateReporter::getStateLastChannelUpdateTime(uint64_t channel_id)
{
int channel_index;
uint64_t result = kIOReportInvalidValue;
lockReporter();
if (getChannelIndex(channel_id, &channel_index) == kIOReturnSuccess) {
result = _lastUpdateTimes[channel_index];
}
unlockReporter();
return result;
}
/* updateChannelValues() is called to refresh state before being
* reported outside the reporter. In the case of IOStateReporter,
* this is primarily an update to the "time in state" data.
*/
IOReturn
IOStateReporter::updateChannelValues(int channel_index)
{
IOReturn kerr, result = kIOReturnError;
int state_index, element_idx;
uint64_t currentTime;
uint64_t last_ch_update_time;
uint64_t time_in_state;
IOStateReportValues state_values;
IOREPORTER_CHECK_LOCK();
if (channel_index < 0 || channel_index >= _nChannels) {
result = kIOReturnBadArgument; goto finish;
}
/* First check to see whether this channel has begun self-
* calculation of time in state. It's possible this channel
* has yet to be initialized or that the driver is updating
* the channel with override/incrementChannelState() which
* never enable automatic time-in-state updates. In that case,
* there is nothing to update and we return success.
*/
last_ch_update_time = _lastUpdateTimes[channel_index];
if (last_ch_update_time == 0) {
result = kIOReturnSuccess; goto finish;
}
// figure out the current state (if any)
state_index = _currentStates[channel_index];
// e.g. given 4 4-state channels, the boundary is ch[3].st[3] <- _elems[15]
if (channel_index < 0 || channel_index > (_nElements - state_index)
/ _channelDimension) {
result = kIOReturnOverrun; goto finish;
}
element_idx = channel_index * _channelDimension + state_index;
// get the current values
kerr = copyElementValues(element_idx, (IOReportElementValues*)&state_values);
if (kerr) {
result = kerr; goto finish;
}
// calculate time in state
currentTime = mach_absolute_time();
time_in_state = currentTime - last_ch_update_time;
state_values.upticks += time_in_state;
// and store the values
kerr = setElementValues(element_idx,
(IOReportElementValues *)&state_values,
currentTime);
if (kerr) {
result = kerr; goto finish;
}
// Record basis for next "prior time" calculation
_lastUpdateTimes[channel_index] = currentTime;
// success
result = kIOReturnSuccess;
finish:
return result;
}
/* static */ OSPtr<IOReportLegendEntry>
IOStateReporter::createLegend(const uint64_t *channelIDs,
const char **channelNames,
int channelCount,
int nstates,
IOReportCategories categories,
IOReportUnit unit)
{
IOReportChannelType channelType = {
.categories = categories,
.report_format = kIOReportFormatState,
.nelements = static_cast<uint16_t>(nstates),
.element_idx = 0
};
return IOReporter::legendWith(channelIDs, channelNames, channelCount, channelType, unit);
}