1 /*
2  * Copyright (c) 2012-2013 Apple Computer, Inc.  All Rights Reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #define IOKIT_ENABLE_SHARED_PTR
30 
31 #define __STDC_LIMIT_MACROS     // what are the C++ equivalents?
32 #include <stdint.h>
33 
34 #include <IOKit/IOKernelReportStructs.h>
35 #include <IOKit/IOKernelReporters.h>
36 #include "IOReporterDefs.h"
37 
38 
39 #define super IOReporter
40 OSDefineMetaClassAndStructors(IOHistogramReporter, IOReporter);
41 
42 /* static */
43 OSSharedPtr<IOHistogramReporter>
44 IOHistogramReporter::with(IOService *reportingService,
45     IOReportCategories categories,
46     uint64_t channelID,
47     const char *channelName,
48     IOReportUnit unit,
49     int nSegments,
50     IOHistogramSegmentConfig *config)
51 {
52 	OSSharedPtr<IOHistogramReporter> reporter = OSMakeShared<IOHistogramReporter>();
53 	OSSharedPtr<const OSSymbol> tmpChannelName;
54 
55 	if (reporter) {
56 		if (channelName) {
57 			tmpChannelName = OSSymbol::withCString(channelName);
58 		}
59 
60 		if (reporter->initWith(reportingService, categories,
61 		    channelID, tmpChannelName.get(),
62 		    unit, nSegments, config)) {
63 			return reporter;
64 		}
65 	}
66 
67 	return nullptr;
68 }
69 
70 
71 bool
72 IOHistogramReporter::initWith(IOService *reportingService,
73     IOReportCategories categories,
74     uint64_t channelID,
75     const OSSymbol *channelName,
76     IOReportUnit unit,
77     int nSegments,
78     IOHistogramSegmentConfig *config)
79 {
80 	bool            result = false;
81 	IOReturn        res;    // for PREFL_MEMOP
82 	size_t          configSize, elementsSize, eCountsSize, boundsSize;
83 	int             cnt, cnt2, cnt3 = 0;
84 	int64_t        bucketBound = 0, previousBucketBound = 0;
85 
86 	// analyzer appeasement
87 	configSize = elementsSize = eCountsSize = boundsSize = 0;
88 
89 	IORLOG("IOHistogramReporter::initWith");
90 
91 	// For now, this reporter is currently limited to a single channel
92 	_nChannels = 1;
93 
94 	IOReportChannelType channelType = {
95 		.categories = categories,
96 		.report_format = kIOReportFormatHistogram,
97 		.nelements = 0, // Initialized when Config is unpacked
98 		.element_idx = 0
99 	};
100 
101 	if (super::init(reportingService, channelType, unit) != true) {
102 		IORLOG("%s - ERROR: super::init failed", __func__);
103 		result = false;
104 		goto finish;
105 	}
106 
107 	// Make sure to call this after the commit init phase
108 	if (channelName) {
109 		_channelNames->setObject(channelName);
110 	}
111 
112 	_segmentCount = nSegments;
113 	if (_segmentCount == 0) {
114 		IORLOG("IOReportHistogram init ERROR. No configuration provided!");
115 		result = false;
116 		goto finish;
117 	}
118 
119 	IORLOG("%s - %u segment(s)", __func__, _segmentCount);
120 
121 	PREFL_MEMOP_FAIL(_segmentCount, IOHistogramSegmentConfig);
122 	configSize = (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig);
123 	_histogramSegmentsConfig = (IOHistogramSegmentConfig*)IOMalloc(configSize);
124 	if (!_histogramSegmentsConfig) {
125 		goto finish;
126 	}
127 	memcpy(_histogramSegmentsConfig, config, configSize);
128 
129 	// Find out how many elements are need to store the histogram
130 	for (cnt = 0; cnt < _segmentCount; cnt++) {
131 		_nElements += _histogramSegmentsConfig[cnt].segment_bucket_count;
132 		_channelDimension += _histogramSegmentsConfig[cnt].segment_bucket_count;
133 
134 		IORLOG("\t\t bucket_base_width: %u | log_scale: %u | buckets: %u",
135 		    _histogramSegmentsConfig[cnt].base_bucket_width,
136 		    _histogramSegmentsConfig[cnt].scale_flag,
137 		    _histogramSegmentsConfig[cnt].segment_bucket_count);
138 
139 		if (_histogramSegmentsConfig[cnt].scale_flag > 1
140 		    || _histogramSegmentsConfig[cnt].base_bucket_width == 0) {
141 			result = false;
142 			goto finish;
143 		}
144 	}
145 
146 	// Update the channel type with discovered dimension
147 	_channelType.nelements = _channelDimension;
148 
149 	IORLOG("%s - %u channel(s) of dimension %u",
150 	    __func__, _nChannels, _channelDimension);
151 
152 	IORLOG("%s %d segments for a total dimension of %d elements",
153 	    __func__, _nChannels, _nElements);
154 
155 	// Allocate memory for the array of report elements
156 	PREFL_MEMOP_FAIL(_nElements, IOReportElement);
157 	elementsSize = (size_t)_nElements * sizeof(IOReportElement);
158 	_elements = (IOReportElement *)IOMalloc(elementsSize);
159 	if (!_elements) {
160 		goto finish;
161 	}
162 	memset(_elements, 0, elementsSize);
163 
164 	// Allocate memory for the array of element watch count
165 	PREFL_MEMOP_FAIL(_nElements, int);
166 	eCountsSize = (size_t)_nChannels * sizeof(int);
167 	_enableCounts = (int *)IOMalloc(eCountsSize);
168 	if (!_enableCounts) {
169 		goto finish;
170 	}
171 	memset(_enableCounts, 0, eCountsSize);
172 
173 	lockReporter();
174 	for (cnt2 = 0; cnt2 < _channelDimension; cnt2++) {
175 		IOHistogramReportValues hist_values;
176 		if (copyElementValues(cnt2, (IOReportElementValues*)&hist_values)) {
177 			goto finish;
178 		}
179 		hist_values.bucket_min = kIOReportInvalidIntValue;
180 		hist_values.bucket_max = kIOReportInvalidIntValue;
181 		hist_values.bucket_sum = kIOReportInvalidIntValue;
182 		if (setElementValues(cnt2, (IOReportElementValues*)&hist_values)) {
183 			goto finish;
184 		}
185 
186 		// Setup IOReporter's channel IDs
187 		_elements[cnt2].channel_id = channelID;
188 
189 		// Setup IOReporter's reporting provider service
190 		_elements[cnt2].provider_id = _driver_id;
191 
192 		// Setup IOReporter's channel type
193 		_elements[cnt2].channel_type = _channelType;
194 		_elements[cnt2].channel_type.element_idx = ((int16_t) cnt2);
195 
196 		//IOREPORTER_DEBUG_ELEMENT(cnt2);
197 	}
198 	unlockReporter();
199 
200 	// Allocate memory for the bucket upper bounds
201 	PREFL_MEMOP_FAIL(_nElements, uint64_t);
202 	boundsSize = (size_t)_nElements * sizeof(uint64_t);
203 	_bucketBounds = (int64_t*)IOMalloc(boundsSize);
204 	if (!_bucketBounds) {
205 		goto finish;
206 	}
207 	memset(_bucketBounds, 0, boundsSize);
208 	_bucketCount = _nElements;
209 
210 	for (cnt = 0; cnt < _segmentCount; cnt++) {
211 		if (_histogramSegmentsConfig[cnt].segment_bucket_count > INT_MAX
212 		    || _histogramSegmentsConfig[cnt].base_bucket_width > INT_MAX) {
213 			goto finish;
214 		}
215 		for (cnt2 = 0; cnt2 < (int)_histogramSegmentsConfig[cnt].segment_bucket_count; cnt2++) {
216 			if (cnt3 >= _nElements) {
217 				IORLOG("ERROR: _bucketBounds init");
218 				result = false;
219 				goto finish;
220 			}
221 
222 			if (_histogramSegmentsConfig[cnt].scale_flag) {
223 				// FIXME: Could use pow() but not sure how to include math.h
224 				int64_t power = 1;
225 				int exponent = cnt2 + 1;
226 				while (exponent) {
227 					power *= _histogramSegmentsConfig[cnt].base_bucket_width;
228 					exponent--;
229 				}
230 				bucketBound = power;
231 			} else {
232 				bucketBound = _histogramSegmentsConfig[cnt].base_bucket_width *
233 				    ((unsigned)cnt2 + 1);
234 			}
235 
236 			if (previousBucketBound >= bucketBound) {
237 				IORLOG("Histogram ERROR: bucket bound does not increase linearly (segment %u / bucket # %u)",
238 				    cnt, cnt2);
239 				result = false;
240 				goto finish;
241 			}
242 
243 			_bucketBounds[cnt3] = bucketBound;
244 			// IORLOG("_bucketBounds[%u] = %llu", cnt3, bucketBound);
245 			previousBucketBound = _bucketBounds[cnt3];
246 			cnt3++;
247 		}
248 	}
249 
250 	// success
251 	result = true;
252 
253 finish:
254 	return result;
255 }
256 
257 
258 void
259 IOHistogramReporter::free(void)
260 {
261 	if (_bucketBounds) {
262 		PREFL_MEMOP_PANIC(_nElements, int64_t);
263 		IOFree(_bucketBounds, (size_t)_nElements * sizeof(int64_t));
264 	}
265 	if (_histogramSegmentsConfig) {
266 		PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig);
267 		IOFree(_histogramSegmentsConfig,
268 		    (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig));
269 	}
270 
271 	super::free();
272 }
273 
274 
275 OSSharedPtr<IOReportLegendEntry>
276 IOHistogramReporter::handleCreateLegend(void)
277 {
278 	OSSharedPtr<IOReportLegendEntry>        legendEntry;
279 	OSSharedPtr<OSData>                     tmpConfigData;
280 	OSDictionary                            *tmpDict;   // no refcount
281 
282 	legendEntry = super::handleCreateLegend();
283 	if (!legendEntry) {
284 		return nullptr;
285 	}
286 
287 	PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig);
288 	tmpConfigData = OSData::withBytes(_histogramSegmentsConfig,
289 	    (unsigned)_segmentCount *
290 	    sizeof(IOHistogramSegmentConfig));
291 	if (!tmpConfigData) {
292 		return nullptr;
293 	}
294 
295 	tmpDict = OSDynamicCast(OSDictionary,
296 	    legendEntry->getObject(kIOReportLegendInfoKey));
297 	if (!tmpDict) {
298 		return nullptr;
299 	}
300 
301 	tmpDict->setObject(kIOReportLegendConfigKey, tmpConfigData.get());
302 
303 	return legendEntry;
304 }
305 
306 IOReturn
307 IOHistogramReporter::overrideBucketValues(unsigned int index,
308     uint64_t bucket_hits,
309     int64_t bucket_min,
310     int64_t bucket_max,
311     int64_t bucket_sum)
312 {
313 	IOReturn result;
314 	IOHistogramReportValues bucket;
315 	lockReporter();
316 
317 	if (index >= (unsigned int)_bucketCount) {
318 		result = kIOReturnBadArgument;
319 		goto finish;
320 	}
321 
322 	bucket.bucket_hits = bucket_hits;
323 	bucket.bucket_min = bucket_min;
324 	bucket.bucket_max = bucket_max;
325 	bucket.bucket_sum = bucket_sum;
326 
327 	result = setElementValues(index, (IOReportElementValues *)&bucket);
328 finish:
329 	unlockReporter();
330 	return result;
331 }
332 
333 int
334 IOHistogramReporter::tallyValue(int64_t value)
335 {
336 	int result = -1;
337 	int cnt = 0, element_index = 0;
338 	IOHistogramReportValues hist_values;
339 
340 	lockReporter();
341 
342 	// Iterate over _bucketCount minus one to make last bucket of infinite width
343 	for (cnt = 0; cnt < _bucketCount - 1; cnt++) {
344 		if (value <= _bucketBounds[cnt]) {
345 			break;
346 		}
347 	}
348 
349 	element_index = cnt;
350 
351 	if (copyElementValues(element_index, (IOReportElementValues *)&hist_values) != kIOReturnSuccess) {
352 		goto finish;
353 	}
354 
355 	// init stats on first hit
356 	if (hist_values.bucket_hits == 0) {
357 		hist_values.bucket_min = hist_values.bucket_max = value;
358 		hist_values.bucket_sum = 0; // += is below
359 	}
360 
361 	// update all values
362 	if (value < hist_values.bucket_min) {
363 		hist_values.bucket_min = value;
364 	} else if (value > hist_values.bucket_max) {
365 		hist_values.bucket_max = value;
366 	}
367 	hist_values.bucket_sum += value;
368 	hist_values.bucket_hits++;
369 
370 	if (setElementValues(element_index, (IOReportElementValues *)&hist_values)
371 	    != kIOReturnSuccess) {
372 		goto finish;
373 	}
374 
375 	// success!
376 	result = element_index;
377 
378 finish:
379 	unlockReporter();
380 	return result;
381 }
382