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 <os/overflow.h>
37 #include "IOReporterDefs.h"
38 
39 
40 #define super IOReporter
41 OSDefineMetaClassAndStructors(IOHistogramReporter, IOReporter);
42 
43 /* static */
44 OSSharedPtr<IOHistogramReporter>
with(IOService * reportingService,IOReportCategories categories,uint64_t channelID,const char * channelName,IOReportUnit unit,int nSegments,IOHistogramSegmentConfig * config)45 IOHistogramReporter::with(IOService *reportingService,
46     IOReportCategories categories,
47     uint64_t channelID,
48     const char *channelName,
49     IOReportUnit unit,
50     int nSegments,
51     IOHistogramSegmentConfig *config)
52 {
53 	OSSharedPtr<IOHistogramReporter> reporter = OSMakeShared<IOHistogramReporter>();
54 	OSSharedPtr<const OSSymbol> tmpChannelName;
55 
56 	if (reporter) {
57 		if (channelName) {
58 			tmpChannelName = OSSymbol::withCString(channelName);
59 		}
60 
61 		if (reporter->initWith(reportingService, categories,
62 		    channelID, tmpChannelName.get(),
63 		    unit, nSegments, config)) {
64 			return reporter;
65 		}
66 	}
67 
68 	return nullptr;
69 }
70 
71 
72 bool
initWith(IOService * reportingService,IOReportCategories categories,uint64_t channelID,const OSSymbol * channelName,IOReportUnit unit,int nSegments,IOHistogramSegmentConfig * config)73 IOHistogramReporter::initWith(IOService *reportingService,
74     IOReportCategories categories,
75     uint64_t channelID,
76     const OSSymbol *channelName,
77     IOReportUnit unit,
78     int nSegments,
79     IOHistogramSegmentConfig *config)
80 {
81 	bool            result = false;
82 	IOReturn        res;    // for PREFL_MEMOP
83 	size_t          configSize, elementsSize, eCountsSize, boundsSize;
84 	int             cnt, cnt2, cnt3 = 0;
85 	int64_t        bucketBound = 0, previousBucketBound = 0;
86 
87 	// analyzer appeasement
88 	configSize = elementsSize = eCountsSize = boundsSize = 0;
89 
90 	IORLOG("IOHistogramReporter::initWith");
91 
92 	// For now, this reporter is currently limited to a single channel
93 	_nChannels = 1;
94 
95 	IOReportChannelType channelType = {
96 		.categories = categories,
97 		.report_format = kIOReportFormatHistogram,
98 		.nelements = 0, // Initialized when Config is unpacked
99 		.element_idx = 0
100 	};
101 
102 	if (super::init(reportingService, channelType, unit) != true) {
103 		IORLOG("%s - ERROR: super::init failed", __func__);
104 		result = false;
105 		goto finish;
106 	}
107 
108 	// Make sure to call this after the commit init phase
109 	if (channelName) {
110 		_channelNames->setObject(channelName);
111 	}
112 
113 	_segmentCount = nSegments;
114 	if (_segmentCount == 0) {
115 		IORLOG("IOReportHistogram init ERROR. No configuration provided!");
116 		result = false;
117 		goto finish;
118 	}
119 
120 	IORLOG("%s - %u segment(s)", __func__, _segmentCount);
121 
122 	PREFL_MEMOP_FAIL(_segmentCount, IOHistogramSegmentConfig);
123 	configSize = (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig);
124 	_histogramSegmentsConfig = (IOHistogramSegmentConfig*)IOMallocData(configSize);
125 	if (!_histogramSegmentsConfig) {
126 		goto finish;
127 	}
128 	memcpy(_histogramSegmentsConfig, config, configSize);
129 
130 	// Find out how many elements are need to store the histogram
131 	for (cnt = 0; cnt < _segmentCount; cnt++) {
132 		_nElements += _histogramSegmentsConfig[cnt].segment_bucket_count;
133 		_channelDimension += _histogramSegmentsConfig[cnt].segment_bucket_count;
134 
135 		IORLOG("\t\t bucket_base_width: %u | log_scale: %u | buckets: %u",
136 		    _histogramSegmentsConfig[cnt].base_bucket_width,
137 		    _histogramSegmentsConfig[cnt].scale_flag,
138 		    _histogramSegmentsConfig[cnt].segment_bucket_count);
139 
140 		if (_histogramSegmentsConfig[cnt].scale_flag > 1
141 		    || _histogramSegmentsConfig[cnt].base_bucket_width == 0) {
142 			result = false;
143 			goto finish;
144 		}
145 	}
146 
147 	// Update the channel type with discovered dimension
148 	_channelType.nelements = _channelDimension;
149 
150 	IORLOG("%s - %u channel(s) of dimension %u",
151 	    __func__, _nChannels, _channelDimension);
152 
153 	IORLOG("%s %d segments for a total dimension of %d elements",
154 	    __func__, _nChannels, _nElements);
155 
156 	// Allocate memory for the array of report elements
157 	PREFL_MEMOP_FAIL(_nElements, IOReportElement);
158 	elementsSize = (size_t)_nElements * sizeof(IOReportElement);
159 	_elements = (IOReportElement *)IOMallocZeroData(elementsSize);
160 	if (!_elements) {
161 		goto finish;
162 	}
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 *)IOMallocZeroData(eCountsSize);
168 	if (!_enableCounts) {
169 		goto finish;
170 	}
171 
172 	lockReporter();
173 	for (cnt2 = 0; cnt2 < _channelDimension; cnt2++) {
174 		IOHistogramReportValues hist_values;
175 		if (copyElementValues(cnt2, (IOReportElementValues*)&hist_values)) {
176 			goto finish;
177 		}
178 		hist_values.bucket_min = kIOReportInvalidIntValue;
179 		hist_values.bucket_max = kIOReportInvalidIntValue;
180 		hist_values.bucket_sum = kIOReportInvalidIntValue;
181 		if (setElementValues(cnt2, (IOReportElementValues*)&hist_values)) {
182 			goto finish;
183 		}
184 
185 		// Setup IOReporter's channel IDs
186 		_elements[cnt2].channel_id = channelID;
187 
188 		// Setup IOReporter's reporting provider service
189 		_elements[cnt2].provider_id = _driver_id;
190 
191 		// Setup IOReporter's channel type
192 		_elements[cnt2].channel_type = _channelType;
193 		_elements[cnt2].channel_type.element_idx = ((int16_t) cnt2);
194 
195 		//IOREPORTER_DEBUG_ELEMENT(cnt2);
196 	}
197 	unlockReporter();
198 
199 	// Allocate memory for the bucket upper bounds
200 	PREFL_MEMOP_FAIL(_nElements, uint64_t);
201 	boundsSize = (size_t)_nElements * sizeof(uint64_t);
202 	_bucketBounds = (int64_t*)IOMallocZeroData(boundsSize);
203 	if (!_bucketBounds) {
204 		goto finish;
205 	}
206 	_bucketCount = _nElements;
207 
208 	for (cnt = 0; cnt < _segmentCount; cnt++) {
209 		if (_histogramSegmentsConfig[cnt].segment_bucket_count > INT_MAX
210 		    || _histogramSegmentsConfig[cnt].base_bucket_width > INT_MAX) {
211 			goto finish;
212 		}
213 		for (cnt2 = 0; cnt2 < (int)_histogramSegmentsConfig[cnt].segment_bucket_count; cnt2++) {
214 			if (cnt3 >= _nElements) {
215 				IORLOG("ERROR: _bucketBounds init");
216 				result = false;
217 				goto finish;
218 			}
219 
220 			if (_histogramSegmentsConfig[cnt].scale_flag) {
221 				int64_t power = 1;
222 				int exponent = cnt2 + 1;
223 				while (exponent) {
224 					power *= _histogramSegmentsConfig[cnt].base_bucket_width;
225 					exponent--;
226 				}
227 				bucketBound = power;
228 			} else {
229 				bucketBound = _histogramSegmentsConfig[cnt].base_bucket_width *
230 				    ((unsigned)cnt2 + 1);
231 			}
232 
233 			if (previousBucketBound >= bucketBound) {
234 				IORLOG("Histogram ERROR: bucket bound does not increase linearly (segment %u / bucket # %u)",
235 				    cnt, cnt2);
236 				result = false;
237 				goto finish;
238 			}
239 
240 			_bucketBounds[cnt3] = bucketBound;
241 			// IORLOG("_bucketBounds[%u] = %llu", cnt3, bucketBound);
242 			previousBucketBound = _bucketBounds[cnt3];
243 			cnt3++;
244 		}
245 	}
246 
247 	// success
248 	result = true;
249 
250 finish:
251 	return result;
252 }
253 
254 
255 void
free(void)256 IOHistogramReporter::free(void)
257 {
258 	if (_bucketBounds) {
259 		PREFL_MEMOP_PANIC(_nElements, int64_t);
260 		IOFreeData(_bucketBounds, (size_t)_nElements * sizeof(int64_t));
261 	}
262 	if (_histogramSegmentsConfig) {
263 		PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig);
264 		IOFreeData(_histogramSegmentsConfig,
265 		    (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig));
266 	}
267 
268 	super::free();
269 }
270 
271 
272 OSSharedPtr<IOReportLegendEntry>
handleCreateLegend(void)273 IOHistogramReporter::handleCreateLegend(void)
274 {
275 	OSSharedPtr<IOReportLegendEntry>        legendEntry;
276 	OSSharedPtr<OSData>                     tmpConfigData;
277 	OSDictionary                            *tmpDict;   // no refcount
278 
279 	legendEntry = super::handleCreateLegend();
280 	if (!legendEntry) {
281 		return nullptr;
282 	}
283 
284 	PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig);
285 	tmpConfigData = OSData::withBytes(_histogramSegmentsConfig,
286 	    (unsigned)_segmentCount *
287 	    sizeof(IOHistogramSegmentConfig));
288 	if (!tmpConfigData) {
289 		return nullptr;
290 	}
291 
292 	tmpDict = OSDynamicCast(OSDictionary,
293 	    legendEntry->getObject(kIOReportLegendInfoKey));
294 	if (!tmpDict) {
295 		return nullptr;
296 	}
297 
298 	tmpDict->setObject(kIOReportLegendConfigKey, tmpConfigData.get());
299 
300 	return legendEntry;
301 }
302 
303 IOReturn
overrideBucketValues(unsigned int index,uint64_t bucket_hits,int64_t bucket_min,int64_t bucket_max,int64_t bucket_sum)304 IOHistogramReporter::overrideBucketValues(unsigned int index,
305     uint64_t bucket_hits,
306     int64_t bucket_min,
307     int64_t bucket_max,
308     int64_t bucket_sum)
309 {
310 	IOReturn result;
311 	IOHistogramReportValues bucket;
312 	lockReporter();
313 
314 	if (index >= (unsigned int)_bucketCount) {
315 		result = kIOReturnBadArgument;
316 		goto finish;
317 	}
318 
319 	bucket.bucket_hits = bucket_hits;
320 	bucket.bucket_min = bucket_min;
321 	bucket.bucket_max = bucket_max;
322 	bucket.bucket_sum = bucket_sum;
323 
324 	result = setElementValues(index, (IOReportElementValues *)&bucket);
325 finish:
326 	unlockReporter();
327 	return result;
328 }
329 
330 int
tallyValue(int64_t value)331 IOHistogramReporter::tallyValue(int64_t value)
332 {
333 	int result = -1;
334 	int cnt = 0, element_index = 0;
335 	int64_t sum = 0;
336 	IOHistogramReportValues hist_values;
337 
338 	lockReporter();
339 
340 	// Iterate over _bucketCount minus one to make last bucket of infinite width
341 	for (cnt = 0; cnt < _bucketCount - 1; cnt++) {
342 		if (value <= _bucketBounds[cnt]) {
343 			break;
344 		}
345 	}
346 
347 	element_index = cnt;
348 
349 	if (copyElementValues(element_index, (IOReportElementValues *)&hist_values) != kIOReturnSuccess) {
350 		goto finish;
351 	}
352 
353 	// init stats on first hit
354 	if (hist_values.bucket_hits == 0) {
355 		hist_values.bucket_min = hist_values.bucket_max = value;
356 		hist_values.bucket_sum = 0; // += is below
357 	}
358 
359 	// update all values
360 	if (value < hist_values.bucket_min) {
361 		hist_values.bucket_min = value;
362 	} else if (value > hist_values.bucket_max) {
363 		hist_values.bucket_max = value;
364 	}
365 	if (os_add_overflow(hist_values.bucket_sum, value, &sum)) {
366 		hist_values.bucket_sum = INT64_MAX;
367 	} else {
368 		hist_values.bucket_sum = sum;
369 	}
370 	hist_values.bucket_hits++;
371 
372 	if (setElementValues(element_index, (IOReportElementValues *)&hist_values)
373 	    != kIOReturnSuccess) {
374 		goto finish;
375 	}
376 
377 	// success!
378 	result = element_index;
379 
380 finish:
381 	unlockReporter();
382 	return result;
383 }
384 
385 /* static */ OSPtr<IOReportLegendEntry>
createLegend(uint64_t channelID,const char * channelName,int segmentCount,IOHistogramSegmentConfig * config,IOReportCategories categories,IOReportUnit unit)386 IOHistogramReporter::createLegend(uint64_t channelID,
387     const char *channelName,
388     int segmentCount,
389     IOHistogramSegmentConfig *config,
390     IOReportCategories categories,
391     IOReportUnit unit)
392 {
393 	OSSharedPtr<IOReportLegendEntry>        legendEntry;
394 	OSSharedPtr<OSData>                     tmpConfigData;
395 	OSDictionary                            *tmpDict;   // no refcount
396 	int                                                                     cnt;
397 
398 	IOReportChannelType channelType = {
399 		.categories = categories,
400 		.report_format = kIOReportFormatHistogram,
401 		.nelements = 0,
402 		.element_idx = 0
403 	};
404 
405 	for (cnt = 0; cnt < segmentCount; cnt++) {
406 		channelType.nelements += config[cnt].segment_bucket_count;
407 	}
408 
409 	legendEntry = IOReporter::legendWith(&channelID, &channelName, 1, channelType, unit);
410 	if (!legendEntry) {
411 		return nullptr;
412 	}
413 
414 	PREFL_MEMOP_PANIC(segmentCount, IOHistogramSegmentConfig);
415 	tmpConfigData = OSData::withBytes(config,
416 	    (unsigned)segmentCount *
417 	    sizeof(IOHistogramSegmentConfig));
418 	if (!tmpConfigData) {
419 		return nullptr;
420 	}
421 
422 	tmpDict = OSDynamicCast(OSDictionary,
423 	    legendEntry->getObject(kIOReportLegendInfoKey));
424 	if (!tmpDict) {
425 		return nullptr;
426 	}
427 
428 	tmpDict->setObject(kIOReportLegendConfigKey, tmpConfigData.get());
429 
430 	return legendEntry;
431 }
432