| 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> |
| 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(cString: channelName); |
| 59 | } |
| 60 | |
| 61 | if (reporter->initWith(reportingService, categories, |
| 62 | channelID, channelName: tmpChannelName.get(), |
| 63 | unit, nSegments, config)) { |
| 64 | return reporter; |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | return nullptr; |
| 69 | } |
| 70 | |
| 71 | |
| 72 | bool |
| 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(dst: _histogramSegmentsConfig, src: config, n: 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(element_index: cnt2, elementValues: (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(element_index: cnt2, values: (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 |
| 256 | IOHistogramReporter::free(void) |
| 257 | { |
| 258 | if (_bucketBounds) { |
| 259 | PREFL_MEMOP_PANIC(_nElements, int64_t); |
| 260 | IOFreeData(address: _bucketBounds, size: (size_t)_nElements * sizeof(int64_t)); |
| 261 | } |
| 262 | if (_histogramSegmentsConfig) { |
| 263 | PREFL_MEMOP_PANIC(_segmentCount, IOHistogramSegmentConfig); |
| 264 | IOFreeData(address: _histogramSegmentsConfig, |
| 265 | size: (size_t)_segmentCount * sizeof(IOHistogramSegmentConfig)); |
| 266 | } |
| 267 | |
| 268 | super::free(); |
| 269 | } |
| 270 | |
| 271 | |
| 272 | OSSharedPtr<IOReportLegendEntry> |
| 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(bytes: _histogramSegmentsConfig, |
| 286 | numBytes: (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, anObject: tmpConfigData.get()); |
| 299 | |
| 300 | return legendEntry; |
| 301 | } |
| 302 | |
| 303 | IOReturn |
| 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(element_index: index, values: (IOReportElementValues *)&bucket); |
| 325 | finish: |
| 326 | unlockReporter(); |
| 327 | return result; |
| 328 | } |
| 329 | |
| 330 | int |
| 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, elementValues: (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, values: (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> |
| 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(channelIDs: &channelID, channelNames: &channelName, channelCount: 1, channelType, unit); |
| 410 | if (!legendEntry) { |
| 411 | return nullptr; |
| 412 | } |
| 413 | |
| 414 | PREFL_MEMOP_PANIC(segmentCount, IOHistogramSegmentConfig); |
| 415 | tmpConfigData = OSData::withBytes(bytes: config, |
| 416 | numBytes: (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, anObject: tmpConfigData.get()); |
| 429 | |
| 430 | return legendEntry; |
| 431 | } |
| 432 | |