/*
 * 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 <IOKit/IOKernelReportStructs.h>
#include <IOKit/IOKernelReporters.h>
#include "IOReporterDefs.h"

#include <string.h>
#include <sys/param.h>
#include <IOKit/IORegistryEntry.h>

#define super OSObject
OSDefineMetaClassAndStructors(IOReporter, OSObject);

static OSSharedPtr<const OSSymbol> gIOReportNoChannelName;

// * We might someday want an IOReportManager (vs. these static funcs)

/**************************************/
/***         STATIC METHODS         ***/
/**************************************/
IOReturn
IOReporter::configureAllReports(OSSet *reporters,
    IOReportChannelList *channelList,
    IOReportConfigureAction action,
    void *result,
    void *destination)
{
	IOReturn rval = kIOReturnError;
	OSSharedPtr<OSCollectionIterator> iterator;

	if (reporters == NULL || channelList == NULL || result == NULL) {
		rval = kIOReturnBadArgument;
		goto finish;
	}

	switch (action) {
	case kIOReportGetDimensions:
	case kIOReportEnable:
	case kIOReportDisable:
	{
		OSObject * object;
		iterator = OSCollectionIterator::withCollection(reporters);

		while ((object = iterator->getNextObject())) {
			IOReporter *rep = OSDynamicCast(IOReporter, object);

			if (rep) {
				(void)rep->configureReport(channelList, action, result, destination);
			} else {
				rval = kIOReturnUnsupported; // kIOReturnNotFound?
				goto finish;
			}
		}

		break;
	}

	case kIOReportTraceOnChange:
	case kIOReportNotifyHubOnChange:
	default:
		rval = kIOReturnUnsupported;
		goto finish;
	}

	rval = kIOReturnSuccess;

finish:
	return rval;
}

// the duplication in these functions almost makes one want Objective-C SEL* ;)
IOReturn
IOReporter::updateAllReports(OSSet *reporters,
    IOReportChannelList *channelList,
    IOReportConfigureAction action,
    void *result,
    void *destination)
{
	IOReturn rval = kIOReturnError;
	OSSharedPtr<OSCollectionIterator> iterator;

	if (reporters == NULL ||
	    channelList == NULL ||
	    result == NULL ||
	    destination == NULL) {
		rval = kIOReturnBadArgument;
		goto finish;
	}

	switch (action) {
	case kIOReportCopyChannelData:
	{
		OSObject * object;
		iterator = OSCollectionIterator::withCollection(reporters);

		while ((object = iterator->getNextObject())) {
			IOReporter *rep = OSDynamicCast(IOReporter, object);

			if (rep) {
				(void)rep->updateReport(channelList, action, result, destination);
			} else {
				rval = kIOReturnUnsupported; // kIOReturnNotFound?
				goto finish;
			}
		}

		break;
	}

	case kIOReportTraceChannelData:
	default:
		rval = kIOReturnUnsupported;
		goto finish;
	}

	rval = kIOReturnSuccess;

finish:
	return rval;
}


/**************************************/
/***       COMMON INIT METHODS      ***/
/**************************************/

bool
IOReporter::init(IOService *reportingService,
    IOReportChannelType channelType,
    IOReportUnit unit)
{
	bool success = false;

	// ::free() relies on these being initialized
	_reporterLock = NULL;
	_configLock = NULL;
	_elements = NULL;
	_enableCounts = NULL;
	_channelNames = nullptr;

	if (channelType.report_format == kIOReportInvalidFormat) {
		IORLOG("init ERROR: Channel Type ill-defined");
		goto finish;
	}

	_driver_id = reportingService->getRegistryEntryID();
	if (_driver_id == 0) {
		IORLOG("init() ERROR: no registry ID");
		goto finish;
	}

	if (!super::init()) {
		return false;
	}

	if (channelType.nelements > INT16_MAX) {
		return false;
	}
	_channelDimension = channelType.nelements;
	_channelType = channelType;
	// FIXME: need to look up dynamically
	if (unit == kIOReportUnitHWTicks) {
#if defined(__arm64__)
		unit = kIOReportUnit24MHzTicks;
#elif defined(__i386__) || defined(__x86_64__)
		// Most, but not all Macs use 1GHz
		unit = kIOReportUnit1GHzTicks;
#else
#error kIOReportUnitHWTicks not defined
#endif
	}
	_unit = unit;

	// Allocate a reporter (data) lock
	_reporterLock = IOSimpleLockAlloc();
	if (!_reporterLock) {
		goto finish;
	}
	_reporterIsLocked = false;

	// Allocate a config lock
	_configLock = IOLockAlloc();
	if (!_configLock) {
		goto finish;
	}
	_reporterConfigIsLocked = false;

	// Allocate channel names array
	_channelNames = OSArray::withCapacity(1);
	if (!_channelNames) {
		goto finish;
	}

	// success
	success = true;

finish:
	return success;
}


/*******************************/
/***      PUBLIC METHODS     ***/
/*******************************/

void
IOReporter::initialize(void)
{
	gIOReportNoChannelName = OSSymbol::withCString("_NO_NAME_4");
}

// init() [possibly via init*()] must be called before free()
// to ensure that _<var> = NULL
void
IOReporter::free(void)
{
	if (_configLock) {
		IOLockFree(_configLock);
	}
	if (_reporterLock) {
		IOSimpleLockFree(_reporterLock);
	}

	if (_elements) {
		PREFL_MEMOP_PANIC(_nElements, IOReportElement);
		IOFreeData(_elements, (size_t)_nElements * sizeof(IOReportElement));
	}
	if (_enableCounts) {
		PREFL_MEMOP_PANIC(_nChannels, int);
		IOFreeData(_enableCounts, (size_t)_nChannels * sizeof(int));
	}

	super::free();
}

/*
 #define TESTALLOC() do { \
 *   void *tbuf;                 \
 *   tbuf = IOMalloc(10);        \
 *   IOFree(tbuf, 10);           \
 *   IORLOG("%s:%d - _reporterIsLocked = %d & allocation successful", \
 *           __PRETTY_FUNCTION__, __LINE__, _reporterIsLocked); \
 *  } while (0);
 */
IOReturn
IOReporter::addChannel(uint64_t channelID,
    const char *channelName /* = NULL */)
{
	IOReturn res = kIOReturnError, kerr;
	OSSharedPtr<const OSSymbol> symChannelName;
	int oldNChannels, newNChannels = 0, freeNChannels = 0;

	IORLOG("IOReporter::addChannel %llx", channelID);

	// protect instance variables (but not contents)
	lockReporterConfig();

	// FIXME: Check if any channel is already present and return error

	// addChannel() always adds one channel
	oldNChannels = _nChannels;
	if (oldNChannels < 0 || oldNChannels > INT_MAX - 1) {
		res = kIOReturnOverrun;
		goto finish;
	}
	newNChannels = oldNChannels + 1;
	freeNChannels = newNChannels;   // until swap success

	// Expand addChannel()-specific data structure
	if (_channelNames->ensureCapacity((unsigned)newNChannels) <
	    (unsigned)newNChannels) {
		res = kIOReturnNoMemory; goto finish;
	}
	if (channelName) {
		symChannelName = OSSymbol::withCString(channelName);
		if (!symChannelName) {
			res = kIOReturnNoMemory; goto finish;
		}
	} else {
		// grab a reference to our shared global
		symChannelName = gIOReportNoChannelName;
	}

	// allocate new buffers into _swap* variables
	if ((kerr = handleSwapPrepare(newNChannels))) {
		// on error, channels are *not* swapped
		res = kerr; goto finish;
	}

	// exchange main and _swap* buffers with buffer contents protected
	// IOReporter::handleAddChannelSwap() also increments _nElements, etc
	lockReporter();
	res = handleAddChannelSwap(channelID, symChannelName.get());
	unlockReporter();
	// On failure, handleAddChannelSwap() leaves *new* buffers in _swap*.
	// On success, it's the old buffers, so we put the right size in here.
	if (res == kIOReturnSuccess) {
		freeNChannels = oldNChannels;
	}

finish:
	// free up not-in-use buffers (tracked by _swap*)
	handleSwapCleanup(freeNChannels);

	unlockReporterConfig();

	return res;
}


OSSharedPtr<IOReportLegendEntry>
IOReporter::createLegend(void)
{
	OSSharedPtr<IOReportLegendEntry> legendEntry;

	lockReporterConfig();

	legendEntry = handleCreateLegend();

	unlockReporterConfig();

	return legendEntry;
}


IOReturn
IOReporter::configureReport(IOReportChannelList *channelList,
    IOReportConfigureAction action,
    void *result,
    void *destination)
{
	IOReturn res = kIOReturnError;

	lockReporterConfig();

	res = handleConfigureReport(channelList, action, result, destination);

	unlockReporterConfig();

	return res;
}


IOReturn
IOReporter::updateReport(IOReportChannelList *channelList,
    IOReportConfigureAction action,
    void *result,
    void *destination)
{
	IOReturn res = kIOReturnError;

	lockReporter();

	res = handleUpdateReport(channelList, action, result, destination);

	unlockReporter();

	return res;
}


/*******************************/
/***    PROTECTED METHODS    ***/
/*******************************/


void
IOReporter::lockReporter()
{
	_interruptState = IOSimpleLockLockDisableInterrupt(_reporterLock);
	_reporterIsLocked = true;
}


void
IOReporter::unlockReporter()
{
	_reporterIsLocked = false;
	IOSimpleLockUnlockEnableInterrupt(_reporterLock, _interruptState);
}

void
IOReporter::lockReporterConfig()
{
	IOLockLock(_configLock);
	_reporterConfigIsLocked = true;
}

void
IOReporter::unlockReporterConfig()
{
	_reporterConfigIsLocked = false;
	IOLockUnlock(_configLock);
}


IOReturn
IOReporter::handleSwapPrepare(int newNChannels)
{
	IOReturn res = kIOReturnError;
	int newNElements;
	size_t newElementsSize, newECSize;

	// analyzer appeasement
	newElementsSize = newECSize = 0;

	//IORLOG("IOReporter::handleSwapPrepare");

	IOREPORTER_CHECK_CONFIG_LOCK();

	if (newNChannels < _nChannels) {
		panic("%s doesn't support shrinking", __func__);
	}
	if (newNChannels <= 0 || _channelDimension <= 0) {
		res = kIOReturnUnderrun;
		goto finish;
	}
	if (_swapElements || _swapEnableCounts) {
		panic("IOReporter::_swap* already in use");
	}

	// calculate the number of elements given #ch & the dimension of each
	if (newNChannels < 0 || newNChannels > INT_MAX / _channelDimension) {
		res = kIOReturnOverrun;
		goto finish;
	}
	newNElements = newNChannels * _channelDimension;

	// Allocate memory for the new array of report elements
	PREFL_MEMOP_FAIL(newNElements, IOReportElement);
	newElementsSize = (size_t)newNElements * sizeof(IOReportElement);
	_swapElements = (IOReportElement *)IOMallocZeroData(newElementsSize);
	if (_swapElements == NULL) {
		res = kIOReturnNoMemory; goto finish;
	}

	// Allocate memory for the new array of channel watch counts
	PREFL_MEMOP_FAIL(newNChannels, int);
	newECSize = (size_t)newNChannels * sizeof(int);
	_swapEnableCounts = (int *)IOMallocZeroData(newECSize);
	if (_swapEnableCounts == NULL) {
		res = kIOReturnNoMemory; goto finish;
	}

	// success
	res = kIOReturnSuccess;

finish:
	if (res) {
		if (_swapElements) {
			IOFreeData(_swapElements, newElementsSize);
			_swapElements = NULL;
		}
		if (_swapEnableCounts) {
			IOFreeData(_swapEnableCounts, newECSize);
			_swapEnableCounts = NULL;
		}
	}

	return res;
}


IOReturn
IOReporter::handleAddChannelSwap(uint64_t channel_id,
    const OSSymbol *symChannelName)
{
	IOReturn res = kIOReturnError;
	int cnt;
	int *tmpWatchCounts = NULL;
	IOReportElement *tmpElements = NULL;
	bool swapComplete = false;

	//IORLOG("IOReporter::handleSwap");

	IOREPORTER_CHECK_CONFIG_LOCK();
	IOREPORTER_CHECK_LOCK();

	if (!_swapElements || !_swapEnableCounts) {
		IORLOG("IOReporter::handleSwap ERROR swap variables uninitialized!");
		goto finish;
	}

	// Copy any existing elements to the new location
	//IORLOG("handleSwap (base) -> copying %u elements over...", _nChannels);
	if (_elements) {
		PREFL_MEMOP_PANIC(_nElements, IOReportElement);
		memcpy(_swapElements, _elements,
		    (size_t)_nElements * sizeof(IOReportElement));

		PREFL_MEMOP_PANIC(_nElements, int);
		memcpy(_swapEnableCounts, _enableCounts,
		    (size_t)_nChannels * sizeof(int));
	}

	// Update principal instance variables, keep old buffers for cleanup
	tmpElements = _elements;
	_elements = _swapElements;
	_swapElements = tmpElements;

	tmpWatchCounts = _enableCounts;
	_enableCounts = _swapEnableCounts;
	_swapEnableCounts = tmpWatchCounts;

	swapComplete = true;

	// but _nChannels & _nElements is still the old (one smaller) size

	// Initialize new element metadata (existing elements copied above)
	for (cnt = 0; cnt < _channelDimension; cnt++) {
		_elements[_nElements + cnt].channel_id = channel_id;
		_elements[_nElements + cnt].provider_id = _driver_id;
		_elements[_nElements + cnt].channel_type = _channelType;
		_elements[_nElements + cnt].channel_type.element_idx = ((int16_t) cnt);

		//IOREPORTER_DEBUG_ELEMENT(_swapNElements + cnt);
	}

	// Store a channel name at the end
	if (!_channelNames->setObject((unsigned)_nChannels, symChannelName)) {
		// Should never happen because we ensured capacity in addChannel()
		res = kIOReturnNoMemory;
		goto finish;
	}

	// And update the metadata: addChannel() always adds just one channel
	_nChannels += 1;
	_nElements += _channelDimension;

	// success
	res = kIOReturnSuccess;

finish:
	if (res && swapComplete) {
		// unswap so new buffers get cleaned up instead of old
		tmpElements = _elements;
		_elements = _swapElements;
		_swapElements = tmpElements;

		tmpWatchCounts = _enableCounts;
		_enableCounts = _swapEnableCounts;
		_swapEnableCounts = tmpWatchCounts;
	}
	return res;
}

void
IOReporter::handleSwapCleanup(int swapNChannels)
{
	int swapNElements;

	if (!_channelDimension || swapNChannels > INT_MAX / _channelDimension) {
		panic("%s - can't free %d channels of dimension %d", __func__,
		    swapNChannels, _channelDimension);
	}
	swapNElements = swapNChannels * _channelDimension;

	IOREPORTER_CHECK_CONFIG_LOCK();

	// release buffers no longer used after swapping
	if (_swapElements) {
		PREFL_MEMOP_PANIC(swapNElements, IOReportElement);
		IOFreeData(_swapElements, (size_t)swapNElements * sizeof(IOReportElement));
		_swapElements = NULL;
	}
	if (_swapEnableCounts) {
		PREFL_MEMOP_PANIC(swapNChannels, int);
		IOFreeData(_swapEnableCounts, (size_t)swapNChannels * sizeof(int));
		_swapEnableCounts = NULL;
	}
}


// The reporter wants to know if its channels have observers.
// Eventually we'll add some sort of bool ::anyChannelsInUse() which
// clients can use to cull unused reporters after configureReport(disable).
IOReturn
IOReporter::handleConfigureReport(IOReportChannelList *channelList,
    IOReportConfigureAction action,
    void *result,
    void *destination)
{
	IOReturn res = kIOReturnError;
	int channel_index = 0;
	uint32_t chIdx;
	int *nElements, *nChannels;

	// Check on channelList and result because used below
	if (!channelList || !result) {
		goto finish;
	}

	//IORLOG("IOReporter::configureReport action %u for %u channels",
	//       action, channelList->nchannels);

	// Make sure channel is present, increase matching watch count, 'result'
	for (chIdx = 0; chIdx < channelList->nchannels; chIdx++) {
		if (getChannelIndex(channelList->channels[chIdx].channel_id,
		    &channel_index) == kIOReturnSuccess) {
			// IORLOG("reporter %p recognizes channel %lld", this, channelList->channels[chIdx].channel_id);

			switch (action) {
			case kIOReportEnable:
				nChannels = (int*)result;
				_enabled++;
				_enableCounts[channel_index]++;
				(*nChannels)++;
				break;

			case kIOReportDisable:
				nChannels = (int*)result;
				_enabled--;
				_enableCounts[channel_index]--;
				(*nChannels)++;
				break;

			case kIOReportGetDimensions:
				nElements = (int *)result;
				*nElements += _channelDimension;
				break;

			default:
				IORLOG("ERROR configureReport unknown action!");
				break;
			}
		}
	}

	// success
	res = kIOReturnSuccess;

finish:
	return res;
}

static const uint32_t UNLOCK_PERIOD = 1 << 5;
static_assert(powerof2(UNLOCK_PERIOD),
    "unlock period not a power of 2: period check must be efficient");
static const uint32_t UNLOCK_PERIOD_MASK = UNLOCK_PERIOD - 1;

IOReturn
IOReporter::handleUpdateReport(IOReportChannelList *channelList,
    IOReportConfigureAction action,
    void *result,
    void *destination)
{
	IOReturn res = kIOReturnError;
	int *nElements = (int *)result;
	int channel_index = 0;
	uint32_t chIdx;
	IOBufferMemoryDescriptor *dest;

	if (!channelList || !result || !destination) {
		goto finish;
	}

	dest = OSDynamicCast(IOBufferMemoryDescriptor, (OSObject *)destination);
	if (dest == NULL) {
		// Invalid destination
		res = kIOReturnBadArgument;
		goto finish;
	}

	if (!_enabled) {
		goto finish;
	}

	for (chIdx = 0; chIdx < channelList->nchannels; chIdx++) {
		// Drop the lock periodically to allow reporters to emit data, as they
		// might be running in interrupt context.
		//
		// This is safe because the spinlock is really only protecting the
		// element array, any other changes to the reporter will need to
		// take the configuration lock.   Keeping the lock between channels
		// isn't protecting us from anything, because there's no API to
		// update multiple channels atomically anyway.
		if ((chIdx & UNLOCK_PERIOD_MASK) == UNLOCK_PERIOD_MASK) {
			unlockReporter();
			delay(1);
			lockReporter();
		}
		if (getChannelIndex(channelList->channels[chIdx].channel_id,
		    &channel_index) == kIOReturnSuccess) {
			//IORLOG("%s - found channel_id %llx @ index %d", __func__,
			//       channelList->channels[chIdx].channel_id,
			//       channel_index);

			switch (action) {
			case kIOReportCopyChannelData:
				res = updateChannelValues(channel_index);
				if (res) {
					IORLOG("ERROR: updateChannelValues() failed: %x", res);
					goto finish;
				}

				res = updateReportChannel(channel_index, nElements, dest);
				if (res) {
					IORLOG("ERROR: updateReportChannel() failed: %x", res);
					goto finish;
				}
				break;

			default:
				IORLOG("ERROR updateReport unknown action!");
				res = kIOReturnError;
				goto finish;
			}
		}
	}

	// success
	res = kIOReturnSuccess;

finish:
	return res;
}


OSSharedPtr<IOReportLegendEntry>
IOReporter::handleCreateLegend(void)
{
	OSSharedPtr<IOReportLegendEntry> legendEntry = nullptr;
	OSSharedPtr<OSArray> channelIDs;

	channelIDs = copyChannelIDs();

	if (channelIDs) {
		legendEntry = IOReporter::legendWith(channelIDs.get(), _channelNames.get(), _channelType, _unit);
	}

	return legendEntry;
}


IOReturn
IOReporter::setElementValues(int element_index,
    IOReportElementValues *values,
    uint64_t record_time /* = 0 */)
{
	IOReturn res = kIOReturnError;

	IOREPORTER_CHECK_LOCK();

	if (record_time == 0) {
		record_time = mach_absolute_time();
	}

	if (element_index >= _nElements || values == NULL) {
		res = kIOReturnBadArgument;
		goto finish;
	}

	memcpy(&_elements[element_index].values, values, sizeof(IOReportElementValues));

	_elements[element_index].timestamp = record_time;

	//IOREPORTER_DEBUG_ELEMENT(index);

	res = kIOReturnSuccess;

finish:
	return res;
}


const IOReportElementValues*
IOReporter::getElementValues(int element_index)
{
	IOReportElementValues *elementValues = NULL;

	IOREPORTER_CHECK_LOCK();

	if (element_index < 0 || element_index >= _nElements) {
		IORLOG("ERROR getElementValues out of bounds!");
		goto finish;
	}

	elementValues = &_elements[element_index].values;

finish:
	return elementValues;
}


IOReturn
IOReporter::updateChannelValues(int channel_index)
{
	return kIOReturnSuccess;
}


IOReturn
IOReporter::updateReportChannel(int channel_index,
    int *nElements,
    IOBufferMemoryDescriptor *destination)
{
	IOReturn res = kIOReturnError;
	int start_element_idx, chElems;
	size_t       size2cpy;

	res = kIOReturnBadArgument;
	if (!nElements || !destination) {
		goto finish;
	}
	if (channel_index > _nChannels) {
		goto finish;
	}

	IOREPORTER_CHECK_LOCK();

	res = kIOReturnOverrun;

	start_element_idx = channel_index * _channelDimension;
	if (start_element_idx >= _nElements) {
		goto finish;
	}

	chElems = _elements[start_element_idx].channel_type.nelements;

	// make sure we don't go beyond the end of _elements[_nElements-1]
	if (start_element_idx + chElems > _nElements) {
		goto finish;
	}

	PREFL_MEMOP_FAIL(chElems, IOReportElement);
	size2cpy = (size_t)chElems * sizeof(IOReportElement);

	// make sure there's space in the destination
	if (size2cpy > (destination->getCapacity() - destination->getLength())) {
		IORLOG("CRITICAL ERROR: Report Buffer Overflow (buffer cap %luB, length %luB, size2cpy %luB",
		    (unsigned long)destination->getCapacity(),
		    (unsigned long)destination->getLength(),
		    (unsigned long)size2cpy);
		goto finish;
	}

	destination->appendBytes(&_elements[start_element_idx], size2cpy);
	*nElements += chElems;

	res = kIOReturnSuccess;

finish:
	return res;
}


IOReturn
IOReporter::copyElementValues(int element_index,
    IOReportElementValues *elementValues)
{
	IOReturn res = kIOReturnError;

	if (!elementValues) {
		goto finish;
	}

	IOREPORTER_CHECK_LOCK();

	if (element_index >= _nElements) {
		IORLOG("ERROR getElementValues out of bounds!");
		res = kIOReturnBadArgument;
		goto finish;
	}

	memcpy(elementValues, &_elements[element_index].values, sizeof(IOReportElementValues));
	res = kIOReturnSuccess;

finish:
	return res;
}


IOReturn
IOReporter::getFirstElementIndex(uint64_t channel_id,
    int *index)
{
	IOReturn res = kIOReturnError;
	int channel_index = 0, element_index = 0;

	if (!index) {
		goto finish;
	}

	res = getChannelIndices(channel_id, &channel_index, &element_index);

	if (res == kIOReturnSuccess) {
		*index = element_index;
	}

finish:
	return res;
}


IOReturn
IOReporter::getChannelIndex(uint64_t channel_id,
    int *index)
{
	IOReturn res = kIOReturnError;
	int channel_index = 0, element_index = 0;

	if (!index) {
		goto finish;
	}

	res = getChannelIndices(channel_id, &channel_index, &element_index);

	if (res == kIOReturnSuccess) {
		*index = channel_index;
	}

finish:
	return res;
}


IOReturn
IOReporter::getChannelIndices(uint64_t channel_id,
    int *channel_index,
    int *element_index)
{
	IOReturn res = kIOReturnNotFound;
	int chIdx, elemIdx;

	if (!channel_index || !element_index) {
		goto finish;
	}

	for (chIdx = 0; chIdx < _nChannels; chIdx++) {
		elemIdx = chIdx * _channelDimension;
		if (elemIdx >= _nElements) {
			IORLOG("ERROR getChannelIndices out of bounds!");
			res = kIOReturnOverrun;
			goto finish;
		}

		if (channel_id == _elements[elemIdx].channel_id) {
			// The channel index does not care about the depth of elements...
			*channel_index = chIdx;
			*element_index = elemIdx;

			res = kIOReturnSuccess;
			goto finish;
		}
	}

finish:
	return res;
}

/********************************/
/***      PRIVATE METHODS     ***/
/********************************/


// copyChannelIDs relies on the caller to take lock
OSSharedPtr<OSArray>
IOReporter::copyChannelIDs()
{
	int    cnt, cnt2;
	OSSharedPtr<OSArray>    channelIDs;
	OSSharedPtr<OSNumber>   tmpNum;

	channelIDs = OSArray::withCapacity((unsigned)_nChannels);

	if (!channelIDs) {
		return nullptr;
	}

	for (cnt = 0; cnt < _nChannels; cnt++) {
		cnt2 = cnt * _channelDimension;

		// Encapsulate the Channel ID in OSNumber
		tmpNum = OSNumber::withNumber(_elements[cnt2].channel_id, 64);
		if (!tmpNum) {
			IORLOG("ERROR: Could not create array of channelIDs");
			return nullptr;
		}

		channelIDs->setObject((unsigned)cnt, tmpNum.get());
		tmpNum.reset();
	}

	return channelIDs;
}


// DO NOT REMOVE THIS METHOD WHICH IS THE MAIN LEGEND CREATION FUNCTION
/*static */ OSPtr<IOReportLegendEntry>
IOReporter::legendWith(OSArray *channelIDs,
    OSArray *channelNames,
    IOReportChannelType channelType,
    IOReportUnit unit)
{
	unsigned int            cnt, chCnt;
	uint64_t                type64;
	OSSharedPtr<OSNumber>   tmpNum;
	const OSSymbol          *tmpSymbol;
	OSSharedPtr<OSArray>    channelLegendArray;
	OSSharedPtr<OSArray>    tmpChannelArray;
	OSSharedPtr<OSDictionary> channelInfoDict;
	OSSharedPtr<IOReportLegendEntry> legendEntry = nullptr;

	// No need to check validity of channelNames because param is optional
	if (!channelIDs) {
		goto finish;
	}
	chCnt = channelIDs->getCount();

	channelLegendArray = OSArray::withCapacity(chCnt);

	for (cnt = 0; cnt < chCnt; cnt++) {
		tmpChannelArray = OSArray::withCapacity(3);

		// Encapsulate the Channel ID in OSNumber
		tmpChannelArray->setObject(kIOReportChannelIDIdx, channelIDs->getObject(cnt));

		// Encapsulate the Channel Type in OSNumber
		memcpy(&type64, &channelType, sizeof(type64));
		tmpNum = OSNumber::withNumber(type64, 64);
		if (!tmpNum) {
			goto finish;
		}
		tmpChannelArray->setObject(kIOReportChannelTypeIdx, tmpNum.get());
		tmpNum.reset();

		// Encapsulate the Channel Name in OSSymbol
		// Use channelNames if provided
		if (channelNames != NULL) {
			tmpSymbol = OSDynamicCast(OSSymbol, channelNames->getObject(cnt));
			if (tmpSymbol && tmpSymbol != gIOReportNoChannelName) {
				tmpChannelArray->setObject(kIOReportChannelNameIdx, tmpSymbol);
			} // Else, skip and leave name field empty
		}

		channelLegendArray->setObject(cnt, tmpChannelArray.get());
		tmpChannelArray.reset();
	}

	// Stuff the legend entry only if we have channels...
	if (channelLegendArray->getCount() != 0) {
		channelInfoDict = OSDictionary::withCapacity(1);

		if (!channelInfoDict) {
			goto finish;
		}

		tmpNum = OSNumber::withNumber(unit, 64);
		if (tmpNum) {
			channelInfoDict->setObject(kIOReportLegendUnitKey, tmpNum.get());
		}

		legendEntry = OSDictionary::withCapacity(1);

		if (legendEntry) {
			legendEntry->setObject(kIOReportLegendChannelsKey, channelLegendArray.get());
			legendEntry->setObject(kIOReportLegendInfoKey, channelInfoDict.get());
		}
	}

finish:
	return legendEntry;
}

/*static */ OSPtr<IOReportLegendEntry>
IOReporter::legendWith(const uint64_t *channelIDs,
    const char **channelNames,
    int channelCount,
    IOReportChannelType channelType,
    IOReportUnit unit)
{
	OSSharedPtr<OSArray>            channelIDsArray;
	OSSharedPtr<OSArray>            channelNamesArray;
	OSSharedPtr<OSNumber>           channelID;
	OSSharedPtr<const OSSymbol>     channelName;
	int                             cnt;

	channelIDsArray = OSArray::withCapacity(channelCount);
	channelNamesArray = OSArray::withCapacity(channelCount);
	if (!channelIDsArray || !channelNamesArray) {
		return nullptr;
	}

	for (cnt = 0; cnt < channelCount; cnt++) {
		channelID = OSNumber::withNumber(*channelIDs++, 64);
		const char *name = *channelNames++;
		if (name) {
			channelName = OSSymbol::withCString(name);
		} else {
			// grab a reference to our shared global
			channelName = gIOReportNoChannelName;
		}
		if (!channelID || !channelName) {
			return nullptr;
		}
		if (!channelIDsArray->setObject(cnt, channelID) ||
		    !channelNamesArray->setObject(cnt, channelName)) {
			return nullptr;
		}
	}

	return legendWith(channelIDsArray.get(), channelNamesArray.get(), channelType, unit);
}
