This is xnu-8019. 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

#define __STDC_LIMIT_MACROS     // what are the C++ equivalents?
#include <stdint.h>

#include <IOKit/IOKernelReportStructs.h>
#include <IOKit/IOKernelReporters.h>
#include <os/overflow.h>
#include "IOReporterDefs.h"


#define super IOReporter
OSDefineMetaClassAndStructors(IOHistogramReporter, IOReporter);

/* static */
OSSharedPtr<IOHistogramReporter>
IOHistogramReporter::with(IOService *reportingService,
    IOReportCategories categories,
    uint64_t channelID,
    const char *channelName,
    IOReportUnit unit,
    int nSegments,
    IOHistogramSegmentConfig *config)
{
	OSSharedPtr<IOHistogramReporter> reporter = OSMakeShared<IOHistogramReporter>();
	OSSharedPtr<const OSSymbol> tmpChannelName;

	if (reporter) {
		if (channelName) {
			tmpChannelName = OSSymbol::withCString(channelName);
		}

		if (reporter->initWith(reportingService, categories,
		    channelID, tmpChannelName.get(),
		    unit, nSegments, config)) {
			return reporter;
		}
	}

	return nullptr;
}


bool
IOHistogramReporter::initWith(IOService *reportingService,
    IOReportCategories categories,
    uint64_t channelID,
    const OSSymbol *channelName,
    IOReportUnit unit,
    int nSegments,
    IOHistogramSegmentConfig *config)
{
	bool            result = false;
	IOReturn        res;    // for PREFL_MEMOP
	size_t          configSize, elementsSize, eCountsSize, boundsSize;
	int             cnt, cnt2, cnt3 = 0;
	int64_t        bucketBound = 0, previousBucketBound = 0;

	// analyzer appeasement
	configSize = elementsSize = eCountsSize = boundsSize = 0;

	IORLOG("IOHistogramReporter::initWith");

	// For now, this reporter is currently limited to a single channel
	_nChannels = 1;

	IOReportChannelType channelType = {
		.categories = categories,
		.report_format = kIOReportFormatHistogram,
		.nelements = 0, // Initialized when Config is unpacked
		.element_idx = 0
	};

	if (super::init(reportingService, channelType, unit) != true) {
		IORLOG("%s - ERROR: super::init failed", __func__);
		result = false;
		goto finish;
	}

	// Make sure to call this after the commit init phase
	if (channelName) {
		_channelNames->setObject(channelName);
	}

	_segmentCount = nSegments;
	if (_segmentCount == 0) {
		IORLOG("IOReportHistogram init ERROR. No configuration provided!");
		result = false;
		goto finish;
	}

	IORLOG("%s - %u segment(s)", __func__, _segmentCount);

	PREFL_MEMOP_FAIL(_segmentCount, IOHistogramSegmentConfig);
	configSize = (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig);
	_histogramSegmentsConfig = (IOHistogramSegmentConfig*)IOMallocData(configSize);
	if (!_histogramSegmentsConfig) {
		goto finish;
	}
	memcpy(_histogramSegmentsConfig, config, configSize);

	// Find out how many elements are need to store the histogram
	for (cnt = 0; cnt < _segmentCount; cnt++) {
		_nElements += _histogramSegmentsConfig[cnt].segment_bucket_count;
		_channelDimension += _histogramSegmentsConfig[cnt].segment_bucket_count;

		IORLOG("\t\t bucket_base_width: %u | log_scale: %u | buckets: %u",
		    _histogramSegmentsConfig[cnt].base_bucket_width,
		    _histogramSegmentsConfig[cnt].scale_flag,
		    _histogramSegmentsConfig[cnt].segment_bucket_count);

		if (_histogramSegmentsConfig[cnt].scale_flag > 1
		    || _histogramSegmentsConfig[cnt].base_bucket_width == 0) {
			result = false;
			goto finish;
		}
	}

	// Update the channel type with discovered dimension
	_channelType.nelements = _channelDimension;

	IORLOG("%s - %u channel(s) of dimension %u",
	    __func__, _nChannels, _channelDimension);

	IORLOG("%s %d segments for a total dimension of %d elements",
	    __func__, _nChannels, _nElements);

	// Allocate memory for the array of report elements
	PREFL_MEMOP_FAIL(_nElements, IOReportElement);
	elementsSize = (size_t)_nElements * sizeof(IOReportElement);
	_elements = (IOReportElement *)IOMallocZeroData(elementsSize);
	if (!_elements) {
		goto finish;
	}

	// Allocate memory for the array of element watch count
	PREFL_MEMOP_FAIL(_nElements, int);
	eCountsSize = (size_t)_nChannels * sizeof(int);
	_enableCounts = (int *)IOMallocZeroData(eCountsSize);
	if (!_enableCounts) {
		goto finish;
	}

	lockReporter();
	for (cnt2 = 0; cnt2 < _channelDimension; cnt2++) {
		IOHistogramReportValues hist_values;
		if (copyElementValues(cnt2, (IOReportElementValues*)&hist_values)) {
			goto finish;
		}
		hist_values.bucket_min = kIOReportInvalidIntValue;
		hist_values.bucket_max = kIOReportInvalidIntValue;
		hist_values.bucket_sum = kIOReportInvalidIntValue;
		if (setElementValues(cnt2, (IOReportElementValues*)&hist_values)) {
			goto finish;
		}

		// Setup IOReporter's channel IDs
		_elements[cnt2].channel_id = channelID;

		// Setup IOReporter's reporting provider service
		_elements[cnt2].provider_id = _driver_id;

		// Setup IOReporter's channel type
		_elements[cnt2].channel_type = _channelType;
		_elements[cnt2].channel_type.element_idx = ((int16_t) cnt2);

		//IOREPORTER_DEBUG_ELEMENT(cnt2);
	}
	unlockReporter();

	// Allocate memory for the bucket upper bounds
	PREFL_MEMOP_FAIL(_nElements, uint64_t);
	boundsSize = (size_t)_nElements * sizeof(uint64_t);
	_bucketBounds = (int64_t*)IOMallocZeroData(boundsSize);
	if (!_bucketBounds) {
		goto finish;
	}
	_bucketCount = _nElements;

	for (cnt = 0; cnt < _segmentCount; cnt++) {
		if (_histogramSegmentsConfig[cnt].segment_bucket_count > INT_MAX
		    || _histogramSegmentsConfig[cnt].base_bucket_width > INT_MAX) {
			goto finish;
		}
		for (cnt2 = 0; cnt2 < (int)_histogramSegmentsConfig[cnt].segment_bucket_count; cnt2++) {
			if (cnt3 >= _nElements) {
				IORLOG("ERROR: _bucketBounds init");
				result = false;
				goto finish;
			}

			if (_histogramSegmentsConfig[cnt].scale_flag) {
				// FIXME: Could use pow() but not sure how to include math.h
				int64_t power = 1;
				int exponent = cnt2 + 1;
				while (exponent) {
					power *= _histogramSegmentsConfig[cnt].base_bucket_width;
					exponent--;
				}
				bucketBound = power;
			} else {
				bucketBound = _histogramSegmentsConfig[cnt].base_bucket_width *
				    ((unsigned)cnt2 + 1);
			}

			if (previousBucketBound >= bucketBound) {
				IORLOG("Histogram ERROR: bucket bound does not increase linearly (segment %u / bucket # %u)",
				    cnt, cnt2);
				result = false;
				goto finish;
			}

			_bucketBounds[cnt3] = bucketBound;
			// IORLOG("_bucketBounds[%u] = %llu", cnt3, bucketBound);
			previousBucketBound = _bucketBounds[cnt3];
			cnt3++;
		}
	}

	// success
	result = true;

finish:
	return result;
}


void
IOHistogramReporter::free(void)
{
	if (_bucketBounds) {
		PREFL_MEMOP_PANIC(_nElements, int64_t);
		IOFreeData(_bucketBounds, (size_t)_nElements * sizeof(int64_t));
	}
	if (_histogramSegmentsConfig) {
		PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig);
		IOFreeData(_histogramSegmentsConfig,
		    (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig));
	}

	super::free();
}


OSSharedPtr<IOReportLegendEntry>
IOHistogramReporter::handleCreateLegend(void)
{
	OSSharedPtr<IOReportLegendEntry>        legendEntry;
	OSSharedPtr<OSData>                     tmpConfigData;
	OSDictionary                            *tmpDict;   // no refcount

	legendEntry = super::handleCreateLegend();
	if (!legendEntry) {
		return nullptr;
	}

	PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig);
	tmpConfigData = OSData::withBytes(_histogramSegmentsConfig,
	    (unsigned)_segmentCount *
	    sizeof(IOHistogramSegmentConfig));
	if (!tmpConfigData) {
		return nullptr;
	}

	tmpDict = OSDynamicCast(OSDictionary,
	    legendEntry->getObject(kIOReportLegendInfoKey));
	if (!tmpDict) {
		return nullptr;
	}

	tmpDict->setObject(kIOReportLegendConfigKey, tmpConfigData.get());

	return legendEntry;
}

IOReturn
IOHistogramReporter::overrideBucketValues(unsigned int index,
    uint64_t bucket_hits,
    int64_t bucket_min,
    int64_t bucket_max,
    int64_t bucket_sum)
{
	IOReturn result;
	IOHistogramReportValues bucket;
	lockReporter();

	if (index >= (unsigned int)_bucketCount) {
		result = kIOReturnBadArgument;
		goto finish;
	}

	bucket.bucket_hits = bucket_hits;
	bucket.bucket_min = bucket_min;
	bucket.bucket_max = bucket_max;
	bucket.bucket_sum = bucket_sum;

	result = setElementValues(index, (IOReportElementValues *)&bucket);
finish:
	unlockReporter();
	return result;
}

int
IOHistogramReporter::tallyValue(int64_t value)
{
	int result = -1;
	int cnt = 0, element_index = 0;
	int64_t sum = 0;
	IOHistogramReportValues hist_values;

	lockReporter();

	// Iterate over _bucketCount minus one to make last bucket of infinite width
	for (cnt = 0; cnt < _bucketCount - 1; cnt++) {
		if (value <= _bucketBounds[cnt]) {
			break;
		}
	}

	element_index = cnt;

	if (copyElementValues(element_index, (IOReportElementValues *)&hist_values) != kIOReturnSuccess) {
		goto finish;
	}

	// init stats on first hit
	if (hist_values.bucket_hits == 0) {
		hist_values.bucket_min = hist_values.bucket_max = value;
		hist_values.bucket_sum = 0; // += is below
	}

	// update all values
	if (value < hist_values.bucket_min) {
		hist_values.bucket_min = value;
	} else if (value > hist_values.bucket_max) {
		hist_values.bucket_max = value;
	}
	if (os_add_overflow(hist_values.bucket_sum, value, &sum)) {
		hist_values.bucket_sum = INT64_MAX;
	} else {
		hist_values.bucket_sum = sum;
	}
	hist_values.bucket_hits++;

	if (setElementValues(element_index, (IOReportElementValues *)&hist_values)
	    != kIOReturnSuccess) {
		goto finish;
	}

	// success!
	result = element_index;

finish:
	unlockReporter();
	return result;
}

/* static */ OSPtr<IOReportLegendEntry>
IOHistogramReporter::createLegend(uint64_t channelID,
    const char *channelName,
    int segmentCount,
    IOHistogramSegmentConfig *config,
    IOReportCategories categories,
    IOReportUnit unit)
{
	OSSharedPtr<IOReportLegendEntry>        legendEntry;
	OSSharedPtr<OSData>                     tmpConfigData;
	OSDictionary                            *tmpDict;   // no refcount
	int                                                                     cnt;

	IOReportChannelType channelType = {
		.categories = categories,
		.report_format = kIOReportFormatHistogram,
		.nelements = 0,
		.element_idx = 0
	};

	for (cnt = 0; cnt < segmentCount; cnt++) {
		channelType.nelements += config[cnt].segment_bucket_count;
	}

	legendEntry = IOReporter::legendWith(&channelID, &channelName, 1, channelType, unit);
	if (!legendEntry) {
		return nullptr;
	}

	PREFL_MEMOP_PANIC(segmentCount, IOHistogramSegmentConfig);
	tmpConfigData = OSData::withBytes(config,
	    (unsigned)segmentCount *
	    sizeof(IOHistogramSegmentConfig));
	if (!tmpConfigData) {
		return nullptr;
	}

	tmpDict = OSDynamicCast(OSDictionary,
	    legendEntry->getObject(kIOReportLegendInfoKey));
	if (!tmpDict) {
		return nullptr;
	}

	tmpDict->setObject(kIOReportLegendConfigKey, tmpConfigData.get());

	return legendEntry;
}